Compare commits

..

90 Commits

Author SHA1 Message Date
Magesh K
ad9f4a8f1f - freeze altsign version for 0.5.10 (refer notes below)
(this was missed while tagging the original release so removed actual tag and created new branch to preserve the changes that went into 0.5.10 as-is incase anyone wants to build from source)
2025-02-25 00:27:02 +05:30
Spidy123222
d7957a8821 Try adding backwards compatibility in trusted
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2025-02-24 23:42:38 +05:30
June Park
01084039df [skipci] Bump to 0.5.10 for new nightly cycle
Signed-off-by: June Park <me@pythonplayer123.dev>
2024-12-24 22:58:19 +09:00
June Park
feb35d2b6e Update stable.yml
Signed-off-by: June Park <me@pythonplayer123.dev>
2024-12-24 22:48:48 +09:00
June
3e941cfb0d attempt to fix long standing bug in bundle ids 2024-12-24 22:37:04 +09:00
June Park
931a34cf7d Update stable.yml
Signed-off-by: June Park <me@pythonplayer123.dev>
2024-12-24 22:09:21 +09:00
June Park
43dc332329 Update pr.yml
Signed-off-by: June Park <me@pythonplayer123.dev>
2024-12-24 22:08:52 +09:00
June Park
c8ae28003f Update nightly.yml
Signed-off-by: June Park <me@pythonplayer123.dev>
2024-12-24 21:40:57 +09:00
June Park
cf32f25457 Use xcbeautify for nightlys
Signed-off-by: June Park <me@pythonplayer123.dev>
2024-12-24 21:38:45 +09:00
June Park
fee5309b50 Merge pull request #803 from neoarz/patch-1
Update README.md
2024-12-24 21:10:16 +09:00
June Park
b8c7f51d94 Merge pull request #809 from SideStore/junepark678/feat/updatealtsign
Add entitlements and other things
2024-12-24 19:29:39 +09:00
June Park
d00ce2bc11 Update Package.resolved
Signed-off-by: June Park <me@pythonplayer123.dev>
2024-12-24 18:08:51 +09:00
June Park
2402655e56 Update Package.resolved
Signed-off-by: June Park <me@pythonplayer123.dev>
2024-12-24 18:04:24 +09:00
June Park
283f7a998a Update project.pbxproj
Signed-off-by: June Park <me@pythonplayer123.dev>
2024-12-24 18:00:15 +09:00
June Park
2b355dbf8c Update AppManager.swift
Signed-off-by: June Park <me@pythonplayer123.dev>
2024-12-24 17:56:40 +09:00
June
ead80e50f9 update altsign 2024-12-24 17:56:40 +09:00
June
71c53d81c2 work harder 2024-12-24 17:56:40 +09:00
June
8498f3df94 update again 2024-12-24 17:56:40 +09:00
June
faa1555294 update altsign again 2024-12-24 17:56:40 +09:00
June Park
110f70e34c cache harder
Signed-off-by: June Park <me@pythonplayer123.dev>
2024-12-24 16:39:59 +09:00
June
cd3b4c46b4 fixes 2024-12-24 15:15:23 +09:00
June
60a0657721 fix roxas 2024-12-24 14:37:41 +09:00
June
62c655c927 fix build 2024-12-24 14:33:50 +09:00
June
84b0f134b0 things 2024-12-24 14:29:50 +09:00
Rose
09902a3f71 Update StoreApp.swift
Signed-off-by: Rose <cool5tarXV@gmail.com>
2024-12-24 14:26:10 +09:00
Rose
3db004c733 added fix + todo
Signed-off-by: Rose <cool5tarXV@gmail.com>
2024-12-24 14:26:10 +09:00
June P
4a566d8823 fix altsign 2024-12-23 01:08:20 +09:00
June P
d37e033d35 Revert "Update AltSign to newer revision"
This reverts commit afca84a852.
2024-12-23 01:07:24 +09:00
June
afca84a852 Update AltSign to newer revision 2024-12-23 00:44:26 +09:00
neoarz
9e0c5dab1a Update README.md
discord link was expired

Signed-off-by: neoarz <164915254+neoarz@users.noreply.github.com>
2024-12-19 22:49:06 -05:00
Magesh K
77eaa2fb5a [dependencies]: updated Package.resolved to use latest commit for altsign 2024-12-12 20:52:09 +05:30
Magesh K
b81ba38d1c [settings]: refined style for last row in REFRESHING APPS section 2024-12-12 20:11:57 +05:30
Magesh K
d5a9050464 [settings]: Fix: siri shortcut missing from settings 2024-12-12 20:11:57 +05:30
Magesh K
3c99fe22d3 [fetch-prebuilt.sh]: force download when libs are missing locally and skip file is absent (#796)
* [fetch-prebuilt.sh] - retain checked-in files from minimuxer repo (assuming minimuxer repo was cloned)

* [fetch-prebuilt.sh]: force download when libs are missing locally and skip file is absent
2024-12-12 00:53:57 -05:00
Magesh K
f5b47d0508 [fetch-prebuilt.sh] - retain checked-in files from minimuxer repo (assuming minimuxer repo was cloned) 2024-12-08 20:21:37 +05:30
Magesh K
db84a38519 updated fetch-prebuilt.sh to reflect libem_proxy-ios.a renaming 2024-12-08 19:37:27 +05:30
Magesh K
c466e7698a Updated LIB_FILE_NAME used in minimuxer shell script to link the binary 2024-12-08 19:30:38 +05:30
Magesh K
6a8283a163 - renamed libem_proxy.a to libem_proxy-ios.a for iOS builds 2024-12-08 19:05:23 +05:30
Magesh K
1ee334ca68 Added launch arguments for coredata debugging and updated Scheme from AltStore to SideStore (#785) 2024-12-08 07:50:33 -05:00
ny
a3a34eb9ef fix: Refreshing via Xcode would cause a crash here 2024-11-30 00:50:10 -05:00
Magesh K
db633c90be - Show Toast regarding server switch due to fallback if UI context is available 2024-11-28 22:01:47 +05:30
Magesh K
5e0e4dbd4e - Fix: AnisetteServers: refresh server request shouldn't use local cache to get proper updates 2024-11-28 19:12:50 +05:30
Magesh K
090967c21d - Fix: refresh(): app was being accessed after de-alloc causing EXC_BREAKPOINT 2024-11-28 18:49:53 +05:30
Magesh K
6c8e9b886d - Fix: Anisette-server-fallback: Try currently selected server first 2024-11-28 18:46:08 +05:30
Magesh K
2bb2eea226 -[Feature]: client-side: Anisette server fallback impl when current server is unreachable (#773) 2024-11-27 18:38:54 -05:00
Magesh K
a2bb26a86e - Fix: DatabaseManager.migrateDatabaseToAppGroupIfNeeded() src and dest for replaceAt() cannot be same 2024-11-28 02:47:01 +05:30
Stern
948a8eb90b Merge pull request #770 from mahee96/develop 2024-11-24 13:23:43 -05:00
Magesh K
e66e223189 Fix: update sign-in screen toast text color to be primary color 2024-11-24 23:06:39 +05:30
Magesh K
2aee6ac57e -[xcode]: revert enabling debug dylib support for widgets 2024-11-24 19:16:18 +05:30
Stephen
66abac80d8 Twitter Updates & Email Support (#762) 2024-11-21 12:12:47 -05:00
Stern
a1e0d5f834 (chore:) Update Twitter Link
Signed-off-by: Stern <stern@sidestore.io>
2024-11-19 16:00:48 -05:00
Joe Mattiello
6a1181b21f Change gitignore, add package.resolve (#748) 2024-11-11 23:10:44 -05:00
kaoneko
c25ae10873 Update message about Disable Idle Timeout setting (#747) 2024-11-11 15:12:49 -05:00
Magesh K
2842c8f669 Widgets-Fix: Ported Widget compatibility for ios 18+ from altstore (#746) 2024-11-11 14:46:33 -05:00
Magesh K
3161892585 Background and shortcut refresh fix - remove only excess extensions from new package during install (#743) 2024-11-10 13:01:31 -05:00
Magesh K
489843f987 Partial-Fix for #723: Error on refresh operation must be set on self.context instead of group.context (#742) 2024-11-10 06:11:44 -05:00
Magesh K
dc0b30ab67 -[diagnostics]: Added diagnostics for RefreshAppOperation failure 2024-11-10 16:31:28 +05:30
Magesh K
c3235cc554 -[cleanup]: added guard check to remove file only if it exists 2024-11-10 16:27:29 +05:30
Magesh K
6568e5918a -[bug-fix]: UI Api invocation needs to be on UI Thread 2024-11-10 16:27:29 +05:30
Michael
91fba6db99 Add missing = sign (#739) 2024-11-09 22:20:23 -05:00
Stern
6b7e9a66f1 Merge pull request #738 from mahee96/develop
-[cleanup]: restore changes dropped from altstore-1.6.3 merge by 1713fcc
2024-11-09 19:05:59 -05:00
Magesh K
3682b65a4a restore changes dropped from altstore by 1713fcc 2024-11-10 03:04:24 +05:30
Magesh K
117412645b diagnostics: improved error logging for OperationError.invalidParameters (#736) 2024-11-09 04:05:18 -05:00
Stern
c784ff6925 chore: We need to add Homebrew to our path or Xcode will not find it.
Signed-off-by: Stern <stern@sidestore.io>
2024-11-08 23:56:27 -05:00
Stern
cf477024fc chore: Add checks to fetch-prebuilt script.
This adds a check that if for example wget isn't installed it'll install from brew via brew install.

Signed-off-by: Stern <stern@sidestore.io>
2024-11-08 23:24:46 -05:00
Magesh K
d595b7037f refresh-app: enhanced error logging when app extension validation check fails (#730)
refresh-app: enhanced error logging when app exenstion validation check fails
2024-11-04 04:05:13 -05:00
June Park
0c5007c8d8 Merge pull request #729 from mahee96/sidestore-crash-xcode
xcode: Debug dylib support is still broken in iOS 18 causing "No Entry Point Found. checked(null)"
2024-11-04 17:39:05 +09:00
Magesh K
8a87445d1f Fix-regression: restored update-check logic(2a392dd) in InstalledApp.swift which was dropped in (1713fcc) (#728) 2024-11-04 03:38:28 -05:00
Magesh K
76a693fae4 xcode: Debug dylib is still broken in iOS 18 causing "No Entry Point Found. checked(null)" 2024-11-04 12:53:30 +05:30
Magesh K
9c150d5f4a Fix for Refresh Operation causing renewal/install of provisioning profiles for removed app extensions (#727)
* Fix AppExtensions not being updated on Disk after db is updated in InstallAppOperation

* refresh-Extensions: Added check to ensure extensions in DB and DISK matches if not then cancel current refresh request
2024-11-03 17:30:39 -05:00
ny
e5febcdc6c More attempts at fixing app limit issues 2024-10-24 00:41:29 -04:00
ny
1e969a0888 Attempt to make the bypass more sane 2024-10-23 07:31:52 -04:00
ny
72bb549ea3 Clean/fix this implementation 2024-10-23 06:44:39 -04:00
ny
3c7cfdd91f Fix detection for SparseRestore patch 2024-10-23 06:07:58 -04:00
Moonsn
b21f80cdd7 feat: show custom anisette server list if set (#717) 2024-10-22 20:16:37 -04:00
nythepegasus
867a9c77e6 Actually fix deprecation warnings 2024-10-17 06:14:44 -04:00
nythepegasus
bc2d2c18fc Update GH Actions to fix deprecation warnings 2024-10-17 05:58:38 -04:00
nythepegasus
ab923d245d Try to fix refreshing and removing extensions
Silly fix

Signed-off-by: nythepegasus <me@nythepegas.us>
2024-10-17 05:18:10 -04:00
nythepegasus
fcf1b9ae03 Remove this
Should fix remove app extensions

Signed-off-by: nythepegasus <me@nythepegas.us>
2024-10-17 04:47:42 -04:00
ny
59896e4f89 Add a few more checks for app limits 2024-10-11 02:50:48 -04:00
ny
2a9f88c810 Add a max limit 2024-10-11 02:33:25 -04:00
ny
e98b0a3758 Fix these actions to point correctly 2024-10-11 02:24:36 -04:00
ny
0cb6da7be4 Fix up this so it actually sets the value 2024-10-11 01:48:01 -04:00
ny
fc3ff41fc4 Add bad app limit toggle implementation 2024-10-11 01:12:22 -04:00
Stern
719ddc8263 (fix:) Update Patreon Secret and links
Signed-off-by: Stern <stern@sidestore.io>
2024-09-16 07:18:41 -04:00
nythepegasus
9f6b1284bb [skipci] Bump to 0.5.9 for new nightly cycle
Signed-off-by: nythepegasus <me@nythepegas.us>
2024-09-07 14:44:14 -04:00
polymo1
bfb4a90fdd legal: developer's certificate of origin 1.1 (#686) 2024-09-07 14:23:16 -04:00
nythepegasus
d1b6bedc30 Attempt to fix sharing ipa files
Yeah, yeah, I shouldn't commit to develop, I have a hunch though

Signed-off-by: nythepegasus <me@nythepegas.us>
2024-09-02 08:19:28 -04:00
June Park
b78c75d829 [skip ci] Update bug_report.yml
Signed-off-by: June Park <me@pythonplayer123.dev>
2024-08-20 17:53:50 +09:00
June
4518f07b5b add entitlements 2024-08-19 11:14:42 +09:00
61 changed files with 1222 additions and 653 deletions

View File

@@ -7,6 +7,7 @@ body:
- type: markdown
attributes:
value: |
## Please note that the issue tracker is not for support
Thanks for taking the time to fill out this bug report! Before you continue filling out the report, please **[search in GitHub Issues](https://github.com/SideStore/SideStore/issues?q=is%3Aissue+is%3Aopen) for the bug you are experiencing** in case it has already been reported.
**Please use [Discord](https://discord.gg/sidestore-949183273383395328) or [GitHub Discussions](https://github.com/SideStore/SideStore/discussions) for support.**

View File

@@ -11,13 +11,13 @@ jobs:
fail-fast: false
matrix:
include:
- os: 'macos-12'
version: '14.2'
- os: 'macos-14'
version: '15.4'
runs-on: ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v4
with:
submodules: recursive
@@ -35,7 +35,7 @@ jobs:
run: echo "${{ steps.version.outputs.version }}"
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1.4.1
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: ${{ matrix.version }}
@@ -91,13 +91,13 @@ jobs:
run: mv SideStore.ipa SideStore-${{ steps.version.outputs.version }}.ipa
- name: Upload SideStore.ipa Artifact
uses: actions/upload-artifact@v3.1.0
uses: actions/upload-artifact@v4
with:
name: SideStore-${{ steps.version.outputs.version }}.ipa
path: SideStore-${{ steps.version.outputs.version }}.ipa
- name: Upload *.dSYM Artifact
uses: actions/upload-artifact@v3.1.0
uses: actions/upload-artifact@v4
with:
name: SideStore-${{ steps.version.outputs.version }}-dSYM
path: ./*.dSYM/

View File

@@ -14,21 +14,24 @@ jobs:
fail-fast: false
matrix:
include:
- os: 'macos-12'
version: '14.2'
- os: 'macos-14'
version: '15.4'
runs-on: ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install dependencies
run: brew install ldid
- name: Install xcbeautify
run: brew install xcbeautify
- name: Cache .nightly-build-num
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: .nightly-build-num
key: nightly-build-num
@@ -44,7 +47,7 @@ jobs:
run: echo "${{ steps.version.outputs.version }}"
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1.4.1
uses: maxim-lobanov/setup-xcode@v1.6.0
with:
xcode-version: ${{ matrix.version }}
@@ -53,9 +56,12 @@ jobs:
with:
key: xcode-cache-deriveddata-${{ github.sha }}
restore-keys: xcode-cache-deriveddata-
swiftpm-cache-key: xcode-cache-sourcedata-${{ github.sha }}
swiftpm-cache-restore-keys: |
xcode-cache-sourcedata-
- name: Build SideStore
run: make build | xcpretty && exit ${PIPESTATUS[0]}
run: NSUnbufferedIO=YES make build 2>&1 | xcbeautify --renderer github-actions && exit ${PIPESTATUS[0]}
- name: Fakesign app
run: make fakesign
@@ -97,13 +103,13 @@ jobs:
run: mv SideStore.ipa SideStore-${{ steps.version.outputs.version }}.ipa
- name: Upload SideStore.ipa Artifact
uses: actions/upload-artifact@v3.1.0
uses: actions/upload-artifact@v4
with:
name: SideStore-${{ steps.version.outputs.version }}.ipa
path: SideStore-${{ steps.version.outputs.version }}.ipa
- name: Upload *.dSYM Artifact
uses: actions/upload-artifact@v3.1.0
uses: actions/upload-artifact@v4
with:
name: SideStore-${{ steps.version.outputs.version }}-dSYM
path: ./*.dSYM/

View File

@@ -9,13 +9,13 @@ jobs:
fail-fast: false
matrix:
include:
- os: 'macos-12'
version: '14.2'
- os: 'macos-14'
version: '15.4'
runs-on: ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v4
with:
submodules: recursive
@@ -35,7 +35,7 @@ jobs:
run: echo "${{ steps.version.outputs.version }}"
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1.4.1
uses: maxim-lobanov/setup-xcode@v1.6.0
with:
xcode-version: ${{ matrix.version }}
@@ -46,7 +46,7 @@ jobs:
restore-keys: xcode-cache-deriveddata-
- name: Build SideStore
run: make build | xcpretty && exit ${PIPESTATUS[0]}
run: NSUnbufferedIO=YES make build | xcbeautify --renderer github-actions && exit ${PIPESTATUS[0]}
- name: Fakesign app
run: make fakesign
@@ -58,13 +58,13 @@ jobs:
run: mv SideStore.ipa SideStore-${{ steps.version.outputs.version }}.ipa
- name: Upload SideStore.ipa Artifact
uses: actions/upload-artifact@v3.1.0
uses: actions/upload-artifact@v4
with:
name: SideStore-${{ steps.version.outputs.version }}.ipa
path: SideStore-${{ steps.version.outputs.version }}.ipa
- name: Upload *.dSYM Artifact
uses: actions/upload-artifact@v3.1.0
uses: actions/upload-artifact@v4
with:
name: SideStore-${{ steps.version.outputs.version }}-dSYM
path: ./*.dSYM/

View File

@@ -3,6 +3,7 @@ on:
push:
tags:
- '[0-9]+.[0-9]+.[0-9]+' # example: 1.0.0
workflow_dispatch:
jobs:
build:
@@ -11,13 +12,13 @@ jobs:
fail-fast: false
matrix:
include:
- os: 'macos-12'
version: '14.2'
- os: 'macos-14'
version: '15.4'
runs-on: ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v4
with:
submodules: recursive
@@ -35,7 +36,7 @@ jobs:
run: echo "${{ steps.version.outputs.version }}"
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1.4.1
uses: maxim-lobanov/setup-xcode@v1.6.0
with:
xcode-version: ${{ matrix.version }}
@@ -44,9 +45,12 @@ jobs:
with:
key: xcode-cache-deriveddata-${{ github.sha }}
restore-keys: xcode-cache-deriveddata-
swiftpm-cache-key: xcode-cache-sourcedata-${{ github.sha }}
swiftpm-cache-restore-keys: |
xcode-cache-sourcedata-
- name: Build SideStore
run: make build | xcpretty && exit ${PIPESTATUS[0]}
run: NSUnbufferedIO=YES make build | xcbeautify --renderer github-actions && exit ${PIPESTATUS[0]}
- name: Fakesign app
run: make fakesign
@@ -87,13 +91,13 @@ jobs:
run: mv SideStore.ipa SideStore-${{ steps.version.outputs.version }}.ipa
- name: Upload SideStore.ipa Artifact
uses: actions/upload-artifact@v3.1.0
uses: actions/upload-artifact@v4
with:
name: SideStore-${{ steps.version.outputs.version }}.ipa
path: SideStore-${{ steps.version.outputs.version }}.ipa
- name: Upload *.dSYM Artifact
uses: actions/upload-artifact@v3.1.0
uses: actions/upload-artifact@v4
with:
name: SideStore-${{ steps.version.outputs.version }}-dSYM
path: ./*.dSYM/

1
.gitignore vendored
View File

@@ -19,7 +19,6 @@ archive.xcarchive
*.perspectivev3
!default.perspectivev3
xcuserdata
AltStore.xcodeproj/project.xcworkspace/xcshareddata/swiftpm
## Other
*.xccheckout
*.moved-aside

View File

@@ -0,0 +1,48 @@
//
// ErrorDetailsViewController.swift
// AltServer
//
// Created by Riley Testut on 10/4/22.
// Copyright © 2022 Riley Testut. All rights reserved.
//
import AppKit
class ErrorDetailsViewController: NSViewController
{
var error: NSError? {
didSet {
self.update()
}
}
@IBOutlet private var errorCodeLabel: NSTextField!
@IBOutlet private var detailedDescriptionLabel: NSTextField!
override func viewDidLoad()
{
super.viewDidLoad()
self.detailedDescriptionLabel.preferredMaxLayoutWidth = 800
}
}
private extension ErrorDetailsViewController
{
func update()
{
if !self.isViewLoaded
{
self.loadView()
}
guard let error = self.error else { return }
self.errorCodeLabel.stringValue = error.localizedErrorCode
let font = self.detailedDescriptionLabel.font ?? NSFont.systemFont(ofSize: 12)
let detailedDescription = error.formattedDetailedDescription(with: font)
self.detailedDescriptionLabel.attributedStringValue = detailedDescription
}
}

View File

@@ -10,6 +10,7 @@
03F06CD52942C27E001C4D68 /* Bundle+AltStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1E314122A05D4C00370A3C /* Bundle+AltStore.swift */; };
0E05025A2BEC83C500879B5C /* OperatingSystemVersion+Comparable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0502592BEC83C500879B5C /* OperatingSystemVersion+Comparable.swift */; };
0E05025C2BEC947000879B5C /* String+SideStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E05025B2BEC947000879B5C /* String+SideStore.swift */; };
0E13E5862CC8F55900E9C0DF /* ProcessInfo+SideStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E13E5852CC8F55900E9C0DF /* ProcessInfo+SideStore.swift */; };
0E1A1F912AE36A9700364CAD /* bytearray.c in Sources */ = {isa = PBXBuildFile; fileRef = 0E1A1F902AE36A9600364CAD /* bytearray.c */; };
0E764E172ADFF5740043DD4E /* AltBackup.ipa in Resources */ = {isa = PBXBuildFile; fileRef = 0E764E162ADFF5740043DD4E /* AltBackup.ipa */; };
0EA1665B2ADFE0D2003015C1 /* out-limd.c in Sources */ = {isa = PBXBuildFile; fileRef = 0EA166472ADFE0D1003015C1 /* out-limd.c */; };
@@ -59,6 +60,7 @@
99F87D0529D8B4E200B40039 /* minimuxer-helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9961EC2D29BE9F2E00AF2C6F /* minimuxer-helpers.swift */; };
99F87D1829D8E4C900B40039 /* SwiftBridgeCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99F87D1629D8E4C900B40039 /* SwiftBridgeCore.swift */; };
99F87D1929D8E4C900B40039 /* minimuxer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99F87D1729D8E4C900B40039 /* minimuxer.swift */; };
A800F7042CE28E3800208744 /* View+AltWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = A800F7032CE28E2F00208744 /* View+AltWidget.swift */; };
B3146ED2284F581E00BBC3FD /* Roxas.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B3146ECD284F580500BBC3FD /* Roxas.framework */; };
B33FFBA8295F8E98002259E6 /* libfragmentzip.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B343F894295F7F9B002B1159 /* libfragmentzip.a */; };
B33FFBAA295F8F78002259E6 /* preboard.c in Sources */ = {isa = PBXBuildFile; fileRef = B33FFBA9295F8F78002259E6 /* preboard.c */; };
@@ -509,6 +511,7 @@
/* Begin PBXFileReference section */
0E0502592BEC83C500879B5C /* OperatingSystemVersion+Comparable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OperatingSystemVersion+Comparable.swift"; sourceTree = "<group>"; };
0E05025B2BEC947000879B5C /* String+SideStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+SideStore.swift"; sourceTree = "<group>"; };
0E13E5852CC8F55900E9C0DF /* ProcessInfo+SideStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProcessInfo+SideStore.swift"; sourceTree = "<group>"; };
0E1A1F902AE36A9600364CAD /* bytearray.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = bytearray.c; path = src/bytearray.c; sourceTree = "<group>"; };
0E764E162ADFF5740043DD4E /* AltBackup.ipa */ = {isa = PBXFileReference; lastKnownFileType = file; name = AltBackup.ipa; path = AltStore/Resources/AltBackup.ipa; sourceTree = SOURCE_ROOT; };
0EA166412ADFE0D1003015C1 /* jplist.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = jplist.c; path = Dependencies/libplist/src/jplist.c; sourceTree = SOURCE_ROOT; };
@@ -557,6 +560,7 @@
9961EC2D29BE9F2E00AF2C6F /* minimuxer-helpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "minimuxer-helpers.swift"; path = "Dependencies/minimuxer/minimuxer-helpers.swift"; sourceTree = SOURCE_ROOT; };
99F87D1629D8E4C900B40039 /* SwiftBridgeCore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SwiftBridgeCore.swift; path = Dependencies/minimuxer/SwiftBridgeCore.swift; sourceTree = SOURCE_ROOT; };
99F87D1729D8E4C900B40039 /* minimuxer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = minimuxer.swift; path = Dependencies/minimuxer/minimuxer.swift; sourceTree = SOURCE_ROOT; };
A800F7032CE28E2F00208744 /* View+AltWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+AltWidget.swift"; sourceTree = "<group>"; };
B3146EC6284F580500BBC3FD /* Roxas.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Roxas.xcodeproj; path = Dependencies/Roxas/Roxas.xcodeproj; sourceTree = "<group>"; };
B33FFBA9295F8F78002259E6 /* preboard.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = preboard.c; path = src/preboard.c; sourceTree = "<group>"; };
B33FFBAB295F8F98002259E6 /* companion_proxy.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = companion_proxy.c; path = src/companion_proxy.c; sourceTree = "<group>"; };
@@ -1006,6 +1010,14 @@
name = Generated;
sourceTree = "<group>";
};
A800F6FE2CE28DE300208744 /* Extensions */ = {
isa = PBXGroup;
children = (
A800F7032CE28E2F00208744 /* View+AltWidget.swift */,
);
path = Extensions;
sourceTree = "<group>";
};
B3146EC7284F580500BBC3FD /* Products */ = {
isa = PBXGroup;
children = (
@@ -1463,6 +1475,7 @@
BF98916C250AABF3002ACF50 /* AltWidget */ = {
isa = PBXGroup;
children = (
A800F6FE2CE28DE300208744 /* Extensions */,
BF8B17F0250AC62400F8157F /* AltWidgetExtension.entitlements */,
BF98917D250AAC4F002ACF50 /* AltWidget.swift */,
BF42345825101C1D006D1EB2 /* WidgetView.swift */,
@@ -1678,6 +1691,7 @@
BFE00A1F2503097F00EB4D0C /* INInteraction+AltStore.swift */,
D57F2C9326E01BC700B9FA39 /* UIDevice+Vibration.swift */,
B376FE3D29258C8900E18883 /* OSLog+SideStore.swift */,
0E13E5852CC8F55900E9C0DF /* ProcessInfo+SideStore.swift */,
);
path = Extensions;
sourceTree = "<group>";
@@ -2486,6 +2500,7 @@
BF42345A25101C35006D1EB2 /* WidgetView.swift in Sources */,
D55E163728776CB700A627A1 /* ComplicationView.swift in Sources */,
BF98917F250AAC4F002ACF50 /* AltWidget.swift in Sources */,
A800F7042CE28E3800208744 /* View+AltWidget.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -2552,6 +2567,7 @@
BF770E5822BC3D0F002A40FE /* RefreshGroup.swift in Sources */,
19B9B7452845E6DF0076EF69 /* SelectTeamViewController.swift in Sources */,
99F87D0529D8B4E200B40039 /* minimuxer-helpers.swift in Sources */,
0E13E5862CC8F55900E9C0DF /* ProcessInfo+SideStore.swift in Sources */,
BF18B0F122E25DF9005C4CF5 /* ToastView.swift in Sources */,
BF3D649F22E7B24C00E9056B /* CollapsingTextView.swift in Sources */,
BF02419622F2199300129732 /* RefreshAttemptsViewController.swift in Sources */,
@@ -3069,6 +3085,7 @@
CODE_SIGN_STYLE = Automatic;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
ENABLE_DEBUG_DYLIB = NO;
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
INFOPLIST_FILE = AltWidget/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
@@ -3099,6 +3116,7 @@
CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
ENABLE_DEBUG_DYLIB = NO;
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
INFOPLIST_FILE = AltWidget/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
@@ -3265,6 +3283,7 @@
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
ENABLE_BITCODE = NO;
ENABLE_DEBUG_DYLIB = NO;
GCC_UNROLL_LOOPS = YES;
INFOPLIST_FILE = AltStore/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
@@ -3304,6 +3323,7 @@
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
ENABLE_BITCODE = NO;
ENABLE_DEBUG_DYLIB = NO;
GCC_OPTIMIZATION_LEVEL = fast;
GCC_UNROLL_LOOPS = YES;
INFOPLIST_FILE = AltStore/Info.plist;
@@ -3424,8 +3444,8 @@
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/SideStore/AltSign";
requirement = {
branch = master;
kind = branch;
kind = revision;
revision = 4323ff794e600ce1759cb6ea57275e13b7ea72f2;
};
};
4879A9602861049C00FC1BBD /* XCRemoteSwiftPackageReference "OpenSSL" */ = {

View File

@@ -0,0 +1,114 @@
{
"originHash" : "ee46302f91cbb62c5234c36750d40856658e961e191f5536cf4fe74d10fc2c94",
"pins" : [
{
"identity" : "altsign",
"kind" : "remoteSourceControl",
"location" : "https://github.com/SideStore/AltSign",
"state" : {
"branch" : "master",
"revision" : "4323ff794e600ce1759cb6ea57275e13b7ea72f2"
}
},
{
"identity" : "appcenter-sdk-apple",
"kind" : "remoteSourceControl",
"location" : "https://github.com/microsoft/appcenter-sdk-apple.git",
"state" : {
"revision" : "b2dc99cfedead0bad4e6573d86c5228c89cff332",
"version" : "4.4.3"
}
},
{
"identity" : "imobiledevice.swift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/SideStore/iMobileDevice.swift",
"state" : {
"revision" : "74e481106dd155c0cd21bca6795fd9fe5f751654",
"version" : "1.0.5"
}
},
{
"identity" : "keychainaccess",
"kind" : "remoteSourceControl",
"location" : "https://github.com/kishikawakatsumi/KeychainAccess.git",
"state" : {
"revision" : "84e546727d66f1adc5439debad16270d0fdd04e7",
"version" : "4.2.2"
}
},
{
"identity" : "launchatlogin",
"kind" : "remoteSourceControl",
"location" : "https://github.com/sindresorhus/LaunchAtLogin.git",
"state" : {
"revision" : "e8171b3e38a2816f579f58f3dac1522aa39efe41",
"version" : "4.2.0"
}
},
{
"identity" : "nuke",
"kind" : "remoteSourceControl",
"location" : "https://github.com/kean/Nuke.git",
"state" : {
"revision" : "9318d02a8a6d20af56505c9673261c1fd3b3aebe",
"version" : "7.6.3"
}
},
{
"identity" : "openssl",
"kind" : "remoteSourceControl",
"location" : "https://github.com/krzyzanowskim/OpenSSL",
"state" : {
"revision" : "8cb1d641ab5ebce2cd7cf31c93baef07bed672d4",
"version" : "1.1.2301"
}
},
{
"identity" : "plcrashreporter",
"kind" : "remoteSourceControl",
"location" : "https://github.com/microsoft/PLCrashReporter.git",
"state" : {
"revision" : "81cdec2b3827feb03286cb297f4c501a8eb98df1",
"version" : "1.10.2"
}
},
{
"identity" : "semanticversion",
"kind" : "remoteSourceControl",
"location" : "https://github.com/SwiftPackageIndex/SemanticVersion.git",
"state" : {
"revision" : "ea8eea9d89842a29af1b8e6c7677f1c86e72fa42",
"version" : "0.4.0"
}
},
{
"identity" : "sparkle",
"kind" : "remoteSourceControl",
"location" : "https://github.com/sparkle-project/Sparkle.git",
"state" : {
"revision" : "0ef1ee0220239b3776f433314515fd849025673f",
"version" : "2.6.4"
}
},
{
"identity" : "starscream",
"kind" : "remoteSourceControl",
"location" : "https://github.com/daltoniam/Starscream.git",
"state" : {
"revision" : "c6bfd1af48efcc9a9ad203665db12375ba6b145a",
"version" : "4.0.8"
}
},
{
"identity" : "stprivilegedtask",
"kind" : "remoteSourceControl",
"location" : "https://github.com/JoeMatt/STPrivilegedTask.git",
"state" : {
"branch" : "master",
"revision" : "10a9150ef32d444af326beba76356ae9af95a3e7"
}
}
],
"version" : 3
}

View File

@@ -1,111 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1150"
version = "1.3">
<BuildAction
parallelizeBuildables = "NO"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "A7A6DC28A6D60809855FE404C6A3EA29"
BuildableName = "libPods-AltDaemon.a"
BlueprintName = "Pods-AltDaemon"
ReferencedContainer = "container:Pods/Pods.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BF1E314F22A0616100370A3C"
BuildableName = "libAltKit.a"
BlueprintName = "AltKit"
ReferencedContainer = "container:AltStore.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BF18BFE624857D7900DD5981"
BuildableName = "AltDaemon"
BlueprintName = "AltDaemon"
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 = "Release"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BF18BFE624857D7900DD5981"
BuildableName = "AltDaemon"
BlueprintName = "AltDaemon"
ReferencedContainer = "container:AltStore.xcodeproj">
</BuildableReference>
</MacroExpansion>
<EnvironmentVariables>
<EnvironmentVariable
key = "THEOS"
value = "~/theos"
isEnabled = "YES">
</EnvironmentVariable>
</EnvironmentVariables>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BF18BFE624857D7900DD5981"
BuildableName = "AltDaemon"
BlueprintName = "AltDaemon"
ReferencedContainer = "container:AltStore.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -1,67 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1120"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BF5C5FC4237DF5AE00EDD0C6"
BuildableName = "AltPlugin.mailbundle"
BlueprintName = "AltPlugin"
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 = "1"
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 = "BF5C5FC4237DF5AE00EDD0C6"
BuildableName = "AltPlugin.mailbundle"
BlueprintName = "AltPlugin"
ReferencedContainer = "container:AltStore.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -1,91 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1020"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BF45868C229872EA00BD7491"
BuildableName = "AltServer.app"
BlueprintName = "AltServer"
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>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BF45868C229872EA00BD7491"
BuildableName = "AltServer.app"
BlueprintName = "AltServer"
ReferencedContainer = "container:AltStore.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</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 = "BF45868C229872EA00BD7491"
BuildableName = "AltServer.app"
BlueprintName = "AltServer"
ReferencedContainer = "container:AltStore.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BF45868C229872EA00BD7491"
BuildableName = "AltServer.app"
BlueprintName = "AltServer"
ReferencedContainer = "container:AltStore.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -1,77 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1230"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BFF7C903257844C900E55F36"
BuildableName = "AltXPC.xpc"
BlueprintName = "AltXPC"
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 = "BF45868C229872EA00BD7491"
BuildableName = "AltServer.app"
BlueprintName = "AltServer"
ReferencedContainer = "container:AltStore.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BFF7C903257844C900E55F36"
BuildableName = "AltXPC.xpc"
BlueprintName = "AltXPC"
ReferencedContainer = "container:AltStore.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -55,7 +55,26 @@
argument = "-com.apple.CoreData.ConcurrencyDebug 1"
isEnabled = "YES">
</CommandLineArgument>
<CommandLineArgument
argument = "-com.apple.CoreData.MigrationDebug 1"
isEnabled = "YES">
</CommandLineArgument>
<CommandLineArgument
argument = "-com.apple.CoreData.SQLiteIntegrityCheck 1"
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "-com.apple.CoreData.SQLDebug 1"
isEnabled = "NO">
</CommandLineArgument>
</CommandLineArguments>
<EnvironmentVariables>
<EnvironmentVariable
key = "OS_ACTIVITY_MODE"
value = "$(DEBUG_ACTIVITY_MODE)"
isEnabled = "YES">
</EnvironmentVariable>
</EnvironmentVariables>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"

View File

@@ -55,7 +55,26 @@
argument = "-com.apple.CoreData.ConcurrencyDebug 1"
isEnabled = "YES">
</CommandLineArgument>
<CommandLineArgument
argument = "-com.apple.CoreData.MigrationDebug 1"
isEnabled = "YES">
</CommandLineArgument>
<CommandLineArgument
argument = "-com.apple.CoreData.SQLiteIntegrityCheck 1"
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "-com.apple.CoreData.SQLDebug 1"
isEnabled = "NO">
</CommandLineArgument>
</CommandLineArguments>
<EnvironmentVariables>
<EnvironmentVariable
key = "OS_ACTIVITY_MODE"
value = "$(DEBUG_ACTIVITY_MODE)"
isEnabled = "YES">
</EnvironmentVariable>
</EnvironmentVariables>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"

View File

@@ -2,6 +2,12 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.kernel.extended-virtual-addressing</key>
<true/>
<key>com.apple.developer.kernel.increased-debugging-memory-limit</key>
<true/>
<key>com.apple.developer.kernel.increased-memory-limit</key>
<true/>
<key>com.apple.developer.siri</key>
<true/>
<key>com.apple.security.application-groups</key>

View File

@@ -112,6 +112,8 @@ private extension AuthenticationViewController
let toastView = ToastView(error: error)
toastView.show(in: self)
toastView.textLabel.textColor = .altPrimary
toastView.detailTextLabel.textColor = .altPrimary
self.toastView = toastView
self.signInButton.isIndicatingActivity = false

View File

@@ -0,0 +1,96 @@
//
// ProcessInfo+SideStore.swift
// SideStore
//
// Created by ny on 10/23/24.
// Copyright © 2024 SideStore. All rights reserved.
//
import Foundation
fileprivate struct BuildVersion: Comparable {
let prefix: String
let numericPart: Int
let suffix: Character?
init?(_ buildString: String) {
// Initialize indices
var index = buildString.startIndex
// Extract prefix (letters before the numeric part)
while index < buildString.endIndex, !buildString[index].isNumber {
index = buildString.index(after: index)
}
guard index > buildString.startIndex else { return nil }
self.prefix = String(buildString[buildString.startIndex..<index])
// Extract numeric part
let startOfNumeric = index
while index < buildString.endIndex, buildString[index].isNumber {
index = buildString.index(after: index)
}
guard let numericValue = Int(buildString[startOfNumeric..<index]) else { return nil }
self.numericPart = numericValue
// Extract suffix (if any)
if index < buildString.endIndex {
self.suffix = buildString[index]
} else {
self.suffix = nil
}
}
// Implement Comparable protocol
static func < (lhs: BuildVersion, rhs: BuildVersion) -> Bool {
// Compare prefixes
if lhs.prefix != rhs.prefix {
return lhs.prefix < rhs.prefix
}
// Compare numeric parts
if lhs.numericPart != rhs.numericPart {
return lhs.numericPart < rhs.numericPart
}
// Compare suffixes
switch (lhs.suffix, rhs.suffix) {
case let (l?, r?):
return l < r
case (nil, _?):
return true // nil is considered less than any character
case (_?, nil):
return false
default:
return false // Both are nil and equal
}
}
static func == (lhs: BuildVersion, rhs: BuildVersion) -> Bool {
return lhs.prefix == rhs.prefix &&
lhs.numericPart == rhs.numericPart &&
lhs.suffix == rhs.suffix
}
}
extension ProcessInfo {
var shortVersion: String {
operatingSystemVersionString
.replacingOccurrences(of: "Version ", with: "")
.replacingOccurrences(of: "Build ", with: "")
}
var operatingSystemBuild: String {
if let start = shortVersion.range(of: "(")?.upperBound,
let end = shortVersion.range(of: ")")?.lowerBound {
shortVersion[start..<end].replacingOccurrences(of: "Build ", with: "")
} else { "???" }
}
var sparseRestorePatched: Bool {
if operatingSystemVersion < OperatingSystemVersion(majorVersion: 18, minorVersion: 1, patchVersion: 0) { false }
else if operatingSystemVersion > OperatingSystemVersion(majorVersion: 18, minorVersion: 1, patchVersion: 1) { true }
else if operatingSystemVersion >= OperatingSystemVersion(majorVersion: 18, minorVersion: 1, patchVersion: 0),
let currentBuild = BuildVersion(operatingSystemBuild),
let targetBuild = BuildVersion("22B5054e") {
currentBuild >= targetBuild
} else { false }
}
}

View File

@@ -92,7 +92,7 @@
<key>LSRequiresIPhoneOS</key>
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
<false/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>

View File

@@ -118,8 +118,9 @@ private extension IntentHandler
{
// Queue response in case refreshing finishes after confirm() but before handle().
self.queuedResponses[intent] = response
UIApplication.shared.perform(#selector(NSXPCConnection.suspend))
DispatchQueue.main.async {
UIApplication.shared.perform(#selector(NSXPCConnection.suspend))
}
}
}
}

View File

@@ -240,7 +240,7 @@ extension AppManager
func deactivateApps(for app: ALTApplication, presentingViewController: UIViewController, completion: @escaping (Result<Void, Error>) -> Void)
{
guard let activeAppsLimit = UserDefaults.standard.activeAppsLimit else { return completion(.success(())) }
guard !UserDefaults.standard.isAppLimitDisabled, let activeAppsLimit = UserDefaults.standard.activeAppsLimit else { return completion(.success(())) }
DispatchQueue.main.async {
let activeApps = InstalledApp.fetchActiveApps(in: DatabaseManager.shared.viewContext)
@@ -1015,89 +1015,133 @@ private extension AppManager
return group
}
func removeAppExtensions(from application: ALTApplication, extensions: Set<ALTApplication>, _ presentingViewController: UIViewController, completion: @escaping (Result<Void, Error>) -> Void)
{
guard !application.appExtensions.isEmpty else { return completion(.success(())) }
let firstSentence: String
if UserDefaults.standard.activeAppLimitIncludesExtensions
{
firstSentence = NSLocalizedString("Non-developer Apple IDs are limited to 3 active apps and app extensions.", comment: "")
}
else
{
firstSentence = NSLocalizedString("Non-developer Apple IDs are limited to creating 10 App IDs per week.", comment: "")
}
let message = firstSentence + " " + NSLocalizedString("Would you like to remove this app's extensions so they don't count towards your limit? There are \(extensions.count) Extensions", comment: "")
let alertController = UIAlertController(title: NSLocalizedString("App Contains Extensions", comment: ""), message: message, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: UIAlertAction.cancel.title, style: UIAlertAction.cancel.style, handler: { (action) in
completion(.failure(OperationError.cancelled))
}))
alertController.addAction(UIAlertAction(title: NSLocalizedString("Keep App Extensions", comment: ""), style: .default) { (action) in
completion(.success(()))
})
alertController.addAction(UIAlertAction(title: NSLocalizedString("Remove App Extensions", comment: ""), style: .destructive) { (action) in
do
{
for appExtension in application.appExtensions
{
try FileManager.default.removeItem(at: appExtension.fileURL)
func removeAppExtensions(
from application: ALTApplication,
existingApp: InstalledApp?,
extensions: Set<ALTApplication>,
_ presentingViewController: UIViewController?,
completion: @escaping (Result<Void, Error>) -> Void
) {
// App-Extensions: Ensure existing app's extensions and currently installing app's extensions must match
if let existingApp {
_ = RSTAsyncBlockOperation { _ in
let existingAppEx: Set<InstalledExtension> = existingApp.appExtensions
let currentAppEx: Set<ALTApplication> = application.appExtensions
let currentAppExNames = currentAppEx.map{ appEx in appEx.bundleIdentifier}
let existingAppExNames = existingAppEx.map{ appEx in appEx.bundleIdentifier}
let excessExtensions = currentAppEx.filter{
!(existingAppExNames.contains($0.bundleIdentifier))
}
completion(.success(()))
let isMatching = (currentAppEx.count == existingAppEx.count) && excessExtensions.isEmpty
let diagnosticsMsg = "AppManager.removeAppExtensions: App Extensions in existingApp and currentApp are matching: \(isMatching)\n"
+ "AppManager.removeAppExtensions: existingAppEx: \(existingAppExNames); currentAppEx: \(String(describing: currentAppExNames))\n"
print(diagnosticsMsg)
// if background mode, then remove only the excess extensions
guard let presentingViewController: UIViewController = presentingViewController else {
// perform silent extensions cleanup for those that aren't already present in existing app
print("\n Performing background mode Extensions removal \n")
print("AppManager.removeAppExtensions: Excess Extensions: \(excessExtensions)")
do {
for appExtension in excessExtensions {
print("Deleting extension \(appExtension.bundleIdentifier)")
try FileManager.default.removeItem(at: appExtension.fileURL)
}
return completion(.success(()))
} catch {
return completion(.failure(error))
}
}
}
catch
{
completion(.failure(error))
}
})
}
guard !application.appExtensions.isEmpty else { return completion(.success(())) }
alertController.addAction(UIAlertAction(title: NSLocalizedString("Choose App Extensions", comment: ""), style: .default) { (action) in
let popoverContentController = AppExtensionViewHostingController(extensions: extensions) { (selection) in
DispatchQueue.main.async {
let firstSentence: String
if UserDefaults.standard.activeAppLimitIncludesExtensions
{
firstSentence = NSLocalizedString("Non-developer Apple IDs are limited to 3 active apps and app extensions.", comment: "")
}
else
{
firstSentence = NSLocalizedString("Non-developer Apple IDs are limited to creating 10 App IDs per week.", comment: "")
}
let message = firstSentence + " " + NSLocalizedString("Would you like to remove this app's extensions so they don't count towards your limit? There are \(extensions.count) Extensions", comment: "")
let alertController = UIAlertController(title: NSLocalizedString("App Contains Extensions", comment: ""), message: message, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: UIAlertAction.cancel.title, style: UIAlertAction.cancel.style, handler: { (action) in
completion(.failure(OperationError.cancelled))
}))
alertController.addAction(UIAlertAction(title: NSLocalizedString("Keep App Extensions", comment: ""), style: .default) { (action) in
completion(.success(()))
})
alertController.addAction(UIAlertAction(title: NSLocalizedString("Remove App Extensions", comment: ""), style: .destructive) { (action) in
do
{
for appExtension in selection
for appExtension in application.appExtensions
{
print("Deleting extension \(appExtension.bundleIdentifier)")
try FileManager.default.removeItem(at: appExtension.fileURL)
}
completion(.success(()))
}
catch
{
completion(.failure(error))
}
return nil
}
})
let suiview = popoverContentController.view!
suiview.translatesAutoresizingMaskIntoConstraints = false
popoverContentController.modalPresentationStyle = .popover
if let popoverPresentationController = popoverContentController.popoverPresentationController {
popoverPresentationController.sourceView = presentingViewController.view
popoverPresentationController.sourceRect = CGRect(x: 50, y: 50, width: 4, height: 4)
popoverPresentationController.delegate = popoverContentController
if let presentingViewController {
alertController.addAction(UIAlertAction(title: NSLocalizedString("Choose App Extensions", comment: ""), style: .default) { (action) in
let popoverContentController = AppExtensionViewHostingController(extensions: extensions) { (selection) in
do
{
for appExtension in selection
{
print("Deleting extension \(appExtension.bundleIdentifier)")
try FileManager.default.removeItem(at: appExtension.fileURL)
}
completion(.success(()))
}
catch
{
completion(.failure(error))
}
return nil
}
let suiview = popoverContentController.view!
suiview.translatesAutoresizingMaskIntoConstraints = false
popoverContentController.modalPresentationStyle = .popover
if let popoverPresentationController = popoverContentController.popoverPresentationController {
popoverPresentationController.sourceView = presentingViewController.view
popoverPresentationController.sourceRect = CGRect(x: 50, y: 50, width: 4, height: 4)
popoverPresentationController.delegate = popoverContentController
presentingViewController.present(popoverContentController, animated: true)
}
})
DispatchQueue.main.async {
presentingViewController.present(popoverContentController, animated: true)
}
presentingViewController.present(alertController, animated: true)
}
})
DispatchQueue.main.async {
presentingViewController.present(alertController, animated: true)
}
}
private func _install(_ app: AppProtocol, operation appOperation: AppOperation, group: RefreshGroup, context: InstallAppOperationContext? = nil, additionalEntitlements: [ALTEntitlement: Any]? = nil, cacheApp: Bool = true, completionHandler: @escaping (Result<InstalledApp, Error>) -> Void) -> Progress
private func _install(_ app: AppProtocol, operation appOperation: AppOperation, group: RefreshGroup, context: InstallAppOperationContext? = nil, additionalEntitlements: [ALTEntitlement: Any] = [.increasedDebuggingMemoryLimit: ALTEntitlement.increasedDebuggingMemoryLimit, .increasedMemoryLimit: ALTEntitlement.increasedMemoryLimit, .extendedVirtualAddressing: ALTEntitlement.extendedVirtualAddressing], cacheApp: Bool = true, completionHandler: @escaping (Result<InstalledApp, Error>) -> Void) -> Progress
{
let progress = Progress.discreteProgress(totalUnitCount: 100)
@@ -1178,18 +1222,26 @@ private extension AppManager
{
throw error
}
/*
guard case .install = appOperation else {
operation.finish()
return
}
*/
guard let extensions = context.app?.appExtensions else {
throw OperationError.invalidParameters("AppManager._install.removeAppExtensionsOperation: context.app?.appExtensions is nil")
}
guard let extensions = context.app?.appExtensions else { throw OperationError.invalidParameters }
guard let app = context.app, let presentingViewController = context.authenticatedContext.presentingViewController else { throw OperationError.invalidParameters }
guard let currentApp = context.app else {
throw OperationError.invalidParameters("AppManager._install.removeAppExtensionsOperation: context.app is nil")
}
self?.removeAppExtensions(from: app, extensions: extensions, presentingViewController) { result in
self?.removeAppExtensions(from: currentApp,
existingApp: app as? InstalledApp,
extensions: extensions,
context.authenticatedContext.presentingViewController
) { result in
switch result {
case .success(): break
case .failure(let error): context.error = error
@@ -1200,7 +1252,7 @@ private extension AppManager
}
catch
{
group.context.error = error
context.error = error
operation.finish()
}
}
@@ -1252,13 +1304,20 @@ private extension AppManager
throw error
}
guard let profiles = context.provisioningProfiles else { throw OperationError.invalidParameters }
guard let profiles = context.provisioningProfiles else {
throw OperationError.invalidParameters("AppManager._install.deactivateAppsOperation: context.provisioningProfiles is nil")
}
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("AppManager._install.deactivateAppsOperation: self.context.app or context.authenticatedContext.presentingViewController is nil")
}
self?.deactivateApps(for: app, presentingViewController: presentingViewController) { result in
switch result
@@ -1297,7 +1356,9 @@ private extension AppManager
throw error
}
guard let app = context.app else { throw OperationError.invalidParameters }
guard let app = context.app else {
throw OperationError.invalidParameters("AppManager._install.patchAppOperation: context.app is nil")
}
guard let isUntetherRequired = app.bundle.infoDictionary?[Bundle.Info.untetherRequired] as? Bool,
let minimumiOSVersionString = app.bundle.infoDictionary?[Bundle.Info.untetherMinimumiOSVersion] as? String,
@@ -1402,57 +1463,7 @@ private extension AppManager
progress.addChild(installOperation.progress, withPendingUnitCount: 30)
installOperation.addDependency(sendAppOperation)
let notificationRegistrationOperation = RSTAsyncBlockOperation { (operation) in
do
{
if let error = context.error
{
throw error
}
guard let app = context.installedApp else { operation.finish(); return }
let content = UNMutableNotificationContent()
content.title = "App Expiring Soon"
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .full
formatter.includesApproximationPhrase = false
formatter.includesTimeRemainingPhrase = false
formatter.allowedUnits = [.day, .hour, .minute]
formatter.maximumUnitCount = 1
let scheduledDate = DateInterval(start: Date(), duration: 60 * 60 * 24 * 6)
guard let timeLeft = formatter.string(from: scheduledDate.end, to: app.expirationDate) else { operation.finish(); return }
content.body = "App \(app.name) is expiring in \(timeLeft). Open SideStore to refresh now"
var dateComponents = DateComponents()
dateComponents.calendar = Calendar.current
let trigger = UNCalendarNotificationTrigger(
dateMatching: dateComponents, repeats: true)
let request = UNNotificationRequest(identifier: app.bundleIdentifier, content: content, trigger: trigger)
let notificationCenter = UNUserNotificationCenter.current()
notificationCenter.add(request) {_ in
operation.finish()
}
}
catch
{
operation.finish()
}
}
notificationRegistrationOperation.addDependency(installOperation)
let operations = [downloadOperation, verifyOperation, removeAppExtensionsOperation, refreshAnisetteDataOperation, fetchProvisioningProfilesOperation, deactivateAppsOperation, patchAppOperation, resignAppOperation, sendAppOperation, installOperation, notificationRegistrationOperation]
let operations = [downloadOperation, verifyOperation, removeAppExtensionsOperation, refreshAnisetteDataOperation, fetchProvisioningProfilesOperation, deactivateAppsOperation, patchAppOperation, resignAppOperation, sendAppOperation, installOperation]
group.add(operations)
self.run(operations, context: group.context)
@@ -1466,6 +1477,24 @@ private extension AppManager
let context = AppOperationContext(bundleIdentifier: app.bundleIdentifier, authenticatedContext: group.context)
context.app = ALTApplication(fileURL: app.fileURL)
//App-Extensions: Ensure DB data and disk state must match
let dbAppEx: Set<InstalledExtension> = Set(app.appExtensions)
let diskAppEx: Set<ALTApplication> = Set(context.app!.appExtensions)
let diskAppExNames = diskAppEx.map { $0.bundleIdentifier }
let dbAppExNames = dbAppEx.map{ $0.bundleIdentifier }
let isMatching = Set(dbAppExNames) == Set(diskAppExNames)
let validateAppExtensionsOperation = RSTAsyncBlockOperation { op in
let errMessage = "AppManager.refresh: App Extensions in DB and Disk are matching: \(isMatching)\n"
+ "AppManager.refresh: dbAppEx: \(dbAppExNames); diskAppEx: \(String(describing: diskAppExNames))\n"
print(errMessage)
if(!isMatching){
completionHandler(.failure(OperationError.refreshAppFailed(message: errMessage)))
}
op.finish()
}
/* Fetch Provisioning Profiles */
let fetchProvisioningProfilesOperation = FetchProvisioningProfilesOperation(context: context)
fetchProvisioningProfilesOperation.resultHandler = { (result) in
@@ -1476,6 +1505,8 @@ private extension AppManager
}
}
progress.addChild(fetchProvisioningProfilesOperation.progress, withPendingUnitCount: 60)
fetchProvisioningProfilesOperation.addDependency(validateAppExtensionsOperation)
/* Refresh */
let refreshAppOperation = RefreshAppOperation(context: context)
@@ -1509,7 +1540,7 @@ private extension AppManager
progress.addChild(refreshAppOperation.progress, withPendingUnitCount: 40)
refreshAppOperation.addDependency(fetchProvisioningProfilesOperation)
let operations = [fetchProvisioningProfilesOperation, refreshAppOperation]
let operations = [validateAppExtensionsOperation, fetchProvisioningProfilesOperation, refreshAppOperation]
group.add(operations)
self.run(operations, context: group.context)
@@ -1970,7 +2001,43 @@ private extension AppManager
UNUserNotificationCenter.current().add(request)
}
func log(_ error: Error, for operation: AppOperation)
{
// Sanitize NSError on same thread before performing background task.
let sanitizedError = (error as NSError).sanitizedForSerialization()
let loggedErrorOperation: LoggedError.Operation = {
switch operation
{
case .install: return .install
case .update: return .update
case .refresh: return .refresh
case .activate: return .activate
case .deactivate: return .deactivate
case .backup: return .backup
case .restore: return .restore
}
}()
DatabaseManager.shared.persistentContainer.performBackgroundTask { context in
var app = operation.app
if let managedApp = app as? NSManagedObject, let tempApp = context.object(with: managedApp.objectID) as? AppProtocol
{
app = tempApp
}
do
{
_ = LoggedError(error: sanitizedError, app: app, operation: loggedErrorOperation, context: context)
try context.save()
}
catch let saveError
{
print("[ALTLog] Failed to log error \(sanitizedError.domain) code \(sanitizedError.code) for \(app.bundleIdentifier):", saveError)
}
}
}
func run(_ operations: [Foundation.Operation], context: OperationContext?, requiresSerialQueue: Bool = false)
{
// Find "Install AltStore" operation if it already exists in `context`

View File

@@ -791,7 +791,9 @@ private extension MyAppsViewController
throw error
}
guard let fileURL = context.fileURL else { throw OperationError.invalidParameters }
guard let fileURL = context.fileURL else {
throw OperationError.invalidParameters("MyAppsViewController.sideloadApp.unzipAppOperation: context.fileURL is nil")
}
defer {
try? FileManager.default.removeItem(at: fileURL)
}
@@ -825,7 +827,9 @@ private extension MyAppsViewController
throw error
}
guard let application = context.application else { throw OperationError.invalidParameters }
guard let application = context.application else {
throw OperationError.invalidParameters("MyAppsViewController.sideloadApp.installAppOperation: context.application is nil")
}
let group = AppManager.shared.install(application, presentingViewController: self) { (result) in
switch result
@@ -994,7 +998,7 @@ private extension MyAppsViewController
}
}
if UserDefaults.standard.activeAppsLimit != nil, #available(iOS 13, *)
if !UserDefaults.standard.isAppLimitDisabled && UserDefaults.standard.activeAppsLimit != nil, #available(iOS 13, *)
{
// UserDefaults.standard.activeAppsLimit is only non-nil on iOS 13.3.1 or later, so the #available check is just so we can use Combine.
@@ -1354,7 +1358,7 @@ extension MyAppsViewController
headerView.layoutMargins.left = self.view.layoutMargins.left
headerView.layoutMargins.right = self.view.layoutMargins.right
if UserDefaults.standard.activeAppsLimit == nil
if UserDefaults.standard.activeAppsLimit == nil || UserDefaults.standard.isAppLimitDisabled
{
headerView.textLabel.text = NSLocalizedString("Installed", comment: "")
}
@@ -1753,7 +1757,7 @@ extension MyAppsViewController: UICollectionViewDragDelegate
return []
case .activeApps, .inactiveApps:
guard UserDefaults.standard.activeAppsLimit != nil else { return [] }
guard UserDefaults.standard.activeAppsLimit != nil && !UserDefaults.standard.isAppLimitDisabled else { return [] }
guard let cell = collectionView.cellForItem(at: indexPath as IndexPath) as? InstalledAppCollectionViewCell else { return [] }
let item = self.dataSource.item(at: indexPath)
@@ -1808,6 +1812,7 @@ extension MyAppsViewController: UICollectionViewDropDelegate
func collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal
{
guard
!UserDefaults.standard.isAppLimitDisabled,
let activeAppsLimit = UserDefaults.standard.activeAppsLimit,
let installedApp = session.items.first?.localObject as? InstalledApp
else { return UICollectionViewDropProposal(operation: .cancel) }

View File

@@ -241,12 +241,11 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A
}
let activeAppsMinimumVersion = OperatingSystemVersion(majorVersion: 13, minorVersion: 3, patchVersion: 1)
if team.type == .free, ProcessInfo.processInfo.isOperatingSystemAtLeast(activeAppsMinimumVersion)
{
if team.type == .free, !UserDefaults.standard.isAppLimitDisabled, ProcessInfo().sparseRestorePatched {
UserDefaults.standard.activeAppsLimit = ALTActiveAppsLimit
}
else
{
} else if UserDefaults.standard.isAppLimitDisabled, !ProcessInfo().sparseRestorePatched {
UserDefaults.standard.activeAppsLimit = 10
} else {
UserDefaults.standard.activeAppsLimit = nil
}

View File

@@ -48,7 +48,9 @@ class BackupAppOperation: ResultOperation<Void>
{
if let error = self.context.error { throw error }
guard let installedApp = self.context.installedApp, let context = installedApp.managedObjectContext else { throw OperationError.invalidParameters }
guard let installedApp = self.context.installedApp, let context = installedApp.managedObjectContext else {
throw OperationError.invalidParameters("BackupAppOperation.main: self.context.installedApp or installedApp.managedObjectContext is nil")
}
context.perform {
do
{

View File

@@ -20,6 +20,7 @@ final class DownloadAppOperation: ResultOperation<ALTApplication>
private let appName: String
private let bundleIdentifier: String
private var sourceURL: URL?
private let destinationURL: URL
private let session = URLSession(configuration: .default)
@@ -32,6 +33,7 @@ final class DownloadAppOperation: ResultOperation<ALTApplication>
self.appName = app.name
self.bundleIdentifier = app.bundleIdentifier
self.sourceURL = app.url
self.destinationURL = destinationURL
super.init()
@@ -111,7 +113,7 @@ private extension DownloadAppOperation {
}
func download(@Managed _ app: AppProtocol) {
guard let sourceURL = $app.url else { return self.finish(.failure(OperationError.appNotFound(name: self.appName))) }
guard let sourceURL = self.sourceURL else { return self.finish(.failure(OperationError.appNotFound(name: self.appName))) }
self.downloadIPA(from: sourceURL) { result in
do

View File

@@ -50,7 +50,9 @@ final class EnableJITOperation<Context: EnableJITContext>: ResultOperation<Void>
return
}
guard let installedApp = self.context.installedApp else { return self.finish(.failure(OperationError.invalidParameters)) }
guard let installedApp = self.context.installedApp else {
return self.finish(.failure(OperationError.invalidParameters("EnableJITOperation.main: self.context.installedApp is nil")))
}
if #available(iOS 17, *) {
let sideJITenabled = UserDefaults.standard.sidejitenable
let SideJITIP = UserDefaults.standard.textInputSideJITServerurl ?? ""

View File

@@ -45,17 +45,122 @@ final class FetchAnisetteDataOperation: ResultOperation<ALTAnisetteData>, WebSoc
return
}
self.url = URL(string: UserDefaults.standard.menuAnisetteURL)
print("Anisette URL: \(self.url!.absoluteString)")
// TODO: Pass in proper view context to show the Toast messages
let viewContext = context.presentingViewController
if let identifier = Keychain.shared.identifier,
let adiPb = Keychain.shared.adiPb {
fetchAnisetteV3(identifier, adiPb)
} else {
provision()
getAnisetteServerUrl(viewContext){ url, error in
guard let urlString = url else {
self.finish(.failure(error!))
return
}
// set as preferred
UserDefaults.standard.menuAnisetteURL = urlString
let url = URL(string: urlString)
self.url = url
print("Anisette URL: \(self.url!.absoluteString)")
if let identifier = Keychain.shared.identifier,
let adiPb = Keychain.shared.adiPb {
self.fetchAnisetteV3(identifier, adiPb)
} else {
self.provision()
}
}
}
func getAnisetteServerUrl(_ viewContext: UIViewController?, completion: @escaping (String?, Error?) -> Void) {
var serverUrls = UserDefaults.standard.menuAnisetteServersList
let currentServer = UserDefaults.standard.menuAnisetteURL
// Prioritize the current server by moving it to the top of the list
if let currentServerIndex = serverUrls.firstIndex(of: currentServer) {
serverUrls.remove(at: currentServerIndex)
serverUrls.insert(currentServer, at: 0)
}
tryNextServer(from: serverUrls, viewContext, currentIndex: 0, completion: completion)
}
private func showToast(viewContext: UIViewController?, message: String){
if let viewContext = viewContext{
let error = OperationError.anisetteV1Error(message: message)
let toastView = ToastView(error: error)
// toastView.textLabel.textColor = .altPrimary
// toastView.detailTextLabel.textColor = .altPrimary
DispatchQueue.main.async {
toastView.show(in: viewContext)
}
}
}
private func tryNextServer(from serverUrls: [String], _ viewContext: UIViewController?,currentIndex: Int, completion: @escaping (String?, Error?) -> Void) {
// Check if all URLs have been exhausted
guard currentIndex < serverUrls.count else {
let error = NSError(domain: "AnisetteError", code: 0, userInfo: [NSLocalizedDescriptionKey: "No valid server found."])
completion(nil, error)
return
}
let currentServerUrlString = serverUrls[currentIndex]
guard let url = URL(string: currentServerUrlString) else {
// Invalid URL, skip to next
let errmsg = "Skipping invalid URL: \(currentServerUrlString)"
print(errmsg)
showToast(viewContext: viewContext, message: errmsg)
tryNextServer(from: serverUrls, viewContext, currentIndex: currentIndex + 1, completion: completion)
return
}
// Attempt to ping the current URL
pingServer(url) { success, error in
if success {
// If the server is reachable, return the URL
let okmsg = "Found working server: \(url.absoluteString)"
print(okmsg)
if(currentIndex > 0){
// notify user if available server is different the user-specified one
self.showToast(viewContext: viewContext, message: okmsg)
}
completion(url.absoluteString, nil)
} else {
// If not, try the next URL
let errmsg = "Failed to reach server: \(url.absoluteString), trying next server."
print(errmsg)
self.showToast(viewContext: viewContext, message: errmsg)
self.tryNextServer(from: serverUrls, viewContext, currentIndex: currentIndex + 1, completion: completion)
}
}
}
func pingServer(_ url: URL, completion: @escaping (Bool, Error?) -> Void) {
var request = URLRequest(url: url)
request.timeoutInterval = 10 // Timeout after 10 seconds
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
if let error = error {
completion(false, error)
return
}
let httpResponse = response as? HTTPURLResponse
let statusCode = httpResponse?.statusCode
guard let statusCode = statusCode,
(200...299).contains(statusCode) else {
let serverError = OperationError.anisetteV3Error(message: "Server unreachable or invalid response: \(String(describing: statusCode ?? nil))")
completion(false, serverError)
return
}
completion(true, nil)
}
task.resume()
}
// MARK: - COMMON
func extractAnisetteData(_ data: Data, _ response: HTTPURLResponse?, v3: Bool) throws {

View File

@@ -39,7 +39,9 @@ final class FetchAppIDsOperation: ResultOperation<([AppID], NSManagedObjectConte
guard
let team = self.context.team,
let session = self.context.session
else { return self.finish(.failure(OperationError.invalidParameters)) }
else {
return self.finish(.failure(OperationError.invalidParameters("FetchAppIDsOperation.main: self.context.team or self.context.session is nil")))
}
ALTAppleAPI.shared.fetchAppIDs(for: team, session: session) { (appIDs, error) in
self.managedObjectContext.perform {

View File

@@ -43,7 +43,8 @@ final class FetchProvisioningProfilesOperation: ResultOperation<[String: ALTProv
guard
let team = self.context.team,
let session = self.context.session
else { return self.finish(.failure(OperationError.invalidParameters)) }
else {
return self.finish(.failure(OperationError.invalidParameters("FetchProvisioningProfilesOperation.main: self.context.team or self.context.session is nil"))) }
guard let app = self.context.app else { return self.finish(.failure(OperationError.appNotFound(name: nil))) }
@@ -262,6 +263,10 @@ extension FetchProvisioningProfilesOperation
{
throw OperationError.maximumAppIDLimitReached(appName: application.name, requiredAppIDs: requiredAppIDs, availableAppIDs: availableAppIDs, expirationDate: expirationDate)
}
else
{
throw ALTAppleAPIError(.maximumAppIDLimitReached)
}
}
}
//App ID name must be ascii. If the name is not ascii, using bundleID instead
@@ -294,6 +299,19 @@ extension FetchProvisioningProfilesOperation
}
}
}
catch ALTAppleAPIError.bundleIdentifierUnavailable {
ALTAppleAPI.shared.fetchAppIDs(for: team, session: session) {res, err in
if let err = err {
return completionHandler(.failure(err))
}
guard let res = res else {return completionHandler(.failure(ALTError(.unknown)))}
for appid in res {
if appid.bundleIdentifier == bundleIdentifier {
completionHandler(.success(appid))
}
}
}
}
catch
{
completionHandler(.failure(error))
@@ -358,7 +376,9 @@ extension FetchProvisioningProfilesOperation
}
}
if updateFeatures
appID.entitlements = entitlements
if updateFeatures || true
{
let appID = appID.copy() as! ALTAppID
appID.features = features

View File

@@ -43,7 +43,9 @@ final class InstallAppOperation: ResultOperation<InstalledApp>
let certificate = self.context.certificate,
let resignedApp = self.context.resignedApp,
let provisioningProfiles = self.context.provisioningProfiles
else { return self.finish(.failure(OperationError.invalidParameters)) }
else {
return self.finish(.failure(OperationError.invalidParameters("InstallAppOperation.main: self.context.certificate or self.context.resignedApp or self.context.provisioningProfiles is nil")))
}
let backgroundContext = DatabaseManager.shared.persistentContainer.newBackgroundContext()
backgroundContext.perform {
@@ -112,6 +114,22 @@ final class InstallAppOperation: ResultOperation<InstalledApp>
}
installedApp.appExtensions = installedExtensions
// Remove stale "PlugIns" (Extensions) from currently installed App
if let installedAppExns = ALTApplication(fileURL: installedApp.fileURL)?.appExtensions {
let currentAppExns = Set(installedApp.appExtensions).map{ $0.bundleIdentifier }
let staleAppExns = installedAppExns.filter{ !currentAppExns.contains($0.bundleIdentifier) }
for staleAppExn in staleAppExns {
do {
try FileManager.default.removeItem(at: staleAppExn.fileURL)
print("InstallAppOperation.appExtensions: removed stale app-extension: \(staleAppExn.fileURL)")
} catch {
print("InstallAppOperation.appExtensions processing error Error: \(error)")
}
}
}
self.context.beginInstallationHandler?(installedApp)
@@ -222,8 +240,10 @@ final class InstallAppOperation: ResultOperation<InstalledApp>
do
{
try FileManager.default.removeItem(at: fileURL)
print("Removed refreshed IPA")
if(FileManager.default.fileExists(atPath: fileURL.path)){
try FileManager.default.removeItem(at: fileURL)
print("Removed refreshed IPA")
}
}
catch
{

View File

@@ -35,6 +35,7 @@ extension OperationError
case noSources
case openAppFailed//(name: String)
case missingAppGroup
case refreshAppFailed
// Connection
case noWiFi = 1200
@@ -55,7 +56,6 @@ extension OperationError
static let notAuthenticated: OperationError = .init(code: .notAuthenticated)
static let unknownUDID: OperationError = .init(code: .unknownUDID)
static let invalidApp: OperationError = .init(code: .invalidApp)
static let invalidParameters: OperationError = .init(code: .invalidParameters)
static let noSources: OperationError = .init(code: .noSources)
static let missingAppGroup: OperationError = .init(code: .missingAppGroup)
@@ -78,6 +78,7 @@ extension OperationError
static func openAppFailed(name: String?) -> OperationError {
OperationError(code: .openAppFailed, appName: name)
}
static let domain = OperationError(code: .unknown)._domain
static func SideJITIssue(error: String?) -> OperationError {
var o = OperationError(code: .SideJITIssue)
@@ -107,6 +108,13 @@ extension OperationError
OperationError(code: .anisetteV3Error, failureReason: message)
}
static func refreshAppFailed(message: String) -> OperationError {
OperationError(code: .refreshAppFailed, failureReason: message)
}
static func invalidParameters(_ message: String? = nil) -> OperationError {
OperationError(code: .invalidParameters, failureReason: message)
}
}
@@ -155,7 +163,6 @@ struct OperationError: ALTLocalizedError {
case .notAuthenticated: return NSLocalizedString("You are not signed in.", comment: "")
case .unknownUDID: return NSLocalizedString("SideStore could not determine this device's UDID.", comment: "")
case .invalidApp: return NSLocalizedString("The app is in an invalid format.", comment: "")
case .invalidParameters: return NSLocalizedString("Invalid parameters.", comment: "")
case .maximumAppIDLimitReached: return NSLocalizedString("Cannot register more than 10 App IDs within a 7 day period.", comment: "")
case .noSources: return NSLocalizedString("There are no SideStore sources.", comment: "")
case .missingAppGroup: return NSLocalizedString("SideStore's shared app group could not be accessed.", comment: "")
@@ -176,7 +183,16 @@ struct OperationError: ALTLocalizedError {
case .anisetteV3Error: return 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: "")
case .cacheClearError: return NSLocalizedString("An error occurred while clearing cache: %@", comment: "")
case .SideJITIssue: return NSLocalizedString("An error occurred while using SideJIT: %@", comment: "")
case .refreshAppFailed:
let message = self._failureReason ?? ""
return String(format: NSLocalizedString("Unable to refresh App\n%@", comment: ""), message)
case .invalidParameters:
let message = self._failureReason.map { ": \n\($0)" } ?? "."
return String(format: NSLocalizedString("Invalid parameters%@", comment: ""), message)
}
}
var recoverySuggestion: String? {

View File

@@ -98,7 +98,9 @@ final class PatchAppOperation: ResultOperation<Void>
return
}
guard let resignedApp = self.context.resignedApp else { return self.finish(.failure(OperationError.invalidParameters)) }
guard let resignedApp = self.context.resignedApp else {
return self.finish(.failure(OperationError.invalidParameters("PatchAppOperation.main: self.context.resignedApp is nil")))
}
self.progressHandler?(self.progress, NSLocalizedString("Downloading iOS firmware...", comment: ""))

View File

@@ -35,9 +35,15 @@ final class RefreshAppOperation: ResultOperation<InstalledApp>
do
{
if let error = self.context.error { return self.finish(.failure(error)) }
if let error = self.context.error {
print("RefreshAppOperation.main: ERROR: self.context.app = \(self.context.app!); self.context.error is \(error)")
return self.finish(.failure(error))
}
guard let profiles = self.context.provisioningProfiles else {
return self.finish(.failure(OperationError.invalidParameters("RefreshAppOperation.main: self.context.provisioningProfiles is nil")))
}
guard let profiles = self.context.provisioningProfiles else { return self.finish(.failure(OperationError.invalidParameters)) }
guard let app = self.context.app else { return self.finish(.failure(OperationError(.appNotFound(name: nil)))) }
for p in profiles {

View File

@@ -35,7 +35,9 @@ final class RemoveAppBackupOperation: ResultOperation<Void>
return
}
guard let installedApp = self.context.installedApp else { return self.finish(.failure(OperationError.invalidParameters)) }
guard let installedApp = self.context.installedApp else {
return self.finish(.failure(OperationError.invalidParameters("RemoveAppBackupOperation.main: self.context.installedApp is nil")))
}
installedApp.managedObjectContext?.perform {
guard let backupDirectoryURL = FileManager.default.backupDirectoryURL(for: installedApp) else { return self.finish(.failure(OperationError.missingAppGroup)) }

View File

@@ -33,7 +33,9 @@ final class RemoveAppOperation: ResultOperation<InstalledApp>
return
}
guard let installedApp = self.context.installedApp else { return self.finish(.failure(OperationError.invalidParameters)) }
guard let installedApp = self.context.installedApp else {
return self.finish(.failure(OperationError.invalidParameters("RemoveAppOperation.main: self.context.installedApp is nil")))
}
installedApp.managedObjectContext?.perform {
let resignedBundleIdentifier = installedApp.resignedBundleIdentifier

View File

@@ -42,7 +42,12 @@ final class ResignAppOperation: ResultOperation<ALTApplication>
let profiles = self.context.provisioningProfiles,
let team = self.context.team,
let certificate = self.context.certificate
else { return self.finish(.failure(OperationError.invalidParameters)) }
else {
return self.finish(.failure(OperationError.invalidParameters("ResignAppOperation.main: " +
"self.context.team or " +
"self.context.provisioningProfiles or" +
"self.context.certificate is nil")))
}
// Prepare app bundle
let prepareAppProgress = Progress.discreteProgress(totalUnitCount: 2)

View File

@@ -36,7 +36,9 @@ final class SendAppOperation: ResultOperation<()>
return self.finish(.failure(error))
}
guard let resignedApp = self.context.resignedApp else { return self.finish(.failure(OperationError.invalidParameters)) }
guard let resignedApp = self.context.resignedApp else {
return self.finish(.failure(OperationError.invalidParameters("SendAppOperation.main: self.resignedApp is nil")))
}
// self.context.resignedApp.fileURL points to the app bundle, but we want the .ipa.
let app = AnyApp(name: resignedApp.name, bundleIdentifier: self.context.bundleIdentifier, url: resignedApp.fileURL)

View File

@@ -116,8 +116,12 @@ final class VerifyAppOperation: ResultOperation<Void>
{
throw error
}
let appName = self.context.app?.name ?? NSLocalizedString("The app", comment: "")
self.localizedFailure = String(format: NSLocalizedString("%@ could not be installed.", comment: ""), appName)
guard let app = self.context.app else { throw OperationError.invalidParameters }
guard let app = self.context.app else {
throw OperationError.invalidParameters("VerifyAppOperation.main: self.context.app is nil")
}
if !["ny.litritt.ignited", "com.litritt.ignited"].contains(where: { $0 == app.bundleIdentifier }) {
guard app.bundleIdentifier == self.context.bundleIdentifier else {

View File

@@ -29,9 +29,21 @@ class AnisetteViewModel: ObservableObject {
@Published var source: String = "https://servers.sidestore.io/servers.json"
@Published var servers: [Server] = []
init() {
// using the custom Anisette list
if !UserDefaults.standard.menuAnisetteList.isEmpty {
self.source = UserDefaults.standard.menuAnisetteList
}
}
func getListOfServers() {
guard let url = URL(string: source) else { return }
URLSession.shared.dataTask(with: url) { data, response, error in
// DO NOT use local cache when fetching anisette servers
var request = URLRequest(url: url)
request.cachePolicy = .reloadIgnoringLocalCacheData
URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
return
}
@@ -41,6 +53,8 @@ class AnisetteViewModel: ObservableObject {
let servers = try decoder.decode(AnisetteServerData.self, from: data)
DispatchQueue.main.async {
self.servers = servers.servers
// store server addresses as list
UserDefaults.standard.menuAnisetteServersList = servers.servers.map(\.self.address)
}
} catch {
// Handle decoding error
@@ -55,6 +69,7 @@ struct AnisetteServers: View {
@Environment(\.presentationMode) var presentationMode
@StateObject var viewModel: AnisetteViewModel = AnisetteViewModel()
@State var selected: String? = nil
@State private var showingConfirmation = false
var errorCallback: () -> ()
var body: some View {
@@ -136,9 +151,13 @@ struct AnisetteServers: View {
SUIButton(action: {
presentationMode.wrappedValue.dismiss()
}) {
Text("Back")
.fontWeight(.semibold)
.frame(maxWidth: .infinity)
HStack{
Spacer()
Text("Back")
.fontWeight(.semibold)
Spacer()
}
.contentShape(Rectangle())
}
.buttonStyle(PlainButtonStyle())
.padding()
@@ -149,26 +168,25 @@ struct AnisetteServers: View {
SUIButton(action: {
viewModel.getListOfServers()
}) {
Text("Refresh Servers")
.fontWeight(.semibold)
.frame(maxWidth: .infinity)
HStack{
Spacer()
Text("Refresh Servers")
.fontWeight(.semibold)
.frame(maxWidth: .infinity)
Spacer()
}
.contentShape(Rectangle())
}
.buttonStyle(PlainButtonStyle())
.padding()
.background(RoundedRectangle(cornerRadius: 10).fill(Color.accentColor))
.foregroundColor(.white)
.shadow(color: Color.accentColor.opacity(0.4), radius: 10, x: 0, y: 5)
}
SUIButton(action: {
#if !DEBUG
if Keychain.shared.adiPb != nil {
Keychain.shared.adiPb = nil
}
#endif
print("Cleared adi.pb from keychain")
errorCallback()
presentationMode.wrappedValue.dismiss()
showingConfirmation = true
}) {
Text("Reset adi.pb")
.fontWeight(.semibold)
@@ -179,6 +197,25 @@ struct AnisetteServers: View {
.background(RoundedRectangle(cornerRadius: 10).fill(Color.red))
.foregroundColor(.white)
.shadow(color: Color.red.opacity(0.4), radius: 10, x: 0, y: 5)
.alert(isPresented: $showingConfirmation) {
Alert(
title: Text("Reset adi.pb"),
message: Text("are you sure to clear the adi.pb from keychain"),
primaryButton: .default(Text("do it")) {
#if !DEBUG
if Keychain.shared.adiPb != nil {
Keychain.shared.adiPb = nil
}
#endif
print("Cleared adi.pb from keychain")
errorCallback()
presentationMode.wrappedValue.dismiss()
},
secondaryButton: .cancel(Text("cancel")) {
print("canceled")
}
)
}
}
.padding(.horizontal)
.padding(.bottom)

View File

@@ -105,8 +105,8 @@ private extension ErrorLogViewController
self?.searchFAQ(for: loggedError)
},
UIAction(title: NSLocalizedString("View More Details", comment: ""), image: UIImage(systemName: "ellipsis.circle")) { [weak self] _ in
}
self?.viewMoreDetails(for: loggedError)
},
])
cell.menuButton.menu = menu

View File

@@ -128,7 +128,7 @@ private extension PatreonViewController
let isPatronText = NSLocalizedString("""
Hey ,
Youre the best. Your account was linked successfully, so you now have access to the beta versions of all of our apps. You can find them all in the Browse tab.
Youre the best. Your account was linked successfully, so you now have access to any beta versions of our apps. You can find them all in the Browse tab.
Thanks for all of your support. Enjoy!
- SideTeam
@@ -175,7 +175,7 @@ private extension PatreonViewController
@objc func openPatreonURL(_ sender: UIButton)
{
let patreonURL = URL(string: "https://www.patreon.com/SideStore")!
let patreonURL = URL(string: "https://www.patreon.com/SideStoreIO")!
let safariViewController = SFSafariViewController(url: patreonURL)
safariViewController.preferredControlTintColor = self.view.tintColor
@@ -184,7 +184,7 @@ private extension PatreonViewController
@objc func openTwitterURL(_ sender: UIButton)
{
let twitterURL = URL(string: "https://twitter.com/SideStore_io")!
let twitterURL = URL(string: "https://twitter.com/sidestoreio")!
let safariViewController = SFSafariViewController(url: twitterURL)
safariViewController.preferredControlTintColor = self.view.tintColor
@@ -348,7 +348,7 @@ extension PatreonViewController: UICollectionViewDelegateFlowLayout
switch section
{
case .about: return .zero
case .patrons: return CGSize(width: 0, height: 0)
case .patrons: return CGSize(width: 320, height: 44)
}
}
}

View File

@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23090" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="5Rz-4h-jJ8">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="5Rz-4h-jJ8">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23079"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22685"/>
<capability name="Named colors" minToolsVersion="9.0"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
@@ -21,7 +21,7 @@
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<color key="separatorColor" white="1" alpha="0.25" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<label key="tableFooterView" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="SideStore 1.0" textAlignment="center" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" id="bUR-rp-Nw2">
<rect key="frame" x="0.0" y="1296" width="375" height="25"/>
<rect key="frame" x="0.0" y="1347" width="375" height="25"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" white="1" alpha="0.69999999999999996" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
@@ -273,7 +273,7 @@
</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"/>
<rect key="frame" x="0.0" y="546" width="375" height="51"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="amC-sE-8O0" id="GEO-2e-E4k">
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
@@ -293,6 +293,42 @@
</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="7PQ-AW-GcV" 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="7PQ-AW-GcV" id="wQ8-9w-iiw">
<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 App Limit" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="W95-fD-NAd" userLabel="Disable App Limit">
<rect key="frame" x="30" y="15.5" width="142.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>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="1aa-og-ZXD">
<rect key="frame" x="296" y="10" width="51" height="31"/>
<connections>
<action selector="toggleDisableAppLimit:" destination="aMk-Xp-UL8" eventType="valueChanged" id="zYc-B2-JPg"/>
</connections>
</switch>
</subviews>
<constraints>
<constraint firstAttribute="trailingMargin" secondItem="1aa-og-ZXD" secondAttribute="trailing" id="1Xa-t6-jAC"/>
<constraint firstItem="W95-fD-NAd" firstAttribute="leading" secondItem="wQ8-9w-iiw" secondAttribute="leadingMargin" id="J49-tg-KMa"/>
<constraint firstItem="1aa-og-ZXD" firstAttribute="centerY" secondItem="wQ8-9w-iiw" secondAttribute="centerY" id="UMz-ax-ln4"/>
<constraint firstItem="W95-fD-NAd" firstAttribute="centerY" secondItem="wQ8-9w-iiw" secondAttribute="centerY" id="bFd-lr-0xw"/>
</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="3"/>
@@ -305,19 +341,19 @@
<tableViewSection headerTitle="" id="eHy-qI-w5w">
<cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="30h-59-88f" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="586" width="375" height="51"/>
<rect key="frame" x="0.0" y="637" width="375" height="51"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="30h-59-88f" id="7qD-DW-Jls">
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="How it works" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="2CC-iw-3bd">
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="How it works" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="2CC-iw-3bd">
<rect key="frame" x="30" y="15.5" width="105" height="20.5"/>
<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="WtV-Dt-sDn">
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="WtV-Dt-sDn">
<rect key="frame" x="327" y="16.5" width="18" height="18"/>
</imageView>
</subviews>
@@ -345,28 +381,28 @@
<tableViewSection headerTitle="" id="J90-vn-u2O">
<cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="i4T-2q-jF3" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="677" width="375" height="51"/>
<rect key="frame" x="0.0" y="728" width="375" height="51"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="i4T-2q-jF3" id="VTQ-H4-aCM">
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Developers" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="hRA-OK-Vjw">
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Developers" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="hRA-OK-Vjw">
<rect key="frame" x="30" y="15.5" width="86" height="20.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" white="1" alpha="0.80000000000000004" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
<stackView opaque="NO" contentMode="scaleToFill" spacing="14" translatesAutoresizingMaskIntoConstraints="NO" id="lx9-35-OSk">
<stackView opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="14" translatesAutoresizingMaskIntoConstraints="NO" id="lx9-35-OSk">
<rect key="frame" x="187.5" y="15.5" width="157.5" height="20.5"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="SideStore Team" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="JAA-iZ-VGb">
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="SideStore Team" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="JAA-iZ-VGb">
<rect key="frame" x="0.0" y="0.0" width="125.5" height="20.5"/>
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="Mmj-3V-fTb">
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="Mmj-3V-fTb">
<rect key="frame" x="139.5" y="0.0" width="18" height="20.5"/>
</imageView>
</subviews>
@@ -389,7 +425,7 @@
</userDefinedRuntimeAttributes>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="oHX-oR-nwJ" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="728" width="375" height="51"/>
<rect key="frame" x="0.0" y="774.5" width="375" height="51"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="oHX-oR-nwJ" id="hN4-i5-igu">
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
@@ -433,7 +469,7 @@
</userDefinedRuntimeAttributes>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="0MT-ht-Sit" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="779" width="375" height="51"/>
<rect key="frame" x="0.0" y="830" width="375" height="51"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="0MT-ht-Sit" id="OZp-WM-5H7">
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
@@ -477,7 +513,7 @@
</userDefinedRuntimeAttributes>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="O5R-Al-lGj" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="830" width="375" height="51"/>
<rect key="frame" x="0.0" y="881" width="375" height="51"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="O5R-Al-lGj" id="CrG-Mr-xQq">
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
@@ -517,7 +553,7 @@
<tableViewSection headerTitle="" id="OMa-EK-hRI">
<cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="FMZ-as-Ljo" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="921" width="375" height="51"/>
<rect key="frame" x="0.0" y="972" width="375" height="51"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="FMZ-as-Ljo" id="JzL-Of-A3T">
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
@@ -550,7 +586,7 @@
</userDefinedRuntimeAttributes>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="Qca-pU-sJh" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="972" width="375" height="51"/>
<rect key="frame" x="0.0" y="1023" width="375" height="51"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Qca-pU-sJh" id="QtU-8J-VQN">
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
@@ -586,7 +622,7 @@
</connections>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="rE2-P4-OaE" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="1023" width="375" height="51"/>
<rect key="frame" x="0.0" y="1074" width="375" height="51"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="rE2-P4-OaE" id="qIT-rz-ZUb">
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
@@ -622,7 +658,7 @@
</connections>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="VrV-qI-zXF" 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="1125" width="375" height="51"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="VrV-qI-zXF" id="w1r-uY-4pD">
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
@@ -655,7 +691,7 @@
</userDefinedRuntimeAttributes>
</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="1125" width="375" height="51"/>
<rect key="frame" x="0.0" y="1176" 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"/>
@@ -688,7 +724,7 @@
</userDefinedRuntimeAttributes>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="VNn-u4-cN8" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="1176" width="375" height="51"/>
<rect key="frame" x="0.0" y="1227" width="375" height="51"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="VNn-u4-cN8" id="4bh-qe-l2N">
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
@@ -721,7 +757,7 @@
</userDefinedRuntimeAttributes>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="e7s-hL-kv9" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="1227" width="375" height="51"/>
<rect key="frame" x="0.0" y="1278" width="375" height="51"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="e7s-hL-kv9" id="yjL-Mu-HTk">
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
@@ -766,6 +802,7 @@
<outlet property="accountNameLabel" destination="CnN-M1-AYK" id="Ldc-Py-Bix"/>
<outlet property="accountTypeLabel" destination="434-MW-Den" id="mNB-QE-4Jg"/>
<outlet property="backgroundRefreshSwitch" destination="DPu-zD-Als" id="eiG-Hv-Vko"/>
<outlet property="disableAppLimitSwitch" destination="1aa-og-ZXD" id="oVL-Md-yZ8"/>
<outlet property="noIdleTimeoutSwitch" destination="iQA-wm-5ag" id="jHC-js-q0Y"/>
<outlet property="versionLabel" destination="bUR-rp-Nw2" id="85I-5R-hqz"/>
</connections>

View File

@@ -31,14 +31,19 @@ extension SettingsViewController
fileprivate enum AppRefreshRow: Int, CaseIterable
{
case backgroundRefresh
case noIdleTimeout
case noIdleTimeout
@available(iOS 14, *)
case addToSiri
case disableAppLimit
static var allCases: [AppRefreshRow] {
guard #available(iOS 14, *) else { return [.backgroundRefresh, .noIdleTimeout] }
return [.backgroundRefresh, .noIdleTimeout, .addToSiri]
var c: [AppRefreshRow] = [.backgroundRefresh, .noIdleTimeout]
guard #available(iOS 14, *) else { return c }
c.append(.addToSiri)
// conditional entries go at the last to preserve ordering
if !ProcessInfo().sparseRestorePatched { c.append(.disableAppLimit) }
return c
}
}
@@ -79,6 +84,7 @@ final class SettingsViewController: UITableViewController
@IBOutlet private var backgroundRefreshSwitch: UISwitch!
@IBOutlet private var noIdleTimeoutSwitch: UISwitch!
@IBOutlet private var disableAppLimitSwitch: UISwitch!
@IBOutlet private var refreshSideJITServer: UILabel!
@@ -190,6 +196,7 @@ private extension SettingsViewController
self.backgroundRefreshSwitch.isOn = UserDefaults.standard.isBackgroundRefreshEnabled
self.noIdleTimeoutSwitch.isOn = UserDefaults.standard.isIdleTimeoutDisableEnabled
self.disableAppLimitSwitch.isOn = UserDefaults.standard.isAppLimitDisabled
if self.isViewLoaded
{
@@ -241,7 +248,7 @@ private extension SettingsViewController
}
else
{
settingsHeaderFooterView.secondaryLabel.text = NSLocalizedString("Enable Background Refresh to automatically refresh apps in the background when connected to Wi-Fi. \n\nDisable the Idle Timeout toggle to allow SideStore to not let your device go to sleep during a refresh or install of any apps.", comment: "")
settingsHeaderFooterView.secondaryLabel.text = NSLocalizedString("Enable Background Refresh to automatically refresh apps in the background when connected to Wi-Fi. \n\nEnable Disable Idle Timeout to allow SideStore to keep your device awake during a refresh or install of any apps.", comment: "")
}
case .instructions:
@@ -317,6 +324,10 @@ private extension SettingsViewController
self.present(alertController, animated: true, completion: nil)
}
@IBAction func toggleDisableAppLimit(_ sender: UISwitch) {
UserDefaults.standard.isAppLimitDisabled = sender.isOn
}
@IBAction func toggleIsBackgroundRefreshEnabled(_ sender: UISwitch)
{
UserDefaults.standard.isBackgroundRefreshEnabled = sender.isOn
@@ -485,6 +496,13 @@ extension SettingsViewController
}
}
if let cell = cell as? InsetGroupTableViewCell,
indexPath.section == Section.appRefresh.rawValue,
indexPath.row == AppRefreshRow.allCases.count-1 // last row
{
cell.setValue(3, forKey: "style")
}
return cell
}
@@ -566,6 +584,7 @@ extension SettingsViewController
{
case .backgroundRefresh: break
case .noIdleTimeout: break
case .disableAppLimit: break
case .addToSiri:
guard #available(iOS 14, *) else { return }
self.addRefreshAppsShortcut()
@@ -576,8 +595,8 @@ extension SettingsViewController
let row = CreditsRow.allCases[indexPath.row]
switch row
{
case .developer: self.openTwitter(username: "sidestore_io")
case .operations: self.openTwitter(username: "sidestore_io")
case .developer: self.openTwitter(username: "sidestoreio")
case .operations: self.openTwitter(username: "sidestoreio")
case .designer: self.openTwitter(username: "lit_ritt")
case .softwareLicenses: break
}
@@ -608,24 +627,24 @@ extension SettingsViewController
})
// Option 3: Mail
// alertController.addAction(UIAlertAction(title: "Send Email", style: .default) { _ in
// if MFMailComposeViewController.canSendMail() {
// let mailViewController = MFMailComposeViewController()
// mailViewController.mailComposeDelegate = self
// mailViewController.setToRecipients(["support@sidestore.io"])
//
// if let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String {
// mailViewController.setSubject("SideStore Beta \(version) Feedback")
// } else {
// mailViewController.setSubject("SideStore Beta Feedback")
// }
//
// self.present(mailViewController, animated: true, completion: nil)
// } else {
// let toastView = ToastView(text: NSLocalizedString("Cannot Send Mail", comment: ""), detailText: nil)
// toastView.show(in: self)
// }
// })
alertController.addAction(UIAlertAction(title: "Send Email", style: .default) { _ in
if MFMailComposeViewController.canSendMail() {
let mailViewController = MFMailComposeViewController()
mailViewController.mailComposeDelegate = self
mailViewController.setToRecipients(["support@sidestore.io"])
if let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String {
mailViewController.setSubject("SideStore Beta \(version) Feedback")
} else {
mailViewController.setSubject("SideStore Beta Feedback")
}
self.present(mailViewController, animated: true, completion: nil)
} else {
let toastView = ToastView(text: NSLocalizedString("Cannot Send Mail", comment: ""), detailText: nil)
toastView.show(in: self)
}
})
// Cancel action
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))

View File

@@ -28,10 +28,12 @@ public extension UserDefaults
@NSManaged var customAnisetteURL: String?
@NSManaged var menuAnisetteURL: String
@NSManaged var menuAnisetteList: String
@NSManaged var menuAnisetteServersList: [String]
@NSManaged var preferredServerID: String?
@NSManaged var isBackgroundRefreshEnabled: Bool
@NSManaged var isIdleTimeoutDisableEnabled: Bool
@NSManaged var isAppLimitDisabled: Bool
@NSManaged var isPairingReset: Bool
@NSManaged var isDebugModeEnabled: Bool
@NSManaged var presentedLaunchReminderNotification: Bool
@@ -77,6 +79,7 @@ public extension UserDefaults
let localServerSupportsRefreshing = !ProcessInfo.processInfo.isOperatingSystemAtLeast(ios14)
let defaults = [
#keyPath(UserDefaults.isAppLimitDisabled): false,
#keyPath(UserDefaults.isBackgroundRefreshEnabled): true,
#keyPath(UserDefaults.isIdleTimeoutDisableEnabled): true,
#keyPath(UserDefaults.isPairingReset): true,
@@ -84,6 +87,7 @@ public extension UserDefaults
#keyPath(UserDefaults.activeAppLimitIncludesExtensions): activeAppLimitIncludesExtensions,
#keyPath(UserDefaults.localServerSupportsRefreshing): localServerSupportsRefreshing,
#keyPath(UserDefaults.requiresAppGroupMigration): true,
#keyPath(UserDefaults.menuAnisetteList): "https://servers.sidestore.io/servers.json",
#keyPath(UserDefaults.menuAnisetteURL): "https://ani.sidestore.io"
] as [String : Any]

View File

@@ -407,7 +407,10 @@ private extension DatabaseManager
// Migrate apps
if FileManager.default.fileExists(atPath: previousAppsDirectoryURL.path, isDirectory: nil)
{
_ = try FileManager.default.replaceItemAt(appsDirectoryURL, withItemAt: previousAppsDirectoryURL)
if(previousAppsDirectoryURL.path != appsDirectoryURL.path)
{
_ = try FileManager.default.replaceItemAt(appsDirectoryURL, withItemAt: previousAppsDirectoryURL)
}
}
finish(.success(()))

View File

@@ -163,8 +163,9 @@ public extension InstalledApp
class func updatesFetchRequest() -> NSFetchRequest<InstalledApp>
{
let fetchRequest = InstalledApp.fetchRequest() as NSFetchRequest<InstalledApp>
fetchRequest.predicate = NSPredicate(format: "%K == YES AND %K != nil AND %K != %K",
#keyPath(InstalledApp.isActive), #keyPath(InstalledApp.storeApp), #keyPath(InstalledApp.version), #keyPath(InstalledApp.storeApp.latestSupportedVersion.version))
fetchRequest.predicate = NSPredicate(format: "%K == YES AND %K == YES",
#keyPath(InstalledApp.isActive), #keyPath(InstalledApp.hasUpdate))
return fetchRequest
}

View File

@@ -169,6 +169,31 @@ public class StoreApp: NSManagedObject, Decodable, Fetchable
return self._versions.array as! [AppVersion]
}
@nonobjc public var size: Int64? {
guard let version = self.latestSupportedVersion else { return nil }
return version.size
}
@nonobjc public var version: String? {
guard let version = self.latestSupportedVersion else { return nil }
return version.version
}
@nonobjc public var versionDescription: String? {
guard let version = self.latestSupportedVersion else { return nil }
return version.localizedDescription
}
@nonobjc public var versionDate: Date? {
guard let version = self.latestSupportedVersion else { return nil }
return version.date
}
@nonobjc public var downloadURL: URL? {
guard let version = self.latestSupportedVersion else { return nil }
return version.downloadURL
}
private override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?)
{
super.init(entity: entity, insertInto: context)
@@ -215,7 +240,7 @@ public class StoreApp: NSManagedObject, Decodable, Fetchable
self.iconURL = try container.decode(URL.self, forKey: .iconURL)
self.screenshotURLs = try container.decodeIfPresent([URL].self, forKey: .screenshotURLs) ?? []
let downloadURL = try container.decodeIfPresent(URL.self, forKey: .downloadURL)
var downloadURL = try container.decodeIfPresent(URL.self, forKey: .downloadURL)
let platformURLs = try container.decodeIfPresent(PlatformURLs.self.self, forKey: .platformURLs)
if let platformURLs = platformURLs {
self.platformURLs = platformURLs
@@ -230,7 +255,22 @@ public class StoreApp: NSManagedObject, Decodable, Fetchable
} else if let downloadURL = downloadURL {
self._downloadURL = downloadURL
} else {
throw DecodingError.dataCorruptedError(forKey: .downloadURL, in: container, debugDescription: "E downloadURL:String or downloadURLs:[[Platform:URL]] key required.")
let version = try container.decode(String.self, forKey: .version)
if let versions = try container.decodeIfPresent([AppVersion].self, forKey: .versions){
for ver in versions {
if ver.version == version {
self._downloadURL = ver.downloadURL
downloadURL = ver.downloadURL // not sure if this is needed
}
}
throw DecodingError.dataCorruptedError(forKey: .downloadURL, in: container, debugDescription: "E downloadURL:String or downloadURLs:[[Platform:URL]] key required.")
} else {
throw DecodingError.dataCorruptedError(forKey: .downloadURL, in: container, debugDescription: "E downloadURL:String or downloadURLs:[[Platform:URL]] key required.")
}
// else {
// throw DecodingError.dataCorruptedError(forKey: .downloadURL, in: container, debugDescription: "E downloadURL:String or downloadURLs:[[Platform:URL]] key required.")
// }
}
if let tintColorHex = try container.decodeIfPresent(String.self, forKey: .tintColor)
@@ -250,6 +290,9 @@ public class StoreApp: NSManagedObject, Decodable, Fetchable
if let versions = try container.decodeIfPresent([AppVersion].self, forKey: .versions)
{
//TODO: Throw error if there isn't at least one version.
if (versions.count == 0){
throw DecodingError.dataCorruptedError(forKey: .versions, in: container, debugDescription: "At least one version is required in key: versions")
}
for version in versions
{

View File

@@ -10,29 +10,30 @@ import Foundation
import AuthenticationServices
import CoreData
private let clientID = "ZMx0EGUWe4TVWYXNZZwK_fbIK5jHFVWoUf1Qb-sqNXmT-YzAGwDPxxq7ak3_W5Q2"
private let clientSecret = "1hktsZB89QyN69cB4R0tu55R4TCPQGXxvebYUUh7Y-5TLSnRswuxs6OUjdJ74IJt"
private let clientID = "my4hpHHG4iVRme6QALnQGlhSBQiKdB_AinrVgPpIpiC-xiHstTYiLKO5vfariFo1"
private let clientSecret = "Zow0ggt9YgwIyd4DVLoO9Z02KuuIXW44xhx4lfL27x2u-_u4FE4rYR48bEKREPS5"
private let campaignID = "2863968"
private let campaignID = "12794837"
typealias PatreonAPIError = PatreonAPIErrorCode.Error
enum PatreonAPIErrorCode: Int, ALTErrorEnum, CaseIterable
{
case unknown
case notAuthenticated
case invalidAccessToken
var errorFailureReason: String {
switch self
{
case .unknown: return NSLocalizedString("An unknown error occurred.", comment: "")
case .notAuthenticated: return NSLocalizedString("No connected Patreon account.", comment: "")
case .invalidAccessToken: return NSLocalizedString("Invalid access token.", comment: "")
}
}
}
extension PatreonAPI
{
enum Error: LocalizedError
{
case unknown
case notAuthenticated
case invalidAccessToken
var errorDescription: String? {
switch self
{
case .unknown: return NSLocalizedString("An unknown error occurred.", comment: "")
case .notAuthenticated: return NSLocalizedString("No connected Patreon account.", comment: "")
case .invalidAccessToken: return NSLocalizedString("Invalid access token.", comment: "")
}
}
}
enum AuthorizationType
{
case none
@@ -110,7 +111,7 @@ public extension PatreonAPI
let components = URLComponents(url: callbackURL, resolvingAgainstBaseURL: false),
let codeQueryItem = components.queryItems?.first(where: { $0.name == "code" }),
let code = codeQueryItem.value
else { throw Error.unknown }
else { throw PatreonAPIError(.unknown) }
self.fetchAccessToken(oauthCode: code) { (result) in
switch result
@@ -151,9 +152,9 @@ public extension PatreonAPI
self.send(request, authorizationType: .user) { (result: Result<AccountResponse, Swift.Error>) in
switch result
{
case .failure(Error.notAuthenticated):
case .failure(~PatreonAPIErrorCode.notAuthenticated):
self.signOut() { (result) in
completion(.failure(Error.notAuthenticated))
completion(.failure(PatreonAPIError(.notAuthenticated)))
}
case .failure(let error): completion(.failure(error))
@@ -357,11 +358,11 @@ private extension PatreonAPI
{
case .none: break
case .creator:
guard let creatorAccessToken = Keychain.shared.patreonCreatorAccessToken else { return completion(.failure(Error.invalidAccessToken)) }
guard let creatorAccessToken = Keychain.shared.patreonCreatorAccessToken else { return completion(.failure(PatreonAPIError(.invalidAccessToken))) }
request.setValue("Bearer " + creatorAccessToken, forHTTPHeaderField: "Authorization")
case .user:
guard let accessToken = Keychain.shared.patreonAccessToken else { return completion(.failure(Error.notAuthenticated)) }
guard let accessToken = Keychain.shared.patreonAccessToken else { return completion(.failure(PatreonAPIError(.notAuthenticated))) }
request.setValue("Bearer " + accessToken, forHTTPHeaderField: "Authorization")
}
@@ -374,8 +375,8 @@ private extension PatreonAPI
{
switch authorizationType
{
case .creator: completion(.failure(Error.invalidAccessToken))
case .none: completion(.failure(Error.notAuthenticated))
case .creator: completion(.failure(PatreonAPIError(.invalidAccessToken)))
case .none: completion(.failure(PatreonAPIError(.notAuthenticated)))
case .user:
self.refreshAccessToken() { (result) in
switch result

View File

@@ -82,6 +82,7 @@ struct ComplicationView: View
}
.gaugeStyle(.accessoryCircularCapacity)
.unredacted()
.widgetBackground(Color.clear)
}
}

View File

@@ -0,0 +1,27 @@
//
// View+AltWidget.swift
// AltStore
//
// Created by Riley Testut on 8/18/23.
// Copyright © 2023 Riley Testut. All rights reserved.
//
import SwiftUI
extension View
{
@ViewBuilder
func widgetBackground(_ backgroundView: some View) -> some View
{
if #available(iOSApplicationExtension 17, *)
{
containerBackground(for: .widget) {
backgroundView
}
}
else
{
background(backgroundView)
}
}
}

View File

@@ -104,7 +104,8 @@ struct WidgetView : View
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
.background(backgroundView(icon: entry.app?.icon, tintColor: entry.app?.tintColor))
.widgetBackground(backgroundView(icon: entry.app?.icon, tintColor: entry.app?.tintColor))
}
}

View File

@@ -1,8 +1,8 @@
// Configuration settings file format documentation can be found at:
// https://help.apple.com/xcode/#/dev745c5c974
MARKETING_VERSION = 0.5.8
CURRENT_PROJECT_VERSION = 5080
MARKETING_VERSION = 0.5.10
CURRENT_PROJECT_VERSION = 5100
// Vars to be overwritten by `CodeSigning.xcconfig` if exists
DEVELOPMENT_TEAM = S32Z3HMYVQ
@@ -25,3 +25,6 @@ PRODUCT_BUNDLE_IDENTIFIER[config=Debug] = $(ORG_PREFIX).SideStore.$(DEVELOPMENT_
EXTENSION_PREFIX = $(PRODUCT_BUNDLE_IDENTIFIER)
APP_GROUP_IDENTIFIER = $(PRODUCT_BUNDLE_IDENTIFIER)
ICLOUD_CONTAINER_IDENTIFIER = iCloud.$(ORG_PREFIX).$(PROJECT_NAME)
// Suppress noise from os activity in xcode console log for release builds
DEBUG_ACTIVITY_MODE = disable

37
CERTIFICATE-OF-ORIGIN.md Normal file
View File

@@ -0,0 +1,37 @@
Developer Certificate of Origin
Version 1.1
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.

View File

@@ -2,6 +2,8 @@
Thank you for your interest in contributing to SideStore! SideStore is a community driven project, and it's made possible by people like you.
By contributing to this Project (SideStore), you agree to the Developer's Certificate of Origin found in [CERTIFICATE-OF-ORIGIN.md](CERTIFICATE-OF-ORIGIN.md). Any contributions to this project after the addition of the Developer's Certificate of Origin are subject to its policy.
There are many ways to contribute to SideStore, so if you aren't a developer, there are still many other ways you can help out:
- [Writing documentation](https://github.com/SideStore/SideStore-Docs)

View File

@@ -23,7 +23,7 @@
outputFiles = (
"$(OBJECT_FILE_DIR)/$(CARGO_XCODE_TARGET_ARCH)-$(EXECUTABLE_NAME)",
);
script = "# generated with cargo-xcode 1.5.0\n# modified to use prebuilt binaries\n\nset -eu;\n\nBUILT_SRC=\"./em_proxy/$LIB_FILE_NAME.a\"\nln -f -- \"$BUILT_SRC\" \"$TARGET_BUILD_DIR/$EXECUTABLE_PATH\" || cp \"$BUILT_SRC\" \"$TARGET_BUILD_DIR/$EXECUTABLE_PATH\"\necho \"$BUILT_SRC -> $TARGET_BUILD_DIR/$EXECUTABLE_PATH\"\n\n# xcode generates dep file, but for its own path, so append our rename to it\n#DEP_FILE_SRC=\"minimuxer/target/${CARGO_XCODE_TARGET_TRIPLE}/release/${CARGO_XCODE_CARGO_DEP_FILE_NAME}\"\n#if [ -f \"$DEP_FILE_SRC\" ]; then\n# DEP_FILE_DST=\"${DERIVED_FILE_DIR}/${CARGO_XCODE_TARGET_ARCH}-${EXECUTABLE_NAME}.d\"\n# cp -f \"$DEP_FILE_SRC\" \"$DEP_FILE_DST\"\n# echo >> \"$DEP_FILE_DST\" \"$SCRIPT_OUTPUT_FILE_0: $BUILT_SRC\"\n#fi\n\n# lipo script needs to know all the platform-specific files that have been built\n# archs is in the file name, so that paths don't stay around after archs change\n# must match input for LipoScript\n#FILE_LIST=\"${DERIVED_FILE_DIR}/${ARCHS}-${EXECUTABLE_NAME}.xcfilelist\"\n#touch \"$FILE_LIST\"\n#if ! egrep -q \"$SCRIPT_OUTPUT_FILE_0\" \"$FILE_LIST\" ; then\n# echo >> \"$FILE_LIST\" \"$SCRIPT_OUTPUT_FILE_0\"\n#fi\n";
script = "# generated with cargo-xcode 1.5.0\n# modified to use prebuilt binaries\n\nset -eu;\n\nBUILT_SRC=\"./em_proxy/$LIB_FILE_NAME.a\"\necho Generating Static lib: $BUILT_SRC\nln -f -- \"$BUILT_SRC\" \"$TARGET_BUILD_DIR/$EXECUTABLE_PATH\" || cp \"$BUILT_SRC\" \"$TARGET_BUILD_DIR/$EXECUTABLE_PATH\"\necho \"$BUILT_SRC -> $TARGET_BUILD_DIR/$EXECUTABLE_PATH\"\n\n# xcode generates dep file, but for its own path, so append our rename to it\n#DEP_FILE_SRC=\"minimuxer/target/${CARGO_XCODE_TARGET_TRIPLE}/release/${CARGO_XCODE_CARGO_DEP_FILE_NAME}\"\n#if [ -f \"$DEP_FILE_SRC\" ]; then\n# DEP_FILE_DST=\"${DERIVED_FILE_DIR}/${CARGO_XCODE_TARGET_ARCH}-${EXECUTABLE_NAME}.d\"\n# cp -f \"$DEP_FILE_SRC\" \"$DEP_FILE_DST\"\n# echo >> \"$DEP_FILE_DST\" \"$SCRIPT_OUTPUT_FILE_0: $BUILT_SRC\"\n#fi\n\n# lipo script needs to know all the platform-specific files that have been built\n# archs is in the file name, so that paths don't stay around after archs change\n# must match input for LipoScript\n#FILE_LIST=\"${DERIVED_FILE_DIR}/${ARCHS}-${EXECUTABLE_NAME}.xcfilelist\"\n#touch \"$FILE_LIST\"\n#if ! egrep -q \"$SCRIPT_OUTPUT_FILE_0\" \"$FILE_LIST\" ; then\n# echo >> \"$FILE_LIST\" \"$SCRIPT_OUTPUT_FILE_0\"\n#fi\n";
};
/* End PBXBuildRule section */
@@ -223,7 +223,7 @@
INSTALL_MODE_FLAG = "";
INSTALL_OWNER = "";
LIB_FILE_NAME = "";
"LIB_FILE_NAME[sdk=iphoneos*]" = libem_proxy;
"LIB_FILE_NAME[sdk=iphoneos*]" = "libem_proxy-ios";
"LIB_FILE_NAME[sdk=iphonesimulator*]" = "libem_proxy-sim";
PRODUCT_NAME = em_proxy_static;
SKIP_INSTALL = YES;
@@ -299,7 +299,7 @@
INSTALL_MODE_FLAG = "";
INSTALL_OWNER = "";
LIB_FILE_NAME = "";
"LIB_FILE_NAME[sdk=iphoneos*]" = libem_proxy;
"LIB_FILE_NAME[sdk=iphoneos*]" = "libem_proxy-ios";
"LIB_FILE_NAME[sdk=iphonesimulator*]" = "libem_proxy-sim";
PRODUCT_NAME = em_proxy_static;
SKIP_INSTALL = YES;

60
Dependencies/fetch-prebuilt.sh vendored Normal file → Executable file
View File

@@ -3,6 +3,34 @@
# Ensure we are in Dependencies directory
cd "$(dirname "$0")"
# Detect if Homebrew is in /opt/homebrew (Apple Silicon) or /usr/local (Intel)
if [[ -d "/opt/homebrew" ]]; then
export PATH="/opt/homebrew/bin:$PATH"
elif [[ -d "/usr/local" ]]; then
export PATH="/usr/local/bin:$PATH"
fi
# Check if wget and curl are installed; if not, install them via Homebrew
if ! command -v wget &> /dev/null; then
echo "wget not found, attempting to install via Homebrew..."
if command -v brew &> /dev/null; then
brew install wget
else
echo "Homebrew is not installed. Please install Homebrew and rerun the script."
exit 1
fi
fi
if ! command -v curl &> /dev/null; then
echo "curl not found, attempting to install via Homebrew..."
if command -v brew &> /dev/null; then
brew install curl
else
echo "Homebrew is not installed. Please install Homebrew and rerun the script."
exit 1
fi
fi
check_for_update() {
if [ -f ".skip-prebuilt-fetch-$1" ]; then
echo "Skipping prebuilt fetch for $1 since .skip-prebuilt-fetch-$1 exists. If you are developing $1 alongside SideStore, don't remove this file, or this script will replace your locally built binaries with the ones built by GitHub Actions."
@@ -16,20 +44,40 @@ check_for_update() {
LAST_FETCH=`cat .last-prebuilt-fetch-$1 | perl -n -e '/([0-9]*),([^ ]*)$/ && print $1'`
LAST_COMMIT=`cat .last-prebuilt-fetch-$1 | perl -n -e '/([0-9]*),([^ ]*)$/ && print $2'`
# fetch if last fetch was over 1 hour ago
if [[ $LAST_FETCH -lt $(expr $(date +%s) - 3600) ]] || [[ "$2" == "force" ]]; then
# Check if required library files exist
FORCE_DOWNLOAD=false
if [ ! -f "$1/lib$1-sim.a" ] || [ ! -f "$1/lib$1-ios.a" ]; then
echo "Required libraries missing for $1, forcing download..."
FORCE_DOWNLOAD=true
fi
# Download if:
# 1. Libraries are missing (FORCE_DOWNLOAD), or
# 2. Last fetch was over 1 hour ago, or
# 3. Force flag was passed
if [ "$FORCE_DOWNLOAD" = true ] || [[ $LAST_FETCH -lt $(expr $(date +%s) - 3600) ]] || [[ "$2" == "force" ]]; then
echo "Checking $1 for update"
echo
LATEST_COMMIT=`curl https://api.github.com/repos/SideStore/$1/releases/latest | perl -n -e '/Commit: https:\\/\\/github\\.com\\/[^\\/]*\\/[^\\/]*\\/commit\\/([^"]*)/ && print $1'`
echo
echo "Last commit: $LAST_COMMIT"
echo "Latest commit: $LATEST_COMMIT"
NOT_UPTODATE=false
if [[ "$LAST_COMMIT" != "$LATEST_COMMIT" ]]; then
echo "Found update, downloading binaries"
echo "Found update on the remote: https://api.github.com/repos/SideStore/$1/releases/latest"
NOT_UPTODATE=true
fi
# Download if:
# 1. Libraries are missing (FORCE_DOWNLOAD), or
# 2. New commit is available
if [ "$FORCE_DOWNLOAD" = true ] || [ "$NOT_UPTODATE" = true ] ;then
echo "downloading binaries"
echo
wget -O "$1/lib$1-sim.a" "https://github.com/SideStore/$1/releases/latest/download/lib$1-sim.a"
if [[ "$1" != "minimuxer" ]]; then
wget -O "$1/lib$1.a" "https://github.com/SideStore/$1/releases/latest/download/lib$1.a"
wget -O "$1/lib$1-ios.a" "https://github.com/SideStore/$1/releases/latest/download/lib$1.a"
wget -O "$1/$1.h" "https://github.com/SideStore/$1/releases/latest/download/$1.h"
echo
else
@@ -39,7 +87,9 @@ check_for_update() {
echo "Unzipping generated.zip"
cd "$1"
unzip ./generated.zip
mv -v generated/* .
cp -v generated/* .
# Remove all files except ones that comes checked-in from minimuxer repository
find generated -type f ! -name 'minimuxer-Bridging-Header.h' ! -name 'minimuxer-helpers.swift' -exec rm -v {} \;
rm generated.zip
rmdir generated/
cd ..

View File

@@ -24,7 +24,7 @@
outputFiles = (
"$(OBJECT_FILE_DIR)/$(CARGO_XCODE_TARGET_ARCH)-$(EXECUTABLE_NAME)",
);
script = "# generated with cargo-xcode 1.5.0\n# modified to use prebuilt binaries\n\nset -eu;\n\nBUILT_SRC=\"./minimuxer/$LIB_FILE_NAME.a\"\nln -f -- \"$BUILT_SRC\" \"$TARGET_BUILD_DIR/$EXECUTABLE_PATH\" || cp \"$BUILT_SRC\" \"$TARGET_BUILD_DIR/$EXECUTABLE_PATH\"\necho \"$BUILT_SRC -> $TARGET_BUILD_DIR/$EXECUTABLE_PATH\"\n\n# xcode generates dep file, but for its own path, so append our rename to it\n#DEP_FILE_SRC=\"minimuxer/target/${CARGO_XCODE_TARGET_TRIPLE}/release/${CARGO_XCODE_CARGO_DEP_FILE_NAME}\"\n#if [ -f \"$DEP_FILE_SRC\" ]; then\n# DEP_FILE_DST=\"${DERIVED_FILE_DIR}/${CARGO_XCODE_TARGET_ARCH}-${EXECUTABLE_NAME}.d\"\n# cp -f \"$DEP_FILE_SRC\" \"$DEP_FILE_DST\"\n# echo >> \"$DEP_FILE_DST\" \"$SCRIPT_OUTPUT_FILE_0: $BUILT_SRC\"\n#fi\n\n# lipo script needs to know all the platform-specific files that have been built\n# archs is in the file name, so that paths don't stay around after archs change\n# must match input for LipoScript\n#FILE_LIST=\"${DERIVED_FILE_DIR}/${ARCHS}-${EXECUTABLE_NAME}.xcfilelist\"\n#touch \"$FILE_LIST\"\n#if ! egrep -q \"$SCRIPT_OUTPUT_FILE_0\" \"$FILE_LIST\" ; then\n# echo >> \"$FILE_LIST\" \"$SCRIPT_OUTPUT_FILE_0\"\n#fi\n";
script = "# generated with cargo-xcode 1.5.0\n# modified to use prebuilt binaries\n\nset -eu;\n\nBUILT_SRC=\"./minimuxer/${LIB_FILE_NAME}.a\"\necho Generating Static lib: $BUILT_SRC\nln -f -- \"$BUILT_SRC\" \"$TARGET_BUILD_DIR/$EXECUTABLE_PATH\" || cp \"$BUILT_SRC\" \"$TARGET_BUILD_DIR/$EXECUTABLE_PATH\"\necho \"$BUILT_SRC -> $TARGET_BUILD_DIR/$EXECUTABLE_PATH\"\n\n# xcode generates dep file, but for its own path, so append our rename to it\n#DEP_FILE_SRC=\"minimuxer/target/${CARGO_XCODE_TARGET_TRIPLE}/release/${CARGO_XCODE_CARGO_DEP_FILE_NAME}\"\n#if [ -f \"$DEP_FILE_SRC\" ]; then\n# DEP_FILE_DST=\"${DERIVED_FILE_DIR}/${CARGO_XCODE_TARGET_ARCH}-${EXECUTABLE_NAME}.d\"\n# cp -f \"$DEP_FILE_SRC\" \"$DEP_FILE_DST\"\n# echo >> \"$DEP_FILE_DST\" \"$SCRIPT_OUTPUT_FILE_0: $BUILT_SRC\"\n#fi\n\n# lipo script needs to know all the platform-specific files that have been built\n# archs is in the file name, so that paths don't stay around after archs change\n# must match input for LipoScript\n#FILE_LIST=\"${DERIVED_FILE_DIR}/${ARCHS}-${EXECUTABLE_NAME}.xcfilelist\"\n#touch \"$FILE_LIST\"\n#if ! egrep -q \"$SCRIPT_OUTPUT_FILE_0\" \"$FILE_LIST\" ; then\n# echo >> \"$FILE_LIST\" \"$SCRIPT_OUTPUT_FILE_0\"\n#fi\n";
};
/* End PBXBuildRule section */

View File

@@ -158,7 +158,7 @@ test:
build:
@xcodebuild -project AltStore.xcodeproj \
-scheme AltStore \
-scheme SideStore \
-sdk iphoneos \
archive -archivePath ./archive \
CODE_SIGNING_REQUIRED=NO \

View File

@@ -6,7 +6,7 @@
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://makeapullrequest.com)
[![Nightly SideStore build](https://github.com/SideStore/SideStore/actions/workflows/nightly.yml/badge.svg)](https://github.com/SideStore/SideStore/actions/workflows/nightly.yml)
[![.github/workflows/beta.yml](https://github.com/SideStore/SideStore/actions/workflows/beta.yml/badge.svg)](https://github.com/SideStore/SideStore/actions/workflows/beta.yml)
[![Discord](https://img.shields.io/discord/949183273383395328?label=Discord)](https://discord.gg/sidestore-949183273383395328)
[![Discord](https://img.shields.io/discord/949183273383395328?label=Discord)](https://discord.gg/sidestore)
![Alt](https://repobeats.axiom.co/api/embed/3a329ce95955690b9a9366f8d5598626a847d96c.svg "Repobeats analytics image")

View File

@@ -73,6 +73,7 @@ public extension NSError
{
var userInfo = self.userInfo
userInfo[NSLocalizedDescriptionKey] = self.localizedDescription
userInfo[NSLocalizedFailureErrorKey] = self.localizedFailure
userInfo[NSLocalizedFailureReasonErrorKey] = self.localizedFailureReason
userInfo[NSLocalizedRecoverySuggestionErrorKey] = self.localizedRecoverySuggestion
userInfo[NSDebugDescriptionErrorKey] = self.localizedDebugDescription
@@ -124,6 +125,7 @@ public extension NSError
var userInfo = self.userInfo
userInfo[NSDebugDescriptionErrorKey] = self.localizedDebugDescription
userInfo[NSLocalizedDescriptionKey] = self.localizedDescription
userInfo[NSLocalizedFailureErrorKey] = self.localizedFailure
userInfo[NSLocalizedFailureReasonErrorKey] = self.localizedFailureReason
userInfo[NSLocalizedRecoverySuggestionErrorKey] = self.localizedRecoverySuggestion

View File

@@ -1,5 +1,54 @@
{
"version": 1,
"sources": [
{
"identifier": "io.sidestore.example"
},
{
"identifier": "com.sidestoreapps.community",
"sourceURL": "https://community-apps.sidestore.io/sidecommunity.json"
},
{
"identifier": "org.provenance-emu.provenance",
"sourceURL": "https://provenance-emu.com/apps.json"
},
{
"identifier": "me.oatmealdome.altstore",
"sourceURL": "https://altstore.oatmealdome.me"
},
{
"identifier": "com.utmapp.repos.UTM",
"sourceURL": "https://alt.getutm.app"
},
{
"identifier": "com.flyinghead.source",
"sourceURL": "https://flyinghead.github.io/flycast-builds/altstore.json"
},
{
"identifier": "dev.crystall1ne.repos.PojavLauncher",
"sourceURL": "https://alt.crystall1ne.dev"
},
{
"identifier": "eu.pokemmo.altstore",
"sourceURL": "https://pokemmo.eu/altstore/"
},
{
"identifier": "dev.theodyssey.sidestore",
"sourceURL": "https://theodyssey.dev/altstore/odysseysource.json"
},
{
"identifier": "stream.yattee",
"sourceURL": "https://repos.yattee.stream/alt/apps.json"
},
{
"identifier": "com.litritt.litsource",
"sourceURL": "https://altstore.ignitedemulator.com/"
},
{
"identifier": "thatstel.la.altsource",
"sourceURL": "https://alt.thatstel.la/"
}
],
"sources": [
{
"identifier": "io.sidestore.example"