mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-09 06:43:25 +01:00
Supports sideloading more than 3 apps via MacDirtyCow exploit
The MacDirtyCow exploit allows users to remove the 3 active apps limit on iOS 16.1.2 and earlier. To support this, we’ve added a new (hidden) “Enforce 3-App Limit” setting that can be disabled to allow sideloading more than 3 apps.
This commit is contained in:
@@ -959,7 +959,7 @@ private extension MyAppsViewController
|
||||
|
||||
@objc func presentInactiveAppsAlert()
|
||||
{
|
||||
let message: String
|
||||
var message: String
|
||||
|
||||
if UserDefaults.standard.activeAppLimitIncludesExtensions
|
||||
{
|
||||
@@ -968,6 +968,12 @@ private extension MyAppsViewController
|
||||
else
|
||||
{
|
||||
message = NSLocalizedString("Non-developer Apple IDs are limited to 3 apps. Inactive apps are backed up and uninstalled so they don't count towards your total, but will be reinstalled with all their data when activated again.", comment: "")
|
||||
|
||||
if UserDefaults.standard.ignoreActiveAppsLimit
|
||||
{
|
||||
message += "\n\n"
|
||||
message += NSLocalizedString("If you're using the MacDirtyCow exploit to remove the 3-app limit, you can install up to 10 apps and app extensions instead.", comment: "")
|
||||
}
|
||||
}
|
||||
|
||||
let alertController = UIAlertController(title: NSLocalizedString("What are inactive apps?", comment: ""), message: message, preferredStyle: .alert)
|
||||
|
||||
@@ -590,6 +590,46 @@
|
||||
</tableViewCell>
|
||||
</cells>
|
||||
</tableViewSection>
|
||||
<tableViewSection headerTitle="" id="2em-H5-kgS">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="n3X-OX-idC" customClass="InsetGroupTableViewCell" customModule="AltStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="957" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="n3X-OX-idC" id="IVp-7k-KdM">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Enforce 3-App Limit" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="IY0-94-5LN">
|
||||
<rect key="frame" x="30" y="15.5" width="163" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Oie-te-KSQ">
|
||||
<rect key="frame" x="296" y="10" width="51" height="31"/>
|
||||
<connections>
|
||||
<action selector="toggleEnforceThreeAppLimit:" destination="aMk-Xp-UL8" eventType="valueChanged" id="tfb-kk-C17"/>
|
||||
</connections>
|
||||
</switch>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="IY0-94-5LN" firstAttribute="leading" secondItem="IVp-7k-KdM" secondAttribute="leadingMargin" id="07y-eS-INC"/>
|
||||
<constraint firstItem="Oie-te-KSQ" firstAttribute="centerY" secondItem="IVp-7k-KdM" secondAttribute="centerY" id="1dS-uM-gb1"/>
|
||||
<constraint firstItem="IY0-94-5LN" firstAttribute="centerY" secondItem="IVp-7k-KdM" secondAttribute="centerY" id="FyZ-BM-Ss0"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="Oie-te-KSQ" secondAttribute="trailing" id="I1v-Ub-eJJ"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<color key="backgroundColor" white="1" alpha="0.14999999999999999" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<edgeInsets key="layoutMargins" top="8" left="30" bottom="8" right="30"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="style">
|
||||
<integer key="value" value="0"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="NO"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
</cells>
|
||||
</tableViewSection>
|
||||
<tableViewSection headerTitle="" id="OMa-EK-hRI">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="FMZ-as-Ljo" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
@@ -844,6 +884,7 @@
|
||||
<outlet property="backgroundRefreshSwitch" destination="DPu-zD-Als" id="eiG-Hv-Vko"/>
|
||||
<outlet property="disableAppLimitSwitch" destination="1aa-og-ZXD" id="oVL-Md-yZ8"/>
|
||||
<outlet property="noIdleTimeoutSwitch" destination="iQA-wm-5ag" id="jHC-js-q0Y"/>
|
||||
<outlet property="enforceThreeAppLimitSwitch" destination="Oie-te-KSQ" id="jKn-t1-gyk"/>
|
||||
<outlet property="versionLabel" destination="bUR-rp-Nw2" id="85I-5R-hqz"/>
|
||||
</connections>
|
||||
</tableViewController>
|
||||
|
||||
@@ -26,6 +26,7 @@ extension SettingsViewController
|
||||
case instructions
|
||||
case techyThings
|
||||
case credits
|
||||
case macDirtyCow
|
||||
case debug
|
||||
}
|
||||
|
||||
@@ -88,6 +89,7 @@ final class SettingsViewController: UITableViewController
|
||||
@IBOutlet private var disableAppLimitSwitch: UISwitch!
|
||||
|
||||
@IBOutlet private var refreshSideJITServer: UILabel!
|
||||
@IBOutlet private var enforceThreeAppLimitSwitch: UISwitch!
|
||||
|
||||
@IBOutlet private var versionLabel: UILabel!
|
||||
|
||||
@@ -198,6 +200,7 @@ private extension SettingsViewController
|
||||
self.backgroundRefreshSwitch.isOn = UserDefaults.standard.isBackgroundRefreshEnabled
|
||||
self.noIdleTimeoutSwitch.isOn = UserDefaults.standard.isIdleTimeoutDisableEnabled
|
||||
self.disableAppLimitSwitch.isOn = UserDefaults.standard.isAppLimitDisabled
|
||||
self.enforceThreeAppLimitSwitch.isOn = !UserDefaults.standard.ignoreActiveAppsLimit
|
||||
|
||||
if self.isViewLoaded
|
||||
{
|
||||
@@ -261,6 +264,16 @@ private extension SettingsViewController
|
||||
case .credits:
|
||||
settingsHeaderFooterView.primaryLabel.text = NSLocalizedString("CREDITS", comment: "")
|
||||
|
||||
case .macDirtyCow:
|
||||
if isHeader
|
||||
{
|
||||
settingsHeaderFooterView.primaryLabel.text = NSLocalizedString("MACDIRTYCOW", comment: "")
|
||||
}
|
||||
else
|
||||
{
|
||||
settingsHeaderFooterView.secondaryLabel.text = NSLocalizedString("If you've removed the 3-sideloaded app limit via the MacDirtyCow exploit, disable this setting to sideload more than 3 apps at a time.", comment: "")
|
||||
}
|
||||
|
||||
case .debug:
|
||||
settingsHeaderFooterView.primaryLabel.text = NSLocalizedString("DEBUG", comment: "")
|
||||
}
|
||||
@@ -277,6 +290,20 @@ private extension SettingsViewController
|
||||
let size = settingsHeaderFooterView.contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
|
||||
return size.height
|
||||
}
|
||||
|
||||
func isSectionHidden(_ section: Section) -> Bool
|
||||
{
|
||||
switch section
|
||||
{
|
||||
case .macDirtyCow:
|
||||
let ios16_2 = OperatingSystemVersion(majorVersion: 16, minorVersion: 2, patchVersion: 0)
|
||||
|
||||
let isMacDirtyCowExploitSupported = !ProcessInfo.processInfo.isOperatingSystemAtLeast(ios16_2)
|
||||
return !(isMacDirtyCowExploitSupported && UserDefaults.standard.isDebugModeEnabled)
|
||||
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension SettingsViewController
|
||||
@@ -342,6 +369,16 @@ private extension SettingsViewController
|
||||
UserDefaults.standard.isIdleTimeoutDisableEnabled = sender.isOn
|
||||
}
|
||||
|
||||
@IBAction func toggleEnforceThreeAppLimit(_ sender: UISwitch)
|
||||
{
|
||||
UserDefaults.standard.ignoreActiveAppsLimit = !sender.isOn
|
||||
|
||||
if UserDefaults.standard.activeAppsLimit != nil
|
||||
{
|
||||
UserDefaults.standard.activeAppsLimit = InstalledApp.freeAccountActiveAppsLimit
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 14, *)
|
||||
@IBAction func addRefreshAppsShortcut()
|
||||
{
|
||||
@@ -470,6 +507,7 @@ extension SettingsViewController
|
||||
let section = Section.allCases[section]
|
||||
switch section
|
||||
{
|
||||
case _ where isSectionHidden(section): return 0
|
||||
case .signIn: return (self.activeTeam == nil) ? 1 : 0
|
||||
case .account: return (self.activeTeam == nil) ? 0 : 3
|
||||
case .appRefresh: return AppRefreshRow.allCases.count
|
||||
@@ -516,9 +554,10 @@ extension SettingsViewController
|
||||
let section = Section.allCases[section]
|
||||
switch section
|
||||
{
|
||||
case _ where isSectionHidden(section): return nil
|
||||
case .signIn where self.activeTeam != nil: return nil
|
||||
case .account where self.activeTeam == nil: return nil
|
||||
case .signIn, .account, .patreon, .appRefresh, .techyThings, .credits, .debug:
|
||||
case .signIn, .account, .patreon, .appRefresh, .techyThings, .credits, .macDirtyCow, .debug:
|
||||
let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "HeaderFooterView") as! SettingsHeaderFooterView
|
||||
self.prepare(headerView, for: section, isHeader: true)
|
||||
return headerView
|
||||
@@ -532,8 +571,9 @@ extension SettingsViewController
|
||||
let section = Section.allCases[section]
|
||||
switch section
|
||||
{
|
||||
case _ where isSectionHidden(section): return nil
|
||||
case .signIn where self.activeTeam != nil: return nil
|
||||
case .signIn, .patreon, .appRefresh:
|
||||
case .signIn, .patreon, .appRefresh, .macDirtyCow:
|
||||
let footerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "HeaderFooterView") as! SettingsHeaderFooterView
|
||||
self.prepare(footerView, for: section, isHeader: false)
|
||||
return footerView
|
||||
@@ -547,9 +587,10 @@ extension SettingsViewController
|
||||
let section = Section.allCases[section]
|
||||
switch section
|
||||
{
|
||||
case _ where isSectionHidden(section): return 1.0
|
||||
case .signIn where self.activeTeam != nil: return 1.0
|
||||
case .account where self.activeTeam == nil: return 1.0
|
||||
case .signIn, .account, .patreon, .appRefresh, .techyThings, .credits, .debug:
|
||||
case .signIn, .account, .patreon, .appRefresh, .techyThings, .credits, .macDirtyCow, .debug:
|
||||
let height = self.preferredHeight(for: self.prototypeHeaderFooterView, in: section, isHeader: true)
|
||||
return height
|
||||
|
||||
@@ -562,9 +603,10 @@ extension SettingsViewController
|
||||
let section = Section.allCases[section]
|
||||
switch section
|
||||
{
|
||||
case _ where isSectionHidden(section): return 1.0
|
||||
case .signIn where self.activeTeam != nil: return 1.0
|
||||
case .account where self.activeTeam == nil: return 1.0
|
||||
case .signIn, .patreon, .appRefresh:
|
||||
case .account where self.activeTeam == nil: return 1.0
|
||||
case .signIn, .patreon, .appRefresh, .macDirtyCow:
|
||||
let height = self.preferredHeight(for: self.prototypeHeaderFooterView, in: section, isHeader: false)
|
||||
return height
|
||||
|
||||
@@ -801,7 +843,7 @@ extension SettingsViewController
|
||||
|
||||
}
|
||||
|
||||
case .account, .patreon, .instructions, .techyThings: break
|
||||
case .account, .patreon, .instructions, .techyThings, .macDirtyCow: break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ public extension UserDefaults
|
||||
@NSManaged var trustedSourceIDs: [String]?
|
||||
@NSManaged var trustedServerURL: String?
|
||||
|
||||
@nonobjc
|
||||
var activeAppsLimit: Int? {
|
||||
get {
|
||||
return self._activeAppsLimit?.intValue
|
||||
@@ -69,6 +70,8 @@ public extension UserDefaults
|
||||
}
|
||||
@NSManaged @objc(activeAppsLimit) private var _activeAppsLimit: NSNumber?
|
||||
|
||||
@NSManaged var ignoreActiveAppsLimit: Bool
|
||||
|
||||
class func registerDefaults()
|
||||
{
|
||||
let ios13_5 = OperatingSystemVersion(majorVersion: 13, minorVersion: 5, patchVersion: 0)
|
||||
@@ -88,7 +91,8 @@ public extension UserDefaults
|
||||
#keyPath(UserDefaults.localServerSupportsRefreshing): localServerSupportsRefreshing,
|
||||
#keyPath(UserDefaults.requiresAppGroupMigration): true,
|
||||
#keyPath(UserDefaults.menuAnisetteList): "https://servers.sidestore.io/servers.json",
|
||||
#keyPath(UserDefaults.menuAnisetteURL): "https://ani.sidestore.io"
|
||||
#keyPath(UserDefaults.menuAnisetteURL): "https://ani.sidestore.io",
|
||||
#keyPath(UserDefaults.ignoreActiveAppsLimit): false
|
||||
] as [String : Any]
|
||||
|
||||
UserDefaults.standard.register(defaults: defaults)
|
||||
|
||||
@@ -12,8 +12,22 @@ import CoreData
|
||||
import AltSign
|
||||
import SemanticVersion
|
||||
|
||||
// Free developer accounts are limited to only 3 active sideloaded apps at a time as of iOS 13.3.1.
|
||||
public let ALTActiveAppsLimit = 3
|
||||
extension InstalledApp
|
||||
{
|
||||
public static var freeAccountActiveAppsLimit: Int {
|
||||
if UserDefaults.standard.ignoreActiveAppsLimit
|
||||
{
|
||||
// MacDirtyCow exploit allows users to remove 3-app limit, so return 10 to match App ID limit per-week.
|
||||
// Don't return nil because that implies there is no limit, which isn't quite true due to App ID limit.
|
||||
return 10
|
||||
}
|
||||
else
|
||||
{
|
||||
// Free developer accounts are limited to only 3 active sideloaded apps at a time as of iOS 13.3.1.
|
||||
return 3
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public protocol InstalledAppProtocol: Fetchable
|
||||
{
|
||||
|
||||
@@ -50,7 +50,7 @@ class InstalledAppToInstalledAppMigrationPolicy: NSEntityMigrationPolicy
|
||||
|
||||
// We can assume there is an active app limit,
|
||||
// but will confirm next time user authenticates.
|
||||
UserDefaults.standard.activeAppsLimit = ALTActiveAppsLimit
|
||||
UserDefaults.standard.activeAppsLimit = InstalledApp.freeAccountActiveAppsLimit
|
||||
}
|
||||
|
||||
return NSNumber(value: isActive)
|
||||
|
||||
Reference in New Issue
Block a user