mirror of
https://github.com/SideStore/SideStore.git
synced 2026-04-08 03:35:40 +02:00
Compare commits
234 Commits
feature/Wi
...
connect-de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
94ee08c718 | ||
|
|
fd7dc94975 | ||
|
|
b78707808d | ||
|
|
d41518581a | ||
|
|
4abbfe6142 | ||
|
|
dae813d80c | ||
|
|
af89b178ad | ||
|
|
8c269207fd | ||
|
|
42ecd38517 | ||
|
|
9f7d4dee49 | ||
|
|
458b8e491e | ||
|
|
495e621e69 | ||
|
|
c986512b5f | ||
|
|
d277754ae5 | ||
|
|
2ef2e2f26b | ||
|
|
23a53034fa | ||
|
|
ce57d72a78 | ||
|
|
502b89d890 | ||
|
|
5f0015fad0 | ||
|
|
c81236957b | ||
|
|
970ab38b27 | ||
|
|
8a5c31b81d | ||
|
|
8508fe79b5 | ||
|
|
3859e98801 | ||
|
|
a759c7be9e | ||
|
|
12fc6cf6e2 | ||
|
|
580db6530e | ||
|
|
9c67c237ee | ||
|
|
357d85a72e | ||
|
|
88ad828ce0 | ||
|
|
a95625a34a | ||
|
|
95e00d81f5 | ||
|
|
c2e386a5c5 | ||
|
|
a76aade4ff | ||
|
|
65c9986103 | ||
|
|
9e2b9b6639 | ||
|
|
cf373634d7 | ||
|
|
b3d5d976b4 | ||
|
|
c3c31995ce | ||
|
|
7e92e17429 | ||
|
|
88ab8fa8d7 | ||
|
|
ebe78932bf | ||
|
|
2e613e6d15 | ||
|
|
35ee92db12 | ||
|
|
04d9f760ad | ||
|
|
4f52743be8 | ||
|
|
32cae7a5b2 | ||
|
|
c2c0e3b790 | ||
|
|
6d36a30787 | ||
|
|
48a86ec6de | ||
|
|
5cff914ff3 | ||
|
|
70ea725ce3 | ||
|
|
78f12e45f9 | ||
|
|
e5061acc20 | ||
|
|
2d7bc51d30 | ||
|
|
9128b67ee8 | ||
|
|
551c004476 | ||
|
|
ed6a8d1379 | ||
|
|
766fb89e0b | ||
|
|
c5b8cb4459 | ||
|
|
0deae92829 | ||
|
|
cc5d2f1813 | ||
|
|
41151d0d49 | ||
|
|
52702264a3 | ||
|
|
6e297e1278 | ||
|
|
e3bb9b425f | ||
|
|
79255be79c | ||
|
|
7c836f5ba1 | ||
|
|
938bcd14ad | ||
|
|
229d79fc05 | ||
|
|
2d3dac2e1d | ||
|
|
e23f5e7894 | ||
|
|
571d27c814 | ||
|
|
dde6bd4fe3 | ||
|
|
6e6dbd9329 | ||
|
|
258268f5ef | ||
|
|
9ae49977fb | ||
|
|
d61c54fa60 | ||
|
|
980699af6f | ||
|
|
cc5c280882 | ||
|
|
090456bba1 | ||
|
|
5354d4eb76 | ||
|
|
b986fae611 | ||
|
|
cfcfc3e928 | ||
|
|
f97548fc3a | ||
|
|
36913b425c | ||
|
|
822ea08d89 | ||
|
|
98dd6f3fe7 | ||
|
|
b3f0dbb155 | ||
|
|
6904d931c3 | ||
|
|
529466a9f7 | ||
|
|
77dc695ba1 | ||
|
|
e17776f651 | ||
|
|
0d2f355a74 | ||
|
|
2ce1576016 | ||
|
|
0f3be3c494 | ||
|
|
8c1ca8503a | ||
|
|
32a59c17f4 | ||
|
|
b4b4ceab0b | ||
|
|
be1f27bb9e | ||
|
|
ed10ddb1cb | ||
|
|
dbdb4b0f32 | ||
|
|
59e537362e | ||
|
|
6d96bf414f | ||
|
|
e7ba778a5f | ||
|
|
933d349cd5 | ||
|
|
3de24dcfce | ||
|
|
3275d16b8b | ||
|
|
5bb4cd1dad | ||
|
|
16b14441fa | ||
|
|
93a6272d30 | ||
|
|
0dc526f778 | ||
|
|
183e185812 | ||
|
|
e02453598c | ||
|
|
24af1b5b5f | ||
|
|
5864c283f6 | ||
|
|
be78fa4b91 | ||
|
|
b3abf69a02 | ||
|
|
c530dc11ae | ||
|
|
d368ddbd11 | ||
|
|
e5c6521a15 | ||
|
|
898a59768e | ||
|
|
a85bc93142 | ||
|
|
c6c1f9faa0 | ||
|
|
0eea19c9cc | ||
|
|
ed2270ff46 | ||
|
|
45b6c3b338 | ||
|
|
84e2284f56 | ||
|
|
1c0d0be622 | ||
|
|
a9ce0f487d | ||
|
|
07533e0365 | ||
|
|
ee5ddd4264 | ||
|
|
f519d22d81 | ||
|
|
51ed87086a | ||
|
|
1ca3aa3cdb | ||
|
|
0178c63f6a | ||
|
|
8a97c409fa | ||
|
|
3dd0735305 | ||
|
|
536f775baa | ||
|
|
00f7a684a3 | ||
|
|
d79b166a6a | ||
|
|
b3d827f56a | ||
|
|
40bcef1dcb | ||
|
|
6146f1bdaa | ||
|
|
f5d82d9ef0 | ||
|
|
b2a29ae606 | ||
|
|
98ccba53a2 | ||
|
|
9bfda36647 | ||
|
|
5710cdf19c | ||
|
|
20cf54bfcd | ||
|
|
2ce639e750 | ||
|
|
b1ed413c4f | ||
|
|
b8c3060037 | ||
|
|
c3ea4940d7 | ||
|
|
40e1225b87 | ||
|
|
0c171122b2 | ||
|
|
6d0f4bb3da | ||
|
|
5e2cc6e20c | ||
|
|
99cb43bbea | ||
|
|
ca7d8277f7 | ||
|
|
337d26333e | ||
|
|
ebb64d255b | ||
|
|
7dcb199f68 | ||
|
|
4334e887de | ||
|
|
4e84dc4cc8 | ||
|
|
1a1ed072bf | ||
|
|
ae457f07c4 | ||
|
|
00095942c3 | ||
|
|
d1caa5fc21 | ||
|
|
813e2f97ac | ||
|
|
bcb5a90f5e | ||
|
|
020a1a3149 | ||
|
|
c4d649ec58 | ||
|
|
c02cf2c284 | ||
|
|
c30afd042e | ||
|
|
17640fe6cf | ||
|
|
2e4f6ee420 | ||
|
|
a3768d9221 | ||
|
|
80c3390363 | ||
|
|
a5e3869d8f | ||
|
|
aa7d7c2d02 | ||
|
|
015f205569 | ||
|
|
e59fb15926 | ||
|
|
173c585f2d | ||
|
|
6f8c27793e | ||
|
|
332b81c803 | ||
|
|
4b343b500d | ||
|
|
e87c537642 | ||
|
|
2e6300cce2 | ||
|
|
09514d15a6 | ||
|
|
0de23dcba0 | ||
|
|
bacb153151 | ||
|
|
a01aa299d8 | ||
|
|
44edbddbd8 | ||
|
|
79d677cf3c | ||
|
|
be39b6512f | ||
|
|
fcfeea35da | ||
|
|
7d0eb8c61e | ||
|
|
4d8438a6b6 | ||
|
|
f611244e35 | ||
|
|
546a978d3b | ||
|
|
70b23fb073 | ||
|
|
a56ca597d6 | ||
|
|
679e0228a8 | ||
|
|
e153394323 | ||
|
|
5bd1fcfcfd | ||
|
|
2a392ddc44 | ||
|
|
b5cb8bc0d9 | ||
|
|
fa170bcf98 | ||
|
|
7939d46949 | ||
|
|
ab9df8201a | ||
|
|
4a670ec091 | ||
|
|
10e57e59c4 | ||
|
|
b9ec43ef34 | ||
|
|
42197cd375 | ||
|
|
704852973b | ||
|
|
056b4200df | ||
|
|
250a7d8627 | ||
|
|
1ba51e161e | ||
|
|
32e58af896 | ||
|
|
312fa6fe76 | ||
|
|
afbe0837ba | ||
|
|
36ad2a720f | ||
|
|
901e3b14bb | ||
|
|
588d209f7b | ||
|
|
b0fac34ffc | ||
|
|
5ede9f7c6b | ||
|
|
c7254fd23e | ||
|
|
55fcea04af | ||
|
|
c212c0a6b2 | ||
|
|
a31fd6709a | ||
|
|
e367fd2b73 | ||
|
|
1ca67d0241 | ||
|
|
8ffa952ff9 |
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@@ -1 +1 @@
|
|||||||
* @JoeMatt @lonkelle
|
* @JoeMatt @lonkelle @nythepegasus @Spidy123222 @SternXD
|
||||||
|
|||||||
34
.github/ISSUE_TEMPLATE/bug_report.md
vendored
34
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,34 +0,0 @@
|
|||||||
---
|
|
||||||
name: Bug report
|
|
||||||
about: Create a report to help us improve
|
|
||||||
title: "[BUG]"
|
|
||||||
labels: bug
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Describe the bug**
|
|
||||||
A clear and concise description of what the bug is.
|
|
||||||
|
|
||||||
**To Reproduce**
|
|
||||||
Steps to reproduce the behavior:
|
|
||||||
1. Go to '...'
|
|
||||||
2. Click on '....'
|
|
||||||
3. Scroll down to '....'
|
|
||||||
4. See error
|
|
||||||
|
|
||||||
**Expected behavior**
|
|
||||||
A clear and concise description of what you expected to happen.
|
|
||||||
|
|
||||||
**Screenshots**
|
|
||||||
If applicable, add screenshots to help explain your problem.
|
|
||||||
|
|
||||||
**Logs**
|
|
||||||
Please send logs generated with [idevicedebug](https://github.com/libimobiledevice/libimobiledevice) or Xcode. We will close the issue if a log is missing.
|
|
||||||
|
|
||||||
**iDevice (please complete the following information):**
|
|
||||||
- Device: [e.g. iPhone6]
|
|
||||||
- OS: [e.g. iOS8.1]
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
Add any other context about the problem here.
|
|
||||||
39
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
39
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
name: Bug Report
|
||||||
|
description: Report a bug
|
||||||
|
title: "[BUG] "
|
||||||
|
labels: ["bug"]
|
||||||
|
assignees: []
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
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.**
|
||||||
|
- type: textarea
|
||||||
|
id: description
|
||||||
|
attributes:
|
||||||
|
label: Describe the bug
|
||||||
|
description: What is the bug and how did you discover it?
|
||||||
|
placeholder: Please be clear and concise with your description.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: how-to-reproduce
|
||||||
|
attributes:
|
||||||
|
label: Instructions to reproduce
|
||||||
|
description: Please include clear and consistent instructions for reproducing the bug to make it easier for us to fix it.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: app-version
|
||||||
|
attributes:
|
||||||
|
label: What version of SideStore are you using?
|
||||||
|
description: To retrieve this, go to `Settings` in the SideStore app and scroll down to the bottom.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: other-info
|
||||||
|
attributes:
|
||||||
|
label: Other info
|
||||||
|
description: If you have any other comments, other info that might be useful, or if you found a workaround, please put it here.
|
||||||
10
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
10
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# force issue template usage
|
||||||
|
blank_issues_enabled: false
|
||||||
|
|
||||||
|
contact_links:
|
||||||
|
- name: Discord
|
||||||
|
url: https://discord.gg/sidestore-949183273383395328
|
||||||
|
about: If you need support, please go here first instead of making an issue!
|
||||||
|
- name: GitHub Discussions
|
||||||
|
url: https://github.com/SideStore/SideStore/discussions
|
||||||
|
about: As an alternative to Discord, you can also make a new GitHub discussion.
|
||||||
32
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
32
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
name: Feature Request
|
||||||
|
description: Suggest a feature
|
||||||
|
title: "[FEATURE REQUEST] "
|
||||||
|
labels: ["enhancement"]
|
||||||
|
assignees: []
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thanks for taking the time to fill out this feature request! Before you continue filling out the form, please **[search in GitHub Issues](https://github.com/SideStore/SideStore/issues?q=is%3Aissue+is%3Aopen) for the feature you are suggestion** in case it has already been suggested.
|
||||||
|
|
||||||
|
**Please use [Discord](https://discord.gg/sidestore-949183273383395328) or [GitHub Discussions](https://github.com/SideStore/SideStore/discussions) for support.**
|
||||||
|
- type: textarea
|
||||||
|
id: description
|
||||||
|
attributes:
|
||||||
|
label: Describe the feature
|
||||||
|
description: What is the feature? How would it work?
|
||||||
|
placeholder: Please be clear and concise with your description.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: use-cases
|
||||||
|
attributes:
|
||||||
|
label: Use cases
|
||||||
|
description: Please include multiple use cases where this feature would be useful.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: alternatives
|
||||||
|
attributes:
|
||||||
|
label: Alternatives
|
||||||
|
description: If you have alternative ideas of how this feature could work, you can put them here.
|
||||||
15
.github/pull_request_template.md
vendored
Normal file
15
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
### Changes
|
||||||
|
|
||||||
|
<!-- Fill this list with what your PR changes. Example: -->
|
||||||
|
- Fix bug
|
||||||
|
- Change UI for QOL
|
||||||
|
|
||||||
|
<!-- If your PR is ready to be merged, you can remove this section. -->
|
||||||
|
### Todo before merge
|
||||||
|
|
||||||
|
<!-- Example: -->
|
||||||
|
- [x] Finish UI changes
|
||||||
|
- [ ] Test
|
||||||
|
|
||||||
|
<!-- If your PR doesn't close an issue, you can remove the next line. -->
|
||||||
|
Closes #1234
|
||||||
22
.github/workflows/attach_build_products.yml
vendored
Normal file
22
.github/workflows/attach_build_products.yml
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
name: Add artifact links to pull request and related issues
|
||||||
|
on:
|
||||||
|
workflow_run:
|
||||||
|
workflows: [Pull Request SideStore build]
|
||||||
|
types: [completed]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
artifacts-url-comments:
|
||||||
|
name: add artifact links to pull request and related issues job
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
||||||
|
steps:
|
||||||
|
- name: add artifact links to pull request and related issues step
|
||||||
|
uses: tonyhallett/artifacts-url-comments@v1.1.0
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
prefix: Builds for this Pull Request are available at
|
||||||
|
suffix: Have a nice day.
|
||||||
|
format: name
|
||||||
|
addTo: pull
|
||||||
|
# addTo: pullandissues
|
||||||
134
.github/workflows/beta.yml
vendored
134
.github/workflows/beta.yml
vendored
@@ -1,16 +1,12 @@
|
|||||||
name: Beta SideStore build
|
name: Beta SideStore build
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
tags:
|
||||||
- develop
|
- '[0-9]+.[0-9]+.[0-9]+-beta.[0-9]+' # example: 1.0.0-beta.1
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Build and upload SideStore Beta
|
name: Build and upload SideStore Beta
|
||||||
if: startsWith(github.event.head_commit.message, '[beta]')
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@@ -25,63 +21,18 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
|
||||||
# - name: Cache rust cargo
|
|
||||||
# id: cache-rust-cargo
|
|
||||||
# uses: actions/cache@v3
|
|
||||||
# env:
|
|
||||||
# cache-name: cache-rust-cargo
|
|
||||||
# with:
|
|
||||||
# path: ~/.cargo
|
|
||||||
# key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
|
||||||
# restore-keys: |
|
|
||||||
# ${{ runner.os }}-build-${{ env.cache-name }}-
|
|
||||||
# ${{ runner.os }}-build-
|
|
||||||
# ${{ runner.os }}-
|
|
||||||
|
|
||||||
# - name: Cache rust minimuxer
|
|
||||||
# id: cache-rust-minimuxer
|
|
||||||
# uses: actions/cache@v3
|
|
||||||
# env:
|
|
||||||
# cache-name: cache-rust-minimuxer
|
|
||||||
# with:
|
|
||||||
# path: ./Dependencies/minimuxer/target
|
|
||||||
# key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
|
||||||
# restore-keys: |
|
|
||||||
# ${{ runner.os }}-build-${{ env.cache-name }}-
|
|
||||||
# ${{ runner.os }}-build-
|
|
||||||
# ${{ runner.os }}-
|
|
||||||
|
|
||||||
# - name: Cache rust em_proxy
|
|
||||||
# id: cache-rust-em_proxy
|
|
||||||
# uses: actions/cache@v3
|
|
||||||
# env:
|
|
||||||
# cache-name: cache-rust-em_proxy
|
|
||||||
# with:
|
|
||||||
# path: ./Dependencies/em_proxy/target
|
|
||||||
# key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
|
||||||
# restore-keys: |
|
|
||||||
# ${{ runner.os }}-build-${{ env.cache-name }}-
|
|
||||||
# ${{ runner.os }}-build-
|
|
||||||
# ${{ runner.os }}-
|
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: brew install ldid
|
run: brew install ldid
|
||||||
|
|
||||||
- name: Install rustup
|
- name: Change version to tag
|
||||||
uses: actions-rs/toolchain@v1
|
run: sed -e '/MARKETING_VERSION = .*/s/= .*/= ${{ github.ref_name }}/' -i '' Build.xcconfig
|
||||||
with:
|
|
||||||
toolchain: stable
|
|
||||||
override: true
|
|
||||||
target: aarch64-apple-ios
|
|
||||||
|
|
||||||
# - name: Create emotional damage
|
- name: Get version
|
||||||
# run: cd Dependencies/em_proxy && cargo build --release --target aarch64-apple-ios
|
id: version
|
||||||
|
run: echo "version=$(grep MARKETING_VERSION Build.xcconfig | sed -e "s/MARKETING_VERSION = //g")" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
# - name: Build minimuxer
|
- name: Echo version
|
||||||
# run: cd Dependencies/minimuxer && cargo build --release --target aarch64-apple-ios
|
run: echo "${{ steps.version.outputs.version }}"
|
||||||
|
|
||||||
- name: Add beta suffix to version
|
|
||||||
run: sed -e '/MARKETING_VERSION = .*/s/$/-beta.${{ github.run_number }}/' -i '' Build.xcconfig
|
|
||||||
|
|
||||||
- name: Setup Xcode
|
- name: Setup Xcode
|
||||||
uses: maxim-lobanov/setup-xcode@v1.4.1
|
uses: maxim-lobanov/setup-xcode@v1.4.1
|
||||||
@@ -89,39 +40,13 @@ jobs:
|
|||||||
xcode-version: ${{ matrix.version }}
|
xcode-version: ${{ matrix.version }}
|
||||||
|
|
||||||
- name: Build SideStore
|
- name: Build SideStore
|
||||||
run: |
|
run: make build | xcpretty && exit ${PIPESTATUS[0]}
|
||||||
xcodebuild -project AltStore.xcodeproj \
|
|
||||||
-scheme AltStore \
|
|
||||||
-sdk iphoneos \
|
|
||||||
archive -archivePath ./archive \
|
|
||||||
CODE_SIGNING_REQUIRED=NO \
|
|
||||||
AD_HOC_CODE_SIGNING_ALLOWED=YES \
|
|
||||||
CODE_SIGNING_ALLOWED=NO \
|
|
||||||
DEVELOPMENT_TEAM=XYZ0123456 \
|
|
||||||
ORG_IDENTIFIER=com.SideStore \
|
|
||||||
| xcpretty && exit ${PIPESTATUS[0]}
|
|
||||||
|
|
||||||
- name: Fakesign app
|
- name: Fakesign app
|
||||||
run: |
|
run: make fakesign
|
||||||
rm -rf archive.xcarchive/Products/Applications/SideStore.app/Frameworks/AltStoreCore.framework/Frameworks/
|
|
||||||
ldid -SAltStore/Resources/tempEnt.plist archive.xcarchive/Products/Applications/SideStore.app/SideStore
|
|
||||||
|
|
||||||
- name: Convert to IPA
|
- name: Convert to IPA
|
||||||
run: |
|
run: make ipa
|
||||||
mkdir Payload
|
|
||||||
mkdir Payload/SideStore.app
|
|
||||||
cp -R archive.xcarchive/Products/Applications/SideStore.app/ Payload/SideStore.app/
|
|
||||||
zip -r SideStore.ipa Payload
|
|
||||||
|
|
||||||
- name: Upload Artifact
|
|
||||||
uses: actions/upload-artifact@v3.1.0
|
|
||||||
with:
|
|
||||||
name: SideStore.ipa
|
|
||||||
path: SideStore.ipa
|
|
||||||
|
|
||||||
- name: Get version
|
|
||||||
id: version
|
|
||||||
run: echo "version=$(grep MARKETING_VERSION Build.xcconfig | sed -e "s/MARKETING_VERSION = //g")" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Get current date
|
- name: Get current date
|
||||||
id: date
|
id: date
|
||||||
@@ -131,22 +56,22 @@ jobs:
|
|||||||
id: date_altstore
|
id: date_altstore
|
||||||
run: echo "date=$(date -u +'%Y-%m-%d')" >> $GITHUB_OUTPUT
|
run: echo "date=$(date -u +'%Y-%m-%d')" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Upload to beta release
|
- name: Upload to new beta release
|
||||||
uses: IsaacShelton/update-existing-release@v1.3.1
|
uses: softprops/action-gh-release@v1
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
release: "Beta"
|
name: ${{ steps.version.outputs.version }}
|
||||||
tag: "beta"
|
tag_name: ${{ github.ref_name }}
|
||||||
|
draft: true
|
||||||
prerelease: true
|
prerelease: true
|
||||||
files: SideStore.ipa
|
files: SideStore.ipa
|
||||||
body: |
|
body: |
|
||||||
This is an ⚠️ **EXPERIMENTAL** ⚠️ beta build for commit [${{ github.sha }}](https://github.com/${{ github.repository }}/commit/${{ github.sha }}).
|
<!-- NOTE: to reset SideSource cache, go to `https://apps.sidestore.io/reset-cache/nightly/<sidesource key>`. This is not included in the GitHub Action since it makes draft releases so they can be edited and have a changelog. -->
|
||||||
|
Beta builds are hand-picked builds from development commits that will allow you to try out new features earlier than normal. However, **they might contain bugs and other issues. Use at your own risk!**
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
Beta builds are hand-picked builds from development commits that will allow you to try out new features earlier than normal, but with a lower chance of bugs than if you used nightly builds. However, since these changes are newer and less tested, they still have a good chance of bugs, so **use at your own risk**.
|
- TODO
|
||||||
|
|
||||||
If you want to be on the bleeding edge and use the latest development builds, you can look at [SideStore Nightly](https://github.com/${{ github.repository }}/releases/tag/nightly). **Please be aware that these builds have a much higher chance of bugs than beta or stable**.
|
|
||||||
|
|
||||||
If you use the `SideStore (Beta)` app, it will use the latest beta build (make sure to update it in "My Apps").
|
|
||||||
|
|
||||||
## Build Info
|
## Build Info
|
||||||
|
|
||||||
@@ -154,3 +79,18 @@ jobs:
|
|||||||
Built at (UTC date): `${{ steps.date_altstore.outputs.date }}`
|
Built at (UTC date): `${{ steps.date_altstore.outputs.date }}`
|
||||||
Commit SHA: `${{ github.sha }}`
|
Commit SHA: `${{ github.sha }}`
|
||||||
Version: `${{ steps.version.outputs.version }}`
|
Version: `${{ steps.version.outputs.version }}`
|
||||||
|
|
||||||
|
- name: Add version to IPA file name
|
||||||
|
run: mv SideStore.ipa SideStore-${{ steps.version.outputs.version }}.ipa
|
||||||
|
|
||||||
|
- name: Upload SideStore.ipa Artifact
|
||||||
|
uses: actions/upload-artifact@v3.1.0
|
||||||
|
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
|
||||||
|
with:
|
||||||
|
name: SideStore-${{ steps.version.outputs.version }}-dSYM
|
||||||
|
path: ./*.dSYM/
|
||||||
|
|||||||
28
.github/workflows/increase-nightly-build-num.sh
vendored
Normal file
28
.github/workflows/increase-nightly-build-num.sh
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Ensure we are in root directory
|
||||||
|
cd "$(dirname "$0")/../.."
|
||||||
|
|
||||||
|
DATE=`date -u +'%Y.%m.%d'`
|
||||||
|
BUILD_NUM=1
|
||||||
|
|
||||||
|
write() {
|
||||||
|
sed -e "/MARKETING_VERSION = .*/s/$/-nightly.$DATE.$BUILD_NUM+$(git rev-parse --short HEAD)/" -i '' Build.xcconfig
|
||||||
|
echo "$DATE,$BUILD_NUM" > .nightly-build-num
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ ! -f ".nightly-build-num" ]; then
|
||||||
|
write
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
LAST_DATE=`cat .nightly-build-num | perl -n -e '/([^,]*),([^ ]*)$/ && print $1'`
|
||||||
|
LAST_BUILD_NUM=`cat .nightly-build-num | perl -n -e '/([^,]*),([^ ]*)$/ && print $2'`
|
||||||
|
|
||||||
|
if [[ "$DATE" != "$LAST_DATE" ]]; then
|
||||||
|
write
|
||||||
|
else
|
||||||
|
BUILD_NUM=`expr $LAST_BUILD_NUM + 1`
|
||||||
|
write
|
||||||
|
fi
|
||||||
|
|
||||||
119
.github/workflows/nightly.yml
vendored
119
.github/workflows/nightly.yml
vendored
@@ -2,7 +2,7 @@ name: Nightly SideStore build
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- develop
|
- connect-develop
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -24,63 +24,24 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
|
||||||
# - name: Cache rust cargo
|
|
||||||
# id: cache-rust-cargo
|
|
||||||
# uses: actions/cache@v3
|
|
||||||
# env:
|
|
||||||
# cache-name: cache-rust-cargo
|
|
||||||
# with:
|
|
||||||
# path: ~/.cargo
|
|
||||||
# key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
|
||||||
# restore-keys: |
|
|
||||||
# ${{ runner.os }}-build-${{ env.cache-name }}-
|
|
||||||
# ${{ runner.os }}-build-
|
|
||||||
# ${{ runner.os }}-
|
|
||||||
|
|
||||||
# - name: Cache rust minimuxer
|
|
||||||
# id: cache-rust-minimuxer
|
|
||||||
# uses: actions/cache@v3
|
|
||||||
# env:
|
|
||||||
# cache-name: cache-rust-minimuxer
|
|
||||||
# with:
|
|
||||||
# path: ./Dependencies/minimuxer/target
|
|
||||||
# key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
|
||||||
# restore-keys: |
|
|
||||||
# ${{ runner.os }}-build-${{ env.cache-name }}-
|
|
||||||
# ${{ runner.os }}-build-
|
|
||||||
# ${{ runner.os }}-
|
|
||||||
|
|
||||||
# - name: Cache rust em_proxy
|
|
||||||
# id: cache-rust-em_proxy
|
|
||||||
# uses: actions/cache@v3
|
|
||||||
# env:
|
|
||||||
# cache-name: cache-rust-em_proxy
|
|
||||||
# with:
|
|
||||||
# path: ./Dependencies/em_proxy/target
|
|
||||||
# key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
|
||||||
# restore-keys: |
|
|
||||||
# ${{ runner.os }}-build-${{ env.cache-name }}-
|
|
||||||
# ${{ runner.os }}-build-
|
|
||||||
# ${{ runner.os }}-
|
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: brew install ldid
|
run: brew install ldid
|
||||||
|
|
||||||
- name: Install rustup
|
- name: Cache .nightly-build-num
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
toolchain: stable
|
path: .nightly-build-num
|
||||||
override: true
|
key: nightly-build-num
|
||||||
target: aarch64-apple-ios
|
|
||||||
|
|
||||||
# - name: Create emotional damage
|
- name: Increase nightly build number and set as version
|
||||||
# run: cd Dependencies/em_proxy && cargo build --release --target aarch64-apple-ios
|
run: bash .github/workflows/increase-nightly-build-num.sh
|
||||||
|
|
||||||
# - name: Build minimuxer
|
- name: Get version
|
||||||
# run: cd Dependencies/minimuxer && cargo build --release --target aarch64-apple-ios
|
id: version
|
||||||
|
run: echo "version=$(grep MARKETING_VERSION Build.xcconfig | sed -e "s/MARKETING_VERSION = //g")" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Add nightly suffix to version
|
- name: Echo version
|
||||||
run: sed -e '/MARKETING_VERSION = .*/s/$/-nightly.${{ github.run_number }}/' -i '' Build.xcconfig
|
run: echo "${{ steps.version.outputs.version }}"
|
||||||
|
|
||||||
- name: Setup Xcode
|
- name: Setup Xcode
|
||||||
uses: maxim-lobanov/setup-xcode@v1.4.1
|
uses: maxim-lobanov/setup-xcode@v1.4.1
|
||||||
@@ -88,39 +49,13 @@ jobs:
|
|||||||
xcode-version: ${{ matrix.version }}
|
xcode-version: ${{ matrix.version }}
|
||||||
|
|
||||||
- name: Build SideStore
|
- name: Build SideStore
|
||||||
run: |
|
run: make build | xcpretty && exit ${PIPESTATUS[0]}
|
||||||
xcodebuild -project AltStore.xcodeproj \
|
|
||||||
-scheme AltStore \
|
|
||||||
-sdk iphoneos \
|
|
||||||
archive -archivePath ./archive \
|
|
||||||
CODE_SIGNING_REQUIRED=NO \
|
|
||||||
AD_HOC_CODE_SIGNING_ALLOWED=YES \
|
|
||||||
CODE_SIGNING_ALLOWED=NO \
|
|
||||||
DEVELOPMENT_TEAM=XYZ0123456 \
|
|
||||||
ORG_IDENTIFIER=com.SideStore \
|
|
||||||
| xcpretty && exit ${PIPESTATUS[0]}
|
|
||||||
|
|
||||||
- name: Fakesign app
|
- name: Fakesign app
|
||||||
run: |
|
run: make fakesign
|
||||||
rm -rf archive.xcarchive/Products/Applications/SideStore.app/Frameworks/AltStoreCore.framework/Frameworks/
|
|
||||||
ldid -SAltStore/Resources/tempEnt.plist archive.xcarchive/Products/Applications/SideStore.app/SideStore
|
|
||||||
|
|
||||||
- name: Convert to IPA
|
- name: Convert to IPA
|
||||||
run: |
|
run: make ipa
|
||||||
mkdir Payload
|
|
||||||
mkdir Payload/SideStore.app
|
|
||||||
cp -R archive.xcarchive/Products/Applications/SideStore.app/ Payload/SideStore.app/
|
|
||||||
zip -r SideStore.ipa Payload
|
|
||||||
|
|
||||||
- name: Upload Artifact
|
|
||||||
uses: actions/upload-artifact@v3.1.0
|
|
||||||
with:
|
|
||||||
name: SideStore.ipa
|
|
||||||
path: SideStore.ipa
|
|
||||||
|
|
||||||
- name: Get version
|
|
||||||
id: version
|
|
||||||
run: echo "version=$(grep MARKETING_VERSION Build.xcconfig | sed -e "s/MARKETING_VERSION = //g")" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Get current date
|
- name: Get current date
|
||||||
id: date
|
id: date
|
||||||
@@ -141,11 +76,9 @@ jobs:
|
|||||||
body: |
|
body: |
|
||||||
This is an ⚠️ **EXPERIMENTAL** ⚠️ nightly build for commit [${{ github.sha }}](https://github.com/${{ github.repository }}/commit/${{ github.sha }}).
|
This is an ⚠️ **EXPERIMENTAL** ⚠️ nightly build for commit [${{ github.sha }}](https://github.com/${{ github.repository }}/commit/${{ github.sha }}).
|
||||||
|
|
||||||
Nightly builds are built from the most recent commit which means you'll be able to try out new features very early. However, since these changes are much newer and less tested, they have a much higher chance of bugs, so **use at your own risk**.
|
Nightly builds are **extremely experimental builds only meant to be used by developers and alpha testers. They often contain bugs and experimental features. Use at your own risk!**
|
||||||
|
|
||||||
If you want to try out new features early but want a lower chance of bugs, you can look at [SideStore Beta](https://github.com/${{ github.repository }}/releases/tag/beta).
|
If you want to try out new features early but want a lower chance of bugs, you can look at [SideStore Beta](https://github.com/${{ github.repository }}/releases?q=beta).
|
||||||
|
|
||||||
If you use the `SideStore (Nightly)` app, it will use the latest nightly build (make sure to update it in "My Apps").
|
|
||||||
|
|
||||||
## Build Info
|
## Build Info
|
||||||
|
|
||||||
@@ -153,3 +86,21 @@ jobs:
|
|||||||
Built at (UTC date): `${{ steps.date_altstore.outputs.date }}`
|
Built at (UTC date): `${{ steps.date_altstore.outputs.date }}`
|
||||||
Commit SHA: `${{ github.sha }}`
|
Commit SHA: `${{ github.sha }}`
|
||||||
Version: `${{ steps.version.outputs.version }}`
|
Version: `${{ steps.version.outputs.version }}`
|
||||||
|
|
||||||
|
- name: Add version to IPA file name
|
||||||
|
run: mv SideStore.ipa SideStore-${{ steps.version.outputs.version }}.ipa
|
||||||
|
|
||||||
|
- name: Upload SideStore.ipa Artifact
|
||||||
|
uses: actions/upload-artifact@v3.1.0
|
||||||
|
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
|
||||||
|
with:
|
||||||
|
name: SideStore-${{ steps.version.outputs.version }}-dSYM
|
||||||
|
path: ./*.dSYM/
|
||||||
|
|
||||||
|
- name: Reset cache for apps.sidestore.io/nightly
|
||||||
|
run: sleep 10 && curl https://apps.sidestore.io/reset-cache/nightly/${{ secrets.SIDESOURCE_KEY }}
|
||||||
|
|||||||
95
.github/workflows/pr.yml
vendored
95
.github/workflows/pr.yml
vendored
@@ -19,60 +19,20 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
|
||||||
# - name: Cache rust cargo
|
|
||||||
# id: cache-rust-cargo
|
|
||||||
# uses: actions/cache@v3
|
|
||||||
# env:
|
|
||||||
# cache-name: cache-rust-cargo
|
|
||||||
# with:
|
|
||||||
# path: ~/.cargo
|
|
||||||
# key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
|
||||||
# restore-keys: |
|
|
||||||
# ${{ runner.os }}-build-${{ env.cache-name }}-
|
|
||||||
# ${{ runner.os }}-build-
|
|
||||||
# ${{ runner.os }}-
|
|
||||||
|
|
||||||
# - name: Cache rust minimuxer
|
|
||||||
# id: cache-rust-minimuxer
|
|
||||||
# uses: actions/cache@v3
|
|
||||||
# env:
|
|
||||||
# cache-name: cache-rust-minimuxer
|
|
||||||
# with:
|
|
||||||
# path: ./Dependencies/minimuxer/target
|
|
||||||
# key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
|
||||||
# restore-keys: |
|
|
||||||
# ${{ runner.os }}-build-${{ env.cache-name }}-
|
|
||||||
# ${{ runner.os }}-build-
|
|
||||||
# ${{ runner.os }}-
|
|
||||||
|
|
||||||
# - name: Cache rust em_proxy
|
|
||||||
# id: cache-rust-em_proxy
|
|
||||||
# uses: actions/cache@v3
|
|
||||||
# env:
|
|
||||||
# cache-name: cache-rust-em_proxy
|
|
||||||
# with:
|
|
||||||
# path: ./Dependencies/em_proxy/target
|
|
||||||
# key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
|
||||||
# restore-keys: |
|
|
||||||
# ${{ runner.os }}-build-${{ env.cache-name }}-
|
|
||||||
# ${{ runner.os }}-build-
|
|
||||||
# ${{ runner.os }}-
|
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: brew install ldid
|
run: brew install ldid
|
||||||
|
|
||||||
- name: Install rustup
|
- name: Add PR suffix to version
|
||||||
uses: actions-rs/toolchain@v1
|
run: sed -e "/MARKETING_VERSION = .*/s/\$/-pr.${{ github.event.pull_request.number }}+$(git rev-parse --short ${COMMIT:-HEAD})/" -i '' Build.xcconfig
|
||||||
with:
|
env:
|
||||||
toolchain: stable
|
COMMIT: ${{ github.event.pull_request.head.sha }}
|
||||||
override: true
|
|
||||||
target: aarch64-apple-ios
|
|
||||||
|
|
||||||
# - name: Create emotional damage
|
- name: Get version
|
||||||
# run: cd Dependencies/em_proxy && cargo build --release --target aarch64-apple-ios
|
id: version
|
||||||
|
run: echo "version=$(grep MARKETING_VERSION Build.xcconfig | sed -e "s/MARKETING_VERSION = //g")" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
# - name: Build minimuxer
|
- name: Echo version
|
||||||
# run: cd Dependencies/minimuxer && cargo build --release --target aarch64-apple-ios
|
run: echo "${{ steps.version.outputs.version }}"
|
||||||
|
|
||||||
- name: Setup Xcode
|
- name: Setup Xcode
|
||||||
uses: maxim-lobanov/setup-xcode@v1.4.1
|
uses: maxim-lobanov/setup-xcode@v1.4.1
|
||||||
@@ -80,32 +40,25 @@ jobs:
|
|||||||
xcode-version: ${{ matrix.version }}
|
xcode-version: ${{ matrix.version }}
|
||||||
|
|
||||||
- name: Build SideStore
|
- name: Build SideStore
|
||||||
run: |
|
run: make build | xcpretty && exit ${PIPESTATUS[0]}
|
||||||
xcodebuild -project AltStore.xcodeproj \
|
|
||||||
-scheme AltStore \
|
|
||||||
-sdk iphoneos \
|
|
||||||
archive -archivePath ./archive \
|
|
||||||
CODE_SIGNING_REQUIRED=NO \
|
|
||||||
AD_HOC_CODE_SIGNING_ALLOWED=YES \
|
|
||||||
CODE_SIGNING_ALLOWED=NO \
|
|
||||||
DEVELOPMENT_TEAM=XYZ0123456 \
|
|
||||||
ORG_IDENTIFIER=com.SideStore \
|
|
||||||
| xcpretty && exit ${PIPESTATUS[0]}
|
|
||||||
|
|
||||||
- name: Fakesign app
|
- name: Fakesign app
|
||||||
run: |
|
run: make fakesign
|
||||||
rm -rf archive.xcarchive/Products/Applications/SideStore.app/Frameworks/AltStoreCore.framework/Frameworks/
|
|
||||||
ldid -SAltStore/Resources/tempEnt.plist archive.xcarchive/Products/Applications/SideStore.app/SideStore
|
|
||||||
|
|
||||||
- name: Convert to IPA
|
- name: Convert to IPA
|
||||||
run: |
|
run: make ipa
|
||||||
mkdir Payload
|
|
||||||
mkdir Payload/SideStore.app
|
|
||||||
cp -R archive.xcarchive/Products/Applications/SideStore.app/ Payload/SideStore.app/
|
|
||||||
zip -r SideStore.ipa Payload
|
|
||||||
|
|
||||||
- name: Upload Artifact
|
- name: Add version to IPA file name
|
||||||
|
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@v3.1.0
|
||||||
with:
|
with:
|
||||||
name: SideStore.ipa
|
name: SideStore-${{ steps.version.outputs.version }}.ipa
|
||||||
path: SideStore.ipa
|
path: SideStore-${{ steps.version.outputs.version }}.ipa
|
||||||
|
|
||||||
|
- name: Upload *.dSYM Artifact
|
||||||
|
uses: actions/upload-artifact@v3.1.0
|
||||||
|
with:
|
||||||
|
name: SideStore-${{ steps.version.outputs.version }}-dSYM
|
||||||
|
path: ./*.dSYM/
|
||||||
|
|||||||
108
.github/workflows/stable.yml
vendored
108
.github/workflows/stable.yml
vendored
@@ -2,7 +2,7 @@ name: Stable SideStore build
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- '[0-9]+.[0-9]+.[0-9]+*'
|
- '[0-9]+.[0-9]+.[0-9]+' # example: 1.0.0
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -21,60 +21,18 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
|
||||||
# - name: Cache rust cargo
|
|
||||||
# id: cache-rust-cargo
|
|
||||||
# uses: actions/cache@v3
|
|
||||||
# env:
|
|
||||||
# cache-name: cache-rust-cargo
|
|
||||||
# with:
|
|
||||||
# path: ~/.cargo
|
|
||||||
# key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
|
||||||
# restore-keys: |
|
|
||||||
# ${{ runner.os }}-build-${{ env.cache-name }}-
|
|
||||||
# ${{ runner.os }}-build-
|
|
||||||
# ${{ runner.os }}-
|
|
||||||
|
|
||||||
# - name: Cache rust minimuxer
|
|
||||||
# id: cache-rust-minimuxer
|
|
||||||
# uses: actions/cache@v3
|
|
||||||
# env:
|
|
||||||
# cache-name: cache-rust-minimuxer
|
|
||||||
# with:
|
|
||||||
# path: ./Dependencies/minimuxer/target
|
|
||||||
# key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
|
||||||
# restore-keys: |
|
|
||||||
# ${{ runner.os }}-build-${{ env.cache-name }}-
|
|
||||||
# ${{ runner.os }}-build-
|
|
||||||
# ${{ runner.os }}-
|
|
||||||
|
|
||||||
# - name: Cache rust em_proxy
|
|
||||||
# id: cache-rust-em_proxy
|
|
||||||
# uses: actions/cache@v3
|
|
||||||
# env:
|
|
||||||
# cache-name: cache-rust-em_proxy
|
|
||||||
# with:
|
|
||||||
# path: ./Dependencies/em_proxy/target
|
|
||||||
# key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
|
||||||
# restore-keys: |
|
|
||||||
# ${{ runner.os }}-build-${{ env.cache-name }}-
|
|
||||||
# ${{ runner.os }}-build-
|
|
||||||
# ${{ runner.os }}-
|
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: brew install ldid
|
run: brew install ldid
|
||||||
|
|
||||||
- name: Install rustup
|
- name: Change version to tag
|
||||||
uses: actions-rs/toolchain@v1
|
run: sed -e '/MARKETING_VERSION = .*/s/= .*/= ${{ github.ref_name }}/' -i '' Build.xcconfig
|
||||||
with:
|
|
||||||
toolchain: stable
|
|
||||||
override: true
|
|
||||||
target: aarch64-apple-ios
|
|
||||||
|
|
||||||
# - name: Create emotional damage
|
- name: Get version
|
||||||
# run: cd Dependencies/em_proxy && cargo build --release --target aarch64-apple-ios
|
id: version
|
||||||
|
run: echo "version=$(grep MARKETING_VERSION Build.xcconfig | sed -e "s/MARKETING_VERSION = //g")" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
# - name: Build minimuxer
|
- name: Echo version
|
||||||
# run: cd Dependencies/minimuxer && cargo build --release --target aarch64-apple-ios
|
run: echo "${{ steps.version.outputs.version }}"
|
||||||
|
|
||||||
- name: Setup Xcode
|
- name: Setup Xcode
|
||||||
uses: maxim-lobanov/setup-xcode@v1.4.1
|
uses: maxim-lobanov/setup-xcode@v1.4.1
|
||||||
@@ -82,39 +40,13 @@ jobs:
|
|||||||
xcode-version: ${{ matrix.version }}
|
xcode-version: ${{ matrix.version }}
|
||||||
|
|
||||||
- name: Build SideStore
|
- name: Build SideStore
|
||||||
run: |
|
run: make build | xcpretty && exit ${PIPESTATUS[0]}
|
||||||
xcodebuild -project AltStore.xcodeproj \
|
|
||||||
-scheme AltStore \
|
|
||||||
-sdk iphoneos \
|
|
||||||
archive -archivePath ./archive \
|
|
||||||
CODE_SIGNING_REQUIRED=NO \
|
|
||||||
AD_HOC_CODE_SIGNING_ALLOWED=YES \
|
|
||||||
CODE_SIGNING_ALLOWED=NO \
|
|
||||||
DEVELOPMENT_TEAM=XYZ0123456 \
|
|
||||||
ORG_IDENTIFIER=com.SideStore \
|
|
||||||
| xcpretty && exit ${PIPESTATUS[0]}
|
|
||||||
|
|
||||||
- name: Fakesign app
|
- name: Fakesign app
|
||||||
run: |
|
run: make fakesign
|
||||||
rm -rf archive.xcarchive/Products/Applications/SideStore.app/Frameworks/AltStoreCore.framework/Frameworks/
|
|
||||||
ldid -SAltStore/Resources/tempEnt.plist archive.xcarchive/Products/Applications/SideStore.app/SideStore
|
|
||||||
|
|
||||||
- name: Convert to IPA
|
- name: Convert to IPA
|
||||||
run: |
|
run: make ipa
|
||||||
mkdir Payload
|
|
||||||
mkdir Payload/SideStore.app
|
|
||||||
cp -R archive.xcarchive/Products/Applications/SideStore.app/ Payload/SideStore.app/
|
|
||||||
zip -r SideStore.ipa Payload
|
|
||||||
|
|
||||||
- name: Upload Artifact
|
|
||||||
uses: actions/upload-artifact@v3.1.0
|
|
||||||
with:
|
|
||||||
name: SideStore.ipa
|
|
||||||
path: SideStore.ipa
|
|
||||||
|
|
||||||
- name: Get version
|
|
||||||
id: version
|
|
||||||
run: echo "version=$(grep MARKETING_VERSION Build.xcconfig | sed -e "s/MARKETING_VERSION = //g")" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Get current date
|
- name: Get current date
|
||||||
id: date
|
id: date
|
||||||
@@ -129,10 +61,11 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
name: ${{ steps.version.outputs.version }}
|
name: ${{ steps.version.outputs.version }}
|
||||||
tag_name: ${{ github.ref }}
|
tag_name: ${{ github.ref_name }}
|
||||||
draft: true
|
draft: true
|
||||||
files: SideStore.ipa
|
files: SideStore.ipa
|
||||||
body: |
|
body: |
|
||||||
|
<!-- NOTE: to reset SideSource cache, go to `https://apps.sidestore.io/reset-cache/nightly/<sidesource key>`. This is not included in the GitHub Action since it makes draft releases so they can be edited and have a changelog. -->
|
||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
- TODO
|
- TODO
|
||||||
@@ -143,3 +76,18 @@ jobs:
|
|||||||
Built at (UTC date): `${{ steps.date_altstore.outputs.date }}`
|
Built at (UTC date): `${{ steps.date_altstore.outputs.date }}`
|
||||||
Commit SHA: `${{ github.sha }}`
|
Commit SHA: `${{ github.sha }}`
|
||||||
Version: `${{ steps.version.outputs.version }}`
|
Version: `${{ steps.version.outputs.version }}`
|
||||||
|
|
||||||
|
- name: Add version to IPA file name
|
||||||
|
run: mv SideStore.ipa SideStore-${{ steps.version.outputs.version }}.ipa
|
||||||
|
|
||||||
|
- name: Upload SideStore.ipa Artifact
|
||||||
|
uses: actions/upload-artifact@v3.1.0
|
||||||
|
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
|
||||||
|
with:
|
||||||
|
name: SideStore-${{ steps.version.outputs.version }}-dSYM
|
||||||
|
path: ./*.dSYM/
|
||||||
|
|||||||
11
.gitignore
vendored
11
.gitignore
vendored
@@ -34,4 +34,13 @@ xcuserdata
|
|||||||
|
|
||||||
## AppCode specific
|
## AppCode specific
|
||||||
.idea/
|
.idea/
|
||||||
/.build
|
|
||||||
|
Payload/
|
||||||
|
SideStore.ipa
|
||||||
|
*.dSYM
|
||||||
|
|
||||||
|
Dependencies/.*-prebuilt-fetch-*
|
||||||
|
Dependencies/minimuxer/*
|
||||||
|
Dependencies/em_proxy/*
|
||||||
|
!Dependencies/**/.gitkeep
|
||||||
|
.nightly-build-num
|
||||||
|
|||||||
8
.gitmodules
vendored
8
.gitmodules
vendored
@@ -9,19 +9,13 @@
|
|||||||
url = https://github.com/libimobiledevice/libusbmuxd.git
|
url = https://github.com/libimobiledevice/libusbmuxd.git
|
||||||
[submodule "Dependencies/libplist"]
|
[submodule "Dependencies/libplist"]
|
||||||
path = Dependencies/libplist
|
path = Dependencies/libplist
|
||||||
url = https://github.com/libimobiledevice/libplist.git
|
url = https://github.com/SideStore/libplist.git
|
||||||
[submodule "Dependencies/MarkdownAttributedString"]
|
[submodule "Dependencies/MarkdownAttributedString"]
|
||||||
path = Dependencies/MarkdownAttributedString
|
path = Dependencies/MarkdownAttributedString
|
||||||
url = https://github.com/chockenberry/MarkdownAttributedString.git
|
url = https://github.com/chockenberry/MarkdownAttributedString.git
|
||||||
[submodule "Dependencies/em_proxy"]
|
|
||||||
path = Dependencies/em_proxy
|
|
||||||
url = https://github.com/jkcoxson/em_proxy
|
|
||||||
[submodule "Dependencies/libimobiledevice-glue"]
|
[submodule "Dependencies/libimobiledevice-glue"]
|
||||||
path = Dependencies/libimobiledevice-glue
|
path = Dependencies/libimobiledevice-glue
|
||||||
url = https://github.com/libimobiledevice/libimobiledevice-glue
|
url = https://github.com/libimobiledevice/libimobiledevice-glue
|
||||||
[submodule "Dependencies/minimuxer"]
|
|
||||||
path = Dependencies/minimuxer
|
|
||||||
url = https://github.com/jkcoxson/minimuxer
|
|
||||||
[submodule "Dependencies/libfragmentzip"]
|
[submodule "Dependencies/libfragmentzip"]
|
||||||
path = Dependencies/libfragmentzip
|
path = Dependencies/libfragmentzip
|
||||||
url = https://github.com/SideStore/libfragmentzip.git
|
url = https://github.com/SideStore/libfragmentzip.git
|
||||||
|
|||||||
@@ -5,9 +5,9 @@
|
|||||||
"color-space" : "srgb",
|
"color-space" : "srgb",
|
||||||
"components" : {
|
"components" : {
|
||||||
"alpha" : "1.000",
|
"alpha" : "1.000",
|
||||||
"blue" : "0.518",
|
"blue" : "175",
|
||||||
"green" : "0.502",
|
"green" : "4",
|
||||||
"red" : "0.004"
|
"red" : "115"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"idiom" : "universal"
|
"idiom" : "universal"
|
||||||
@@ -23,9 +23,9 @@
|
|||||||
"color-space" : "srgb",
|
"color-space" : "srgb",
|
||||||
"components" : {
|
"components" : {
|
||||||
"alpha" : "1.000",
|
"alpha" : "1.000",
|
||||||
"blue" : "0.404",
|
"blue" : "150",
|
||||||
"green" : "0.322",
|
"green" : "3",
|
||||||
"red" : "0.008"
|
"red" : "99"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"idiom" : "universal"
|
"idiom" : "universal"
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
#include "Build.xcconfig"
|
#include "Build.xcconfig"
|
||||||
|
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = $(ORG_PREFIX).$(PRODUCT_NAME)
|
PRODUCT_BUNDLE_IDENTIFIER = $(PRODUCT_BUNDLE_IDENTIFIER)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,113 @@
|
|||||||
|
{
|
||||||
|
"pins" : [
|
||||||
|
{
|
||||||
|
"identity" : "altsign",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/SideStore/AltSign",
|
||||||
|
"state" : {
|
||||||
|
"branch" : "master",
|
||||||
|
"revision" : "cc6189f0f7cd8e5bd24943af9322e0ff9420e9f4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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" : "0faf71a188bcfdf0245cab42886b9b240ca71c52",
|
||||||
|
"version" : "1.1.2200"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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" : "a70840d5fca686ae3bd2fcf8aecc5ded0bd4f125",
|
||||||
|
"version" : "0.3.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "sparkle",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/sparkle-project/Sparkle.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "f0ceaf5cc9f3f23daa0ccb6dcebd79fc96ccc7d9",
|
||||||
|
"version" : "2.5.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "starscream",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/daltoniam/Starscream.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "ac6c0fc9da221873e01bd1a0d4818498a71eef33",
|
||||||
|
"version" : "4.0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "stprivilegedtask",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/JoeMatt/STPrivilegedTask.git",
|
||||||
|
"state" : {
|
||||||
|
"branch" : "master",
|
||||||
|
"revision" : "10a9150ef32d444af326beba76356ae9af95a3e7"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version" : 2
|
||||||
|
}
|
||||||
@@ -4,35 +4,11 @@
|
|||||||
<dict>
|
<dict>
|
||||||
<key>aps-environment</key>
|
<key>aps-environment</key>
|
||||||
<string>development</string>
|
<string>development</string>
|
||||||
<key>com.apple.developer.associated-domains</key>
|
|
||||||
<array>
|
|
||||||
<string>webcredentials:sidestore.io</string>
|
|
||||||
</array>
|
|
||||||
<key>com.apple.developer.icloud-container-identifiers</key>
|
|
||||||
<array/>
|
|
||||||
<key>com.apple.developer.kernel.increased-memory-limit</key>
|
|
||||||
<true/>
|
|
||||||
<key>com.apple.developer.networking.multipath</key>
|
|
||||||
<true/>
|
|
||||||
<key>com.apple.developer.networking.vpn.api</key>
|
|
||||||
<array>
|
|
||||||
<string>allow-vpn</string>
|
|
||||||
</array>
|
|
||||||
<key>com.apple.developer.networking.wifi-info</key>
|
|
||||||
<true/>
|
|
||||||
<key>com.apple.developer.shared-with-you</key>
|
|
||||||
<true/>
|
|
||||||
<key>com.apple.developer.siri</key>
|
<key>com.apple.developer.siri</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>com.apple.developer.ubiquity-kvstore-identifier</key>
|
|
||||||
<string>$(TeamIdentifierPrefix)$(CFBundleIdentifier)</string>
|
|
||||||
<key>com.apple.security.application-groups</key>
|
<key>com.apple.security.application-groups</key>
|
||||||
<array>
|
<array>
|
||||||
<string>group.$(APP_GROUP_IDENTIFIER)</string>
|
<string>group.$(APP_GROUP_IDENTIFIER)</string>
|
||||||
</array>
|
</array>
|
||||||
<key>keychain-access-groups</key>
|
|
||||||
<array>
|
|
||||||
<string>$(AppIdentifierPrefix)$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ extension AnalyticsManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AnalyticsManager
|
final class AnalyticsManager
|
||||||
{
|
{
|
||||||
static let shared = AnalyticsManager()
|
static let shared = AnalyticsManager()
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ extension AppContentViewController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AppContentViewController: UITableViewController
|
final class AppContentViewController: UITableViewController
|
||||||
{
|
{
|
||||||
var app: StoreApp!
|
var app: StoreApp!
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class PermissionCollectionViewCell: UICollectionViewCell
|
final class PermissionCollectionViewCell: UICollectionViewCell
|
||||||
{
|
{
|
||||||
@IBOutlet var button: UIButton!
|
@IBOutlet var button: UIButton!
|
||||||
@IBOutlet var textLabel: UILabel!
|
@IBOutlet var textLabel: UILabel!
|
||||||
@@ -29,7 +29,7 @@ class PermissionCollectionViewCell: UICollectionViewCell
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AppContentTableViewCell: UITableViewCell
|
final class AppContentTableViewCell: UITableViewCell
|
||||||
{
|
{
|
||||||
override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize
|
override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import Roxas
|
|||||||
|
|
||||||
import Nuke
|
import Nuke
|
||||||
|
|
||||||
class AppViewController: UIViewController
|
final class AppViewController: UIViewController
|
||||||
{
|
{
|
||||||
var app: StoreApp!
|
var app: StoreApp!
|
||||||
|
|
||||||
@@ -217,8 +217,8 @@ class AppViewController: UIViewController
|
|||||||
|
|
||||||
self._shouldResetLayout = false
|
self._shouldResetLayout = false
|
||||||
}
|
}
|
||||||
|
|
||||||
let statusBarHeight = UIApplication.shared.statusBarFrame.height
|
let statusBarHeight = self.view.window?.windowScene?.statusBarManager?.statusBarFrame.height ?? 0
|
||||||
let cornerRadius = self.contentViewControllerShadowView.layer.cornerRadius
|
let cornerRadius = self.contentViewControllerShadowView.layer.cornerRadius
|
||||||
|
|
||||||
let inset = 12 as CGFloat
|
let inset = 12 as CGFloat
|
||||||
@@ -323,7 +323,7 @@ class AppViewController: UIViewController
|
|||||||
|
|
||||||
self.backButtonContainerView.layer.cornerRadius = self.backButtonContainerView.bounds.midY
|
self.backButtonContainerView.layer.cornerRadius = self.backButtonContainerView.bounds.midY
|
||||||
|
|
||||||
self.scrollView.scrollIndicatorInsets.top = statusBarHeight
|
self.scrollView.verticalScrollIndicatorInsets.top = statusBarHeight
|
||||||
|
|
||||||
// Adjust content offset + size.
|
// Adjust content offset + size.
|
||||||
let contentOffset = self.scrollView.contentOffset
|
let contentOffset = self.scrollView.contentOffset
|
||||||
@@ -352,7 +352,7 @@ class AppViewController: UIViewController
|
|||||||
|
|
||||||
extension AppViewController
|
extension AppViewController
|
||||||
{
|
{
|
||||||
class func makeAppViewController(app: StoreApp) -> AppViewController
|
final class func makeAppViewController(app: StoreApp) -> AppViewController
|
||||||
{
|
{
|
||||||
let storyboard = UIStoryboard(name: "Main", bundle: nil)
|
let storyboard = UIStoryboard(name: "Main", bundle: nil)
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import UIKit
|
|||||||
|
|
||||||
import AltStoreCore
|
import AltStoreCore
|
||||||
|
|
||||||
class PermissionPopoverViewController: UIViewController
|
final class PermissionPopoverViewController: UIViewController
|
||||||
{
|
{
|
||||||
var permission: AppPermission!
|
var permission: AppPermission!
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import UIKit
|
|||||||
import AltStoreCore
|
import AltStoreCore
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
class AppIDsViewController: UICollectionViewController
|
final class AppIDsViewController: UICollectionViewController
|
||||||
{
|
{
|
||||||
private lazy var dataSource = self.makeDataSource()
|
private lazy var dataSource = self.makeDataSource()
|
||||||
|
|
||||||
@@ -90,14 +90,21 @@ private extension AppIDsViewController
|
|||||||
cell.bannerView.button.isUserInteractionEnabled = false
|
cell.bannerView.button.isUserInteractionEnabled = false
|
||||||
|
|
||||||
cell.bannerView.buttonLabel.isHidden = false
|
cell.bannerView.buttonLabel.isHidden = false
|
||||||
|
|
||||||
let currentDate = Date()
|
let currentDate = Date()
|
||||||
|
|
||||||
let numberOfDays = expirationDate.numberOfCalendarDays(since: currentDate)
|
let formatter = DateComponentsFormatter()
|
||||||
let numberOfDaysText = (numberOfDays == 1) ? NSLocalizedString("1 day", comment: "") : String(format: NSLocalizedString("%@ days", comment: ""), NSNumber(value: numberOfDays))
|
formatter.unitsStyle = .full
|
||||||
cell.bannerView.button.setTitle(numberOfDaysText.uppercased(), for: .normal)
|
formatter.includesApproximationPhrase = false
|
||||||
|
formatter.includesTimeRemainingPhrase = false
|
||||||
|
formatter.allowedUnits = [.minute, .hour, .day]
|
||||||
|
formatter.maximumUnitCount = 1
|
||||||
|
|
||||||
attributedAccessibilityLabel.mutableString.append(String(format: NSLocalizedString("Expires in %@.", comment: ""), numberOfDaysText) + " ")
|
cell.bannerView.button.setTitle((formatter.string(from: currentDate, to: expirationDate) ?? NSLocalizedString("Unknown", comment: "")).uppercased(), for: .normal)
|
||||||
|
|
||||||
|
// formatter.includesTimeRemainingPhrase = true
|
||||||
|
|
||||||
|
// attributedAccessibilityLabel.mutableString.append((formatter.string(from: currentDate, to: expirationDate) ?? NSLocalizedString("Unknown", comment: "")) + " ")
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ extension AppDelegate
|
|||||||
}
|
}
|
||||||
|
|
||||||
@UIApplicationMain
|
@UIApplicationMain
|
||||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
final class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
|
|
||||||
var window: UIWindow?
|
var window: UIWindow?
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import UIKit
|
|||||||
|
|
||||||
import AltSign
|
import AltSign
|
||||||
|
|
||||||
class AuthenticationViewController: UIViewController
|
final class AuthenticationViewController: UIViewController
|
||||||
{
|
{
|
||||||
var authenticationHandler: ((String, String, @escaping (Result<(ALTAccount, ALTAppleAPISession), Error>) -> Void) -> Void)?
|
var authenticationHandler: ((String, String, @escaping (Result<(ALTAccount, ALTAppleAPISession), Error>) -> Void) -> Void)?
|
||||||
var completionHandler: (((ALTAccount, ALTAppleAPISession, String)?) -> Void)?
|
var completionHandler: (((ALTAccount, ALTAppleAPISession, String)?) -> Void)?
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class InstructionsViewController: UIViewController
|
final class InstructionsViewController: UIViewController
|
||||||
{
|
{
|
||||||
var completionHandler: (() -> Void)?
|
var completionHandler: (() -> Void)?
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import AltStoreCore
|
|||||||
import AltSign
|
import AltSign
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
class RefreshAltStoreViewController: UIViewController
|
final class RefreshAltStoreViewController: UIViewController
|
||||||
{
|
{
|
||||||
var context: AuthenticatedOperationContext!
|
var context: AuthenticatedOperationContext!
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import IntentsUI
|
|||||||
|
|
||||||
import AltSign
|
import AltSign
|
||||||
|
|
||||||
class SelectTeamViewController: UITableViewController
|
final class SelectTeamViewController: UITableViewController
|
||||||
{
|
{
|
||||||
public var teams: [ALTTeam]?
|
public var teams: [ALTTeam]?
|
||||||
public var completionHandler: ((Result<ALTTeam, Swift.Error>) -> Void)?
|
public var completionHandler: ((Result<ALTTeam, Swift.Error>) -> Void)?
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import Roxas
|
|||||||
|
|
||||||
import Nuke
|
import Nuke
|
||||||
|
|
||||||
@objc class BrowseCollectionViewCell: UICollectionViewCell
|
@objc final class BrowseCollectionViewCell: UICollectionViewCell
|
||||||
{
|
{
|
||||||
var imageURLs: [URL] = [] {
|
var imageURLs: [URL] = [] {
|
||||||
didSet {
|
didSet {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
|
import minimuxer
|
||||||
import AltStoreCore
|
import AltStoreCore
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
@@ -264,7 +265,13 @@ private extension BrowseViewController
|
|||||||
previousProgress?.cancel()
|
previousProgress?.cancel()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !minimuxer.ready() {
|
||||||
|
let toastView = ToastView(error: MinimuxerError.NoConnection)
|
||||||
|
toastView.show(in: self)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
_ = AppManager.shared.install(app, presentingViewController: self) { (result) in
|
_ = AppManager.shared.install(app, presentingViewController: self) { (result) in
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
switch result
|
switch result
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class AppIconImageView: UIImageView
|
final class AppIconImageView: UIImageView
|
||||||
{
|
{
|
||||||
override func awakeFromNib()
|
override func awakeFromNib()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
import AVFoundation
|
import AVFoundation
|
||||||
|
|
||||||
class BackgroundTaskManager
|
final class BackgroundTaskManager
|
||||||
{
|
{
|
||||||
static let shared = BackgroundTaskManager()
|
static let shared = BackgroundTaskManager()
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class BannerCollectionViewCell: UICollectionViewCell
|
final class BannerCollectionViewCell: UICollectionViewCell
|
||||||
{
|
{
|
||||||
private(set) var errorBadge: UIView?
|
private(set) var errorBadge: UIView?
|
||||||
@IBOutlet private(set) var bannerView: AppBannerView!
|
@IBOutlet private(set) var bannerView: AppBannerView!
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class Button: UIButton
|
final class Button: UIButton
|
||||||
{
|
{
|
||||||
override var intrinsicContentSize: CGSize {
|
override var intrinsicContentSize: CGSize {
|
||||||
var size = super.intrinsicContentSize
|
var size = super.intrinsicContentSize
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class CollapsingTextView: UITextView
|
final class CollapsingTextView: UITextView
|
||||||
{
|
{
|
||||||
var isCollapsed = true {
|
var isCollapsed = true {
|
||||||
didSet {
|
didSet {
|
||||||
@@ -22,7 +22,7 @@ class CollapsingTextView: UITextView
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var lineSpacing: CGFloat = 2 {
|
var lineSpacing: Double = 2 {
|
||||||
didSet {
|
didSet {
|
||||||
self.setNeedsLayout()
|
self.setNeedsLayout()
|
||||||
}
|
}
|
||||||
@@ -34,7 +34,19 @@ class CollapsingTextView: UITextView
|
|||||||
{
|
{
|
||||||
super.awakeFromNib()
|
super.awakeFromNib()
|
||||||
|
|
||||||
self.layoutManager.delegate = self
|
self.initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func initialize()
|
||||||
|
{
|
||||||
|
if #available(iOS 16, *)
|
||||||
|
{
|
||||||
|
self.updateText()
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self.layoutManager.delegate = self
|
||||||
|
}
|
||||||
|
|
||||||
self.textContainerInset = .zero
|
self.textContainerInset = .zero
|
||||||
self.textContainer.lineFragmentPadding = 0
|
self.textContainer.lineFragmentPadding = 0
|
||||||
@@ -108,6 +120,25 @@ private extension CollapsingTextView
|
|||||||
{
|
{
|
||||||
self.isCollapsed.toggle()
|
self.isCollapsed.toggle()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(iOS 16, *)
|
||||||
|
func updateText()
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
let style = NSMutableParagraphStyle()
|
||||||
|
style.lineSpacing = self.lineSpacing
|
||||||
|
|
||||||
|
var attributedText = try AttributedString(self.attributedText, including: \.uiKit)
|
||||||
|
attributedText[AttributeScopes.UIKitAttributes.ParagraphStyleAttribute.self] = style
|
||||||
|
|
||||||
|
self.attributedText = NSAttributedString(attributedText)
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
print("[ALTLog] Failed to update CollapsingTextView line spacing:", error)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension CollapsingTextView: NSLayoutManagerDelegate
|
extension CollapsingTextView: NSLayoutManagerDelegate
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class ForwardingNavigationController: UINavigationController
|
final class ForwardingNavigationController: UINavigationController
|
||||||
{
|
{
|
||||||
override var childForStatusBarStyle: UIViewController? {
|
override var childForStatusBarStyle: UIViewController? {
|
||||||
return self.topViewController
|
return self.topViewController
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import UIKit
|
|||||||
|
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
class NavigationBar: UINavigationBar
|
final class NavigationBar: UINavigationBar
|
||||||
{
|
{
|
||||||
@IBInspectable var automaticallyAdjustsItemPositions: Bool = true
|
@IBInspectable var automaticallyAdjustsItemPositions: Bool = true
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,13 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class PillButton: UIButton
|
extension PillButton
|
||||||
|
{
|
||||||
|
static let minimumSize = CGSize(width: 77, height: 31)
|
||||||
|
static let contentInsets = NSDirectionalEdgeInsets(top: 7, leading: 13, bottom: 7, trailing: 13)
|
||||||
|
}
|
||||||
|
|
||||||
|
final class PillButton: UIButton
|
||||||
{
|
{
|
||||||
override var accessibilityValue: String? {
|
override var accessibilityValue: String? {
|
||||||
get {
|
get {
|
||||||
@@ -70,9 +76,7 @@ class PillButton: UIButton
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
override var intrinsicContentSize: CGSize {
|
override var intrinsicContentSize: CGSize {
|
||||||
var size = super.intrinsicContentSize
|
let size = self.sizeThatFits(CGSize(width: Double.infinity, height: .infinity))
|
||||||
size.width += 26
|
|
||||||
size.height += 3
|
|
||||||
return size
|
return size
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,6 +92,8 @@ class PillButton: UIButton
|
|||||||
self.layer.masksToBounds = true
|
self.layer.masksToBounds = true
|
||||||
self.accessibilityTraits.formUnion([.updatesFrequently, .button])
|
self.accessibilityTraits.formUnion([.updatesFrequently, .button])
|
||||||
|
|
||||||
|
self.contentEdgeInsets = UIEdgeInsets(top: Self.contentInsets.top, left: Self.contentInsets.leading, bottom: Self.contentInsets.bottom, right: Self.contentInsets.trailing)
|
||||||
|
|
||||||
self.activityIndicatorView.style = .medium
|
self.activityIndicatorView.style = .medium
|
||||||
self.activityIndicatorView.isUserInteractionEnabled = false
|
self.activityIndicatorView.isUserInteractionEnabled = false
|
||||||
|
|
||||||
@@ -119,6 +125,15 @@ class PillButton: UIButton
|
|||||||
|
|
||||||
self.update()
|
self.update()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func sizeThatFits(_ size: CGSize) -> CGSize
|
||||||
|
{
|
||||||
|
var size = super.sizeThatFits(size)
|
||||||
|
size.width = max(size.width, PillButton.minimumSize.width)
|
||||||
|
size.height = max(size.height, PillButton.minimumSize.height)
|
||||||
|
|
||||||
|
return size
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension PillButton
|
private extension PillButton
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ extension TimeInterval
|
|||||||
static let longToastViewDuration = 8.0
|
static let longToastViewDuration = 8.0
|
||||||
}
|
}
|
||||||
|
|
||||||
class ToastView: RSTToastView
|
final class ToastView: RSTToastView
|
||||||
{
|
{
|
||||||
var preferredDuration: TimeInterval
|
var preferredDuration: TimeInterval
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import OSLog
|
import OSLog
|
||||||
|
|
||||||
let customLog = OSLog(subsystem: "org.sidestore.sidestore",
|
public let customLog = OSLog(subsystem: "org.sidestore.sidestore",
|
||||||
category: "ios")
|
category: "ios")
|
||||||
|
|
||||||
|
|
||||||
@@ -18,6 +18,7 @@ public extension OSLog {
|
|||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - message: String or format string
|
/// - message: String or format string
|
||||||
/// - args: optional args for format string
|
/// - args: optional args for format string
|
||||||
|
@inlinable
|
||||||
static func error(_ message: StaticString, _ args: CVarArg...) {
|
static func error(_ message: StaticString, _ args: CVarArg...) {
|
||||||
os_log(message, log: customLog, type: .error, args)
|
os_log(message, log: customLog, type: .error, args)
|
||||||
}
|
}
|
||||||
@@ -26,6 +27,7 @@ public extension OSLog {
|
|||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - message: String or format string
|
/// - message: String or format string
|
||||||
/// - args: optional args for format string
|
/// - args: optional args for format string
|
||||||
|
@inlinable
|
||||||
static func info(_ message: StaticString, _ args: CVarArg...) {
|
static func info(_ message: StaticString, _ args: CVarArg...) {
|
||||||
os_log(message, log: customLog, type: .info, args)
|
os_log(message, log: customLog, type: .info, args)
|
||||||
}
|
}
|
||||||
@@ -34,6 +36,7 @@ public extension OSLog {
|
|||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - message: String or format string
|
/// - message: String or format string
|
||||||
/// - args: optional args for format string
|
/// - args: optional args for format string
|
||||||
|
@inlinable
|
||||||
static func debug(_ message: StaticString, _ args: CVarArg...) {
|
static func debug(_ message: StaticString, _ args: CVarArg...) {
|
||||||
os_log(message, log: customLog, type: .debug, args)
|
os_log(message, log: customLog, type: .debug, args)
|
||||||
}
|
}
|
||||||
@@ -45,6 +48,7 @@ public extension OSLog {
|
|||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - message: String or format string
|
/// - message: String or format string
|
||||||
/// - args: optional args for format string
|
/// - args: optional args for format string
|
||||||
|
@inlinable
|
||||||
public func ELOG(_ message: StaticString, file: StaticString = #file, function: StaticString = #function, line: UInt = #line, _ args: CVarArg...) {
|
public func ELOG(_ message: StaticString, file: StaticString = #file, function: StaticString = #function, line: UInt = #line, _ args: CVarArg...) {
|
||||||
OSLog.error(message, args)
|
OSLog.error(message, args)
|
||||||
}
|
}
|
||||||
@@ -53,6 +57,7 @@ public func ELOG(_ message: StaticString, file: StaticString = #file, function:
|
|||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - message: String or format string
|
/// - message: String or format string
|
||||||
/// - args: optional args for format string
|
/// - args: optional args for format string
|
||||||
|
@inlinable
|
||||||
public func ILOG(_ message: StaticString, file: StaticString = #file, function: StaticString = #function, line: UInt = #line, _ args: CVarArg...) {
|
public func ILOG(_ message: StaticString, file: StaticString = #file, function: StaticString = #function, line: UInt = #line, _ args: CVarArg...) {
|
||||||
OSLog.info(message, args)
|
OSLog.info(message, args)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
<key>ALTPairingFile</key>
|
<key>ALTPairingFile</key>
|
||||||
<string><insert pairing file here></string>
|
<string><insert pairing file here></string>
|
||||||
<key>ALTAnisetteURL</key>
|
<key>ALTAnisetteURL</key>
|
||||||
<string>https://sideloadly.io/anisette/irGb3Quww8zrhgqnzmrx</string>
|
<string>https://ani.sidestore.io</string>
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
<key>CFBundleDocumentTypes</key>
|
<key>CFBundleDocumentTypes</key>
|
||||||
@@ -58,7 +58,7 @@
|
|||||||
<key>CFBundleURLSchemes</key>
|
<key>CFBundleURLSchemes</key>
|
||||||
<array>
|
<array>
|
||||||
<string>altstore</string>
|
<string>altstore</string>
|
||||||
<string>sidestore</string>
|
<string>sidestore</string>
|
||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
<dict>
|
<dict>
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
<key>CFBundleURLSchemes</key>
|
<key>CFBundleURLSchemes</key>
|
||||||
<array>
|
<array>
|
||||||
<string>altstore-com.rileytestut.AltStore</string>
|
<string>altstore-com.rileytestut.AltStore</string>
|
||||||
<string>sidestore-com.SideStore.SideStore</string>
|
<string>sidestore-com.SideStore.SideStore</string>
|
||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
@@ -93,40 +93,6 @@
|
|||||||
</array>
|
</array>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>NSAppTransportSecurity</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSExceptionDomains</key>
|
|
||||||
<dict>
|
|
||||||
<key>127.0.0.1</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSExceptionAllowsInsecureHTTPLoads</key>
|
|
||||||
<true/>
|
|
||||||
<key>NSIncludesSubdomains</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
|
||||||
<key>New Exception Domain 1</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSExceptionAllowsInsecureHTTPLoads</key>
|
|
||||||
<true/>
|
|
||||||
<key>NSIncludesSubdomains</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
|
||||||
<key>apps.sidestore.io</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSExceptionAllowsInsecureHTTPLoads</key>
|
|
||||||
<true/>
|
|
||||||
<key>NSIncludesSubdomains</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
|
||||||
<key>sidestore.io</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSExceptionAllowsInsecureHTTPLoads</key>
|
|
||||||
<true/>
|
|
||||||
<key>NSIncludesSubdomains</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<key>NSBonjourServices</key>
|
<key>NSBonjourServices</key>
|
||||||
<array>
|
<array>
|
||||||
<string>_altserver._tcp</string>
|
<string>_altserver._tcp</string>
|
||||||
@@ -163,7 +129,6 @@
|
|||||||
<array>
|
<array>
|
||||||
<string>audio</string>
|
<string>audio</string>
|
||||||
<string>fetch</string>
|
<string>fetch</string>
|
||||||
<string>processing</string>
|
|
||||||
<string>remote-notification</string>
|
<string>remote-notification</string>
|
||||||
</array>
|
</array>
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
@@ -239,5 +204,7 @@
|
|||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
|
<key>UIFileSharingEnabled</key>
|
||||||
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import Foundation
|
|||||||
import AltStoreCore
|
import AltStoreCore
|
||||||
|
|
||||||
@available(iOS 14, *)
|
@available(iOS 14, *)
|
||||||
class IntentHandler: NSObject, RefreshAllIntentHandling
|
final class IntentHandler: NSObject, RefreshAllIntentHandling
|
||||||
{
|
{
|
||||||
private let queue = DispatchQueue(label: "io.altstore.IntentHandler")
|
private let queue = DispatchQueue(label: "io.altstore.IntentHandler")
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ import minimuxer
|
|||||||
import AltStoreCore
|
import AltStoreCore
|
||||||
import UniformTypeIdentifiers
|
import UniformTypeIdentifiers
|
||||||
|
|
||||||
class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDelegate
|
let pairingFileName = "ALTPairingFile.mobiledevicepairing"
|
||||||
|
|
||||||
|
final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDelegate
|
||||||
{
|
{
|
||||||
private var didFinishLaunching = false
|
private var didFinishLaunching = false
|
||||||
|
|
||||||
@@ -47,6 +49,7 @@ class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDelegate
|
|||||||
|
|
||||||
override func viewDidAppear(_ animated: Bool) {
|
override func viewDidAppear(_ animated: Bool) {
|
||||||
super.viewDidAppear(true)
|
super.viewDidAppear(true)
|
||||||
|
#if !targetEnvironment(simulator)
|
||||||
start_em_proxy(bind_addr: Consts.Proxy.serverURL)
|
start_em_proxy(bind_addr: Consts.Proxy.serverURL)
|
||||||
|
|
||||||
guard let pf = fetchPairingFile() else {
|
guard let pf = fetchPairingFile() else {
|
||||||
@@ -54,6 +57,7 @@ class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDelegate
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
start_minimuxer_threads(pf)
|
start_minimuxer_threads(pf)
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchPairingFile() -> String? {
|
func fetchPairingFile() -> String? {
|
||||||
@@ -77,7 +81,7 @@ class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDelegate
|
|||||||
} else {
|
} else {
|
||||||
// Show an alert explaining the pairing file
|
// Show an alert explaining the pairing file
|
||||||
// Create new Alert
|
// Create new Alert
|
||||||
let dialogMessage = UIAlertController(title: "Pairing File", message: "Select the pairing file for your device. For more information, go to https://youtu.be/dQw4w9WgXcQ", preferredStyle: .alert)
|
let dialogMessage = UIAlertController(title: "Pairing File", message: "Select the pairing file for your device. For more information, go to https://wiki.sidestore.io/guides/getting-started/#pairing-file", preferredStyle: .alert)
|
||||||
|
|
||||||
// Create OK button with action handler
|
// Create OK button with action handler
|
||||||
let ok = UIAlertAction(title: "OK", style: .default, handler: { (action) -> Void in
|
let ok = UIAlertAction(title: "OK", style: .default, handler: { (action) -> Void in
|
||||||
@@ -123,14 +127,11 @@ class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDelegate
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Save to a file for next launch
|
// Save to a file for next launch
|
||||||
let filename = "ALTPairingFile.mobiledevicepairing"
|
let pairingFile = FileManager.default.documentsDirectory.appendingPathComponent("\(pairingFileName)")
|
||||||
let fm = FileManager.default
|
try pairing_string?.write(to: pairingFile, atomically: true, encoding: String.Encoding.utf8)
|
||||||
let documentsPath = fm.documentsDirectory.appendingPathComponent("/\(filename)")
|
|
||||||
try pairing_string?.write(to: documentsPath, atomically: true, encoding: String.Encoding.utf8)
|
|
||||||
|
|
||||||
// Start minimuxer now that we have a file
|
// Start minimuxer now that we have a file
|
||||||
start_minimuxer_threads(pairing_string!)
|
start_minimuxer_threads(pairing_string!)
|
||||||
|
|
||||||
} catch {
|
} catch {
|
||||||
displayError("Unable to read pairing file")
|
displayError("Unable to read pairing file")
|
||||||
}
|
}
|
||||||
@@ -146,12 +147,20 @@ class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDelegate
|
|||||||
}
|
}
|
||||||
|
|
||||||
func start_minimuxer_threads(_ pairing_file: String) {
|
func start_minimuxer_threads(_ pairing_file: String) {
|
||||||
set_usbmuxd_socket()
|
target_minimuxer_address()
|
||||||
let res = start_minimuxer(pairing_file: pairing_file)
|
let documentsDirectory = FileManager.default.documentsDirectory.absoluteString
|
||||||
if res != 0 {
|
do {
|
||||||
displayError("minimuxer failed to start. Incorrect arguments were passed.")
|
try start(pairing_file, documentsDirectory)
|
||||||
|
} catch {
|
||||||
|
try! FileManager.default.removeItem(at: FileManager.default.documentsDirectory.appendingPathComponent("\(pairingFileName)"))
|
||||||
|
displayError("minimuxer failed to start, please restart SideStore. \((error as? LocalizedError)?.failureReason ?? "UNKNOWN ERROR!!!!!! REPORT TO GITHUB ISSUES!")")
|
||||||
|
}
|
||||||
|
if #available(iOS 17, *) {
|
||||||
|
// TODO: iOS 17 and above have a new JIT implementation that is completely broken in SideStore :(
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
start_auto_mounter(documentsDirectory)
|
||||||
}
|
}
|
||||||
auto_mount_dev_image()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ extension AppManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
@available(iOS 13, *)
|
@available(iOS 13, *)
|
||||||
class AppManagerPublisher: ObservableObject
|
final class AppManagerPublisher: ObservableObject
|
||||||
{
|
{
|
||||||
@Published
|
@Published
|
||||||
fileprivate(set) var installationProgress = [String: Progress]()
|
fileprivate(set) var installationProgress = [String: Progress]()
|
||||||
@@ -42,7 +42,7 @@ private func ==(lhs: OperatingSystemVersion, rhs: OperatingSystemVersion) -> Boo
|
|||||||
return (lhs.majorVersion == rhs.majorVersion && lhs.minorVersion == rhs.minorVersion && lhs.patchVersion == rhs.patchVersion)
|
return (lhs.majorVersion == rhs.majorVersion && lhs.minorVersion == rhs.minorVersion && lhs.patchVersion == rhs.patchVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
class AppManager
|
final class AppManager
|
||||||
{
|
{
|
||||||
static let shared = AppManager()
|
static let shared = AppManager()
|
||||||
|
|
||||||
@@ -307,6 +307,16 @@ extension AppManager
|
|||||||
presentingViewController.present(alertController, animated: true, completion: nil)
|
presentingViewController.present(alertController, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func clearAppCache(completion: @escaping (Result<Void, Error>) -> Void)
|
||||||
|
{
|
||||||
|
let clearAppCacheOperation = ClearAppCacheOperation()
|
||||||
|
clearAppCacheOperation.resultHandler = { result in
|
||||||
|
completion(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.run([clearAppCacheOperation], context: nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension AppManager
|
extension AppManager
|
||||||
@@ -392,7 +402,8 @@ extension AppManager
|
|||||||
func fetchAppIDs(completionHandler: @escaping (Result<([AppID], NSManagedObjectContext), Error>) -> Void)
|
func fetchAppIDs(completionHandler: @escaping (Result<([AppID], NSManagedObjectContext), Error>) -> Void)
|
||||||
{
|
{
|
||||||
let authenticationOperation = self.authenticate(presentingViewController: nil) { (result) in
|
let authenticationOperation = self.authenticate(presentingViewController: nil) { (result) in
|
||||||
print("Authenticated for fetching App IDs with result:", result)
|
// result contains name, email, auth token, OTP and other possibly personal/account specific info. we don't want this logged
|
||||||
|
//print("Authenticated for fetching App IDs with result:", result)
|
||||||
}
|
}
|
||||||
|
|
||||||
let fetchAppIDsOperation = FetchAppIDsOperation(context: authenticationOperation.context)
|
let fetchAppIDsOperation = FetchAppIDsOperation(context: authenticationOperation.context)
|
||||||
@@ -664,7 +675,7 @@ extension AppManager
|
|||||||
@available(iOS 14, *)
|
@available(iOS 14, *)
|
||||||
func enableJIT(for installedApp: InstalledApp, completionHandler: @escaping (Result<Void, Error>) -> Void)
|
func enableJIT(for installedApp: InstalledApp, completionHandler: @escaping (Result<Void, Error>) -> Void)
|
||||||
{
|
{
|
||||||
class Context: OperationContext, EnableJITContext
|
final class Context: OperationContext, EnableJITContext
|
||||||
{
|
{
|
||||||
var installedApp: InstalledApp?
|
var installedApp: InstalledApp?
|
||||||
}
|
}
|
||||||
@@ -684,7 +695,7 @@ extension AppManager
|
|||||||
@available(iOS 14.0, *)
|
@available(iOS 14.0, *)
|
||||||
func patch(resignedApp: ALTApplication, presentingViewController: UIViewController, context authContext: AuthenticatedOperationContext, completionHandler: @escaping (Result<InstalledApp, Error>) -> Void) -> PatchAppOperation
|
func patch(resignedApp: ALTApplication, presentingViewController: UIViewController, context authContext: AuthenticatedOperationContext, completionHandler: @escaping (Result<InstalledApp, Error>) -> Void) -> PatchAppOperation
|
||||||
{
|
{
|
||||||
class Context: InstallAppOperationContext, PatchAppContext
|
final class Context: InstallAppOperationContext, PatchAppContext
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -753,6 +764,12 @@ extension AppManager
|
|||||||
let progress = self.refreshProgress[app.bundleIdentifier]
|
let progress = self.refreshProgress[app.bundleIdentifier]
|
||||||
return progress
|
return progress
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isActivelyManagingApp(withBundleID bundleID: String) -> Bool
|
||||||
|
{
|
||||||
|
let isActivelyManaging = self.installationProgress.keys.contains(bundleID) || self.refreshProgress.keys.contains(bundleID)
|
||||||
|
return isActivelyManaging
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension AppManager
|
extension AppManager
|
||||||
@@ -807,12 +824,6 @@ private extension AppManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func isActivelyManagingApp(withBundleID bundleID: String) -> Bool
|
|
||||||
{
|
|
||||||
let isActivelyManaging = self.installationProgress.keys.contains(bundleID) || self.refreshProgress.keys.contains(bundleID)
|
|
||||||
return isActivelyManaging
|
|
||||||
}
|
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
private func perform(_ operations: [AppOperation], presentingViewController: UIViewController?, group: RefreshGroup) -> RefreshGroup
|
private func perform(_ operations: [AppOperation], presentingViewController: UIViewController?, group: RefreshGroup) -> RefreshGroup
|
||||||
{
|
{
|
||||||
@@ -875,7 +886,9 @@ private extension AppManager
|
|||||||
|
|
||||||
if app.certificateSerialNumber != group.context.certificate?.serialNumber ||
|
if app.certificateSerialNumber != group.context.certificate?.serialNumber ||
|
||||||
uti != nil ||
|
uti != nil ||
|
||||||
app.needsResign
|
app.needsResign ||
|
||||||
|
// We need to reinstall ourselves on refresh to ensure the new provisioning profile is used
|
||||||
|
app.bundleIdentifier == StoreApp.altstoreAppID
|
||||||
{
|
{
|
||||||
// Resign app instead of just refreshing profiles because either:
|
// Resign app instead of just refreshing profiles because either:
|
||||||
// * Refreshing using different certificate
|
// * Refreshing using different certificate
|
||||||
@@ -945,7 +958,13 @@ private extension AppManager
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
DispatchQueue.main.schedule {
|
||||||
|
UIApplication.shared.isIdleTimerDisabled = UserDefaults.standard.isIdleTimeoutDisableEnabled
|
||||||
|
}
|
||||||
performAppOperations()
|
performAppOperations()
|
||||||
|
DispatchQueue.main.schedule {
|
||||||
|
UIApplication.shared.isIdleTimerDisabled = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return group
|
return group
|
||||||
@@ -1024,6 +1043,32 @@ private extension AppManager
|
|||||||
verifyOperation.addDependency(downloadOperation)
|
verifyOperation.addDependency(downloadOperation)
|
||||||
|
|
||||||
|
|
||||||
|
/* Refresh Anisette Data */
|
||||||
|
let refreshAnisetteDataOperation = FetchAnisetteDataOperation(context: group.context)
|
||||||
|
refreshAnisetteDataOperation.resultHandler = { (result) in
|
||||||
|
switch result
|
||||||
|
{
|
||||||
|
case .failure(let error): context.error = error
|
||||||
|
case .success(let anisetteData): group.context.session?.anisetteData = anisetteData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
refreshAnisetteDataOperation.addDependency(verifyOperation)
|
||||||
|
|
||||||
|
|
||||||
|
/* Fetch Provisioning Profiles */
|
||||||
|
let fetchProvisioningProfilesOperation = FetchProvisioningProfilesOperation(context: context)
|
||||||
|
fetchProvisioningProfilesOperation.additionalEntitlements = additionalEntitlements
|
||||||
|
fetchProvisioningProfilesOperation.resultHandler = { (result) in
|
||||||
|
switch result
|
||||||
|
{
|
||||||
|
case .failure(let error): context.error = error
|
||||||
|
case .success(let provisioningProfiles): context.provisioningProfiles = provisioningProfiles
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fetchProvisioningProfilesOperation.addDependency(refreshAnisetteDataOperation)
|
||||||
|
progress.addChild(fetchProvisioningProfilesOperation.progress, withPendingUnitCount: 5)
|
||||||
|
|
||||||
|
|
||||||
/* Deactivate Apps (if necessary) */
|
/* Deactivate Apps (if necessary) */
|
||||||
let deactivateAppsOperation = RSTAsyncBlockOperation { [weak self] (operation) in
|
let deactivateAppsOperation = RSTAsyncBlockOperation { [weak self] (operation) in
|
||||||
do
|
do
|
||||||
@@ -1039,6 +1084,12 @@ private extension AppManager
|
|||||||
{
|
{
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
guard let profiles = context.provisioningProfiles else { throw OperationError.invalidParameters }
|
||||||
|
if !profiles.contains(where: { $1.isFreeProvisioningProfile == true }) {
|
||||||
|
operation.finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
guard let app = context.app, let presentingViewController = context.authenticatedContext.presentingViewController else { throw OperationError.invalidParameters }
|
guard let app = context.app, let presentingViewController = context.authenticatedContext.presentingViewController else { throw OperationError.invalidParameters }
|
||||||
|
|
||||||
@@ -1058,7 +1109,7 @@ private extension AppManager
|
|||||||
operation.finish()
|
operation.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
deactivateAppsOperation.addDependency(verifyOperation)
|
deactivateAppsOperation.addDependency(fetchProvisioningProfilesOperation)
|
||||||
|
|
||||||
|
|
||||||
/* Patch App */
|
/* Patch App */
|
||||||
@@ -1133,32 +1184,6 @@ private extension AppManager
|
|||||||
patchAppOperation.addDependency(deactivateAppsOperation)
|
patchAppOperation.addDependency(deactivateAppsOperation)
|
||||||
|
|
||||||
|
|
||||||
/* Refresh Anisette Data */
|
|
||||||
let refreshAnisetteDataOperation = FetchAnisetteDataOperation(context: group.context)
|
|
||||||
refreshAnisetteDataOperation.resultHandler = { (result) in
|
|
||||||
switch result
|
|
||||||
{
|
|
||||||
case .failure(let error): context.error = error
|
|
||||||
case .success(let anisetteData): group.context.session?.anisetteData = anisetteData
|
|
||||||
}
|
|
||||||
}
|
|
||||||
refreshAnisetteDataOperation.addDependency(patchAppOperation)
|
|
||||||
|
|
||||||
|
|
||||||
/* Fetch Provisioning Profiles */
|
|
||||||
let fetchProvisioningProfilesOperation = FetchProvisioningProfilesOperation(context: context)
|
|
||||||
fetchProvisioningProfilesOperation.additionalEntitlements = additionalEntitlements
|
|
||||||
fetchProvisioningProfilesOperation.resultHandler = { (result) in
|
|
||||||
switch result
|
|
||||||
{
|
|
||||||
case .failure(let error): context.error = error
|
|
||||||
case .success(let provisioningProfiles): context.provisioningProfiles = provisioningProfiles
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fetchProvisioningProfilesOperation.addDependency(refreshAnisetteDataOperation)
|
|
||||||
progress.addChild(fetchProvisioningProfilesOperation.progress, withPendingUnitCount: 5)
|
|
||||||
|
|
||||||
|
|
||||||
/* Resign */
|
/* Resign */
|
||||||
let resignAppOperation = ResignAppOperation(context: context)
|
let resignAppOperation = ResignAppOperation(context: context)
|
||||||
resignAppOperation.resultHandler = { (result) in
|
resignAppOperation.resultHandler = { (result) in
|
||||||
@@ -1168,7 +1193,7 @@ private extension AppManager
|
|||||||
case .success(let resignedApp): context.resignedApp = resignedApp
|
case .success(let resignedApp): context.resignedApp = resignedApp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resignAppOperation.addDependency(fetchProvisioningProfilesOperation)
|
resignAppOperation.addDependency(patchAppOperation)
|
||||||
progress.addChild(resignAppOperation.progress, withPendingUnitCount: 20)
|
progress.addChild(resignAppOperation.progress, withPendingUnitCount: 20)
|
||||||
|
|
||||||
|
|
||||||
@@ -1211,7 +1236,7 @@ private extension AppManager
|
|||||||
progress.addChild(installOperation.progress, withPendingUnitCount: 30)
|
progress.addChild(installOperation.progress, withPendingUnitCount: 30)
|
||||||
installOperation.addDependency(sendAppOperation)
|
installOperation.addDependency(sendAppOperation)
|
||||||
|
|
||||||
let operations = [downloadOperation, verifyOperation, deactivateAppsOperation, patchAppOperation, refreshAnisetteDataOperation, fetchProvisioningProfilesOperation, resignAppOperation, sendAppOperation, installOperation]
|
let operations = [downloadOperation, verifyOperation, refreshAnisetteDataOperation, fetchProvisioningProfilesOperation, deactivateAppsOperation, patchAppOperation, resignAppOperation, sendAppOperation, installOperation]
|
||||||
group.add(operations)
|
group.add(operations)
|
||||||
self.run(operations, context: group.context)
|
self.run(operations, context: group.context)
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class InstalledAppsCollectionHeaderView: UICollectionReusableView
|
final class InstalledAppsCollectionHeaderView: UICollectionReusableView
|
||||||
{
|
{
|
||||||
let textLabel: UILabel
|
let textLabel: UILabel
|
||||||
let button: UIButton
|
let button: UIButton
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
import UIKit
|
import UIKit
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
class InstalledAppCollectionViewCell: UICollectionViewCell
|
final class InstalledAppCollectionViewCell: UICollectionViewCell
|
||||||
{
|
{
|
||||||
private(set) var deactivateBadge: UIView?
|
private(set) var deactivateBadge: UIView?
|
||||||
|
|
||||||
@@ -55,13 +55,13 @@ class InstalledAppCollectionViewCell: UICollectionViewCell
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class InstalledAppsCollectionFooterView: UICollectionReusableView
|
final class InstalledAppsCollectionFooterView: UICollectionReusableView
|
||||||
{
|
{
|
||||||
@IBOutlet var textLabel: UILabel!
|
@IBOutlet var textLabel: UILabel!
|
||||||
@IBOutlet var button: UIButton!
|
@IBOutlet var button: UIButton!
|
||||||
}
|
}
|
||||||
|
|
||||||
class NoUpdatesCollectionViewCell: UICollectionViewCell
|
final class NoUpdatesCollectionViewCell: UICollectionViewCell
|
||||||
{
|
{
|
||||||
@IBOutlet var blurView: UIVisualEffectView!
|
@IBOutlet var blurView: UIVisualEffectView!
|
||||||
|
|
||||||
@@ -73,7 +73,7 @@ class NoUpdatesCollectionViewCell: UICollectionViewCell
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class UpdatesCollectionHeaderView: UICollectionReusableView
|
final class UpdatesCollectionHeaderView: UICollectionReusableView
|
||||||
{
|
{
|
||||||
let button = PillButton(type: .system)
|
let button = PillButton(type: .system)
|
||||||
|
|
||||||
|
|||||||
@@ -10,10 +10,12 @@ import UIKit
|
|||||||
import MobileCoreServices
|
import MobileCoreServices
|
||||||
import Intents
|
import Intents
|
||||||
import Combine
|
import Combine
|
||||||
|
import UniformTypeIdentifiers
|
||||||
|
|
||||||
import AltStoreCore
|
import AltStoreCore
|
||||||
import AltSign
|
import AltSign
|
||||||
import Roxas
|
import Roxas
|
||||||
|
import minimuxer
|
||||||
|
|
||||||
import Nuke
|
import Nuke
|
||||||
|
|
||||||
@@ -30,7 +32,7 @@ extension MyAppsViewController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyAppsViewController: UICollectionViewController
|
final class MyAppsViewController: UICollectionViewController
|
||||||
{
|
{
|
||||||
private let coordinator = NSFileCoordinator()
|
private let coordinator = NSFileCoordinator()
|
||||||
private let operationQueue = OperationQueue()
|
private let operationQueue = OperationQueue()
|
||||||
@@ -327,21 +329,25 @@ private extension MyAppsViewController
|
|||||||
let currentDate = Date()
|
let currentDate = Date()
|
||||||
|
|
||||||
let numberOfDays = installedApp.expirationDate.numberOfCalendarDays(since: currentDate)
|
let numberOfDays = installedApp.expirationDate.numberOfCalendarDays(since: currentDate)
|
||||||
let numberOfDaysText: String
|
|
||||||
|
|
||||||
if numberOfDays == 1
|
let formatter = DateComponentsFormatter()
|
||||||
{
|
formatter.unitsStyle = .full
|
||||||
numberOfDaysText = NSLocalizedString("1 day", comment: "")
|
formatter.includesApproximationPhrase = false
|
||||||
}
|
formatter.includesTimeRemainingPhrase = false
|
||||||
else
|
|
||||||
{
|
formatter.allowedUnits = [.day, .hour, .minute]
|
||||||
numberOfDaysText = String(format: NSLocalizedString("%@ days", comment: ""), NSNumber(value: numberOfDays))
|
|
||||||
}
|
formatter.maximumUnitCount = 1
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
cell.bannerView.button.setTitle(formatter.string(from: currentDate, to: installedApp.expirationDate)?.uppercased(), for: .normal)
|
||||||
|
|
||||||
cell.bannerView.button.setTitle(numberOfDaysText.uppercased(), for: .normal)
|
|
||||||
cell.bannerView.button.accessibilityLabel = String(format: NSLocalizedString("Refresh %@", comment: ""), installedApp.name)
|
cell.bannerView.button.accessibilityLabel = String(format: NSLocalizedString("Refresh %@", comment: ""), installedApp.name)
|
||||||
|
|
||||||
cell.bannerView.accessibilityLabel? += ". " + String(format: NSLocalizedString("Expires in %@", comment: ""), numberOfDaysText)
|
formatter.includesTimeRemainingPhrase = true
|
||||||
|
|
||||||
|
cell.bannerView.accessibilityLabel? += ". " + (formatter.string(from: currentDate, to: installedApp.expirationDate) ?? NSLocalizedString("Unknown", comment: "")) + " "
|
||||||
|
|
||||||
// Make sure refresh button is correct size.
|
// Make sure refresh button is correct size.
|
||||||
cell.layoutIfNeeded()
|
cell.layoutIfNeeded()
|
||||||
@@ -639,6 +645,12 @@ private extension MyAppsViewController
|
|||||||
|
|
||||||
@IBAction func refreshAllApps(_ sender: UIBarButtonItem)
|
@IBAction func refreshAllApps(_ sender: UIBarButtonItem)
|
||||||
{
|
{
|
||||||
|
if !minimuxer.ready() {
|
||||||
|
let toastView = ToastView(error: MinimuxerError.NoConnection)
|
||||||
|
toastView.show(in: self)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
self.isRefreshingAllApps = true
|
self.isRefreshingAllApps = true
|
||||||
self.collectionView.collectionViewLayout.invalidateLayout()
|
self.collectionView.collectionViewLayout.invalidateLayout()
|
||||||
|
|
||||||
@@ -701,18 +713,15 @@ private extension MyAppsViewController
|
|||||||
|
|
||||||
@IBAction func sideloadApp(_ sender: UIBarButtonItem)
|
@IBAction func sideloadApp(_ sender: UIBarButtonItem)
|
||||||
{
|
{
|
||||||
let supportedTypes: [String]
|
if !minimuxer.ready() {
|
||||||
|
let toastView = ToastView(error: MinimuxerError.NoConnection)
|
||||||
if let types = UTTypeCreateAllIdentifiersForTag(kUTTagClassFilenameExtension, "ipa" as CFString, nil)?.takeRetainedValue()
|
toastView.show(in: self)
|
||||||
{
|
return
|
||||||
supportedTypes = (types as NSArray).map { $0 as! String }
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
supportedTypes = ["com.apple.itunes.ipa"] // Declared by the system.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let supportedTypes = UTType.types(tag: "ipa", tagClass: .filenameExtension, conformingTo: nil)
|
||||||
|
|
||||||
let documentPickerViewController = UIDocumentPickerViewController(documentTypes: supportedTypes, in: .import)
|
let documentPickerViewController = UIDocumentPickerViewController(forOpeningContentTypes: supportedTypes, asCopy: true)
|
||||||
documentPickerViewController.delegate = self
|
documentPickerViewController.delegate = self
|
||||||
self.present(documentPickerViewController, animated: true, completion: nil)
|
self.present(documentPickerViewController, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
@@ -1014,6 +1023,12 @@ private extension MyAppsViewController
|
|||||||
|
|
||||||
func refresh(_ installedApp: InstalledApp)
|
func refresh(_ installedApp: InstalledApp)
|
||||||
{
|
{
|
||||||
|
if !minimuxer.ready() {
|
||||||
|
let toastView = ToastView(error: MinimuxerError.NoConnection)
|
||||||
|
toastView.show(in: self)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
let previousProgress = AppManager.shared.refreshProgress(for: installedApp)
|
let previousProgress = AppManager.shared.refreshProgress(for: installedApp)
|
||||||
guard previousProgress == nil else {
|
guard previousProgress == nil else {
|
||||||
previousProgress?.cancel()
|
previousProgress?.cancel()
|
||||||
@@ -1035,6 +1050,12 @@ private extension MyAppsViewController
|
|||||||
|
|
||||||
func activate(_ installedApp: InstalledApp)
|
func activate(_ installedApp: InstalledApp)
|
||||||
{
|
{
|
||||||
|
if !minimuxer.ready() {
|
||||||
|
let toastView = ToastView(error: MinimuxerError.NoConnection)
|
||||||
|
toastView.show(in: self)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func finish(_ result: Result<InstalledApp, Error>)
|
func finish(_ result: Result<InstalledApp, Error>)
|
||||||
{
|
{
|
||||||
do
|
do
|
||||||
@@ -1111,6 +1132,11 @@ private extension MyAppsViewController
|
|||||||
func deactivate(_ installedApp: InstalledApp, completionHandler: ((Result<InstalledApp, Error>) -> Void)? = nil)
|
func deactivate(_ installedApp: InstalledApp, completionHandler: ((Result<InstalledApp, Error>) -> Void)? = nil)
|
||||||
{
|
{
|
||||||
guard installedApp.isActive else { return }
|
guard installedApp.isActive else { return }
|
||||||
|
if !minimuxer.ready() {
|
||||||
|
let toastView = ToastView(error: MinimuxerError.NoConnection)
|
||||||
|
toastView.show(in: self)
|
||||||
|
return
|
||||||
|
}
|
||||||
installedApp.isActive = false
|
installedApp.isActive = false
|
||||||
|
|
||||||
AppManager.shared.deactivate(installedApp, presentingViewController: self) { (result) in
|
AppManager.shared.deactivate(installedApp, presentingViewController: self) { (result) in
|
||||||
@@ -1172,6 +1198,11 @@ private extension MyAppsViewController
|
|||||||
|
|
||||||
func backup(_ installedApp: InstalledApp)
|
func backup(_ installedApp: InstalledApp)
|
||||||
{
|
{
|
||||||
|
if !minimuxer.ready() {
|
||||||
|
let toastView = ToastView(error: MinimuxerError.NoConnection)
|
||||||
|
toastView.show(in: self)
|
||||||
|
return
|
||||||
|
}
|
||||||
let title = NSLocalizedString("Start Backup?", comment: "")
|
let title = NSLocalizedString("Start Backup?", comment: "")
|
||||||
let message = NSLocalizedString("This will replace any previous backups. Please leave SideStore open until the backup is complete.", comment: "")
|
let message = NSLocalizedString("This will replace any previous backups. Please leave SideStore open until the backup is complete.", comment: "")
|
||||||
|
|
||||||
@@ -1211,6 +1242,11 @@ private extension MyAppsViewController
|
|||||||
|
|
||||||
func restore(_ installedApp: InstalledApp)
|
func restore(_ installedApp: InstalledApp)
|
||||||
{
|
{
|
||||||
|
if !minimuxer.ready() {
|
||||||
|
let toastView = ToastView(error: MinimuxerError.NoConnection)
|
||||||
|
toastView.show(in: self)
|
||||||
|
return
|
||||||
|
}
|
||||||
let message = String(format: NSLocalizedString("This will replace all data you currently have in %@.", comment: ""), installedApp.name)
|
let message = String(format: NSLocalizedString("This will replace all data you currently have in %@.", comment: ""), installedApp.name)
|
||||||
let alertController = UIAlertController(title: NSLocalizedString("Are you sure you want to restore this backup?", comment: ""), message: message, preferredStyle: .actionSheet)
|
let alertController = UIAlertController(title: NSLocalizedString("Are you sure you want to restore this backup?", comment: ""), message: message, preferredStyle: .actionSheet)
|
||||||
alertController.addAction(.cancel)
|
alertController.addAction(.cancel)
|
||||||
@@ -1246,8 +1282,11 @@ private extension MyAppsViewController
|
|||||||
{
|
{
|
||||||
guard let backupURL = FileManager.default.backupDirectoryURL(for: installedApp) else { return }
|
guard let backupURL = FileManager.default.backupDirectoryURL(for: installedApp) else { return }
|
||||||
|
|
||||||
let documentPicker = UIDocumentPickerViewController(url: backupURL, in: .exportToService)
|
let documentPicker = UIDocumentPickerViewController(forExporting: [backupURL], asCopy: true)
|
||||||
documentPicker.delegate = self
|
|
||||||
|
// Don't set delegate to avoid conflicting with import callbacks.
|
||||||
|
// documentPicker.delegate = self
|
||||||
|
|
||||||
self.present(documentPicker, animated: true, completion: nil)
|
self.present(documentPicker, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1311,6 +1350,16 @@ private extension MyAppsViewController
|
|||||||
@available(iOS 14, *)
|
@available(iOS 14, *)
|
||||||
func enableJIT(for installedApp: InstalledApp)
|
func enableJIT(for installedApp: InstalledApp)
|
||||||
{
|
{
|
||||||
|
if #available(iOS 17, *) {
|
||||||
|
let toastView = ToastView(error: OperationError.tooNewError)
|
||||||
|
toastView.show(in: self)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !minimuxer.ready() {
|
||||||
|
let toastView = ToastView(error: MinimuxerError.NoConnection)
|
||||||
|
toastView.show(in: self)
|
||||||
|
return
|
||||||
|
}
|
||||||
AppManager.shared.enableJIT(for: installedApp) { result in
|
AppManager.shared.enableJIT(for: installedApp) { result in
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
switch result
|
switch result
|
||||||
@@ -1318,7 +1367,7 @@ private extension MyAppsViewController
|
|||||||
case .success: break
|
case .success: break
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
let toastView = ToastView(error: error)
|
let toastView = ToastView(error: error)
|
||||||
toastView.show(in: self)
|
toastView.show(in: self.navigationController?.view ?? self.view, duration: 5)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1465,7 +1514,7 @@ extension MyAppsViewController
|
|||||||
let registeredAppIDs = team.appIDs.count
|
let registeredAppIDs = team.appIDs.count
|
||||||
|
|
||||||
let maximumAppIDCount = 10
|
let maximumAppIDCount = 10
|
||||||
let remainingAppIDs = max(maximumAppIDCount - registeredAppIDs, 0)
|
let remainingAppIDs = maximumAppIDCount - registeredAppIDs
|
||||||
|
|
||||||
if remainingAppIDs == 1
|
if remainingAppIDs == 1
|
||||||
{
|
{
|
||||||
@@ -1476,7 +1525,7 @@ extension MyAppsViewController
|
|||||||
footerView.textLabel.text = String(format: NSLocalizedString("%@ App IDs Remaining", comment: ""), NSNumber(value: remainingAppIDs))
|
footerView.textLabel.text = String(format: NSLocalizedString("%@ App IDs Remaining", comment: ""), NSNumber(value: remainingAppIDs))
|
||||||
}
|
}
|
||||||
|
|
||||||
footerView.textLabel.isHidden = false
|
footerView.textLabel.isHidden = remainingAppIDs < 0
|
||||||
|
|
||||||
case .individual, .organization, .unknown: footerView.textLabel.isHidden = true
|
case .individual, .organization, .unknown: footerView.textLabel.isHidden = true
|
||||||
@unknown default: break
|
@unknown default: break
|
||||||
@@ -2050,15 +2099,8 @@ extension MyAppsViewController: UIDocumentPickerDelegate
|
|||||||
{
|
{
|
||||||
guard let fileURL = urls.first else { return }
|
guard let fileURL = urls.first else { return }
|
||||||
|
|
||||||
switch controller.documentPickerMode
|
self.sideloadApp(at: fileURL) { (result) in
|
||||||
{
|
print("Sideloaded app at \(fileURL) with result:", result)
|
||||||
case .import, .open:
|
|
||||||
self.sideloadApp(at: fileURL) { (result) in
|
|
||||||
print("Sideloaded app at \(fileURL) with result:", result)
|
|
||||||
}
|
|
||||||
|
|
||||||
case .exportToService, .moveToService: break
|
|
||||||
@unknown default: break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ extension UpdateCollectionViewCell
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc class UpdateCollectionViewCell: UICollectionViewCell
|
@objc final class UpdateCollectionViewCell: UICollectionViewCell
|
||||||
{
|
{
|
||||||
var mode: Mode = .expanded {
|
var mode: Mode = .expanded {
|
||||||
didSet {
|
didSet {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class NewsCollectionViewCell: UICollectionViewCell
|
final class NewsCollectionViewCell: UICollectionViewCell
|
||||||
{
|
{
|
||||||
@IBOutlet var titleLabel: UILabel!
|
@IBOutlet var titleLabel: UILabel!
|
||||||
@IBOutlet var captionLabel: UILabel!
|
@IBOutlet var captionLabel: UILabel!
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import Roxas
|
|||||||
|
|
||||||
import Nuke
|
import Nuke
|
||||||
|
|
||||||
private class AppBannerFooterView: UICollectionReusableView
|
private final class AppBannerFooterView: UICollectionReusableView
|
||||||
{
|
{
|
||||||
let bannerView = AppBannerView(frame: .zero)
|
let bannerView = AppBannerView(frame: .zero)
|
||||||
let tapGestureRecognizer = UITapGestureRecognizer(target: nil, action: nil)
|
let tapGestureRecognizer = UITapGestureRecognizer(target: nil, action: nil)
|
||||||
@@ -41,7 +41,7 @@ private class AppBannerFooterView: UICollectionReusableView
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class NewsViewController: UICollectionViewController
|
final class NewsViewController: UICollectionViewController
|
||||||
{
|
{
|
||||||
private lazy var dataSource = self.makeDataSource()
|
private lazy var dataSource = self.makeDataSource()
|
||||||
private lazy var placeholderView = RSTPlaceholderView(frame: .zero)
|
private lazy var placeholderView = RSTPlaceholderView(frame: .zero)
|
||||||
@@ -426,6 +426,10 @@ extension NewsViewController: UICollectionViewDelegateFlowLayout
|
|||||||
return previousSize
|
return previousSize
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Take layout margins into account.
|
||||||
|
self.prototypeCell.layoutMargins.left = self.view.layoutMargins.left
|
||||||
|
self.prototypeCell.layoutMargins.right = self.view.layoutMargins.right
|
||||||
|
|
||||||
let widthConstraint = self.prototypeCell.contentView.widthAnchor.constraint(equalToConstant: collectionView.bounds.width)
|
let widthConstraint = self.prototypeCell.contentView.widthAnchor.constraint(equalToConstant: collectionView.bounds.width)
|
||||||
NSLayoutConstraint.activate([widthConstraint])
|
NSLayoutConstraint.activate([widthConstraint])
|
||||||
defer { NSLayoutConstraint.deactivate([widthConstraint]) }
|
defer { NSLayoutConstraint.deactivate([widthConstraint]) }
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import Network
|
|||||||
|
|
||||||
import AltStoreCore
|
import AltStoreCore
|
||||||
import AltSign
|
import AltSign
|
||||||
|
import minimuxer
|
||||||
|
|
||||||
enum AuthenticationError: LocalizedError
|
enum AuthenticationError: LocalizedError
|
||||||
{
|
{
|
||||||
@@ -34,7 +35,7 @@ enum AuthenticationError: LocalizedError
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc(AuthenticationOperation)
|
@objc(AuthenticationOperation)
|
||||||
class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, ALTAppleAPISession)>
|
final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, ALTAppleAPISession)>
|
||||||
{
|
{
|
||||||
let context: AuthenticatedOperationContext
|
let context: AuthenticatedOperationContext
|
||||||
|
|
||||||
@@ -593,7 +594,7 @@ private extension AuthenticationOperation
|
|||||||
|
|
||||||
func registerCurrentDevice(for team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTDevice, Error>) -> Void)
|
func registerCurrentDevice(for team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTDevice, Error>) -> Void)
|
||||||
{
|
{
|
||||||
guard let udid = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.deviceID) as? String else {
|
guard let udid = fetch_udid()?.toString() else {
|
||||||
return completionHandler(.failure(OperationError.unknownUDID))
|
return completionHandler(.failure(OperationError.unknownUDID))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import CoreData
|
|||||||
|
|
||||||
import AltStoreCore
|
import AltStoreCore
|
||||||
import EmotionalDamage
|
import EmotionalDamage
|
||||||
|
import minimuxer
|
||||||
|
|
||||||
enum RefreshError: LocalizedError
|
enum RefreshError: LocalizedError
|
||||||
{
|
{
|
||||||
@@ -51,7 +52,7 @@ private let ReceivedApplicationState: @convention(c) (CFNotificationCenter?, Uns
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc(BackgroundRefreshAppsOperation)
|
@objc(BackgroundRefreshAppsOperation)
|
||||||
class BackgroundRefreshAppsOperation: ResultOperation<[String: Result<InstalledApp, Error>]>
|
final class BackgroundRefreshAppsOperation: ResultOperation<[String: Result<InstalledApp, Error>]>
|
||||||
{
|
{
|
||||||
let installedApps: [InstalledApp]
|
let installedApps: [InstalledApp]
|
||||||
private let managedObjectContext: NSManagedObjectContext
|
private let managedObjectContext: NSManagedObjectContext
|
||||||
@@ -97,7 +98,20 @@ class BackgroundRefreshAppsOperation: ResultOperation<[String: Result<InstalledA
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
start_em_proxy(bind_addr: Consts.Proxy.serverURL)
|
start_em_proxy(bind_addr: Consts.Proxy.serverURL)
|
||||||
|
target_minimuxer_address()
|
||||||
|
let documentsDirectory = FileManager.default.documentsDirectory.absoluteString
|
||||||
|
do {
|
||||||
|
try minimuxer.start(try String(contentsOf: FileManager.default.documentsDirectory.appendingPathComponent("\(pairingFileName)")), documentsDirectory)
|
||||||
|
} catch {
|
||||||
|
self.finish(.failure(error))
|
||||||
|
}
|
||||||
|
if #available(iOS 17, *) {
|
||||||
|
// TODO: iOS 17 and above have a new JIT implementation that is completely broken in SideStore :(
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
start_auto_mounter(documentsDirectory)
|
||||||
|
}
|
||||||
|
|
||||||
self.managedObjectContext.perform {
|
self.managedObjectContext.perform {
|
||||||
print("Apps to refresh:", self.installedApps.map(\.bundleIdentifier))
|
print("Apps to refresh:", self.installedApps.map(\.bundleIdentifier))
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ class BackupAppOperation: ResultOperation<Void>
|
|||||||
private var appName: String?
|
private var appName: String?
|
||||||
private var timeoutTimer: Timer?
|
private var timeoutTimer: Timer?
|
||||||
|
|
||||||
|
private weak var applicationWillReturnObserver: NSObjectProtocol?
|
||||||
|
private weak var backupResponseObserver: NSObjectProtocol?
|
||||||
|
|
||||||
init(action: Action, context: InstallAppOperationContext)
|
init(action: Action, context: InstallAppOperationContext)
|
||||||
{
|
{
|
||||||
self.action = action
|
self.action = action
|
||||||
@@ -43,10 +46,7 @@ class BackupAppOperation: ResultOperation<Void>
|
|||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
if let error = self.context.error
|
if let error = self.context.error { throw 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 }
|
||||||
context.perform {
|
context.perform {
|
||||||
@@ -55,9 +55,8 @@ class BackupAppOperation: ResultOperation<Void>
|
|||||||
let appName = installedApp.name
|
let appName = installedApp.name
|
||||||
self.appName = appName
|
self.appName = appName
|
||||||
|
|
||||||
guard let altstoreApp = InstalledApp.fetchAltStore(in: context) else { throw OperationError.appNotFound }
|
let altstoreOpenURL = URL(string: "sidestore://")!
|
||||||
let altstoreOpenURL = altstoreApp.openAppURL
|
|
||||||
|
|
||||||
var returnURLComponents = URLComponents(url: altstoreOpenURL, resolvingAgainstBaseURL: false)
|
var returnURLComponents = URLComponents(url: altstoreOpenURL, resolvingAgainstBaseURL: false)
|
||||||
returnURLComponents?.host = "appBackupResponse"
|
returnURLComponents?.host = "appBackupResponse"
|
||||||
guard let returnURL = returnURLComponents?.url else { throw OperationError.openAppFailed(name: appName) }
|
guard let returnURL = returnURLComponents?.url else { throw OperationError.openAppFailed(name: appName) }
|
||||||
@@ -153,8 +152,11 @@ private extension BackupAppOperation
|
|||||||
{
|
{
|
||||||
func registerObservers()
|
func registerObservers()
|
||||||
{
|
{
|
||||||
var applicationWillReturnObserver: NSObjectProtocol!
|
self.applicationWillReturnObserver = NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: .main) { [weak self] (notification) in
|
||||||
applicationWillReturnObserver = NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: .main) { [weak self] (notification) in
|
defer {
|
||||||
|
self?.applicationWillReturnObserver.map { NotificationCenter.default.removeObserver($0) }
|
||||||
|
}
|
||||||
|
|
||||||
guard let self = self, !self.isFinished else { return }
|
guard let self = self, !self.isFinished else { return }
|
||||||
|
|
||||||
self.timeoutTimer = Timer.scheduledTimer(withTimeInterval: 5, repeats: false) { [weak self] (timer) in
|
self.timeoutTimer = Timer.scheduledTimer(withTimeInterval: 5, repeats: false) { [weak self] (timer) in
|
||||||
@@ -166,18 +168,17 @@ private extension BackupAppOperation
|
|||||||
self.finish(.failure(OperationError.timedOut))
|
self.finish(.failure(OperationError.timedOut))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NotificationCenter.default.removeObserver(applicationWillReturnObserver!)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var backupResponseObserver: NSObjectProtocol!
|
self.backupResponseObserver = NotificationCenter.default.addObserver(forName: AppDelegate.appBackupDidFinish, object: nil, queue: nil) { [weak self] (notification) in
|
||||||
backupResponseObserver = NotificationCenter.default.addObserver(forName: AppDelegate.appBackupDidFinish, object: nil, queue: nil) { [weak self] (notification) in
|
defer {
|
||||||
|
self?.backupResponseObserver.map { NotificationCenter.default.removeObserver($0) }
|
||||||
|
}
|
||||||
|
|
||||||
self?.timeoutTimer?.invalidate()
|
self?.timeoutTimer?.invalidate()
|
||||||
|
|
||||||
let result = notification.userInfo?[AppDelegate.appBackupResultKey] as? Result<Void, Error> ?? .failure(OperationError.unknownResult)
|
let result = notification.userInfo?[AppDelegate.appBackupResultKey] as? Result<Void, Error> ?? .failure(OperationError.unknownResult)
|
||||||
self?.finish(result)
|
self?.finish(result)
|
||||||
|
|
||||||
NotificationCenter.default.removeObserver(backupResponseObserver!)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
208
AltStore/Operations/ClearAppCacheOperation.swift
Normal file
208
AltStore/Operations/ClearAppCacheOperation.swift
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
//
|
||||||
|
// ClearAppCacheOperation.swift
|
||||||
|
// AltStore
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 9/27/22.
|
||||||
|
// Copyright © 2022 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import AltStoreCore
|
||||||
|
/*
|
||||||
|
struct BatchError: ALTLocalizedError
|
||||||
|
{
|
||||||
|
|
||||||
|
enum Code: Int, ALTErrorCode
|
||||||
|
{
|
||||||
|
typealias Error = BatchError
|
||||||
|
|
||||||
|
case batchError
|
||||||
|
}
|
||||||
|
|
||||||
|
var code: Code = .batchError
|
||||||
|
var underlyingErrors: [Error]
|
||||||
|
|
||||||
|
var errorTitle: String?
|
||||||
|
var errorFailure: String?
|
||||||
|
|
||||||
|
init(errors: [Error])
|
||||||
|
{
|
||||||
|
self.underlyingErrors = errors
|
||||||
|
}
|
||||||
|
|
||||||
|
var errorFailureReason: String {
|
||||||
|
guard !self.underlyingErrors.isEmpty else { return NSLocalizedString("An unknown error occured.", comment: "") }
|
||||||
|
|
||||||
|
let errorMessages = self.underlyingErrors.map { $0.localizedDescription }
|
||||||
|
|
||||||
|
let message = errorMessages.joined(separator: "\n\n")
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
@objc(ClearAppCacheOperation)
|
||||||
|
class ClearAppCacheOperation: ResultOperation<Void>
|
||||||
|
{
|
||||||
|
private let coordinator = NSFileCoordinator()
|
||||||
|
private let coordinatorQueue = OperationQueue()
|
||||||
|
|
||||||
|
override init()
|
||||||
|
{
|
||||||
|
self.coordinatorQueue.name = "AltStore - ClearAppCacheOperation Queue"
|
||||||
|
}
|
||||||
|
|
||||||
|
override func main()
|
||||||
|
{
|
||||||
|
super.main()
|
||||||
|
|
||||||
|
var allErrors = [Error]()
|
||||||
|
|
||||||
|
self.clearTemporaryDirectory { result in
|
||||||
|
switch result
|
||||||
|
{
|
||||||
|
//case .failure(let batchError as BatchError): allErrors.append(contentsOf: batchError.underlyingErrors)
|
||||||
|
case .failure(let error): allErrors.append(error)
|
||||||
|
case .success: break
|
||||||
|
}
|
||||||
|
|
||||||
|
self.removeUninstalledAppBackupDirectories { result in
|
||||||
|
switch result
|
||||||
|
{
|
||||||
|
//case .failure(let batchError as BatchError): allErrors.append(contentsOf: batchError.underlyingErrors)
|
||||||
|
case .failure(let error): allErrors.append(error)
|
||||||
|
case .success: break
|
||||||
|
}
|
||||||
|
|
||||||
|
if allErrors.isEmpty
|
||||||
|
{
|
||||||
|
self.finish(.success(()))
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self.finish(.failure(OperationError.cacheClearError(errors: allErrors.map({ error in
|
||||||
|
return error.localizedDescription
|
||||||
|
}))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension ClearAppCacheOperation
|
||||||
|
{
|
||||||
|
func clearTemporaryDirectory(completion: @escaping (Result<Void, Error>) -> Void)
|
||||||
|
{
|
||||||
|
let intent = NSFileAccessIntent.writingIntent(with: FileManager.default.temporaryDirectory, options: [.forDeleting])
|
||||||
|
self.coordinator.coordinate(with: [intent], queue: self.coordinatorQueue) { (error) in
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if let error
|
||||||
|
{
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
|
let fileURLs = try FileManager.default.contentsOfDirectory(at: intent.url,
|
||||||
|
includingPropertiesForKeys: [],
|
||||||
|
options: [.skipsSubdirectoryDescendants, .skipsHiddenFiles])
|
||||||
|
var errors = [Error]()
|
||||||
|
|
||||||
|
for fileURL in fileURLs
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
print("[ALTLog] Removing item from temporary directory:", fileURL.lastPathComponent)
|
||||||
|
try FileManager.default.removeItem(at: fileURL)
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
print("[ALTLog] Failed to remove \(fileURL.lastPathComponent) from temporary directory.", error)
|
||||||
|
errors.append(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !errors.isEmpty
|
||||||
|
{
|
||||||
|
completion(.failure(OperationError.cacheClearError(errors: errors.map({ error in
|
||||||
|
return error.localizedDescription
|
||||||
|
}))))
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
completion(.success(()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
completion(.failure(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeUninstalledAppBackupDirectories(completion: @escaping (Result<Void, Error>) -> Void)
|
||||||
|
{
|
||||||
|
guard let backupsDirectory = FileManager.default.appBackupsDirectory else { return completion(.failure(OperationError.missingAppGroup)) }
|
||||||
|
|
||||||
|
DatabaseManager.shared.persistentContainer.performBackgroundTask { context in
|
||||||
|
let installedAppBundleIDs = Set(InstalledApp.all(in: context).map { $0.bundleIdentifier })
|
||||||
|
|
||||||
|
let intent = NSFileAccessIntent.writingIntent(with: backupsDirectory, options: [.forDeleting])
|
||||||
|
self.coordinator.coordinate(with: [intent], queue: self.coordinatorQueue) { (error) in
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if let error
|
||||||
|
{
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
|
var isDirectory: ObjCBool = false
|
||||||
|
guard FileManager.default.fileExists(atPath: intent.url.path, isDirectory: &isDirectory), isDirectory.boolValue else {
|
||||||
|
completion(.success(()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let fileURLs = try FileManager.default.contentsOfDirectory(at: intent.url,
|
||||||
|
includingPropertiesForKeys: [.isDirectoryKey, .nameKey],
|
||||||
|
options: [.skipsSubdirectoryDescendants, .skipsHiddenFiles])
|
||||||
|
var errors = [Error]()
|
||||||
|
|
||||||
|
|
||||||
|
for backupDirectory in fileURLs
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
let resourceValues = try backupDirectory.resourceValues(forKeys: [.isDirectoryKey, .nameKey])
|
||||||
|
guard let isDirectory = resourceValues.isDirectory, let bundleID = resourceValues.name else { continue }
|
||||||
|
|
||||||
|
if isDirectory && !installedAppBundleIDs.contains(bundleID) && !AppManager.shared.isActivelyManagingApp(withBundleID: bundleID)
|
||||||
|
{
|
||||||
|
print("[ALTLog] Removing backup directory for uninstalled app:", bundleID)
|
||||||
|
try FileManager.default.removeItem(at: backupDirectory)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
print("[ALTLog] Failed to remove app backup directory:", error)
|
||||||
|
errors.append(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !errors.isEmpty
|
||||||
|
{
|
||||||
|
completion(.failure(OperationError.cacheClearError(errors: errors.map({ error in
|
||||||
|
return error.localizedDescription
|
||||||
|
}))))
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
completion(.success(()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
print("[ALTLog] Failed to remove app backup directory:", error)
|
||||||
|
completion(.failure(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,7 +14,7 @@ import Roxas
|
|||||||
import minimuxer
|
import minimuxer
|
||||||
|
|
||||||
@objc(DeactivateAppOperation)
|
@objc(DeactivateAppOperation)
|
||||||
class DeactivateAppOperation: ResultOperation<InstalledApp>
|
final class DeactivateAppOperation: ResultOperation<InstalledApp>
|
||||||
{
|
{
|
||||||
let app: InstalledApp
|
let app: InstalledApp
|
||||||
let context: OperationContext
|
let context: OperationContext
|
||||||
@@ -31,11 +31,7 @@ class DeactivateAppOperation: ResultOperation<InstalledApp>
|
|||||||
{
|
{
|
||||||
super.main()
|
super.main()
|
||||||
|
|
||||||
if let error = self.context.error
|
if let error = self.context.error { return self.finish(.failure(error)) }
|
||||||
{
|
|
||||||
self.finish(.failure(error))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
||||||
let installedApp = context.object(with: self.app.objectID) as! InstalledApp
|
let installedApp = context.object(with: self.app.objectID) as! InstalledApp
|
||||||
@@ -44,20 +40,15 @@ class DeactivateAppOperation: ResultOperation<InstalledApp>
|
|||||||
|
|
||||||
for profile in allIdentifiers {
|
for profile in allIdentifiers {
|
||||||
do {
|
do {
|
||||||
let res = try remove_provisioning_profile(id: profile)
|
try remove_provisioning_profile(profile)
|
||||||
if case Uhoh.Bad(let code) = res {
|
self.progress.completedUnitCount += 1
|
||||||
self.finish(.failure(minimuxer_to_operation(code: code)))
|
installedApp.isActive = false
|
||||||
}
|
self.finish(.success(installedApp))
|
||||||
} catch Uhoh.Bad(let code) {
|
break
|
||||||
self.finish(.failure(minimuxer_to_operation(code: code)))
|
|
||||||
} catch {
|
} catch {
|
||||||
self.finish(.failure(ALTServerError(.unknownResponse)))
|
self.finish(.failure(error))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.progress.completedUnitCount += 1
|
|
||||||
installedApp.isActive = false
|
|
||||||
self.finish(.success(installedApp))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ private extension DownloadAppOperation
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc(DownloadAppOperation)
|
@objc(DownloadAppOperation)
|
||||||
class DownloadAppOperation: ResultOperation<ALTApplication>
|
final class DownloadAppOperation: ResultOperation<ALTApplication>
|
||||||
{
|
{
|
||||||
let app: AppProtocol
|
let app: AppProtocol
|
||||||
let context: AppOperationContext
|
let context: AppOperationContext
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ protocol EnableJITContext
|
|||||||
}
|
}
|
||||||
|
|
||||||
@available(iOS 14, *)
|
@available(iOS 14, *)
|
||||||
class EnableJITOperation<Context: EnableJITContext>: ResultOperation<Void>
|
final class EnableJITOperation<Context: EnableJITContext>: ResultOperation<Void>
|
||||||
{
|
{
|
||||||
let context: Context
|
let context: Context
|
||||||
|
|
||||||
@@ -45,23 +45,19 @@ class EnableJITOperation<Context: EnableJITContext>: ResultOperation<Void>
|
|||||||
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)) }
|
||||||
|
|
||||||
installedApp.managedObjectContext?.perform {
|
installedApp.managedObjectContext?.perform {
|
||||||
let v = minimuxer_to_operation(code: 1)
|
var retries = 3
|
||||||
|
while (retries > 0){
|
||||||
do {
|
do {
|
||||||
var x = try debug_app(app_id: installedApp.resignedBundleIdentifier)
|
try debug_app(installedApp.resignedBundleIdentifier)
|
||||||
switch x {
|
|
||||||
case .Good:
|
|
||||||
self.finish(.success(()))
|
self.finish(.success(()))
|
||||||
case .Bad(let code):
|
retries = 0
|
||||||
self.finish(.failure(minimuxer_to_operation(code: code)))
|
} catch {
|
||||||
|
retries -= 1
|
||||||
|
if (retries <= 0){
|
||||||
|
self.finish(.failure(error))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch Uhoh.Bad(let code) {
|
|
||||||
self.finish(.failure(minimuxer_to_operation(code: code)))
|
|
||||||
} catch {
|
|
||||||
self.finish(.failure(OperationError.unknown))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,15 +7,28 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import CommonCrypto
|
||||||
|
import Starscream
|
||||||
|
|
||||||
import AltStoreCore
|
import AltStoreCore
|
||||||
import AltSign
|
import AltSign
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
@objc(FetchAnisetteDataOperation)
|
@objc(FetchAnisetteDataOperation)
|
||||||
class FetchAnisetteDataOperation: ResultOperation<ALTAnisetteData>
|
final class FetchAnisetteDataOperation: ResultOperation<ALTAnisetteData>, WebSocketDelegate
|
||||||
{
|
{
|
||||||
let context: OperationContext
|
let context: OperationContext
|
||||||
|
var socket: WebSocket!
|
||||||
|
|
||||||
|
var url: URL?
|
||||||
|
var startProvisioningURL: URL?
|
||||||
|
var endProvisioningURL: URL?
|
||||||
|
|
||||||
|
var clientInfo: String?
|
||||||
|
var userAgent: String?
|
||||||
|
|
||||||
|
var mdLu: String?
|
||||||
|
var deviceId: String?
|
||||||
|
|
||||||
init(context: OperationContext)
|
init(context: OperationContext)
|
||||||
{
|
{
|
||||||
@@ -32,32 +45,412 @@ class FetchAnisetteDataOperation: ResultOperation<ALTAnisetteData>
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let url = AnisetteManager.currentURL
|
self.url = AnisetteManager.currentURL
|
||||||
DLOG("Anisette URL: %@", url.absoluteString)
|
print("Anisette URL: \(self.url!.absoluteString)")
|
||||||
|
|
||||||
let task = URLSession.shared.dataTask(with: url) { data, response, error in
|
if let identifier = Keychain.shared.identifier,
|
||||||
guard let data = data, error == nil else { return }
|
let adiPb = Keychain.shared.adiPb {
|
||||||
|
fetchAnisetteV3(identifier, adiPb)
|
||||||
do {
|
} else {
|
||||||
// make sure this JSON is in the format we expect
|
provision()
|
||||||
// convert data to json
|
}
|
||||||
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: String] {
|
}
|
||||||
// try to read out a dictionary
|
|
||||||
//for some reason serial number isn't needed but it doesn't work unless it has a value
|
// MARK: - COMMON
|
||||||
let formattedJSON: [String: String] = ["machineID": json["X-Apple-I-MD-M"]!, "oneTimePassword": json["X-Apple-I-MD"]!, "localUserID": json["X-Apple-I-MD-LU"]!, "routingInfo": json["X-Apple-I-MD-RINFO"]!, "deviceUniqueIdentifier": json["X-Mme-Device-Id"]!, "deviceDescription": json["X-MMe-Client-Info"]!, "date": json["X-Apple-I-Client-Time"]!, "locale": json["X-Apple-Locale"]!, "timeZone": json["X-Apple-I-TimeZone"]!, "deviceSerialNumber": "1"]
|
|
||||||
|
func extractAnisetteData(_ data: Data, _ response: HTTPURLResponse?, v3: Bool) throws {
|
||||||
if let anisette = ALTAnisetteData(json: formattedJSON) {
|
// make sure this JSON is in the format we expect
|
||||||
DLOG("Anisette used: %@", formattedJSON)
|
// convert data to json
|
||||||
self.finish(.success(anisette))
|
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: String] {
|
||||||
}
|
if v3 {
|
||||||
|
if json["result"] == "GetHeadersError" {
|
||||||
|
let message = json["message"]
|
||||||
|
print("Error getting V3 headers: \(message ?? "no message")")
|
||||||
|
if let message = message,
|
||||||
|
message.contains("-45061") {
|
||||||
|
print("Error message contains -45061 (not provisioned), resetting adi.pb and retrying")
|
||||||
|
Keychain.shared.adiPb = nil
|
||||||
|
return provision()
|
||||||
|
} else { throw OperationError.anisetteV3Error(message: message ?? "Unknown error") }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to read out a dictionary
|
||||||
|
// for some reason serial number isn't needed but it doesn't work unless it has a value
|
||||||
|
var formattedJSON: [String: String] = ["deviceSerialNumber": "0"]
|
||||||
|
if let machineID = json["X-Apple-I-MD-M"] { formattedJSON["machineID"] = machineID }
|
||||||
|
if let oneTimePassword = json["X-Apple-I-MD"] { formattedJSON["oneTimePassword"] = oneTimePassword }
|
||||||
|
if let routingInfo = json["X-Apple-I-MD-RINFO"] { formattedJSON["routingInfo"] = routingInfo }
|
||||||
|
|
||||||
|
if v3 {
|
||||||
|
formattedJSON["deviceDescription"] = self.clientInfo!
|
||||||
|
formattedJSON["localUserID"] = self.mdLu!
|
||||||
|
formattedJSON["deviceUniqueIdentifier"] = self.deviceId!
|
||||||
|
|
||||||
|
// Generate date stuff on client
|
||||||
|
let formatter = DateFormatter()
|
||||||
|
formatter.locale = Locale(identifier: "en_US_POSIX")
|
||||||
|
formatter.calendar = Calendar(identifier: .gregorian)
|
||||||
|
formatter.timeZone = TimeZone.current
|
||||||
|
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss'Z'"
|
||||||
|
let dateString = formatter.string(from: Date())
|
||||||
|
formattedJSON["date"] = dateString
|
||||||
|
formattedJSON["locale"] = Locale.current.identifier
|
||||||
|
formattedJSON["timeZone"] = TimeZone.current.abbreviation()
|
||||||
|
} else {
|
||||||
|
if let deviceDescription = json["X-MMe-Client-Info"] { formattedJSON["deviceDescription"] = deviceDescription }
|
||||||
|
if let localUserID = json["X-Apple-I-MD-LU"] { formattedJSON["localUserID"] = localUserID }
|
||||||
|
if let deviceUniqueIdentifier = json["X-Mme-Device-Id"] { formattedJSON["deviceUniqueIdentifier"] = deviceUniqueIdentifier }
|
||||||
|
|
||||||
|
if let date = json["X-Apple-I-Client-Time"] { formattedJSON["date"] = date }
|
||||||
|
if let locale = json["X-Apple-Locale"] { formattedJSON["locale"] = locale }
|
||||||
|
if let timeZone = json["X-Apple-I-TimeZone"] { formattedJSON["timeZone"] = timeZone }
|
||||||
|
}
|
||||||
|
|
||||||
|
if let response = response,
|
||||||
|
let version = response.value(forHTTPHeaderField: "Implementation-Version") {
|
||||||
|
print("Implementation-Version: \(version)")
|
||||||
|
} else { print("No Implementation-Version header") }
|
||||||
|
|
||||||
|
print("Anisette used: \(formattedJSON)")
|
||||||
|
print("Original JSON: \(json)")
|
||||||
|
if let anisette = ALTAnisetteData(json: formattedJSON) {
|
||||||
|
print("Anisette is valid!")
|
||||||
|
self.finish(.success(anisette))
|
||||||
|
} else {
|
||||||
|
print("Anisette is invalid!!!!")
|
||||||
|
if v3 {
|
||||||
|
throw OperationError.anisetteV3Error(message: "Invalid anisette (the returned data may not have all the required fields)")
|
||||||
|
} else {
|
||||||
|
throw OperationError.anisetteV1Error(message: "Invalid anisette (the returned data may not have all the required fields)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if v3 {
|
||||||
|
throw OperationError.anisetteV3Error(message: "Invalid anisette (the returned data may not be in JSON)")
|
||||||
|
} else {
|
||||||
|
throw OperationError.anisetteV1Error(message: "Invalid anisette (the returned data may not be in JSON)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - V1
|
||||||
|
|
||||||
|
func handleV1() {
|
||||||
|
print("Server is V1")
|
||||||
|
|
||||||
|
if UserDefaults.shared.trustedServerURL == AnisetteManager.currentURLString {
|
||||||
|
print("Server has already been trusted, fetching anisette")
|
||||||
|
return self.fetchAnisetteV1()
|
||||||
|
}
|
||||||
|
|
||||||
|
print("Alerting user about outdated server")
|
||||||
|
let alert = UIAlertController(title: "WARNING: Outdated anisette server", message: "We've detected you are using an older anisette server. Using this server has a higher likelihood of locking your account and causing other issues. Are you sure you want to continue?", preferredStyle: UIAlertController.Style.alert)
|
||||||
|
alert.addAction(UIAlertAction(title: "Continue", style: UIAlertAction.Style.destructive, handler: { action in
|
||||||
|
print("Fetching anisette via V1")
|
||||||
|
UserDefaults.shared.trustedServerURL = AnisetteManager.currentURLString
|
||||||
|
self.fetchAnisetteV1()
|
||||||
|
}))
|
||||||
|
alert.addAction(UIAlertAction(title: "Cancel", style: UIAlertAction.Style.cancel, handler: { action in
|
||||||
|
print("Cancelled anisette operation")
|
||||||
|
self.finish(.failure(OperationError.cancelled))
|
||||||
|
}))
|
||||||
|
|
||||||
|
let keyWindow = UIApplication.shared.windows.filter { $0.isKeyWindow }.first
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
if let presentingController = keyWindow?.rootViewController?.presentedViewController {
|
||||||
|
presentingController.present(alert, animated: true)
|
||||||
|
} else {
|
||||||
|
keyWindow?.rootViewController?.present(alert, animated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchAnisetteV1() {
|
||||||
|
print("Fetching anisette V1")
|
||||||
|
URLSession.shared.dataTask(with: self.url!) { data, response, error in
|
||||||
|
do {
|
||||||
|
guard let data = data, error == nil else { throw OperationError.anisetteV1Error(message: "Unable to fetch data\(error != nil ? " (\(error!.localizedDescription))" : "")") }
|
||||||
|
|
||||||
|
try self.extractAnisetteData(data, response as? HTTPURLResponse, v3: false)
|
||||||
} catch let error as NSError {
|
} catch let error as NSError {
|
||||||
print("Failed to load: \(error.localizedDescription)")
|
print("Failed to load: \(error.localizedDescription)")
|
||||||
self.finish(.failure(error))
|
self.finish(.failure(error))
|
||||||
}
|
}
|
||||||
|
}.resume()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - V3: PROVISIONING
|
||||||
|
|
||||||
|
func provision() {
|
||||||
|
fetchClientInfo {
|
||||||
|
print("Getting provisioning URLs")
|
||||||
|
var request = self.buildAppleRequest(url: URL(string: "https://gsa.apple.com/grandslam/GsService2/lookup")!)
|
||||||
|
request.httpMethod = "GET"
|
||||||
|
URLSession.shared.dataTask(with: request) { data, response, error in
|
||||||
|
if let data = data,
|
||||||
|
let plist = try? PropertyListSerialization.propertyList(from: data, format: nil) as? Dictionary<String, Dictionary<String, Any>>,
|
||||||
|
let startProvisioningString = plist["urls"]?["midStartProvisioning"] as? String,
|
||||||
|
let startProvisioningURL = URL(string: startProvisioningString),
|
||||||
|
let endProvisioningString = plist["urls"]?["midFinishProvisioning"] as? String,
|
||||||
|
let endProvisioningURL = URL(string: endProvisioningString) {
|
||||||
|
self.startProvisioningURL = startProvisioningURL
|
||||||
|
self.endProvisioningURL = endProvisioningURL
|
||||||
|
print("startProvisioningURL: \(self.startProvisioningURL!.absoluteString)")
|
||||||
|
print("endProvisioningURL: \(self.endProvisioningURL!.absoluteString)")
|
||||||
|
print("Starting a provisioning session")
|
||||||
|
self.startProvisioningSession()
|
||||||
|
} else {
|
||||||
|
print("Apple didn't give valid URLs! Got response: \(String(data: data ?? Data("nothing".utf8), encoding: .utf8) ?? "not utf8")")
|
||||||
|
self.finish(.failure(OperationError.provisioningError(result: "Apple didn't give valid URLs. Please try again later", message: nil)))
|
||||||
|
}
|
||||||
|
}.resume()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func startProvisioningSession() {
|
||||||
|
let provisioningSessionURL = self.url!.appendingPathComponent("v3").appendingPathComponent("provisioning_session")
|
||||||
|
var wsRequest = URLRequest(url: provisioningSessionURL)
|
||||||
|
wsRequest.timeoutInterval = 5
|
||||||
|
self.socket = WebSocket(request: wsRequest)
|
||||||
|
self.socket.delegate = self
|
||||||
|
self.socket.connect()
|
||||||
|
}
|
||||||
|
|
||||||
|
func didReceive(event: WebSocketEvent, client: WebSocketClient) {
|
||||||
|
switch event {
|
||||||
|
case .text(let string):
|
||||||
|
do {
|
||||||
|
if let json = try JSONSerialization.jsonObject(with: string.data(using: .utf8)!, options: []) as? [String: Any] {
|
||||||
|
guard let result = json["result"] as? String else {
|
||||||
|
print("The server didn't give us a result")
|
||||||
|
client.disconnect(closeCode: 0)
|
||||||
|
self.finish(.failure(OperationError.provisioningError(result: "The server didn't give us a result", message: nil)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
print("Received result: \(result)")
|
||||||
|
switch result {
|
||||||
|
case "GiveIdentifier":
|
||||||
|
print("Giving identifier")
|
||||||
|
client.json(["identifier": Keychain.shared.identifier!])
|
||||||
|
|
||||||
|
case "GiveStartProvisioningData":
|
||||||
|
print("Getting start provisioning data")
|
||||||
|
let body = [
|
||||||
|
"Header": [String: Any](),
|
||||||
|
"Request": [String: Any](),
|
||||||
|
]
|
||||||
|
var request = self.buildAppleRequest(url: self.startProvisioningURL!)
|
||||||
|
request.httpMethod = "POST"
|
||||||
|
request.httpBody = try! PropertyListSerialization.data(fromPropertyList: body, format: .xml, options: 0)
|
||||||
|
URLSession.shared.dataTask(with: request) { data, response, error in
|
||||||
|
if let data = data,
|
||||||
|
let plist = try? PropertyListSerialization.propertyList(from: data, format: nil) as? Dictionary<String, Dictionary<String, Any>>,
|
||||||
|
let spim = plist["Response"]?["spim"] as? String {
|
||||||
|
print("Giving start provisioning data")
|
||||||
|
client.json(["spim": spim])
|
||||||
|
} else {
|
||||||
|
print("Apple didn't give valid start provisioning data! Got response: \(String(data: data ?? Data("nothing".utf8), encoding: .utf8) ?? "not utf8")")
|
||||||
|
client.disconnect(closeCode: 0)
|
||||||
|
self.finish(.failure(OperationError.provisioningError(result: "Apple didn't give valid start provisioning data. Please try again later", message: nil)))
|
||||||
|
}
|
||||||
|
}.resume()
|
||||||
|
|
||||||
|
case "GiveEndProvisioningData":
|
||||||
|
print("Getting end provisioning data")
|
||||||
|
guard let cpim = json["cpim"] as? String else {
|
||||||
|
print("The server didn't give us a cpim")
|
||||||
|
client.disconnect(closeCode: 0)
|
||||||
|
self.finish(.failure(OperationError.provisioningError(result: "The server didn't give us a cpim", message: nil)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let body = [
|
||||||
|
"Header": [String: Any](),
|
||||||
|
"Request": [
|
||||||
|
"cpim": cpim,
|
||||||
|
],
|
||||||
|
]
|
||||||
|
var request = self.buildAppleRequest(url: self.endProvisioningURL!)
|
||||||
|
request.httpMethod = "POST"
|
||||||
|
request.httpBody = try! PropertyListSerialization.data(fromPropertyList: body, format: .xml, options: 0)
|
||||||
|
URLSession.shared.dataTask(with: request) { data, response, error in
|
||||||
|
if let data = data,
|
||||||
|
let plist = try? PropertyListSerialization.propertyList(from: data, format: nil) as? Dictionary<String, Dictionary<String, Any>>,
|
||||||
|
let ptm = plist["Response"]?["ptm"] as? String,
|
||||||
|
let tk = plist["Response"]?["tk"] as? String {
|
||||||
|
print("Giving end provisioning data")
|
||||||
|
client.json(["ptm": ptm, "tk": tk])
|
||||||
|
} else {
|
||||||
|
print("Apple didn't give valid end provisioning data! Got response: \(String(data: data ?? Data("nothing".utf8), encoding: .utf8) ?? "not utf8")")
|
||||||
|
client.disconnect(closeCode: 0)
|
||||||
|
self.finish(.failure(OperationError.provisioningError(result: "Apple didn't give valid end provisioning data. Please try again later", message: nil)))
|
||||||
|
}
|
||||||
|
}.resume()
|
||||||
|
|
||||||
|
case "ProvisioningSuccess":
|
||||||
|
print("Provisioning succeeded!")
|
||||||
|
client.disconnect(closeCode: 0)
|
||||||
|
guard let adiPb = json["adi_pb"] as? String else {
|
||||||
|
print("The server didn't give us an adi.pb file")
|
||||||
|
self.finish(.failure(OperationError.provisioningError(result: "The server didn't give us an adi.pb file", message: nil)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Keychain.shared.adiPb = adiPb
|
||||||
|
self.fetchAnisetteV3(Keychain.shared.identifier!, Keychain.shared.adiPb!)
|
||||||
|
|
||||||
|
default:
|
||||||
|
if result.contains("Error") || result.contains("Invalid") || result == "ClosingPerRequest" || result == "Timeout" || result == "TextOnly" {
|
||||||
|
print("Failing because of \(result)")
|
||||||
|
self.finish(.failure(OperationError.provisioningError(result: result, message: json["message"] as? String)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch let error as NSError {
|
||||||
|
print("Failed to handle text: \(error.localizedDescription)")
|
||||||
|
self.finish(.failure(OperationError.provisioningError(result: error.localizedDescription, message: nil)))
|
||||||
|
}
|
||||||
|
|
||||||
|
case .connected:
|
||||||
|
print("Connected")
|
||||||
|
|
||||||
|
case .disconnected(let string, let code):
|
||||||
|
print("Disconnected: \(code); \(string)")
|
||||||
|
|
||||||
|
case .error(let error):
|
||||||
|
print("Got error: \(String(describing: error))")
|
||||||
|
|
||||||
|
default:
|
||||||
|
print("Unknown event: \(event)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildAppleRequest(url: URL) -> URLRequest {
|
||||||
|
var request = URLRequest(url: url)
|
||||||
|
request.setValue(self.clientInfo!, forHTTPHeaderField: "X-Mme-Client-Info")
|
||||||
|
request.setValue(self.userAgent!, forHTTPHeaderField: "User-Agent")
|
||||||
|
request.setValue("text/x-xml-plist", forHTTPHeaderField: "Content-Type")
|
||||||
|
request.setValue("*/*", forHTTPHeaderField: "Accept")
|
||||||
|
|
||||||
|
request.setValue(self.mdLu!, forHTTPHeaderField: "X-Apple-I-MD-LU")
|
||||||
|
request.setValue(self.deviceId!, forHTTPHeaderField: "X-Mme-Device-Id")
|
||||||
|
|
||||||
|
let formatter = DateFormatter()
|
||||||
|
formatter.locale = Locale(identifier: "en_US_POSIX")
|
||||||
|
formatter.calendar = Calendar(identifier: .gregorian)
|
||||||
|
formatter.timeZone = TimeZone(identifier: "UTC")
|
||||||
|
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss'Z'"
|
||||||
|
let dateString = formatter.string(from: Date())
|
||||||
|
request.setValue(dateString, forHTTPHeaderField: "X-Apple-I-Client-Time")
|
||||||
|
request.setValue(Locale.current.identifier, forHTTPHeaderField: "X-Apple-Locale")
|
||||||
|
request.setValue(TimeZone.current.abbreviation(), forHTTPHeaderField: "X-Apple-I-TimeZone")
|
||||||
|
return request
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - V3: FETCHING
|
||||||
|
|
||||||
|
func fetchClientInfo(_ callback: @escaping () -> Void) {
|
||||||
|
if self.clientInfo != nil &&
|
||||||
|
self.userAgent != nil &&
|
||||||
|
self.mdLu != nil &&
|
||||||
|
self.deviceId != nil &&
|
||||||
|
Keychain.shared.identifier != nil {
|
||||||
|
print("Skipping client_info fetch since all the properties we need aren't nil")
|
||||||
|
return callback()
|
||||||
|
}
|
||||||
|
print("Trying to get client_info")
|
||||||
|
let clientInfoURL = self.url!.appendingPathComponent("v3").appendingPathComponent("client_info")
|
||||||
|
URLSession.shared.dataTask(with: clientInfoURL) { data, response, error in
|
||||||
|
do {
|
||||||
|
guard let data = data, error == nil else {
|
||||||
|
return self.finish(.failure(OperationError.anisetteV3Error(message: "Couldn't fetch client info. The server may be down\(error != nil ? " (\(error!.localizedDescription))" : "")")))
|
||||||
|
}
|
||||||
|
|
||||||
|
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: String] {
|
||||||
|
if let clientInfo = json["client_info"] {
|
||||||
|
print("Server is V3")
|
||||||
|
|
||||||
|
self.clientInfo = clientInfo
|
||||||
|
self.userAgent = json["user_agent"]!
|
||||||
|
print("Client-Info: \(self.clientInfo!)")
|
||||||
|
print("User-Agent: \(self.userAgent!)")
|
||||||
|
|
||||||
|
if Keychain.shared.identifier == nil {
|
||||||
|
print("Generating identifier")
|
||||||
|
var bytes = [Int8](repeating: 0, count: 16)
|
||||||
|
let status = SecRandomCopyBytes(kSecRandomDefault, bytes.count, &bytes)
|
||||||
|
|
||||||
|
if status != errSecSuccess {
|
||||||
|
print("ERROR GENERATING IDENTIFIER!!! \(status)")
|
||||||
|
return self.finish(.failure(OperationError.provisioningError(result: "Couldn't generate identifier", message: nil)))
|
||||||
|
}
|
||||||
|
|
||||||
|
Keychain.shared.identifier = Data(bytes: &bytes, count: bytes.count).base64EncodedString()
|
||||||
|
}
|
||||||
|
|
||||||
|
let decoded = Data(base64Encoded: Keychain.shared.identifier!)!
|
||||||
|
self.mdLu = decoded.sha256().hexEncodedString()
|
||||||
|
print("X-Apple-I-MD-LU: \(self.mdLu!)")
|
||||||
|
let uuid: UUID = decoded.object()
|
||||||
|
self.deviceId = uuid.uuidString.uppercased()
|
||||||
|
print("X-Mme-Device-Id: \(self.deviceId!)")
|
||||||
|
|
||||||
|
callback()
|
||||||
|
} else { self.handleV1() }
|
||||||
|
} else { self.finish(.failure(OperationError.anisetteV3Error(message: "Couldn't fetch client info. The returned data may not be in JSON"))) }
|
||||||
|
} catch let error as NSError {
|
||||||
|
print("Failed to load: \(error.localizedDescription)")
|
||||||
|
self.handleV1()
|
||||||
|
}
|
||||||
|
}.resume()
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchAnisetteV3(_ identifier: String, _ adiPb: String) {
|
||||||
|
fetchClientInfo {
|
||||||
|
print("Fetching anisette V3")
|
||||||
|
var request = URLRequest(url: self.url!.appendingPathComponent("v3").appendingPathComponent("get_headers"))
|
||||||
|
request.httpMethod = "POST"
|
||||||
|
request.httpBody = try! JSONSerialization.data(withJSONObject: [
|
||||||
|
"identifier": identifier,
|
||||||
|
"adi_pb": adiPb
|
||||||
|
], options: [])
|
||||||
|
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||||
|
URLSession.shared.dataTask(with: request) { data, response, error in
|
||||||
|
do {
|
||||||
|
guard let data = data, error == nil else { throw OperationError.anisetteV3Error(message: "Couldn't fetch anisette") }
|
||||||
|
|
||||||
|
try self.extractAnisetteData(data, response as? HTTPURLResponse, v3: true)
|
||||||
|
} catch let error as NSError {
|
||||||
|
print("Failed to load: \(error.localizedDescription)")
|
||||||
|
self.finish(.failure(error))
|
||||||
|
}
|
||||||
|
}.resume()
|
||||||
}
|
}
|
||||||
|
|
||||||
task.resume()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension WebSocketClient {
|
||||||
|
func json(_ dictionary: [String: String]) {
|
||||||
|
let data = try! JSONSerialization.data(withJSONObject: dictionary, options: [])
|
||||||
|
self.write(string: String(data: data, encoding: .utf8)!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Data {
|
||||||
|
// https://stackoverflow.com/a/25391020
|
||||||
|
func sha256() -> Data {
|
||||||
|
var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
|
||||||
|
self.withUnsafeBytes {
|
||||||
|
_ = CC_SHA256($0.baseAddress, CC_LONG(self.count), &hash)
|
||||||
|
}
|
||||||
|
return Data(hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://stackoverflow.com/a/40089462
|
||||||
|
func hexEncodedString() -> String {
|
||||||
|
return self.map { String(format: "%02hhX", $0) }.joined()
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://stackoverflow.com/a/59127761
|
||||||
|
func object<T>() -> T { self.withUnsafeBytes { $0.load(as: T.self) } }
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import AltSign
|
|||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
@objc(FetchAppIDsOperation)
|
@objc(FetchAppIDsOperation)
|
||||||
class FetchAppIDsOperation: ResultOperation<([AppID], NSManagedObjectContext)>
|
final class FetchAppIDsOperation: ResultOperation<([AppID], NSManagedObjectContext)>
|
||||||
{
|
{
|
||||||
let context: AuthenticatedOperationContext
|
let context: AuthenticatedOperationContext
|
||||||
let managedObjectContext: NSManagedObjectContext
|
let managedObjectContext: NSManagedObjectContext
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import AltSign
|
|||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
@objc(FetchProvisioningProfilesOperation)
|
@objc(FetchProvisioningProfilesOperation)
|
||||||
class FetchProvisioningProfilesOperation: ResultOperation<[String: ALTProvisioningProfile]>
|
final class FetchProvisioningProfilesOperation: ResultOperation<[String: ALTProvisioningProfile]>
|
||||||
{
|
{
|
||||||
let context: AppOperationContext
|
let context: AppOperationContext
|
||||||
|
|
||||||
@@ -262,14 +262,19 @@ extension FetchProvisioningProfilesOperation
|
|||||||
{
|
{
|
||||||
throw OperationError.maximumAppIDLimitReached(application: application, requiredAppIDs: requiredAppIDs, availableAppIDs: availableAppIDs, nextExpirationDate: expirationDate)
|
throw OperationError.maximumAppIDLimitReached(application: application, requiredAppIDs: requiredAppIDs, availableAppIDs: availableAppIDs, nextExpirationDate: expirationDate)
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
throw ALTAppleAPIError(.maximumAppIDLimitReached)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//App ID name must be ascii. If the name is not ascii, using bundleID instead
|
||||||
|
let appIDName: String
|
||||||
|
if !name.allSatisfy({ $0.isASCII }) {
|
||||||
|
//Contains non ASCII (Such as Chinese/Japanese...), using bundleID
|
||||||
|
appIDName = bundleIdentifier
|
||||||
|
}else {
|
||||||
|
//ASCII text, keep going as usual
|
||||||
|
appIDName = name
|
||||||
|
}
|
||||||
|
|
||||||
ALTAppleAPI.shared.addAppID(withName: name, bundleIdentifier: bundleIdentifier, team: team, session: session) { (appID, error) in
|
ALTAppleAPI.shared.addAppID(withName: appIDName, bundleIdentifier: bundleIdentifier, team: team, session: session) { (appID, error) in
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
do
|
do
|
||||||
@@ -384,19 +389,39 @@ extension FetchProvisioningProfilesOperation
|
|||||||
|
|
||||||
if app.isAltStoreApp
|
if app.isAltStoreApp
|
||||||
{
|
{
|
||||||
|
print("Application groups before modifying for SideStore: \(applicationGroups)")
|
||||||
|
|
||||||
|
// Remove app groups that contain AltStore since they can be problematic (cause SideStore to expire early)
|
||||||
|
for (index, group) in applicationGroups.enumerated() {
|
||||||
|
if group.contains("AltStore") {
|
||||||
|
print("Removing application group: \(group)")
|
||||||
|
applicationGroups.remove(at: index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure we add .AltWidget for the widget
|
||||||
|
var altStoreAppGroupID = Bundle.baseAltStoreAppGroupID
|
||||||
|
for (_, group) in applicationGroups.enumerated() {
|
||||||
|
if group.contains("AltWidget") {
|
||||||
|
altStoreAppGroupID += ".AltWidget"
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Potentially updating app groups for this specific AltStore.
|
// Potentially updating app groups for this specific AltStore.
|
||||||
// Find the (unique) AltStore app group, then replace it
|
// Find the (unique) AltStore app group, then replace it
|
||||||
// with the correct "base" app group ID.
|
// with the correct "base" app group ID.
|
||||||
// Otherwise, we may append a duplicate team identifier to the end.
|
// Otherwise, we may append a duplicate team identifier to the end.
|
||||||
if let index = applicationGroups.firstIndex(where: { $0.contains(Bundle.baseAltStoreAppGroupID) })
|
if let index = applicationGroups.firstIndex(where: { $0.contains(Bundle.baseAltStoreAppGroupID) })
|
||||||
{
|
{
|
||||||
applicationGroups[index] = Bundle.baseAltStoreAppGroupID
|
applicationGroups[index] = altStoreAppGroupID
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
applicationGroups.append(Bundle.baseAltStoreAppGroupID)
|
applicationGroups.append(altStoreAppGroupID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
print("Application groups: \(applicationGroups)")
|
||||||
|
|
||||||
// Dispatch onto global queue to prevent appGroupsLock deadlock.
|
// Dispatch onto global queue to prevent appGroupsLock deadlock.
|
||||||
DispatchQueue.global().async {
|
DispatchQueue.global().async {
|
||||||
@@ -478,10 +503,13 @@ extension FetchProvisioningProfilesOperation
|
|||||||
ALTAppleAPI.shared.delete(profile, for: team, session: session) { (success, error) in
|
ALTAppleAPI.shared.delete(profile, for: team, session: session) { (success, error) in
|
||||||
switch Result(success, error)
|
switch Result(success, error)
|
||||||
{
|
{
|
||||||
case .failure(let error): completionHandler(.failure(error))
|
case .failure:
|
||||||
case .success:
|
// As of March 20, 2023, the free provisioning profile is re-generated each fetch, and you can no longer delete it.
|
||||||
|
// So instead, we just return the fetched profile from above.
|
||||||
|
completionHandler(.success(profile))
|
||||||
|
|
||||||
// Fetch new provisiong profile
|
case .success:
|
||||||
|
// Fetch new provisioning profile
|
||||||
ALTAppleAPI.shared.fetchProvisioningProfile(for: appID, deviceType: .iphone, team: team, session: session) { (profile, error) in
|
ALTAppleAPI.shared.fetchProvisioningProfile(for: appID, deviceType: .iphone, team: team, session: session) { (profile, error) in
|
||||||
completionHandler(Result(profile, error))
|
completionHandler(Result(profile, error))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import AltStoreCore
|
|||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
@objc(FetchSourceOperation)
|
@objc(FetchSourceOperation)
|
||||||
class FetchSourceOperation: ResultOperation<Source>
|
final class FetchSourceOperation: ResultOperation<Source>
|
||||||
{
|
{
|
||||||
let sourceURL: URL
|
let sourceURL: URL
|
||||||
let managedObjectContext: NSManagedObjectContext
|
let managedObjectContext: NSManagedObjectContext
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ extension FetchTrustedSourcesOperation
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class FetchTrustedSourcesOperation: ResultOperation<[FetchTrustedSourcesOperation.TrustedSource]>
|
final class FetchTrustedSourcesOperation: ResultOperation<[FetchTrustedSourcesOperation.TrustedSource]>
|
||||||
{
|
{
|
||||||
override func main()
|
override func main()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -11,9 +11,10 @@ import Network
|
|||||||
import AltStoreCore
|
import AltStoreCore
|
||||||
import AltSign
|
import AltSign
|
||||||
import Roxas
|
import Roxas
|
||||||
|
import minimuxer
|
||||||
|
|
||||||
@objc(InstallAppOperation)
|
@objc(InstallAppOperation)
|
||||||
class InstallAppOperation: ResultOperation<InstalledApp>
|
final class InstallAppOperation: ResultOperation<InstalledApp>
|
||||||
{
|
{
|
||||||
let context: InstallAppOperationContext
|
let context: InstallAppOperationContext
|
||||||
|
|
||||||
@@ -40,12 +41,14 @@ class InstallAppOperation: ResultOperation<InstalledApp>
|
|||||||
|
|
||||||
guard
|
guard
|
||||||
let certificate = self.context.certificate,
|
let certificate = self.context.certificate,
|
||||||
let resignedApp = self.context.resignedApp
|
let resignedApp = self.context.resignedApp,
|
||||||
|
let provisioningProfiles = self.context.provisioningProfiles
|
||||||
else { return self.finish(.failure(OperationError.invalidParameters)) }
|
else { return self.finish(.failure(OperationError.invalidParameters)) }
|
||||||
|
|
||||||
let backgroundContext = DatabaseManager.shared.persistentContainer.newBackgroundContext()
|
let backgroundContext = DatabaseManager.shared.persistentContainer.newBackgroundContext()
|
||||||
backgroundContext.perform {
|
backgroundContext.perform {
|
||||||
|
|
||||||
|
|
||||||
/* App */
|
/* App */
|
||||||
let installedApp: InstalledApp
|
let installedApp: InstalledApp
|
||||||
|
|
||||||
@@ -115,8 +118,7 @@ class InstallAppOperation: ResultOperation<InstalledApp>
|
|||||||
// Temporary directory and resigned .ipa no longer needed, so delete them now to ensure AltStore doesn't quit before we get the chance to.
|
// Temporary directory and resigned .ipa no longer needed, so delete them now to ensure AltStore doesn't quit before we get the chance to.
|
||||||
self.cleanUp()
|
self.cleanUp()
|
||||||
|
|
||||||
var activeProfiles: Set<String>?
|
if let sideloadedAppsLimit = UserDefaults.standard.activeAppsLimit, provisioningProfiles.contains(where: { $1.isFreeProvisioningProfile == true })
|
||||||
if let sideloadedAppsLimit = UserDefaults.standard.activeAppsLimit
|
|
||||||
{
|
{
|
||||||
// When installing these new profiles, AltServer will remove all non-active profiles to ensure we remain under limit.
|
// When installing these new profiles, AltServer will remove all non-active profiles to ensure we remain under limit.
|
||||||
|
|
||||||
@@ -141,23 +143,70 @@ class InstallAppOperation: ResultOperation<InstalledApp>
|
|||||||
installedApp.isActive = false
|
installedApp.isActive = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
activeProfiles = Set(activeApps.flatMap { (installedApp) -> [String] in
|
else
|
||||||
let appExtensionProfiles = installedApp.appExtensions.map { $0.resignedBundleIdentifier }
|
{
|
||||||
return [installedApp.resignedBundleIdentifier] + appExtensionProfiles
|
installedApp.isActive = true
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let ns_bundle = NSString(string: installedApp.bundleIdentifier)
|
var installing = true
|
||||||
let ns_bundle_ptr = UnsafeMutablePointer<CChar>(mutating: ns_bundle.utf8String)
|
if installedApp.storeApp?.bundleIdentifier.range(of: Bundle.Info.appbundleIdentifier) != nil {
|
||||||
|
// Reinstalling ourself will hang until we leave the app, so we need to exit it without force closing
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
|
||||||
|
if UIApplication.shared.applicationState != .active {
|
||||||
|
print("We are not in the foreground, let's not do anything")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !installing {
|
||||||
|
print("Installing finished")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
print("We are still installing after 3 seconds")
|
||||||
|
UNUserNotificationCenter.current().getNotificationSettings { settings in
|
||||||
|
switch (settings.authorizationStatus) {
|
||||||
|
case .authorized, .ephemeral, .provisional:
|
||||||
|
print("Notifications are enabled")
|
||||||
|
|
||||||
|
let content = UNMutableNotificationContent()
|
||||||
|
content.title = "Refreshing..."
|
||||||
|
content.body = "SideStore will automatically move to the homescreen to finish refreshing!"
|
||||||
|
let notification = UNNotificationRequest(identifier: Bundle.Info.appbundleIdentifier + ".FinishRefreshNotification", content: content, trigger: UNTimeIntervalNotificationTrigger(timeInterval: 2, repeats: false))
|
||||||
|
UNUserNotificationCenter.current().add(notification)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
print("Notifications are not enabled")
|
||||||
|
|
||||||
|
let alert = UIAlertController(title: "Finish Refresh", message: "Please reopen SideStore after the process is finished.To finish refreshing, SideStore must be moved to the background. To do this, you can either go to the Home Screen manually or by hitting Continue. Please reopen SideStore after doing this.", preferredStyle: .alert)
|
||||||
|
alert.addAction(UIAlertAction(title: NSLocalizedString("Continue", comment: ""), style: .default, handler: { _ in
|
||||||
|
print("Going home")
|
||||||
|
UIApplication.shared.perform(#selector(NSXPCConnection.suspend))
|
||||||
|
}))
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
let keyWindow = UIApplication.shared.windows.filter { $0.isKeyWindow }.first
|
||||||
|
if var topController = keyWindow?.rootViewController {
|
||||||
|
while let presentedViewController = topController.presentedViewController {
|
||||||
|
topController = presentedViewController
|
||||||
|
}
|
||||||
|
topController.present(alert, animated: true)
|
||||||
|
} else {
|
||||||
|
print("No key window? Let's just go home")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UIApplication.shared.perform(#selector(NSXPCConnection.suspend))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let res = minimuxer_install_ipa(ns_bundle_ptr)
|
do {
|
||||||
if res == 0 {
|
try install_ipa(installedApp.bundleIdentifier)
|
||||||
|
installing = false
|
||||||
installedApp.refreshedDate = Date()
|
installedApp.refreshedDate = Date()
|
||||||
self.finish(.success(installedApp))
|
self.finish(.success(installedApp))
|
||||||
|
} catch let error {
|
||||||
} else {
|
installing = false
|
||||||
self.finish(.failure(minimuxer_to_operation(code: res)))
|
self.finish(.failure(error))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -174,10 +223,11 @@ class InstallAppOperation: ResultOperation<InstalledApp>
|
|||||||
do
|
do
|
||||||
{
|
{
|
||||||
try FileManager.default.removeItem(at: fileURL)
|
try FileManager.default.removeItem(at: fileURL)
|
||||||
|
print("Removed refreshed IPA")
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
print("Failed to remove refreshed .ipa:", error)
|
print("Failed to remove refreshed .ipa: \(error)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ class OperationContext
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AuthenticatedOperationContext: OperationContext
|
final class AuthenticatedOperationContext: OperationContext
|
||||||
{
|
{
|
||||||
var session: ALTAppleAPISession?
|
var session: ALTAppleAPISession?
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import AltSign
|
import AltSign
|
||||||
|
import minimuxer
|
||||||
|
|
||||||
enum OperationError: LocalizedError
|
enum OperationError: LocalizedError
|
||||||
{
|
{
|
||||||
@@ -33,18 +34,13 @@ enum OperationError: LocalizedError
|
|||||||
case openAppFailed(name: String)
|
case openAppFailed(name: String)
|
||||||
case missingAppGroup
|
case missingAppGroup
|
||||||
|
|
||||||
case noDevice
|
case noWiFi
|
||||||
case createService(name: String)
|
case tooNewError
|
||||||
case getFromDevice(name: String)
|
case anisetteV1Error(message: String)
|
||||||
case setArgument(name: String)
|
case provisioningError(result: String, message: String?)
|
||||||
case afc
|
case anisetteV3Error(message: String)
|
||||||
case install
|
|
||||||
case uninstall
|
case cacheClearError(errors: [String])
|
||||||
case lookupApps
|
|
||||||
case detach
|
|
||||||
case functionArguments
|
|
||||||
case profileInstall
|
|
||||||
case noConnection
|
|
||||||
|
|
||||||
var failureReason: String? {
|
var failureReason: String? {
|
||||||
switch self {
|
switch self {
|
||||||
@@ -61,18 +57,12 @@ enum OperationError: LocalizedError
|
|||||||
case .openAppFailed(let name): return String(format: NSLocalizedString("SideStore was denied permission to launch %@.", comment: ""), name)
|
case .openAppFailed(let name): return String(format: NSLocalizedString("SideStore was denied permission to launch %@.", comment: ""), name)
|
||||||
case .missingAppGroup: return NSLocalizedString("SideStore's shared app group could not be found.", comment: "")
|
case .missingAppGroup: return NSLocalizedString("SideStore's shared app group could not be found.", comment: "")
|
||||||
case .maximumAppIDLimitReached: return NSLocalizedString("Cannot register more than 10 App IDs.", comment: "")
|
case .maximumAppIDLimitReached: return NSLocalizedString("Cannot register more than 10 App IDs.", comment: "")
|
||||||
case .noDevice: return NSLocalizedString("Cannot fetch the device from the muxer", comment: "")
|
case .noWiFi: return NSLocalizedString("You do not appear to be connected to WiFi!\nSideStore will never be able to install or refresh applications without WiFi.", comment: "")
|
||||||
case .createService(let name): return String(format: NSLocalizedString("Cannot start a %@ server on the device.", comment: ""), name)
|
case .tooNewError: return NSLocalizedString("iOS 17 has changed how JIT is enabled therefore SideStore cannot enable it at this time, sorry for any inconvenience.\nWe will let everyone know once we have a solution!", comment: "")
|
||||||
case .getFromDevice(let name): return String(format: NSLocalizedString("Cannot fetch %@ from the device.", comment: ""), name)
|
case .anisetteV1Error(let message): return String(format: NSLocalizedString("An error occurred when getting anisette data from a V1 server: %@. Try using another anisette server.", comment: ""), message)
|
||||||
case .setArgument(let name): return String(format: NSLocalizedString("Cannot set %@ on the device.", comment: ""), name)
|
case .provisioningError(let result, let message): return String(format: NSLocalizedString("An error occurred when provisioning: %@%@. Please try again. If the issue persists, report it on GitHub Issues!", comment: ""), result, message != nil ? (" (" + message! + ")") : "")
|
||||||
case .afc: return NSLocalizedString("AFC was unable to manage files on the device", comment: "")
|
case .anisetteV3Error(let message): return String(format: NSLocalizedString("An error occurred when getting anisette data from a V3 server: %@. Please try again. If the issue persists, report it on GitHub Issues!", comment: ""), message)
|
||||||
case .install: return NSLocalizedString("Unable to install the app from the staging directory", comment: "")
|
case .cacheClearError(let errors): return String(format: NSLocalizedString("An error occurred while clearing cache: %@", comment: ""), errors.joined(separator: "\n"))
|
||||||
case .uninstall: return NSLocalizedString("Unable to uninstall the app", comment: "")
|
|
||||||
case .lookupApps: return NSLocalizedString("Unable to fetch apps from the device", comment: "")
|
|
||||||
case .detach: return NSLocalizedString("Unable to detach from the app's process", comment: "")
|
|
||||||
case .functionArguments: return NSLocalizedString("A function was passed invalid arguments", comment: "")
|
|
||||||
case .profileInstall: return NSLocalizedString("Unable to manage profiles on the device", comment: "")
|
|
||||||
case .noConnection: return NSLocalizedString("Unable to connect to the device, make sure Wireguard is enabled and you're connected to WiFi", comment: "")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,49 +108,66 @@ enum OperationError: LocalizedError
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func minimuxer_to_operation(code: Int32) -> OperationError {
|
extension MinimuxerError: LocalizedError {
|
||||||
switch code {
|
public var failureReason: String? {
|
||||||
case -1:
|
switch self {
|
||||||
return OperationError.noDevice
|
case .NoDevice:
|
||||||
case -2:
|
return NSLocalizedString("Cannot fetch the device from the muxer", comment: "")
|
||||||
return OperationError.createService(name: "debug")
|
case .NoConnection:
|
||||||
case -3:
|
return NSLocalizedString("Unable to connect to the device, make sure Wireguard is enabled and you're connected to WiFi", comment: "")
|
||||||
return OperationError.createService(name: "instproxy")
|
case .PairingFile:
|
||||||
case -4:
|
return NSLocalizedString("Invalid pairing file. Your pairing file either didn't have a UDID, or it wasn't a valid plist. Please use jitterbugpair to generate it", comment: "")
|
||||||
return OperationError.getFromDevice(name: "installed apps")
|
|
||||||
case -5:
|
case .CreateDebug:
|
||||||
return OperationError.getFromDevice(name: "path to the app")
|
return self.createService(name: "debug")
|
||||||
case -6:
|
case .LookupApps:
|
||||||
return OperationError.getFromDevice(name: "bundle path")
|
return self.getFromDevice(name: "installed apps")
|
||||||
case -7:
|
case .FindApp:
|
||||||
return OperationError.setArgument(name: "max packet")
|
return self.getFromDevice(name: "path to the app")
|
||||||
case -8:
|
case .BundlePath:
|
||||||
return OperationError.setArgument(name: "working directory")
|
return self.getFromDevice(name: "bundle path")
|
||||||
case -9:
|
case .MaxPacket:
|
||||||
return OperationError.setArgument(name: "argv")
|
return self.setArgument(name: "max packet")
|
||||||
case -10:
|
case .WorkingDirectory:
|
||||||
return OperationError.getFromDevice(name: "launch success")
|
return self.setArgument(name: "working directory")
|
||||||
case -11:
|
case .Argv:
|
||||||
return OperationError.detach
|
return self.setArgument(name: "argv")
|
||||||
case -12:
|
case .LaunchSuccess:
|
||||||
return OperationError.functionArguments
|
return self.getFromDevice(name: "launch success")
|
||||||
case -13:
|
case .Detach:
|
||||||
return OperationError.createService(name: "AFC")
|
return NSLocalizedString("Unable to detach from the app's process", comment: "")
|
||||||
case -14:
|
case .Attach:
|
||||||
return OperationError.afc
|
return NSLocalizedString("Unable to attach to the app's process", comment: "")
|
||||||
case -15:
|
|
||||||
return OperationError.install
|
case .CreateInstproxy:
|
||||||
case -16:
|
return self.createService(name: "instproxy")
|
||||||
return OperationError.uninstall
|
case .CreateAfc:
|
||||||
case -17:
|
return self.createService(name: "AFC")
|
||||||
return OperationError.createService(name: "misagent")
|
case .RwAfc:
|
||||||
case -18:
|
return NSLocalizedString("AFC was unable to manage files on the device", comment: "")
|
||||||
return OperationError.profileInstall
|
case .InstallApp(let message):
|
||||||
case -19:
|
return NSLocalizedString("Unable to install the app: \(message.toString())", comment: "")
|
||||||
return OperationError.profileInstall
|
case .UninstallApp:
|
||||||
case -20:
|
return NSLocalizedString("Unable to uninstall the app", comment: "")
|
||||||
return OperationError.noConnection
|
|
||||||
default:
|
case .CreateMisagent:
|
||||||
return OperationError.unknown
|
return self.createService(name: "misagent")
|
||||||
|
case .ProfileInstall:
|
||||||
|
return NSLocalizedString("Unable to manage profiles on the device", comment: "")
|
||||||
|
case .ProfileRemove:
|
||||||
|
return NSLocalizedString("Unable to manage profiles on the device", comment: "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func createService(name: String) -> String {
|
||||||
|
return String(format: NSLocalizedString("Cannot start a %@ server on the device.", comment: ""), name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func getFromDevice(name: String) -> String {
|
||||||
|
return String(format: NSLocalizedString("Cannot fetch %@ from the device.", comment: ""), name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func setArgument(name: String) -> String {
|
||||||
|
return String(format: NSLocalizedString("Cannot set %@ on the device.", comment: ""), name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ private struct OTAUpdate
|
|||||||
}
|
}
|
||||||
|
|
||||||
@available(iOS 14, *)
|
@available(iOS 14, *)
|
||||||
class PatchAppOperation: ResultOperation<Void>
|
final class PatchAppOperation: ResultOperation<Void>
|
||||||
{
|
{
|
||||||
let context: PatchAppContext
|
let context: PatchAppContext
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ extension PatchViewController
|
|||||||
}
|
}
|
||||||
|
|
||||||
@available(iOS 14.0, *)
|
@available(iOS 14.0, *)
|
||||||
class PatchViewController: UIViewController
|
final class PatchViewController: UIViewController
|
||||||
{
|
{
|
||||||
var patchApp: AnyApp?
|
var patchApp: AnyApp?
|
||||||
var installedApp: InstalledApp?
|
var installedApp: InstalledApp?
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import Roxas
|
|||||||
import minimuxer
|
import minimuxer
|
||||||
|
|
||||||
@objc(RefreshAppOperation)
|
@objc(RefreshAppOperation)
|
||||||
class RefreshAppOperation: ResultOperation<InstalledApp>
|
final class RefreshAppOperation: ResultOperation<InstalledApp>
|
||||||
{
|
{
|
||||||
let context: AppOperationContext
|
let context: AppOperationContext
|
||||||
|
|
||||||
@@ -35,34 +35,28 @@ class RefreshAppOperation: ResultOperation<InstalledApp>
|
|||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
if let error = self.context.error
|
if let error = self.context.error { return self.finish(.failure(error)) }
|
||||||
{
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let profiles = self.context.provisioningProfiles else { throw OperationError.invalidParameters }
|
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)) }
|
||||||
|
|
||||||
guard let app = self.context.app else { throw OperationError.appNotFound }
|
for p in profiles {
|
||||||
|
do {
|
||||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
let bytes = p.value.data.toRustByteSlice()
|
||||||
print("Sending refresh app request...")
|
try install_provisioning_profile(bytes.forRust())
|
||||||
|
} catch {
|
||||||
|
self.finish(.failure(MinimuxerError.ProfileInstall))
|
||||||
|
}
|
||||||
|
|
||||||
for p in profiles {
|
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
||||||
do {
|
print("Sending refresh app request...")
|
||||||
let x = try install_provisioning_profile(plist: p.value.data)
|
|
||||||
if case .Bad(let code) = x {
|
|
||||||
self.finish(.failure(minimuxer_to_operation(code: code)))
|
|
||||||
}
|
|
||||||
} catch Uhoh.Bad(let code) {
|
|
||||||
self.finish(.failure(minimuxer_to_operation(code: code)))
|
|
||||||
} catch {
|
|
||||||
self.finish(.failure(OperationError.unknown))
|
|
||||||
}
|
|
||||||
self.progress.completedUnitCount += 1
|
self.progress.completedUnitCount += 1
|
||||||
|
|
||||||
let predicate = NSPredicate(format: "%K == %@", #keyPath(InstalledApp.bundleIdentifier), app.bundleIdentifier)
|
let predicate = NSPredicate(format: "%K == %@", #keyPath(InstalledApp.bundleIdentifier), app.bundleIdentifier)
|
||||||
self.managedObjectContext.perform {
|
self.managedObjectContext.perform {
|
||||||
guard let installedApp = InstalledApp.first(satisfying: predicate, in: self.managedObjectContext) else {
|
guard let installedApp = InstalledApp.first(satisfying: predicate, in: self.managedObjectContext) else {
|
||||||
|
self.finish(.failure(OperationError.appNotFound))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
installedApp.update(provisioningProfile: p.value)
|
installedApp.update(provisioningProfile: p.value)
|
||||||
@@ -75,9 +69,5 @@ class RefreshAppOperation: ResultOperation<InstalledApp>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch
|
|
||||||
{
|
|
||||||
self.finish(.failure(error))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import CoreData
|
|||||||
import AltStoreCore
|
import AltStoreCore
|
||||||
import AltSign
|
import AltSign
|
||||||
|
|
||||||
class RefreshGroup: NSObject
|
final class RefreshGroup: NSObject
|
||||||
{
|
{
|
||||||
let context: AuthenticatedOperationContext
|
let context: AuthenticatedOperationContext
|
||||||
let progress = Progress.discreteProgress(totalUnitCount: 0)
|
let progress = Progress.discreteProgress(totalUnitCount: 0)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
@objc(RemoveAppBackupOperation)
|
@objc(RemoveAppBackupOperation)
|
||||||
class RemoveAppBackupOperation: ResultOperation<Void>
|
final class RemoveAppBackupOperation: ResultOperation<Void>
|
||||||
{
|
{
|
||||||
let context: InstallAppOperationContext
|
let context: InstallAppOperationContext
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import AltStoreCore
|
|||||||
import minimuxer
|
import minimuxer
|
||||||
|
|
||||||
@objc(RemoveAppOperation)
|
@objc(RemoveAppOperation)
|
||||||
class RemoveAppOperation: ResultOperation<InstalledApp>
|
final class RemoveAppOperation: ResultOperation<InstalledApp>
|
||||||
{
|
{
|
||||||
let context: InstallAppOperationContext
|
let context: InstallAppOperationContext
|
||||||
|
|
||||||
@@ -39,15 +39,11 @@ class RemoveAppOperation: ResultOperation<InstalledApp>
|
|||||||
let resignedBundleIdentifier = installedApp.resignedBundleIdentifier
|
let resignedBundleIdentifier = installedApp.resignedBundleIdentifier
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let res = try remove_app(app_id: resignedBundleIdentifier)
|
try remove_app(resignedBundleIdentifier)
|
||||||
if case Uhoh.Bad(let code) = res {
|
|
||||||
self.finish(.failure(minimuxer_to_operation(code: code)))
|
|
||||||
}
|
|
||||||
} catch Uhoh.Bad(let code) {
|
|
||||||
self.finish(.failure(minimuxer_to_operation(code: code)))
|
|
||||||
} catch {
|
} catch {
|
||||||
self.finish(.failure(ALTServerError(.appDeletionFailed)))
|
return self.finish(.failure(error))
|
||||||
}
|
}
|
||||||
|
|
||||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
||||||
self.progress.completedUnitCount += 1
|
self.progress.completedUnitCount += 1
|
||||||
|
|
||||||
|
|||||||
@@ -11,9 +11,10 @@ import Roxas
|
|||||||
|
|
||||||
import AltStoreCore
|
import AltStoreCore
|
||||||
import AltSign
|
import AltSign
|
||||||
|
import minimuxer
|
||||||
|
|
||||||
@objc(ResignAppOperation)
|
@objc(ResignAppOperation)
|
||||||
class ResignAppOperation: ResultOperation<ALTApplication>
|
final class ResignAppOperation: ResultOperation<ALTApplication>
|
||||||
{
|
{
|
||||||
let context: InstallAppOperationContext
|
let context: InstallAppOperationContext
|
||||||
|
|
||||||
@@ -61,6 +62,7 @@ class ResignAppOperation: ResultOperation<ALTApplication>
|
|||||||
{
|
{
|
||||||
let destinationURL = InstalledApp.refreshedIPAURL(for: app)
|
let destinationURL = InstalledApp.refreshedIPAURL(for: app)
|
||||||
try FileManager.default.copyItem(at: resignedURL, to: destinationURL, shouldReplace: true)
|
try FileManager.default.copyItem(at: resignedURL, to: destinationURL, shouldReplace: true)
|
||||||
|
print("Successfully resigned app to \(destinationURL.absoluteString)")
|
||||||
|
|
||||||
// Use appBundleURL since we need an app bundle, not .ipa.
|
// Use appBundleURL since we need an app bundle, not .ipa.
|
||||||
guard let resignedApplication = ALTApplication(fileURL: appBundleURL) else { throw OperationError.invalidApp }
|
guard let resignedApplication = ALTApplication(fileURL: appBundleURL) else { throw OperationError.invalidApp }
|
||||||
@@ -147,6 +149,14 @@ private extension ResignAppOperation
|
|||||||
infoDictionary[Bundle.Info.exportedUTIs] = exportedUTIs
|
infoDictionary[Bundle.Info.exportedUTIs] = exportedUTIs
|
||||||
|
|
||||||
try (infoDictionary as NSDictionary).write(to: bundle.infoPlistURL)
|
try (infoDictionary as NSDictionary).write(to: bundle.infoPlistURL)
|
||||||
|
|
||||||
|
// Remove _CodeSignature folder (if it exists) because it will be added when resigning and it may have files that aren't overwritten when resigning
|
||||||
|
// These files might be the cause of some ApplicationVerificationFailed errors
|
||||||
|
let codeSignaturePath = bundle.bundleURL.appendingPathComponent("_CodeSignature").absoluteString.replacingOccurrences(of: "file://", with: "")
|
||||||
|
if FileManager.default.fileExists(atPath: codeSignaturePath) {
|
||||||
|
try FileManager.default.removeItem(atPath: codeSignaturePath)
|
||||||
|
print("Removed _CodeSignature folder at \(codeSignaturePath)")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DispatchQueue.global().async {
|
DispatchQueue.global().async {
|
||||||
@@ -172,7 +182,7 @@ private extension ResignAppOperation
|
|||||||
|
|
||||||
if app.isAltStoreApp
|
if app.isAltStoreApp
|
||||||
{
|
{
|
||||||
guard let udid = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.deviceID) as? String else { throw OperationError.unknownUDID }
|
guard let udid = fetch_udid()?.toString() as? String else { throw OperationError.unknownUDID }
|
||||||
guard let pairingFileString = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.devicePairingString) as? String else { throw OperationError.unknownUDID }
|
guard let pairingFileString = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.devicePairingString) as? String else { throw OperationError.unknownUDID }
|
||||||
additionalValues[Bundle.Info.devicePairingString] = pairingFileString
|
additionalValues[Bundle.Info.devicePairingString] = pairingFileString
|
||||||
additionalValues[Bundle.Info.deviceID] = udid
|
additionalValues[Bundle.Info.deviceID] = udid
|
||||||
@@ -193,7 +203,7 @@ private extension ResignAppOperation
|
|||||||
// The embedded certificate + certificate identifier are already in app bundle, no need to update them.
|
// The embedded certificate + certificate identifier are already in app bundle, no need to update them.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if infoDictionary.keys.contains(Bundle.Info.deviceID), let udid = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.deviceID) as? String
|
else if infoDictionary.keys.contains(Bundle.Info.deviceID), let udid = fetch_udid()?.toString() as? String
|
||||||
{
|
{
|
||||||
// There is an ALTDeviceID entry, so assume the app is using AltKit and replace it with the device's UDID.
|
// There is an ALTDeviceID entry, so assume the app is using AltKit and replace it with the device's UDID.
|
||||||
additionalValues[Bundle.Info.deviceID] = udid
|
additionalValues[Bundle.Info.deviceID] = udid
|
||||||
@@ -218,6 +228,7 @@ private extension ResignAppOperation
|
|||||||
|
|
||||||
// Prepare app
|
// Prepare app
|
||||||
try prepare(appBundle, additionalInfoDictionaryValues: additionalValues)
|
try prepare(appBundle, additionalInfoDictionaryValues: additionalValues)
|
||||||
|
try self.removeMissingAppExtensionReferences(from: appBundle)
|
||||||
|
|
||||||
if let directory = appBundle.builtInPlugInsURL, let enumerator = FileManager.default.enumerator(at: directory, includingPropertiesForKeys: nil, options: [.skipsSubdirectoryDescendants])
|
if let directory = appBundle.builtInPlugInsURL, let enumerator = FileManager.default.enumerator(at: directory, includingPropertiesForKeys: nil, options: [.skipsSubdirectoryDescendants])
|
||||||
{
|
{
|
||||||
@@ -258,4 +269,28 @@ private extension ResignAppOperation
|
|||||||
|
|
||||||
return progress
|
return progress
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func removeMissingAppExtensionReferences(from bundle: Bundle) throws
|
||||||
|
{
|
||||||
|
// If app extensions have been removed from an app (either by AltStore or the developer),
|
||||||
|
// we must remove all references to them from SC_Info/Manifest.plist (if it exists).
|
||||||
|
|
||||||
|
let scInfoURL = bundle.bundleURL.appendingPathComponent("SC_Info")
|
||||||
|
let manifestPlistURL = scInfoURL.appendingPathComponent("Manifest.plist")
|
||||||
|
|
||||||
|
guard let manifestPlist = NSMutableDictionary(contentsOf: manifestPlistURL), let sinfReplicationPaths = manifestPlist["SinfReplicationPaths"] as? [String] else { return }
|
||||||
|
|
||||||
|
// Remove references to missing files.
|
||||||
|
let filteredReplicationPaths = sinfReplicationPaths.filter { path in
|
||||||
|
guard let fileURL = URL(string: path, relativeTo: bundle.bundleURL) else { return false }
|
||||||
|
|
||||||
|
let fileExists = FileManager.default.fileExists(atPath: fileURL.path)
|
||||||
|
return fileExists
|
||||||
|
}
|
||||||
|
|
||||||
|
manifestPlist["SinfReplicationPaths"] = filteredReplicationPaths
|
||||||
|
|
||||||
|
// Save updated Manifest.plist to disk.
|
||||||
|
try manifestPlist.write(to: manifestPlistURL)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,9 +9,10 @@ import Foundation
|
|||||||
import Network
|
import Network
|
||||||
|
|
||||||
import AltStoreCore
|
import AltStoreCore
|
||||||
|
import minimuxer
|
||||||
|
|
||||||
@objc(SendAppOperation)
|
@objc(SendAppOperation)
|
||||||
class SendAppOperation: ResultOperation<()>
|
final class SendAppOperation: ResultOperation<()>
|
||||||
{
|
{
|
||||||
let context: InstallAppOperationContext
|
let context: InstallAppOperationContext
|
||||||
|
|
||||||
@@ -32,8 +33,7 @@ class SendAppOperation: ResultOperation<()>
|
|||||||
|
|
||||||
if let error = self.context.error
|
if let error = self.context.error
|
||||||
{
|
{
|
||||||
self.finish(.failure(error))
|
return self.finish(.failure(error))
|
||||||
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)) }
|
||||||
@@ -44,25 +44,20 @@ class SendAppOperation: ResultOperation<()>
|
|||||||
|
|
||||||
print("AFC App `fileURL`: \(fileURL.absoluteString)")
|
print("AFC App `fileURL`: \(fileURL.absoluteString)")
|
||||||
|
|
||||||
let ns_bundle = NSString(string: app.bundleIdentifier)
|
|
||||||
let ns_bundle_ptr = UnsafeMutablePointer<CChar>(mutating: ns_bundle.utf8String)
|
|
||||||
|
|
||||||
if let data = NSData(contentsOf: fileURL) {
|
if let data = NSData(contentsOf: fileURL) {
|
||||||
let pls = UnsafeMutablePointer<UInt8>.allocate(capacity: data.length)
|
do {
|
||||||
for (index, data) in data.enumerated() {
|
let bytes = Data(data).toRustByteSlice()
|
||||||
pls[index] = data
|
try yeet_app_afc(app.bundleIdentifier, bytes.forRust())
|
||||||
}
|
self.progress.completedUnitCount += 1
|
||||||
let res = minimuxer_yeet_app_afc(ns_bundle_ptr, pls, UInt(data.length))
|
self.finish(.success(()))
|
||||||
if res == 0 {
|
} catch {
|
||||||
print("minimuxer_yeet_app_afc `res` == \(res)")
|
self.finish(.failure(MinimuxerError.RwAfc))
|
||||||
self.progress.completedUnitCount += 1
|
self.progress.completedUnitCount += 1
|
||||||
self.finish(.success(()))
|
self.finish(.success(()))
|
||||||
} else {
|
|
||||||
self.finish(.failure(minimuxer_to_operation(code: res)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
self.finish(.failure(ALTServerError(.underlyingError)))
|
print("IPA doesn't exist????")
|
||||||
|
self.finish(.failure(OperationError.appNotFound))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ extension UpdatePatronsOperation
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class UpdatePatronsOperation: ResultOperation<Void>
|
final class UpdatePatronsOperation: ResultOperation<Void>
|
||||||
{
|
{
|
||||||
let context: NSManagedObjectContext
|
let context: NSManagedObjectContext
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ enum VerificationError: ALTLocalizedError
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc(VerifyAppOperation)
|
@objc(VerifyAppOperation)
|
||||||
class VerifyAppOperation: ResultOperation<Void>
|
final class VerifyAppOperation: ResultOperation<Void>
|
||||||
{
|
{
|
||||||
let context: AppOperationContext
|
let context: AppOperationContext
|
||||||
var verificationHandler: ((VerificationError) -> Bool)?
|
var verificationHandler: ((VerificationError) -> Bool)?
|
||||||
|
|||||||
Binary file not shown.
@@ -1 +1,158 @@
|
|||||||
{"images":[{"size":"60x60","expected-size":"180","filename":"180.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"40x40","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"60x60","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"57x57","expected-size":"57","filename":"57.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"87","filename":"87.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"57x57","expected-size":"114","filename":"114.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"60","filename":"60.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"1024x1024","filename":"1024.png","expected-size":"1024","idiom":"ios-marketing","folder":"Assets.xcassets/AppIcon.appiconset/","scale":"1x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"72x72","expected-size":"72","filename":"72.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"76x76","expected-size":"152","filename":"152.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"50x50","expected-size":"100","filename":"100.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"76x76","expected-size":"76","filename":"76.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"50x50","expected-size":"50","filename":"50.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"72x72","expected-size":"144","filename":"144.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"40x40","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"83.5x83.5","expected-size":"167","filename":"167.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"20x20","expected-size":"20","filename":"20.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"}]}
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "40.png",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "20x20"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "60.png",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"scale" : "3x",
|
||||||
|
"size" : "20x20"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "29.png",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"scale" : "1x",
|
||||||
|
"size" : "29x29"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "58.png",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "29x29"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "87.png",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"scale" : "3x",
|
||||||
|
"size" : "29x29"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "80.png",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "40x40"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "120.png",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"scale" : "3x",
|
||||||
|
"size" : "40x40"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "57.png",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"scale" : "1x",
|
||||||
|
"size" : "57x57"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "114.png",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "57x57"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "120.png",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "60x60"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "180.png",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"scale" : "3x",
|
||||||
|
"size" : "60x60"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "20.png",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"scale" : "1x",
|
||||||
|
"size" : "20x20"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "40.png",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "20x20"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "29.png",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"scale" : "1x",
|
||||||
|
"size" : "29x29"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "58.png",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "29x29"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "40.png",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"scale" : "1x",
|
||||||
|
"size" : "40x40"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "80.png",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "40x40"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "50.png",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"scale" : "1x",
|
||||||
|
"size" : "50x50"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "100.png",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "50x50"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "72.png",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"scale" : "1x",
|
||||||
|
"size" : "72x72"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "144.png",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "72x72"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "76.png",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"scale" : "1x",
|
||||||
|
"size" : "76x76"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "152.png",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "76x76"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "167.png",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "83.5x83.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "1024.png",
|
||||||
|
"idiom" : "ios-marketing",
|
||||||
|
"scale" : "1x",
|
||||||
|
"size" : "1024x1024"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"filename" : "riley.jpg",
|
|
||||||
"idiom" : "universal"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 73 KiB |
@@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"filename" : "shane.jpeg",
|
|
||||||
"idiom" : "universal"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 43 KiB |
BIN
AltStore/Resources/Assets.xcassets/SideStore.imageset/1024.png
vendored
Normal file
BIN
AltStore/Resources/Assets.xcassets/SideStore.imageset/1024.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 846 KiB |
21
AltStore/Resources/Assets.xcassets/SideStore.imageset/Contents.json
vendored
Normal file
21
AltStore/Resources/Assets.xcassets/SideStore.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "1024.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,18 +3,18 @@
|
|||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>application-identifier</key>
|
<key>application-identifier</key>
|
||||||
<string>A72ZC8AJ5X.com.SideStore.AltStore</string>
|
<string>XYZ0123456.com.SideStore.SideStore</string>
|
||||||
<key>aps-environment</key>
|
<key>aps-environment</key>
|
||||||
<string>development</string>
|
<string>development</string>
|
||||||
<key>com.apple.developer.siri</key>
|
<key>com.apple.developer.siri</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>com.apple.developer.team-identifier</key>
|
<key>com.apple.developer.team-identifier</key>
|
||||||
<string>A72ZC8AJ5X</string>
|
<string>XYZ0123456</string>
|
||||||
<key>com.apple.security.application-groups</key>
|
<key>com.apple.security.application-groups</key>
|
||||||
<array>
|
<array>
|
||||||
<string>group.com.SideStore.AltStore</string>
|
<string>group.com.SideStore.SideStore</string>
|
||||||
</array>
|
</array>
|
||||||
<key>get-task-allow</key>
|
<key>get-task-allow</key>
|
||||||
<true/>
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
@@ -1,269 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "AltStore",
|
|
||||||
"identifier": "com.rileytestut.AltStore",
|
|
||||||
"sourceURL": "https://cdn.altstore.io/file/altstore/apps.json",
|
|
||||||
"apps": [
|
|
||||||
{
|
|
||||||
"name": "AltStore",
|
|
||||||
"bundleIdentifier": "com.rileytestut.AltStore",
|
|
||||||
"developerName": "Riley Testut",
|
|
||||||
"version": "1.5.1",
|
|
||||||
"versionDate": "2022-07-14T12:00:00-05:00",
|
|
||||||
"versionDescription": "This update fixes the following issues:\n\n• Using Apple IDs that contain capital letters\n• Using Apple IDs with 2FA enabled without any trusted devices\n• Repeatedly asking some users to sign in every refresh\n• \"Incorrect Apple ID or password\" error after changing Apple ID email address\n• “Application is missing application-identifier” error when sideloading or (de-)activating certain apps\n• Potential crash when receiving unknown error codes from AltServer",
|
|
||||||
"downloadURL": "https://cdn.altstore.io/file/altstore/apps/altstore/1_5_1.ipa",
|
|
||||||
"localizedDescription": "AltStore is an alternative app store for non-jailbroken devices. \n\nThis version of AltStore allows you to install Delta, an all-in-one emulator for iOS, as well as sideload other .ipa files from the Files app.",
|
|
||||||
"iconURL": "https://user-images.githubusercontent.com/705880/65270980-1eb96f80-dad1-11e9-9367-78ccd25ceb02.png",
|
|
||||||
"tintColor": "018084",
|
|
||||||
"size": 5465976,
|
|
||||||
"screenshotURLs": [
|
|
||||||
"https://user-images.githubusercontent.com/705880/78942028-acf54300-7a6d-11ea-821c-5bb7a9b3e73a.PNG",
|
|
||||||
"https://user-images.githubusercontent.com/705880/78942222-0fe6da00-7a6e-11ea-9f2a-dda16157583c.PNG",
|
|
||||||
"https://user-images.githubusercontent.com/705880/65605577-332cba80-df5e-11e9-9f00-b369ce974f71.PNG"
|
|
||||||
],
|
|
||||||
"permissions": [
|
|
||||||
{
|
|
||||||
"type": "background-fetch",
|
|
||||||
"usageDescription": "AltStore periodically refreshes apps in the background to prevent them from expiring."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "background-audio",
|
|
||||||
"usageDescription": "Allows AltStore to run longer than 30 seconds when refreshing apps in background."
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "AltStore",
|
|
||||||
"bundleIdentifier": "com.rileytestut.AltStore.Beta",
|
|
||||||
"developerName": "Riley Testut",
|
|
||||||
"subtitle": "An alternative App Store for iOS.",
|
|
||||||
"version": "1.6b2",
|
|
||||||
"versionDate": "2022-09-21T13:00:00-05:00",
|
|
||||||
"versionDescription": "• Fixed “error migrating persistent store” issue on launch\n\nPREVIOUS VERSION\n\nLock Screen Widget (iOS 16+)\n• Counts down days until AltStore expires\n• Comes in 2 different styles: “icon” and “text”\n\nError Log\n• View past errors in more detail\n• Tap an error to copy the error message or error code\n• Search for error code directly in AltStore FAQ",
|
|
||||||
"downloadURL": "https://cdn.altstore.io/file/altstore/apps/altstore/1_6_b2.ipa",
|
|
||||||
"localizedDescription": "AltStore is an alternative app store for non-jailbroken devices. \n\nThis beta release of AltStore adds support for 3rd party sources, allowing you to download apps from other developers directly through AltStore.",
|
|
||||||
"iconURL": "https://user-images.githubusercontent.com/705880/65270980-1eb96f80-dad1-11e9-9367-78ccd25ceb02.png",
|
|
||||||
"tintColor": "018084",
|
|
||||||
"size": 5465933,
|
|
||||||
"beta": true,
|
|
||||||
"screenshotURLs": [
|
|
||||||
"https://user-images.githubusercontent.com/705880/78942028-acf54300-7a6d-11ea-821c-5bb7a9b3e73a.PNG",
|
|
||||||
"https://user-images.githubusercontent.com/705880/78942222-0fe6da00-7a6e-11ea-9f2a-dda16157583c.PNG",
|
|
||||||
"https://user-images.githubusercontent.com/705880/65605577-332cba80-df5e-11e9-9f00-b369ce974f71.PNG"
|
|
||||||
],
|
|
||||||
"permissions": [
|
|
||||||
{
|
|
||||||
"type": "background-fetch",
|
|
||||||
"usageDescription": "AltStore periodically refreshes apps in the background to prevent them from expiring."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "background-audio",
|
|
||||||
"usageDescription": "Allows AltStore to run longer than 30 seconds when refreshing apps in background."
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Delta",
|
|
||||||
"bundleIdentifier": "com.rileytestut.Delta",
|
|
||||||
"developerName": "Riley Testut",
|
|
||||||
"subtitle": "Classic games in your pocket.",
|
|
||||||
"version": "1.3.1",
|
|
||||||
"versionDate": "2021-12-02T13:30:00-08:00",
|
|
||||||
"versionDescription": "• Fixes game artwork not loading\n• Fixes using deprecated DeSmuME core over melonDS core for some users",
|
|
||||||
"downloadURL": "https://cdn.altstore.io/file/altstore/apps/delta/1_3_1.ipa",
|
|
||||||
"localizedDescription": "Delta is an all-in-one emulator for iOS. Delta builds upon the strengths of its predecessor, GBA4iOS, while expanding to include support for more game systems such as NES, SNES, and N64.\n\nFEATURES\n\nSupported Game Systems\n• Nintendo Entertainment System\n• Super Nintendo Entertainment System\n• Nintendo 64\n• Game Boy (Color)\n• Game Boy Advance\n• Nintendo DS\n• And plenty more to come!\n\nController Support\n• Supports PS4, PS5, Xbox One S, Xbox Series X, and MFi game controllers.\n• Supports bluetooth (and wired) keyboards, as well as the Apple Smart Keyboard.\n• Completely customize button mappings on a per-system, per-controller basis.\n• Map buttons to special “Quick Save”, “Quick Load,” and “Fast Forward” actions.\n\nSave States\n• Save and load save states for any game from the pause menu.\n• Lock save states to prevent them from being accidentally overwritten.\n• Automatically makes backup save states to ensure you never lose your progress.\n• Support for “Quick Saves,” save states that can be quickly saved/loaded with a single button press (requires external controller).\n\nCheats\n• Supports various types of cheat codes for each supported system:\n• NES: Game Genie\n• SNES: Game Genie, Pro Action Replay\n• N64: GameShark\n• GBC: Game Genie, GameShark\n• GBA: Action Replay, Code Breaker, GameShark\n• DS: Action Replay\n\nDelta Sync\n• Sync your games, game saves, save states, cheats, controller skins, and controller mappings between devices.\n• View version histories of everything you sync and optionally restore them to earlier versions.\n• Supports both Google Drive and Dropbox.\n\nCustom Controller Skins\n• Beautiful built-in controller skins for all systems.\n• Import controller skins made by others, or even make your own to share with the world!\n\nHold Button\n• Choose buttons for Delta to hold down on your behalf, freeing up your thumbs to press other buttons instead.\n• Perfect for games that typically require one button be held down constantly (ex: run button in Mario games, or the A button in Mario Kart).\n\nFast Forward\n• Speed through slower parts of games by running the game much faster than normal.\n• Easily enable or disable from the pause menu, or optionally with a mapped button on an external controller.\n\n3D/Haptic Touch\n• Use 3D or Haptic Touch to “peek” at games, save states, and cheat codes.\n• App icon shortcuts allow quick access to your most recently played games, or optionally customize the shortcuts to always include certain games.\n\nGame Artwork\n• Automatically displays appropriate box art for imported games.\n• Change a game’s artwork to anything you want, or select from the built-in game artwork database.\n\nMisc.\n• Gyroscope support (WarioWare: Twisted! only)\n• Microphone support (DS only)\n• Support for delta:// URL scheme to jump directly into a specific game.\n\n**Delta and AltStore LLC are in no way affiliated with Nintendo. The name \"Nintendo\" and all associated game console names are registered trademarks of Nintendo Co., Ltd.**",
|
|
||||||
"iconURL": "https://user-images.githubusercontent.com/705880/63391976-4d311700-c37a-11e9-91a8-4fb0c454413d.png",
|
|
||||||
"tintColor": "8A28F7",
|
|
||||||
"size": 19739373,
|
|
||||||
"permissions": [
|
|
||||||
{
|
|
||||||
"type": "photos",
|
|
||||||
"usageDescription": "Allows Delta to use images from your Photo Library as game artwork."
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"screenshotURLs": [
|
|
||||||
"https://user-images.githubusercontent.com/705880/65600448-f7d9be00-df54-11e9-9e3e-d4c31296da94.PNG",
|
|
||||||
"https://user-images.githubusercontent.com/705880/65813009-f2ae8600-e183-11e9-9eb7-704effc11173.png",
|
|
||||||
"https://user-images.githubusercontent.com/705880/65601117-58b5c600-df56-11e9-9c19-9a5ba5da54cf.PNG",
|
|
||||||
"https://user-images.githubusercontent.com/705880/65601125-5b182000-df56-11e9-9e7e-261480e893c0.PNG"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Delta",
|
|
||||||
"bundleIdentifier": "com.rileytestut.Delta.Beta",
|
|
||||||
"developerName": "Riley Testut",
|
|
||||||
"subtitle": "Classic games in your pocket.",
|
|
||||||
"version": "1.4b2",
|
|
||||||
"versionDate": "2022-08-16T08:00:00-05:00",
|
|
||||||
"versionDescription": "NEW\n• Supports Split View and Stage Manager multitasking on iPad\n• Automatically pauses + resumes emulation when switching between foreground apps with Stage Manager\n• Optimized full screen-width controller skins when using Split View, Slide Over, or Stage Manager\n• Supports controller skins with new `placement` parameter\n• Supports controller skins with custom screens that don’t have explicit `outputFrame`\n\nFIXED\n• Fixed not detecting keyboard presses when remapping inputs\n• Fixed potential crash rendering game screen after changing EAGLContext\n• Fixed incorrect game screen frame when software keyboard appears on iOS 16\n• Fixed software keyboard sometimes appearing when not emulating anything",
|
|
||||||
"downloadURL": "https://cdn.altstore.io/file/altstore/apps/delta/1_4_b2.ipa",
|
|
||||||
"localizedDescription": "The next consoles for Delta are coming: this beta version of Delta brings support for playing Nintendo DS and Sega Genesis games!\n\nPlease report any issues you find to support@altstore.io. Thanks!",
|
|
||||||
"iconURL": "https://user-images.githubusercontent.com/705880/63391976-4d311700-c37a-11e9-91a8-4fb0c454413d.png",
|
|
||||||
"tintColor": "8A28F7",
|
|
||||||
"size": 42968657,
|
|
||||||
"beta": true,
|
|
||||||
"permissions": [
|
|
||||||
{
|
|
||||||
"type": "photos",
|
|
||||||
"usageDescription": "Allows Delta to use images from your Photo Library as game artwork."
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"screenshotURLs": [
|
|
||||||
"https://user-images.githubusercontent.com/705880/65600448-f7d9be00-df54-11e9-9e3e-d4c31296da94.PNG",
|
|
||||||
"https://user-images.githubusercontent.com/705880/65601942-e5ad4f00-df57-11e9-9255-1463e0296e46.PNG",
|
|
||||||
"https://user-images.githubusercontent.com/705880/65813009-f2ae8600-e183-11e9-9eb7-704effc11173.png",
|
|
||||||
"https://user-images.githubusercontent.com/705880/65601117-58b5c600-df56-11e9-9c19-9a5ba5da54cf.PNG"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Clip",
|
|
||||||
"bundleIdentifier": "com.rileytestut.Clip",
|
|
||||||
"subtitle": "Manage your clipboard history with ease.",
|
|
||||||
"developerName": "Riley Testut",
|
|
||||||
"version": "1.0",
|
|
||||||
"versionDate": "2020-06-17T12:30:00-07:00",
|
|
||||||
"versionDescription": "Initial version 🎉",
|
|
||||||
"downloadURL": "https://f000.backblazeb2.com/file/altstore/apps/clip/1_0.ipa",
|
|
||||||
"localizedDescription": "Clip is a simple clipboard manager for iOS. \n\nUnlike other clipboard managers, Clip can continue monitoring your clipboard while in the background. No longer do you need to remember to manually open or share to an app to save your clipboard; just copy and paste as you would normally do, and Clip will have your back.\n\nIn addition to background monitoring, Clip also has these features:\n\n• Save text, URLs, and images copied to the clipboard.\n• Copy, delete, or share any clippings saved to Clip.\n• Customizable history limit.\n\nDownload Clip today, and never worry about losing your clipboard again!",
|
|
||||||
"iconURL": "https://user-images.githubusercontent.com/705880/63391981-5326f800-c37a-11e9-99d8-760fd06bb601.png",
|
|
||||||
"tintColor": "EC008C",
|
|
||||||
"size": 445056,
|
|
||||||
"permissions": [
|
|
||||||
{
|
|
||||||
"type": "background-audio",
|
|
||||||
"usageDescription": "Allows Clip to continuously monitor your clipboard in the background."
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"screenshotURLs": [
|
|
||||||
"https://user-images.githubusercontent.com/705880/63391950-34286600-c37a-11e9-965f-832efe3da507.png",
|
|
||||||
"https://user-images.githubusercontent.com/705880/70830209-8e738980-1da4-11ea-8b3b-6e5fbc78adff.png"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Clip",
|
|
||||||
"bundleIdentifier": "com.rileytestut.Clip.Beta",
|
|
||||||
"subtitle": "Manage your clipboard history with ease.",
|
|
||||||
"developerName": "Riley Testut",
|
|
||||||
"version": "1.1b1",
|
|
||||||
"versionDate": "2020-06-17T12:30:00-07:00",
|
|
||||||
"versionDescription": "This update adds a Custom Keyboard app extension for quick access to clippings when editing text.\n\nTo enable the keyboard, go to Settings > General > Keyboard > Keyboards > Add New Keyboard... and add \"ClipBoard\". Once added, make sure to then enable \"Allow Full Access\" for ClipBoard so it can access your clippings.",
|
|
||||||
"downloadURL": "https://f000.backblazeb2.com/file/altstore/apps/clip/1_1_b1.ipa",
|
|
||||||
"localizedDescription": "Clip is a simple clipboard manager for iOS. \n\nUnlike other clipboard managers, Clip can continue monitoring your clipboard while in the background. No longer do you need to remember to manually open or share to an app to save your clipboard; just copy and paste as you would normally do, and Clip will have your back.\n\nIn addition to background monitoring, Clip also has these features:\n\n• Save text, URLs, and images copied to the clipboard.\n• Copy, delete, or share any clippings saved to Clip.\n• Customizable history limit.\n\nDownload Clip today, and never worry about losing your clipboard again!",
|
|
||||||
"iconURL": "https://user-images.githubusercontent.com/705880/63391981-5326f800-c37a-11e9-99d8-760fd06bb601.png",
|
|
||||||
"tintColor": "EC008C",
|
|
||||||
"size": 462771,
|
|
||||||
"beta": true,
|
|
||||||
"permissions": [
|
|
||||||
{
|
|
||||||
"type": "background-audio",
|
|
||||||
"usageDescription": "Allows Clip to continuously monitor your clipboard in the background."
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"screenshotURLs": [
|
|
||||||
"https://user-images.githubusercontent.com/705880/63391950-34286600-c37a-11e9-965f-832efe3da507.png",
|
|
||||||
"https://user-images.githubusercontent.com/705880/70830209-8e738980-1da4-11ea-8b3b-6e5fbc78adff.png",
|
|
||||||
"https://user-images.githubusercontent.com/705880/84842227-70a80b00-aff9-11ea-8b04-bedb1f49c4a7.PNG",
|
|
||||||
"https://user-images.githubusercontent.com/705880/84842231-7271ce80-aff9-11ea-9272-e128aeceb95b.PNG"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"news": [
|
|
||||||
{
|
|
||||||
"title": "Delta Gaining DS Support",
|
|
||||||
"identifier": "delta-ds-support",
|
|
||||||
"caption": "Available this Saturday for patrons, coming soon for everyone else.",
|
|
||||||
"tintColor": "8A28F7",
|
|
||||||
"imageURL": "https://user-images.githubusercontent.com/705880/65603159-0676a400-df5a-11e9-882e-dc5566f4d50a.png",
|
|
||||||
"date": "2019-09-25",
|
|
||||||
"notify": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Delta Now Available",
|
|
||||||
"identifier": "delta-now-available",
|
|
||||||
"caption": "Finally, relive your favorite NES, SNES, GB(C), GBA, and N64 games.",
|
|
||||||
"tintColor": "8A28F7",
|
|
||||||
"imageURL": "https://user-images.githubusercontent.com/705880/65604130-c1ec0800-df5b-11e9-8150-7657c474e3c3.png",
|
|
||||||
"appID": "com.rileytestut.Delta",
|
|
||||||
"date": "2019-09-28",
|
|
||||||
"notify": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Sideloading is Here!",
|
|
||||||
"identifier": "sideloading-is-here",
|
|
||||||
"caption": "Update to AltStore 1.3 to install any app directly from Files.",
|
|
||||||
"tintColor": "018084",
|
|
||||||
"imageURL": "https://user-images.githubusercontent.com/705880/79022069-02932380-7b32-11ea-8bad-49907cb97ece.png",
|
|
||||||
"date": "2020-04-10T13:00:00-07:00",
|
|
||||||
"notify": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "iOS 13.4 Fixes App Crashes",
|
|
||||||
"identifier": "ios-13-4-now-available",
|
|
||||||
"caption": "Update to iOS 13.4 to fix some sideloaded apps crashing on launch.",
|
|
||||||
"tintColor": "34C759",
|
|
||||||
"date": "2020-04-10T13:30:00-07:00",
|
|
||||||
"notify": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Clip Now Available!",
|
|
||||||
"identifier": "clip-now-available",
|
|
||||||
"caption": "Finally, a clipboard manager that can run in the background — no jailbreak required.",
|
|
||||||
"tintColor": "EC008C",
|
|
||||||
"imageURL": "https://user-images.githubusercontent.com/705880/65606598-04afdf00-df60-11e9-8f93-af6345d39557.png",
|
|
||||||
"appID": "com.rileytestut.Clip",
|
|
||||||
"date": "2020-06-17",
|
|
||||||
"notify": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Delta, Meet Nintendo DS",
|
|
||||||
"identifier": "delta-meet-ds",
|
|
||||||
"caption": "Update to Delta 1.3 to relive all your favorite Nintendo DS games.",
|
|
||||||
"tintColor": "8A28F7",
|
|
||||||
"imageURL": "https://user-images.githubusercontent.com/705880/115617602-6ce2b600-a2a6-11eb-984e-2197a30c71e2.png",
|
|
||||||
"appID": "com.rileytestut.Delta",
|
|
||||||
"date": "2021-04-21",
|
|
||||||
"notify": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "#StandWithUkraine",
|
|
||||||
"identifier": "support-ukraine",
|
|
||||||
"caption": "Find out how you can help support those impacted by the Russian invasion.",
|
|
||||||
"tintColor": "003e80",
|
|
||||||
"imageURL": "https://user-images.githubusercontent.com/705880/156053447-a158cac7-df5f-4497-8025-15c3c2e10b48.png",
|
|
||||||
"url": "https://linktr.ee/razomforukraine",
|
|
||||||
"date": "2022-03-01",
|
|
||||||
"notify": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "The Biggest AltServer Update Yet!",
|
|
||||||
"identifier": "altserver-1-5",
|
|
||||||
"caption": "Update to AltServer 1.5 to use AltJIT and other exciting new features.",
|
|
||||||
"tintColor": "018084",
|
|
||||||
"imageURL": "https://user-images.githubusercontent.com/705880/166509576-744be578-6868-4b7d-b4fd-b9418c084327.png",
|
|
||||||
"url": "https://faq.altstore.io/release-notes/altserver",
|
|
||||||
"date": "2022-05-03",
|
|
||||||
"notify": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "More Apps in AltStore!",
|
|
||||||
"identifier": "trusted-sources",
|
|
||||||
"caption": "Update to AltStore 1.5 to easily download some of our favorite apps.",
|
|
||||||
"tintColor": "00CAB3",
|
|
||||||
"imageURL": "https://user-images.githubusercontent.com/705880/167026375-ddcb004f-7160-405c-b3e3-87a6795d2f43.png",
|
|
||||||
"url": "https://faq.altstore.io/release-notes/altstore",
|
|
||||||
"date": "2022-05-05",
|
|
||||||
"notify": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "New to AltStore?",
|
|
||||||
"identifier": "updated-faq",
|
|
||||||
"caption": "Check out our updated guide to learn how to sideload apps!",
|
|
||||||
"tintColor": "018084",
|
|
||||||
"url": "https://faq.altstore.io",
|
|
||||||
"date": "2022-07-28",
|
|
||||||
"notify": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"userInfo": {
|
|
||||||
"patreonAccessToken": "uqoDoTxH8dY1ImE8tK76wxrzKk67gjyjBAcK8sD3RLU"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -11,7 +11,7 @@ import AltStoreCore
|
|||||||
import EmotionalDamage
|
import EmotionalDamage
|
||||||
|
|
||||||
@available(iOS 13, *)
|
@available(iOS 13, *)
|
||||||
class SceneDelegate: UIResponder, UIWindowSceneDelegate
|
final class SceneDelegate: UIResponder, UIWindowSceneDelegate
|
||||||
{
|
{
|
||||||
var window: UIWindow?
|
var window: UIWindow?
|
||||||
|
|
||||||
|
|||||||
@@ -16,14 +16,13 @@
|
|||||||
<key>Key</key>
|
<key>Key</key>
|
||||||
<string>customAnisetteURL</string>
|
<string>customAnisetteURL</string>
|
||||||
<key>DefaultValue</key>
|
<key>DefaultValue</key>
|
||||||
<string>http://191.101.206.188:6969</string>
|
<string>https://ani.sidestore.io</string>
|
||||||
<key>Titles</key>
|
<key>Titles</key>
|
||||||
<array>
|
<array>
|
||||||
|
<string>SideStore</string>
|
||||||
<string>Macley (US)</string>
|
<string>Macley (US)</string>
|
||||||
<string>Macley (DE)</string>
|
<string>Macley (DE)</string>
|
||||||
<string>DrPudding</string>
|
<string>DrPudding</string>
|
||||||
<string>jkcoxson (AltServer)</string>
|
|
||||||
<string>jkcoxson (Provision)</string>
|
|
||||||
<string>Sideloadly</string>
|
<string>Sideloadly</string>
|
||||||
<string>Nick</string>
|
<string>Nick</string>
|
||||||
<string>Jawshoeadan</string>
|
<string>Jawshoeadan</string>
|
||||||
@@ -31,11 +30,10 @@
|
|||||||
</array>
|
</array>
|
||||||
<key>Values</key>
|
<key>Values</key>
|
||||||
<array>
|
<array>
|
||||||
<string>http://us1.sternserv.tech</string>
|
<string>https://ani.sidestore.io</string>
|
||||||
<string>http://de1.sternserv.tech</string>
|
<string>http://5.249.163.88:6969/</string>
|
||||||
|
<string>http://45.132.246.138:6969/</string>
|
||||||
<string>https://sign.rheaa.xyz</string>
|
<string>https://sign.rheaa.xyz</string>
|
||||||
<string>http://jkcoxson.com:2095</string>
|
|
||||||
<string>http://jkcoxson.com:2052</string>
|
|
||||||
<string>https://sideloadly.io/anisette/irGb3Quww8zrhgqnzmrx</string>
|
<string>https://sideloadly.io/anisette/irGb3Quww8zrhgqnzmrx</string>
|
||||||
<string>http://45.33.29.114</string>
|
<string>http://45.33.29.114</string>
|
||||||
<string>https://anisette.jawshoeadan.me</string>
|
<string>https://anisette.jawshoeadan.me</string>
|
||||||
|
|||||||
@@ -1,27 +1,27 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21225" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<deployment identifier="iOS"/>
|
<deployment identifier="iOS"/>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21207"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
|
||||||
<capability name="Named colors" minToolsVersion="9.0"/>
|
<capability name="Named colors" minToolsVersion="9.0"/>
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<objects>
|
<objects>
|
||||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||||
<collectionReusableView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" reuseIdentifier="AboutHeader" id="xq2-Pl-zaG" customClass="AboutPatreonHeaderView" customModule="AltStore" customModuleProvider="target">
|
<collectionReusableView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" reuseIdentifier="AboutHeader" id="xq2-Pl-zaG" customClass="AboutPatreonHeaderView" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="445"/>
|
<rect key="frame" x="0.0" y="0.0" width="390" height="682"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" axis="vertical" spacing="25" translatesAutoresizingMaskIntoConstraints="NO" id="XiA-Jf-XMp">
|
<stackView opaque="NO" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" axis="vertical" spacing="25" translatesAutoresizingMaskIntoConstraints="NO" id="XiA-Jf-XMp">
|
||||||
<rect key="frame" x="16" y="2" width="343" height="393"/>
|
<rect key="frame" x="16" y="2" width="358" height="630"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="10" translatesAutoresizingMaskIntoConstraints="NO" id="5Ol-zN-wYv">
|
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="10" translatesAutoresizingMaskIntoConstraints="NO" id="5Ol-zN-wYv">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="343" height="317"/>
|
<rect key="frame" x="0.0" y="0.0" width="358" height="426"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="10" translatesAutoresizingMaskIntoConstraints="NO" id="f7H-EV-7Sx">
|
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="10" translatesAutoresizingMaskIntoConstraints="NO" id="f7H-EV-7Sx">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="343" height="55"/>
|
<rect key="frame" x="0.0" y="0.0" width="358" height="55"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="SideStore" translatesAutoresizingMaskIntoConstraints="NO" id="pn6-Ic-MJm">
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="SideStore" translatesAutoresizingMaskIntoConstraints="NO" id="pn6-Ic-MJm">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="55" height="55"/>
|
<rect key="frame" x="0.0" y="0.0" width="55" height="55"/>
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
</constraints>
|
</constraints>
|
||||||
</imageView>
|
</imageView>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" distribution="equalSpacing" spacing="10" translatesAutoresizingMaskIntoConstraints="NO" id="si2-MA-3RH">
|
<stackView opaque="NO" contentMode="scaleToFill" distribution="equalSpacing" spacing="10" translatesAutoresizingMaskIntoConstraints="NO" id="si2-MA-3RH">
|
||||||
<rect key="frame" x="65" y="0.0" width="278" height="55"/>
|
<rect key="frame" x="65" y="0.0" width="293" height="55"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="2" translatesAutoresizingMaskIntoConstraints="NO" id="hkS-oz-wiT">
|
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="2" translatesAutoresizingMaskIntoConstraints="NO" id="hkS-oz-wiT">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="83" height="55"/>
|
<rect key="frame" x="0.0" y="0.0" width="83" height="55"/>
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
</subviews>
|
</subviews>
|
||||||
</stackView>
|
</stackView>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="2" translatesAutoresizingMaskIntoConstraints="NO" id="TFB-qo-cbh">
|
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="2" translatesAutoresizingMaskIntoConstraints="NO" id="TFB-qo-cbh">
|
||||||
<rect key="frame" x="195" y="0.0" width="83" height="55"/>
|
<rect key="frame" x="210" y="0.0" width="83" height="55"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="Zpb-k3-y7l">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="Zpb-k3-y7l">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="83" height="50"/>
|
<rect key="frame" x="0.0" y="0.0" width="83" height="50"/>
|
||||||
@@ -75,11 +75,13 @@
|
|||||||
</constraints>
|
</constraints>
|
||||||
</stackView>
|
</stackView>
|
||||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" editable="NO" textAlignment="natural" selectable="NO" translatesAutoresizingMaskIntoConstraints="NO" id="FeG-e5-LJl">
|
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" editable="NO" textAlignment="natural" selectable="NO" translatesAutoresizingMaskIntoConstraints="NO" id="FeG-e5-LJl">
|
||||||
<rect key="frame" x="0.0" y="65" width="343" height="252"/>
|
<rect key="frame" x="0.0" y="65" width="358" height="361"/>
|
||||||
<color key="backgroundColor" white="1" alpha="0.13" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="backgroundColor" white="1" alpha="0.13" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<string key="text">Hello, thank you for using SideStore!
|
<string key="text">Thank you for using SideStore!
|
||||||
|
|
||||||
If you would subscribe to the patreon that would support us and make sure we can continue developing SideStore for you.
|
Subscribing to the patreon supports us and makes sure we can continue developing SideStore for you.
|
||||||
|
|
||||||
|
Following us on social media allows us to give quick updates and spread the word about sideloading!
|
||||||
|
|
||||||
-SideTeam</string>
|
-SideTeam</string>
|
||||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
@@ -89,10 +91,10 @@ If you would subscribe to the patreon that would support us and make sure we can
|
|||||||
</subviews>
|
</subviews>
|
||||||
</stackView>
|
</stackView>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="13" translatesAutoresizingMaskIntoConstraints="NO" id="QS9-vO-bj8">
|
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="13" translatesAutoresizingMaskIntoConstraints="NO" id="QS9-vO-bj8">
|
||||||
<rect key="frame" x="0.0" y="342" width="343" height="51"/>
|
<rect key="frame" x="0.0" y="451" width="358" height="179"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="yEi-L6-kQ8">
|
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="yEi-L6-kQ8">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="343" height="51"/>
|
<rect key="frame" x="0.0" y="0.0" width="358" height="51"/>
|
||||||
<color key="backgroundColor" name="SettingsHighlighted"/>
|
<color key="backgroundColor" name="SettingsHighlighted"/>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstAttribute="height" constant="51" id="l4o-vb-cMy"/>
|
<constraint firstAttribute="height" constant="51" id="l4o-vb-cMy"/>
|
||||||
@@ -102,6 +104,28 @@ If you would subscribe to the patreon that would support us and make sure we can
|
|||||||
<color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
</state>
|
</state>
|
||||||
</button>
|
</button>
|
||||||
|
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="hov-Ce-LaM" userLabel="Twitter Button">
|
||||||
|
<rect key="frame" x="0.0" y="64" width="358" height="51"/>
|
||||||
|
<color key="backgroundColor" name="SettingsHighlighted"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="height" constant="51" id="m0M-GX-KKG"/>
|
||||||
|
</constraints>
|
||||||
|
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="19"/>
|
||||||
|
<state key="normal" title="Follow us on Twitter!">
|
||||||
|
<color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
</state>
|
||||||
|
</button>
|
||||||
|
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="VdY-7Q-amF" userLabel="Twitter Button">
|
||||||
|
<rect key="frame" x="0.0" y="128" width="358" height="51"/>
|
||||||
|
<color key="backgroundColor" name="SettingsHighlighted"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="height" constant="51" id="kDo-b8-6tZ"/>
|
||||||
|
</constraints>
|
||||||
|
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="19"/>
|
||||||
|
<state key="normal" title="Follow us on Instagram!">
|
||||||
|
<color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
</state>
|
||||||
|
</button>
|
||||||
</subviews>
|
</subviews>
|
||||||
</stackView>
|
</stackView>
|
||||||
</subviews>
|
</subviews>
|
||||||
@@ -114,19 +138,21 @@ If you would subscribe to the patreon that would support us and make sure we can
|
|||||||
<constraint firstItem="XiA-Jf-XMp" firstAttribute="top" secondItem="xq2-Pl-zaG" secondAttribute="top" constant="2" id="j8p-JX-Dcz"/>
|
<constraint firstItem="XiA-Jf-XMp" firstAttribute="top" secondItem="xq2-Pl-zaG" secondAttribute="top" constant="2" id="j8p-JX-Dcz"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
<connections>
|
<connections>
|
||||||
|
<outlet property="instagramButton" destination="VdY-7Q-amF" id="5kj-9x-k4F"/>
|
||||||
<outlet property="rileyImageView" destination="pn6-Ic-MJm" id="60i-Q0-ojz"/>
|
<outlet property="rileyImageView" destination="pn6-Ic-MJm" id="60i-Q0-ojz"/>
|
||||||
<outlet property="rileyLabel" destination="DTd-Yu-HXr" id="O0y-JB-gWp"/>
|
<outlet property="rileyLabel" destination="DTd-Yu-HXr" id="O0y-JB-gWp"/>
|
||||||
<outlet property="shaneLabel" destination="Zpb-k3-y7l" id="aQN-6B-s5T"/>
|
<outlet property="shaneLabel" destination="Zpb-k3-y7l" id="aQN-6B-s5T"/>
|
||||||
<outlet property="supportButton" destination="yEi-L6-kQ8" id="Dzo-vd-SnD"/>
|
<outlet property="supportButton" destination="yEi-L6-kQ8" id="Dzo-vd-SnD"/>
|
||||||
<outlet property="textView" destination="FeG-e5-LJl" id="K0M-lF-I6u"/>
|
<outlet property="textView" destination="FeG-e5-LJl" id="K0M-lF-I6u"/>
|
||||||
|
<outlet property="twitterButton" destination="hov-Ce-LaM" id="gib-Lt-qtY"/>
|
||||||
</connections>
|
</connections>
|
||||||
<point key="canvasLocation" x="138" y="138"/>
|
<point key="canvasLocation" x="147.82608695652175" y="58.258928571428569"/>
|
||||||
</collectionReusableView>
|
</collectionReusableView>
|
||||||
</objects>
|
</objects>
|
||||||
<resources>
|
<resources>
|
||||||
<image name="SideStore" width="180" height="180"/>
|
<image name="SideStore" width="1024" height="1024"/>
|
||||||
<namedColor name="SettingsHighlighted">
|
<namedColor name="SettingsHighlighted">
|
||||||
<color red="0.23529411764705882" green="0.0" blue="0.40392156862745099" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color red="0.38823529411764707" green="0.011764705882352941" blue="0.58823529411764708" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
</namedColor>
|
</namedColor>
|
||||||
</resources>
|
</resources>
|
||||||
</document>
|
</document>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
@objc(ErrorLogTableViewCell)
|
@objc(ErrorLogTableViewCell)
|
||||||
class ErrorLogTableViewCell: UITableViewCell
|
final class ErrorLogTableViewCell: UITableViewCell
|
||||||
{
|
{
|
||||||
@IBOutlet var appIconImageView: AppIconImageView!
|
@IBOutlet var appIconImageView: AppIconImageView!
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ import Roxas
|
|||||||
|
|
||||||
import Nuke
|
import Nuke
|
||||||
|
|
||||||
class ErrorLogViewController: UITableViewController
|
import QuickLook
|
||||||
|
|
||||||
|
final class ErrorLogViewController: UITableViewController
|
||||||
{
|
{
|
||||||
private lazy var dataSource = self.makeDataSource()
|
private lazy var dataSource = self.makeDataSource()
|
||||||
private var expandedErrorIDs = Set<NSManagedObjectID>()
|
private var expandedErrorIDs = Set<NSManagedObjectID>()
|
||||||
@@ -176,6 +178,15 @@ private extension ErrorLogViewController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@IBAction func showMinimuxerLogs(_ sender: UIBarButtonItem)
|
||||||
|
{
|
||||||
|
// Show minimuxer.log
|
||||||
|
let previewController = QLPreviewController()
|
||||||
|
previewController.dataSource = self
|
||||||
|
let navigationController = UINavigationController(rootViewController: previewController)
|
||||||
|
present(navigationController, animated: true, completion: nil)
|
||||||
|
}
|
||||||
|
|
||||||
@IBAction func clearLoggedErrors(_ sender: UIBarButtonItem)
|
@IBAction func clearLoggedErrors(_ sender: UIBarButtonItem)
|
||||||
{
|
{
|
||||||
let alertController = UIAlertController(title: NSLocalizedString("Are you sure you want to clear the error log?", comment: ""), message: nil, preferredStyle: .actionSheet)
|
let alertController = UIAlertController(title: NSLocalizedString("Are you sure you want to clear the error log?", comment: ""), message: nil, preferredStyle: .actionSheet)
|
||||||
@@ -299,3 +310,14 @@ extension ErrorLogViewController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension ErrorLogViewController: QLPreviewControllerDataSource {
|
||||||
|
func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
|
||||||
|
let fileURL = FileManager.default.documentsDirectory.appendingPathComponent("minimuxer.log")
|
||||||
|
return fileURL as QLPreviewItem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ extension InsetGroupTableViewCell
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class InsetGroupTableViewCell: UITableViewCell
|
final class InsetGroupTableViewCell: UITableViewCell
|
||||||
{
|
{
|
||||||
#if !TARGET_INTERFACE_BUILDER
|
#if !TARGET_INTERFACE_BUILDER
|
||||||
@IBInspectable var style: Style = .single {
|
@IBInspectable var style: Style = .single {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class LicensesViewController: UIViewController
|
final class LicensesViewController: UIViewController
|
||||||
{
|
{
|
||||||
private var _didAppear = false
|
private var _didAppear = false
|
||||||
|
|
||||||
|
|||||||
@@ -8,12 +8,12 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class PatronCollectionViewCell: UICollectionViewCell
|
final class PatronCollectionViewCell: UICollectionViewCell
|
||||||
{
|
{
|
||||||
@IBOutlet var textLabel: UILabel!
|
@IBOutlet var textLabel: UILabel!
|
||||||
}
|
}
|
||||||
|
|
||||||
class PatronsHeaderView: UICollectionReusableView
|
final class PatronsHeaderView: UICollectionReusableView
|
||||||
{
|
{
|
||||||
let textLabel = UILabel()
|
let textLabel = UILabel()
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ class PatronsHeaderView: UICollectionReusableView
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PatronsFooterView: UICollectionReusableView
|
final class PatronsFooterView: UICollectionReusableView
|
||||||
{
|
{
|
||||||
let button = UIButton(type: .system)
|
let button = UIButton(type: .system)
|
||||||
|
|
||||||
@@ -53,9 +53,11 @@ class PatronsFooterView: UICollectionReusableView
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AboutPatreonHeaderView: UICollectionReusableView
|
final class AboutPatreonHeaderView: UICollectionReusableView
|
||||||
{
|
{
|
||||||
@IBOutlet var supportButton: UIButton!
|
@IBOutlet var supportButton: UIButton!
|
||||||
|
@IBOutlet var twitterButton: UIButton!
|
||||||
|
@IBOutlet var instagramButton: UIButton!
|
||||||
@IBOutlet var accountButton: UIButton!
|
@IBOutlet var accountButton: UIButton!
|
||||||
@IBOutlet var textView: UITextView!
|
@IBOutlet var textView: UITextView!
|
||||||
|
|
||||||
@@ -79,12 +81,12 @@ class AboutPatreonHeaderView: UICollectionReusableView
|
|||||||
imageView.layer.cornerRadius = imageView.bounds.midY
|
imageView.layer.cornerRadius = imageView.bounds.midY
|
||||||
}
|
}
|
||||||
|
|
||||||
for button in [self.supportButton, self.accountButton].compactMap({ $0 })
|
for button in [self.supportButton, self.accountButton, self.twitterButton, self.instagramButton].compactMap({ $0 })
|
||||||
{
|
{
|
||||||
button.clipsToBounds = true
|
button.clipsToBounds = true
|
||||||
button.layer.cornerRadius = 16
|
button.layer.cornerRadius = 16
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func layoutMarginsDidChange()
|
override func layoutMarginsDidChange()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ extension PatreonViewController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PatreonViewController: UICollectionViewController
|
final class PatreonViewController: UICollectionViewController
|
||||||
{
|
{
|
||||||
private lazy var dataSource = self.makeDataSource()
|
private lazy var dataSource = self.makeDataSource()
|
||||||
private lazy var patronsDataSource = self.makePatronsDataSource()
|
private lazy var patronsDataSource = self.makePatronsDataSource()
|
||||||
@@ -111,7 +111,9 @@ private extension PatreonViewController
|
|||||||
headerView.layoutMargins = self.view.layoutMargins
|
headerView.layoutMargins = self.view.layoutMargins
|
||||||
|
|
||||||
headerView.supportButton.addTarget(self, action: #selector(PatreonViewController.openPatreonURL(_:)), for: .primaryActionTriggered)
|
headerView.supportButton.addTarget(self, action: #selector(PatreonViewController.openPatreonURL(_:)), for: .primaryActionTriggered)
|
||||||
|
headerView.twitterButton.addTarget(self, action: #selector(PatreonViewController.openTwitterURL(_:)), for: .primaryActionTriggered)
|
||||||
|
headerView.instagramButton.addTarget(self, action: #selector(PatreonViewController.openInstagramURL(_:)), for: .primaryActionTriggered)
|
||||||
|
|
||||||
let defaultSupportButtonTitle = NSLocalizedString("Become a patron", comment: "")
|
let defaultSupportButtonTitle = NSLocalizedString("Become a patron", comment: "")
|
||||||
let isPatronSupportButtonTitle = NSLocalizedString("View Patreon", comment: "")
|
let isPatronSupportButtonTitle = NSLocalizedString("View Patreon", comment: "")
|
||||||
|
|
||||||
@@ -173,14 +175,31 @@ private extension PatreonViewController
|
|||||||
|
|
||||||
@objc func openPatreonURL(_ sender: UIButton)
|
@objc func openPatreonURL(_ sender: UIButton)
|
||||||
{
|
{
|
||||||
// TODO: Is this the final URL? @JoeMatt
|
let patreonURL = URL(string: "https://www.patreon.com/SideStore")!
|
||||||
let patreonURL = URL(string: "https://www.patreon.com/JitStreamer")!
|
|
||||||
|
|
||||||
let safariViewController = SFSafariViewController(url: patreonURL)
|
let safariViewController = SFSafariViewController(url: patreonURL)
|
||||||
safariViewController.preferredControlTintColor = self.view.tintColor
|
safariViewController.preferredControlTintColor = self.view.tintColor
|
||||||
self.present(safariViewController, animated: true, completion: nil)
|
self.present(safariViewController, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc func openTwitterURL(_ sender: UIButton)
|
||||||
|
{
|
||||||
|
let twitterURL = URL(string: "https://twitter.com/SideStore_io")!
|
||||||
|
|
||||||
|
let safariViewController = SFSafariViewController(url: twitterURL)
|
||||||
|
safariViewController.preferredControlTintColor = self.view.tintColor
|
||||||
|
self.present(safariViewController, animated: true, completion: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func openInstagramURL(_ sender: UIButton)
|
||||||
|
{
|
||||||
|
let twitterURL = URL(string: "https://instagram.com/sidestore.io")!
|
||||||
|
|
||||||
|
let safariViewController = SFSafariViewController(url: twitterURL)
|
||||||
|
safariViewController.preferredControlTintColor = self.view.tintColor
|
||||||
|
self.present(safariViewController, animated: true, completion: nil)
|
||||||
|
}
|
||||||
|
|
||||||
@IBAction func authenticate(_ sender: UIBarButtonItem)
|
@IBAction func authenticate(_ sender: UIBarButtonItem)
|
||||||
{
|
{
|
||||||
PatreonAPI.shared.authenticate { (result) in
|
PatreonAPI.shared.authenticate { (result) in
|
||||||
|
|||||||
@@ -12,14 +12,14 @@ import AltStoreCore
|
|||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
@objc(RefreshAttemptTableViewCell)
|
@objc(RefreshAttemptTableViewCell)
|
||||||
private class RefreshAttemptTableViewCell: UITableViewCell
|
private final class RefreshAttemptTableViewCell: UITableViewCell
|
||||||
{
|
{
|
||||||
@IBOutlet var successLabel: UILabel!
|
@IBOutlet var successLabel: UILabel!
|
||||||
@IBOutlet var dateLabel: UILabel!
|
@IBOutlet var dateLabel: UILabel!
|
||||||
@IBOutlet var errorDescriptionLabel: UILabel!
|
@IBOutlet var errorDescriptionLabel: UILabel!
|
||||||
}
|
}
|
||||||
|
|
||||||
class RefreshAttemptsViewController: UITableViewController
|
final class RefreshAttemptsViewController: UITableViewController
|
||||||
{
|
{
|
||||||
private lazy var dataSource = self.makeDataSource()
|
private lazy var dataSource = self.makeDataSource()
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<color key="separatorColor" white="1" alpha="0.25" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="separatorColor" white="1" alpha="0.25" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<label key="tableFooterView" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="SideStore 1.0" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="bUR-rp-Nw2">
|
<label key="tableFooterView" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="SideStore 1.0" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="bUR-rp-Nw2">
|
||||||
<rect key="frame" x="0.0" y="1092" width="375" height="25"/>
|
<rect key="frame" x="0.0" y="1245" width="375" height="25"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="0.69999999999999996" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="0.69999999999999996" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
@@ -167,8 +167,8 @@
|
|||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Join the beta" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="3Il-5a-5Zp">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Support the team" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="3Il-5a-5Zp">
|
||||||
<rect key="frame" x="30" y="15.5" width="106" height="20.5"/>
|
<rect key="frame" x="30" y="15.5" width="142.5" height="20.5"/>
|
||||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
@@ -236,14 +236,50 @@
|
|||||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="NO"/>
|
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="NO"/>
|
||||||
</userDefinedRuntimeAttributes>
|
</userDefinedRuntimeAttributes>
|
||||||
</tableViewCell>
|
</tableViewCell>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="amC-sE-8O0" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="GYp-O0-pse" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="444" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="444" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="GYp-O0-pse" id="vDG-ZV-xRS">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<subviews>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Disable Idle Timeout" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="PCh-Up-aJJ">
|
||||||
|
<rect key="frame" x="30" y="15.5" width="166" height="20.5"/>
|
||||||
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||||
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="iQA-wm-5ag">
|
||||||
|
<rect key="frame" x="296" y="10" width="51" height="31"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="toggleNoIdleTimeoutEnabled:" destination="aMk-Xp-UL8" eventType="valueChanged" id="WSl-Jc-g5J"/>
|
||||||
|
</connections>
|
||||||
|
</switch>
|
||||||
|
</subviews>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="trailingMargin" secondItem="iQA-wm-5ag" secondAttribute="trailing" id="MJ1-HF-Dln"/>
|
||||||
|
<constraint firstItem="PCh-Up-aJJ" firstAttribute="leading" secondItem="vDG-ZV-xRS" secondAttribute="leadingMargin" id="Ocu-jn-RAQ"/>
|
||||||
|
<constraint firstItem="iQA-wm-5ag" firstAttribute="centerY" secondItem="vDG-ZV-xRS" secondAttribute="centerY" id="c6W-bN-VAb"/>
|
||||||
|
<constraint firstItem="PCh-Up-aJJ" firstAttribute="centerY" secondItem="vDG-ZV-xRS" secondAttribute="centerY" id="mL6-LB-cjn"/>
|
||||||
|
</constraints>
|
||||||
|
</tableViewCellContentView>
|
||||||
|
<color key="backgroundColor" white="1" alpha="0.14999999999999999" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<edgeInsets key="layoutMargins" top="8" left="30" bottom="8" right="30"/>
|
||||||
|
<userDefinedRuntimeAttributes>
|
||||||
|
<userDefinedRuntimeAttribute type="number" keyPath="style">
|
||||||
|
<integer key="value" value="2"/>
|
||||||
|
</userDefinedRuntimeAttribute>
|
||||||
|
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="YES"/>
|
||||||
|
</userDefinedRuntimeAttributes>
|
||||||
|
</tableViewCell>
|
||||||
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="amC-sE-8O0" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
|
<rect key="frame" x="0.0" y="495" width="375" height="51"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="amC-sE-8O0" id="GEO-2e-E4k">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="amC-sE-8O0" id="GEO-2e-E4k">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Add to Siri…" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="c6K-fI-CVr">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Allow Siri To Refresh Apps…" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="c6K-fI-CVr">
|
||||||
<rect key="frame" x="30" y="15.5" width="100.5" height="20.5"/>
|
<rect key="frame" x="30" y="15.5" width="100.5" height="20.5"/>
|
||||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
@@ -269,7 +305,7 @@
|
|||||||
<tableViewSection headerTitle="" id="eHy-qI-w5w">
|
<tableViewSection headerTitle="" id="eHy-qI-w5w">
|
||||||
<cells>
|
<cells>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="30h-59-88f" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="30h-59-88f" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="535" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="586" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="30h-59-88f" id="7qD-DW-Jls">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="30h-59-88f" id="7qD-DW-Jls">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
@@ -309,7 +345,7 @@
|
|||||||
<tableViewSection headerTitle="" id="J90-vn-u2O">
|
<tableViewSection headerTitle="" id="J90-vn-u2O">
|
||||||
<cells>
|
<cells>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="i4T-2q-jF3" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="i4T-2q-jF3" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="626" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="677" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="i4T-2q-jF3" id="VTQ-H4-aCM">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="i4T-2q-jF3" id="VTQ-H4-aCM">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
@@ -353,7 +389,7 @@
|
|||||||
</userDefinedRuntimeAttributes>
|
</userDefinedRuntimeAttributes>
|
||||||
</tableViewCell>
|
</tableViewCell>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="oHX-oR-nwJ" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="oHX-oR-nwJ" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="677" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="728" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="oHX-oR-nwJ" id="hN4-i5-igu">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="oHX-oR-nwJ" id="hN4-i5-igu">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
@@ -397,28 +433,28 @@
|
|||||||
</userDefinedRuntimeAttributes>
|
</userDefinedRuntimeAttributes>
|
||||||
</tableViewCell>
|
</tableViewCell>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="0MT-ht-Sit" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="0MT-ht-Sit" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="728" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="779" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="0MT-ht-Sit" id="OZp-WM-5H7">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="0MT-ht-Sit" id="OZp-WM-5H7">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Asset Designer" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fGU-Fp-XgM">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Asset Designer" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fGU-Fp-XgM">
|
||||||
<rect key="frame" x="30" y="15.5" width="115.5" height="20.5"/>
|
<rect key="frame" x="30" y="15.5" width="115.5" height="20.5"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="0.80000000000000004" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="0.80000000000000004" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" spacing="14" translatesAutoresizingMaskIntoConstraints="NO" id="R8B-DW-7mY">
|
<stackView opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="14" translatesAutoresizingMaskIntoConstraints="NO" id="R8B-DW-7mY">
|
||||||
<rect key="frame" x="206" y="15.5" width="139" height="20.5"/>
|
<rect key="frame" x="206" y="15.5" width="139" height="20.5"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Chris (LitRitt)" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="hId-3P-41T">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Chris (LitRitt)" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="hId-3P-41T">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="107" height="20.5"/>
|
<rect key="frame" x="0.0" y="0.0" width="107" height="20.5"/>
|
||||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
|
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="baq-cE-fMY">
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="baq-cE-fMY">
|
||||||
<rect key="frame" x="121" y="0.0" width="18" height="20.5"/>
|
<rect key="frame" x="121" y="0.0" width="18" height="20.5"/>
|
||||||
</imageView>
|
</imageView>
|
||||||
</subviews>
|
</subviews>
|
||||||
@@ -441,19 +477,19 @@
|
|||||||
</userDefinedRuntimeAttributes>
|
</userDefinedRuntimeAttributes>
|
||||||
</tableViewCell>
|
</tableViewCell>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="O5R-Al-lGj" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="O5R-Al-lGj" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="779" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="830" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="O5R-Al-lGj" id="CrG-Mr-xQq">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="O5R-Al-lGj" id="CrG-Mr-xQq">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Licenses" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="D6b-cd-pVK">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Licenses" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="D6b-cd-pVK">
|
||||||
<rect key="frame" x="30" y="15.5" width="67.5" height="20.5"/>
|
<rect key="frame" x="30" y="15.5" width="67.5" height="20.5"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="0.80000000000000004" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="0.80000000000000004" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="s79-GQ-khr">
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="s79-GQ-khr">
|
||||||
<rect key="frame" x="327" y="16.5" width="18" height="18"/>
|
<rect key="frame" x="327" y="16.5" width="18" height="18"/>
|
||||||
</imageView>
|
</imageView>
|
||||||
</subviews>
|
</subviews>
|
||||||
@@ -481,19 +517,19 @@
|
|||||||
<tableViewSection headerTitle="" id="OMa-EK-hRI">
|
<tableViewSection headerTitle="" id="OMa-EK-hRI">
|
||||||
<cells>
|
<cells>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="FMZ-as-Ljo" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="FMZ-as-Ljo" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="870" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="921" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="FMZ-as-Ljo" id="JzL-Of-A3T">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="FMZ-as-Ljo" id="JzL-Of-A3T">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Send Feedback" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="pMI-Aj-nQF">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Send Feedback" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="pMI-Aj-nQF">
|
||||||
<rect key="frame" x="30" y="15.5" width="125.5" height="20.5"/>
|
<rect key="frame" x="30" y="15.5" width="125.5" height="20.5"/>
|
||||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="Jyy-x0-Owj">
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="Jyy-x0-Owj">
|
||||||
<rect key="frame" x="327" y="16.5" width="18" height="18"/>
|
<rect key="frame" x="327" y="16.5" width="18" height="18"/>
|
||||||
</imageView>
|
</imageView>
|
||||||
</subviews>
|
</subviews>
|
||||||
@@ -514,19 +550,19 @@
|
|||||||
</userDefinedRuntimeAttributes>
|
</userDefinedRuntimeAttributes>
|
||||||
</tableViewCell>
|
</tableViewCell>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="Qca-pU-sJh" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="Qca-pU-sJh" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="921" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="972" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Qca-pU-sJh" id="QtU-8J-VQN">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Qca-pU-sJh" id="QtU-8J-VQN">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="View Refresh Attempts" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="sni-07-q0M">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="View Refresh Attempts" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="sni-07-q0M">
|
||||||
<rect key="frame" x="30" y="15.5" width="187.5" height="20.5"/>
|
<rect key="frame" x="30" y="15.5" width="187.5" height="20.5"/>
|
||||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="4d3-me-Hqc">
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="4d3-me-Hqc">
|
||||||
<rect key="frame" x="327" y="16.5" width="18" height="18"/>
|
<rect key="frame" x="327" y="16.5" width="18" height="18"/>
|
||||||
</imageView>
|
</imageView>
|
||||||
</subviews>
|
</subviews>
|
||||||
@@ -550,19 +586,19 @@
|
|||||||
</connections>
|
</connections>
|
||||||
</tableViewCell>
|
</tableViewCell>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="rE2-P4-OaE" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="rE2-P4-OaE" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="972" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="1023" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="rE2-P4-OaE" id="qIT-rz-ZUb">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="rE2-P4-OaE" id="qIT-rz-ZUb">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="View Error Log" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="PWC-OG-5jx">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="View Error Log" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="PWC-OG-5jx">
|
||||||
<rect key="frame" x="30" y="15.5" width="119" height="20.5"/>
|
<rect key="frame" x="30" y="15.5" width="119" height="20.5"/>
|
||||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="VfB-c5-5wG">
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="VfB-c5-5wG">
|
||||||
<rect key="frame" x="327" y="16.5" width="18" height="18"/>
|
<rect key="frame" x="327" y="16.5" width="18" height="18"/>
|
||||||
</imageView>
|
</imageView>
|
||||||
</subviews>
|
</subviews>
|
||||||
@@ -577,28 +613,127 @@
|
|||||||
<edgeInsets key="layoutMargins" top="8" left="30" bottom="8" right="30"/>
|
<edgeInsets key="layoutMargins" top="8" left="30" bottom="8" right="30"/>
|
||||||
<userDefinedRuntimeAttributes>
|
<userDefinedRuntimeAttributes>
|
||||||
<userDefinedRuntimeAttribute type="number" keyPath="style">
|
<userDefinedRuntimeAttribute type="number" keyPath="style">
|
||||||
<integer key="value" value="3"/>
|
<integer key="value" value="2"/>
|
||||||
</userDefinedRuntimeAttribute>
|
</userDefinedRuntimeAttribute>
|
||||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="YES"/>
|
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="YES"/>
|
||||||
</userDefinedRuntimeAttributes>
|
</userDefinedRuntimeAttributes>
|
||||||
<connections>
|
<connections>
|
||||||
<segue destination="g8a-Rf-zWa" kind="show" identifier="showErrorLog" id="SSW-vL-86I"/>
|
<segue destination="g8a-Rf-zWa" kind="show" id="vFC-Id-Ww6"/>
|
||||||
</connections>
|
</connections>
|
||||||
</tableViewCell>
|
</tableViewCell>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="fj2-EJ-Z98" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
<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="1023" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="1023" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="eZ3-BT-q4D" id="17m-VV-hzf">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<subviews>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Clear Cache" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="IbH-V1-ce3">
|
||||||
|
<rect key="frame" x="30" y="15.5" width="98.5" height="20.5"/>
|
||||||
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||||
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="FZe-BJ-fOm">
|
||||||
|
<rect key="frame" x="327" y="16.5" width="18" height="18"/>
|
||||||
|
</imageView>
|
||||||
|
</subviews>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="FZe-BJ-fOm" firstAttribute="centerY" secondItem="17m-VV-hzf" secondAttribute="centerY" id="bGv-Np-5aO"/>
|
||||||
|
<constraint firstAttribute="trailingMargin" secondItem="FZe-BJ-fOm" secondAttribute="trailing" id="ccb-JP-Eqi"/>
|
||||||
|
<constraint firstItem="IbH-V1-ce3" firstAttribute="centerY" secondItem="17m-VV-hzf" secondAttribute="centerY" id="iQJ-gN-sRF"/>
|
||||||
|
<constraint firstItem="IbH-V1-ce3" firstAttribute="leading" secondItem="17m-VV-hzf" secondAttribute="leadingMargin" id="m1g-Y6-aT5"/>
|
||||||
|
</constraints>
|
||||||
|
</tableViewCellContentView>
|
||||||
|
<color key="backgroundColor" white="1" alpha="0.14999999999999999" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<edgeInsets key="layoutMargins" top="8" left="30" bottom="8" right="30"/>
|
||||||
|
<userDefinedRuntimeAttributes>
|
||||||
|
<userDefinedRuntimeAttribute type="number" keyPath="style">
|
||||||
|
<integer key="value" value="2"/>
|
||||||
|
</userDefinedRuntimeAttribute>
|
||||||
|
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="YES"/>
|
||||||
|
</userDefinedRuntimeAttributes>
|
||||||
|
</tableViewCell>
|
||||||
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="VNn-u4-cN8" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
|
<rect key="frame" x="0.0" y="1074" width="375" height="51"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="VNn-u4-cN8" id="4bh-qe-l2N">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<subviews>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Reset Pairing File" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ysS-9s-dXm">
|
||||||
|
<rect key="frame" x="30" y="15.5" width="140" height="20.5"/>
|
||||||
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||||
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="r09-mH-pOD">
|
||||||
|
<rect key="frame" x="327" y="16.5" width="18" height="18"/>
|
||||||
|
</imageView>
|
||||||
|
</subviews>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="r09-mH-pOD" firstAttribute="centerY" secondItem="4bh-qe-l2N" secondAttribute="centerY" id="02u-Os-L7r"/>
|
||||||
|
<constraint firstItem="ysS-9s-dXm" firstAttribute="centerY" secondItem="4bh-qe-l2N" secondAttribute="centerY" id="QOA-3E-85e"/>
|
||||||
|
<constraint firstItem="ysS-9s-dXm" firstAttribute="leading" secondItem="4bh-qe-l2N" secondAttribute="leadingMargin" id="gRE-CM-w21"/>
|
||||||
|
<constraint firstAttribute="trailingMargin" secondItem="r09-mH-pOD" secondAttribute="trailing" id="udf-VS-o6t"/>
|
||||||
|
</constraints>
|
||||||
|
</tableViewCellContentView>
|
||||||
|
<color key="backgroundColor" white="1" alpha="0.14999999999999999" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<edgeInsets key="layoutMargins" top="8" left="30" bottom="8" right="30"/>
|
||||||
|
<userDefinedRuntimeAttributes>
|
||||||
|
<userDefinedRuntimeAttribute type="number" keyPath="style">
|
||||||
|
<integer key="value" value="2"/>
|
||||||
|
</userDefinedRuntimeAttribute>
|
||||||
|
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="YES"/>
|
||||||
|
</userDefinedRuntimeAttributes>
|
||||||
|
</tableViewCell>
|
||||||
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="e7s-hL-kv9" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
|
<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="e7s-hL-kv9" id="yjL-Mu-HTk">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<subviews>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Reset adi.pb" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="eds-Dj-36y">
|
||||||
|
<rect key="frame" x="30" y="15.5" width="102" height="20.5"/>
|
||||||
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||||
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="0dh-yd-7i9">
|
||||||
|
<rect key="frame" x="327" y="16.5" width="18" height="18"/>
|
||||||
|
</imageView>
|
||||||
|
</subviews>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="0dh-yd-7i9" firstAttribute="centerY" secondItem="yjL-Mu-HTk" secondAttribute="centerY" id="8OI-PI-weT"/>
|
||||||
|
<constraint firstItem="eds-Dj-36y" firstAttribute="leading" secondItem="yjL-Mu-HTk" secondAttribute="leadingMargin" id="BqG-Ef-xQo"/>
|
||||||
|
<constraint firstAttribute="trailingMargin" secondItem="0dh-yd-7i9" secondAttribute="trailing" id="TFW-nu-jo4"/>
|
||||||
|
<constraint firstItem="eds-Dj-36y" firstAttribute="centerY" secondItem="yjL-Mu-HTk" secondAttribute="centerY" id="YiJ-OF-FXE"/>
|
||||||
|
</constraints>
|
||||||
|
</tableViewCellContentView>
|
||||||
|
<color key="backgroundColor" white="1" alpha="0.14999999999999999" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<edgeInsets key="layoutMargins" top="8" left="30" bottom="8" right="30"/>
|
||||||
|
<userDefinedRuntimeAttributes>
|
||||||
|
<userDefinedRuntimeAttribute type="number" keyPath="style">
|
||||||
|
<integer key="value" value="2"/>
|
||||||
|
</userDefinedRuntimeAttribute>
|
||||||
|
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="YES"/>
|
||||||
|
</userDefinedRuntimeAttributes>
|
||||||
|
</tableViewCell>
|
||||||
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="fj2-EJ-Z98" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
|
<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="fj2-EJ-Z98" id="BcT-Fs-KNg">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="fj2-EJ-Z98" id="BcT-Fs-KNg">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Advanced Settings" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="OcM-OM-uDE">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Advanced Settings" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="OcM-OM-uDE">
|
||||||
<rect key="frame" x="30" y="15.5" width="154" height="20.5"/>
|
<rect key="frame" x="30" y="15.5" width="154" height="20.5"/>
|
||||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="Pcu-Sy-yfZ">
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="Pcu-Sy-yfZ">
|
||||||
<rect key="frame" x="327" y="16.5" width="18" height="18"/>
|
<rect key="frame" x="327" y="16.5" width="18" height="18"/>
|
||||||
</imageView>
|
</imageView>
|
||||||
</subviews>
|
</subviews>
|
||||||
@@ -623,7 +758,6 @@
|
|||||||
</sections>
|
</sections>
|
||||||
<connections>
|
<connections>
|
||||||
<outlet property="dataSource" destination="aMk-Xp-UL8" id="c6c-fR-8C4"/>
|
<outlet property="dataSource" destination="aMk-Xp-UL8" id="c6c-fR-8C4"/>
|
||||||
<outlet property="delegate" destination="aMk-Xp-UL8" id="moP-1B-lRq"/>
|
|
||||||
</connections>
|
</connections>
|
||||||
</tableView>
|
</tableView>
|
||||||
<navigationItem key="navigationItem" title="Settings" id="Ddg-UQ-KJ8"/>
|
<navigationItem key="navigationItem" title="Settings" id="Ddg-UQ-KJ8"/>
|
||||||
@@ -632,6 +766,7 @@
|
|||||||
<outlet property="accountNameLabel" destination="CnN-M1-AYK" id="Ldc-Py-Bix"/>
|
<outlet property="accountNameLabel" destination="CnN-M1-AYK" id="Ldc-Py-Bix"/>
|
||||||
<outlet property="accountTypeLabel" destination="434-MW-Den" id="mNB-QE-4Jg"/>
|
<outlet property="accountTypeLabel" destination="434-MW-Den" id="mNB-QE-4Jg"/>
|
||||||
<outlet property="backgroundRefreshSwitch" destination="DPu-zD-Als" id="eiG-Hv-Vko"/>
|
<outlet property="backgroundRefreshSwitch" destination="DPu-zD-Als" id="eiG-Hv-Vko"/>
|
||||||
|
<outlet property="noIdleTimeoutSwitch" destination="iQA-wm-5ag" id="jHC-js-q0Y"/>
|
||||||
<outlet property="versionLabel" destination="bUR-rp-Nw2" id="85I-5R-hqz"/>
|
<outlet property="versionLabel" destination="bUR-rp-Nw2" id="85I-5R-hqz"/>
|
||||||
</connections>
|
</connections>
|
||||||
</tableViewController>
|
</tableViewController>
|
||||||
@@ -851,7 +986,7 @@ Settings by i cons from the Noun Project</string>
|
|||||||
</objects>
|
</objects>
|
||||||
<point key="canvasLocation" x="1697" y="313"/>
|
<point key="canvasLocation" x="1697" y="313"/>
|
||||||
</scene>
|
</scene>
|
||||||
<!--Patreon-->
|
<!--Support us-->
|
||||||
<scene sceneID="Lnh-9P-HnL">
|
<scene sceneID="Lnh-9P-HnL">
|
||||||
<objects>
|
<objects>
|
||||||
<collectionViewController id="dp8-8j-vt9" customClass="PatreonViewController" customModule="SideStore" customModuleProvider="target" sceneMemberID="viewController">
|
<collectionViewController id="dp8-8j-vt9" customClass="PatreonViewController" customModule="SideStore" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
@@ -898,7 +1033,7 @@ Settings by i cons from the Noun Project</string>
|
|||||||
<outlet property="delegate" destination="dp8-8j-vt9" id="790-Kr-6l7"/>
|
<outlet property="delegate" destination="dp8-8j-vt9" id="790-Kr-6l7"/>
|
||||||
</connections>
|
</connections>
|
||||||
</collectionView>
|
</collectionView>
|
||||||
<navigationItem key="navigationItem" title="Patreon" largeTitleDisplayMode="always" id="uUV-1f-xEq"/>
|
<navigationItem key="navigationItem" title="Support us" largeTitleDisplayMode="always" id="uUV-1f-xEq"/>
|
||||||
</collectionViewController>
|
</collectionViewController>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="qq3-Hj-S9f" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="qq3-Hj-S9f" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
</objects>
|
</objects>
|
||||||
@@ -1018,11 +1153,18 @@ Settings by i cons from the Noun Project</string>
|
|||||||
</connections>
|
</connections>
|
||||||
</tableView>
|
</tableView>
|
||||||
<navigationItem key="navigationItem" title="Error Log" largeTitleDisplayMode="never" id="a1p-3W-bSi">
|
<navigationItem key="navigationItem" title="Error Log" largeTitleDisplayMode="never" id="a1p-3W-bSi">
|
||||||
<barButtonItem key="rightBarButtonItem" systemItem="trash" id="BnQ-Eh-1gC">
|
<rightBarButtonItems>
|
||||||
<connections>
|
<barButtonItem systemItem="trash" id="BnQ-Eh-1gC">
|
||||||
<action selector="clearLoggedErrors:" destination="g8a-Rf-zWa" id="faq-89-H5j"/>
|
<connections>
|
||||||
</connections>
|
<action selector="clearLoggedErrors:" destination="g8a-Rf-zWa" id="faq-89-H5j"/>
|
||||||
</barButtonItem>
|
</connections>
|
||||||
|
</barButtonItem>
|
||||||
|
<barButtonItem image="ladybug" catalog="system" id="1cD-4y-vTJ" userLabel="Share">
|
||||||
|
<connections>
|
||||||
|
<action selector="showMinimuxerLogs:" destination="g8a-Rf-zWa" id="V0f-0y-C6C"/>
|
||||||
|
</connections>
|
||||||
|
</barButtonItem>
|
||||||
|
</rightBarButtonItems>
|
||||||
</navigationItem>
|
</navigationItem>
|
||||||
</tableViewController>
|
</tableViewController>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="rU1-TZ-TD8" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="rU1-TZ-TD8" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
@@ -1033,6 +1175,7 @@ Settings by i cons from the Noun Project</string>
|
|||||||
<resources>
|
<resources>
|
||||||
<image name="Next" width="18" height="18"/>
|
<image name="Next" width="18" height="18"/>
|
||||||
<image name="Settings" width="20" height="20"/>
|
<image name="Settings" width="20" height="20"/>
|
||||||
|
<image name="ladybug" catalog="system" width="128" height="122"/>
|
||||||
<namedColor name="SettingsBackground">
|
<namedColor name="SettingsBackground">
|
||||||
<color red="0.45098039215686275" green="0.015686274509803921" blue="0.68627450980392157" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color red="0.45098039215686275" green="0.015686274509803921" blue="0.68627450980392157" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
</namedColor>
|
</namedColor>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import UIKit
|
|||||||
|
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
class SettingsHeaderFooterView: UITableViewHeaderFooterView
|
final class SettingsHeaderFooterView: UITableViewHeaderFooterView
|
||||||
{
|
{
|
||||||
@IBOutlet var primaryLabel: UILabel!
|
@IBOutlet var primaryLabel: UILabel!
|
||||||
@IBOutlet var secondaryLabel: UILabel!
|
@IBOutlet var secondaryLabel: UILabel!
|
||||||
|
|||||||
@@ -30,13 +30,14 @@ extension SettingsViewController
|
|||||||
fileprivate enum AppRefreshRow: Int, CaseIterable
|
fileprivate enum AppRefreshRow: Int, CaseIterable
|
||||||
{
|
{
|
||||||
case backgroundRefresh
|
case backgroundRefresh
|
||||||
|
case noIdleTimeout
|
||||||
|
|
||||||
@available(iOS 14, *)
|
@available(iOS 14, *)
|
||||||
case addToSiri
|
case addToSiri
|
||||||
|
|
||||||
static var allCases: [AppRefreshRow] {
|
static var allCases: [AppRefreshRow] {
|
||||||
guard #available(iOS 14, *) else { return [.backgroundRefresh] }
|
guard #available(iOS 14, *) else { return [.backgroundRefresh, .noIdleTimeout] }
|
||||||
return [.backgroundRefresh, .addToSiri]
|
return [.backgroundRefresh, .noIdleTimeout, .addToSiri]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,11 +54,15 @@ extension SettingsViewController
|
|||||||
case sendFeedback
|
case sendFeedback
|
||||||
case refreshAttempts
|
case refreshAttempts
|
||||||
case errorLog
|
case errorLog
|
||||||
|
case clearCache
|
||||||
|
case resetPairingFile
|
||||||
|
case resetAdiPb
|
||||||
case advancedSettings
|
case advancedSettings
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SettingsViewController: UITableViewController
|
final class SettingsViewController: UITableViewController
|
||||||
{
|
{
|
||||||
private var activeTeam: Team?
|
private var activeTeam: Team?
|
||||||
|
|
||||||
@@ -71,6 +76,7 @@ class SettingsViewController: UITableViewController
|
|||||||
@IBOutlet private var accountTypeLabel: UILabel!
|
@IBOutlet private var accountTypeLabel: UILabel!
|
||||||
|
|
||||||
@IBOutlet private var backgroundRefreshSwitch: UISwitch!
|
@IBOutlet private var backgroundRefreshSwitch: UISwitch!
|
||||||
|
@IBOutlet private var noIdleTimeoutSwitch: UISwitch!
|
||||||
|
|
||||||
@IBOutlet private var versionLabel: UILabel!
|
@IBOutlet private var versionLabel: UILabel!
|
||||||
|
|
||||||
@@ -146,6 +152,7 @@ private extension SettingsViewController
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.backgroundRefreshSwitch.isOn = UserDefaults.standard.isBackgroundRefreshEnabled
|
self.backgroundRefreshSwitch.isOn = UserDefaults.standard.isBackgroundRefreshEnabled
|
||||||
|
self.noIdleTimeoutSwitch.isOn = UserDefaults.standard.isIdleTimeoutDisableEnabled
|
||||||
|
|
||||||
if self.isViewLoaded
|
if self.isViewLoaded
|
||||||
{
|
{
|
||||||
@@ -176,11 +183,11 @@ private extension SettingsViewController
|
|||||||
case .patreon:
|
case .patreon:
|
||||||
if isHeader
|
if isHeader
|
||||||
{
|
{
|
||||||
settingsHeaderFooterView.primaryLabel.text = NSLocalizedString("PATREON", comment: "")
|
settingsHeaderFooterView.primaryLabel.text = NSLocalizedString("SUPPORT US", comment: "")
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
settingsHeaderFooterView.secondaryLabel.text = NSLocalizedString("Support the SideStore Team by becoming a patron!", comment: "")
|
settingsHeaderFooterView.secondaryLabel.text = NSLocalizedString("Support the SideStore Team by following our socials or becoming a patron!", comment: "")
|
||||||
}
|
}
|
||||||
|
|
||||||
case .account:
|
case .account:
|
||||||
@@ -197,7 +204,7 @@ private extension SettingsViewController
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
settingsHeaderFooterView.secondaryLabel.text = NSLocalizedString("Enable Background Refresh to automatically refresh apps in the background when connected to Wi-Fi.", comment: "")
|
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: "")
|
||||||
}
|
}
|
||||||
|
|
||||||
case .instructions:
|
case .instructions:
|
||||||
@@ -268,6 +275,8 @@ private extension SettingsViewController
|
|||||||
let alertController = UIAlertController(title: NSLocalizedString("Are you sure you want to sign out?", comment: ""), message: NSLocalizedString("You will no longer be able to install or refresh apps once you sign out.", comment: ""), preferredStyle: .actionSheet)
|
let alertController = UIAlertController(title: NSLocalizedString("Are you sure you want to sign out?", comment: ""), message: NSLocalizedString("You will no longer be able to install or refresh apps once you sign out.", comment: ""), preferredStyle: .actionSheet)
|
||||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Sign Out", comment: ""), style: .destructive) { _ in signOut() })
|
alertController.addAction(UIAlertAction(title: NSLocalizedString("Sign Out", comment: ""), style: .destructive) { _ in signOut() })
|
||||||
alertController.addAction(.cancel)
|
alertController.addAction(.cancel)
|
||||||
|
//Fix crash on iPad
|
||||||
|
alertController.popoverPresentationController?.barButtonItem = sender
|
||||||
self.present(alertController, animated: true, completion: nil)
|
self.present(alertController, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,6 +285,11 @@ private extension SettingsViewController
|
|||||||
UserDefaults.standard.isBackgroundRefreshEnabled = sender.isOn
|
UserDefaults.standard.isBackgroundRefreshEnabled = sender.isOn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@IBAction func toggleNoIdleTimeoutEnabled(_ sender: UISwitch)
|
||||||
|
{
|
||||||
|
UserDefaults.standard.isIdleTimeoutDisableEnabled = sender.isOn
|
||||||
|
}
|
||||||
|
|
||||||
@available(iOS 14, *)
|
@available(iOS 14, *)
|
||||||
@IBAction func addRefreshAppsShortcut()
|
@IBAction func addRefreshAppsShortcut()
|
||||||
{
|
{
|
||||||
@@ -287,6 +301,39 @@ private extension SettingsViewController
|
|||||||
self.present(viewController, animated: true, completion: nil)
|
self.present(viewController, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func clearCache()
|
||||||
|
{
|
||||||
|
let alertController = UIAlertController(title: NSLocalizedString("Are you sure you want to clear SideStore's cache?", comment: ""),
|
||||||
|
message: NSLocalizedString("This will remove all temporary files as well as backups for uninstalled apps.", comment: ""),
|
||||||
|
preferredStyle: .actionSheet)
|
||||||
|
alertController.addAction(UIAlertAction(title: UIAlertAction.cancel.title, style: UIAlertAction.cancel.style) { [weak self] _ in
|
||||||
|
self?.tableView.indexPathForSelectedRow.map { self?.tableView.deselectRow(at: $0, animated: true) }
|
||||||
|
})
|
||||||
|
alertController.addAction(UIAlertAction(title: NSLocalizedString("Clear Cache", comment: ""), style: .destructive) { [weak self] _ in
|
||||||
|
AppManager.shared.clearAppCache { result in
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self?.tableView.indexPathForSelectedRow.map { self?.tableView.deselectRow(at: $0, animated: true) }
|
||||||
|
|
||||||
|
switch result
|
||||||
|
{
|
||||||
|
case .success: break
|
||||||
|
case .failure(let error):
|
||||||
|
let alertController = UIAlertController(title: NSLocalizedString("Unable to Clear Cache", comment: ""), message: error.localizedDescription, preferredStyle: .alert)
|
||||||
|
alertController.addAction(.ok)
|
||||||
|
self?.present(alertController, animated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if let popoverController = alertController.popoverPresentationController {
|
||||||
|
popoverController.sourceView = self.view
|
||||||
|
popoverController.sourceRect = CGRect(x: self.view.bounds.midX, y: self.view.bounds.midY, width: 0, height: 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.present(alertController, animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
@IBAction func handleDebugModeGesture(_ gestureRecognizer: UISwipeGestureRecognizer)
|
@IBAction func handleDebugModeGesture(_ gestureRecognizer: UISwipeGestureRecognizer)
|
||||||
{
|
{
|
||||||
self.debugGestureCounter += 1
|
self.debugGestureCounter += 1
|
||||||
@@ -373,15 +420,26 @@ extension SettingsViewController
|
|||||||
{
|
{
|
||||||
let cell = super.tableView(tableView, cellForRowAt: indexPath)
|
let cell = super.tableView(tableView, cellForRowAt: indexPath)
|
||||||
|
|
||||||
if #available(iOS 14, *) {}
|
// if #available(iOS 14, *) {}
|
||||||
else if let cell = cell as? InsetGroupTableViewCell,
|
// else if let cell = cell as? InsetGroupTableViewCell,
|
||||||
indexPath.section == Section.appRefresh.rawValue,
|
// indexPath.section == Section.appRefresh.rawValue,
|
||||||
indexPath.row == AppRefreshRow.backgroundRefresh.rawValue
|
// indexPath.row == AppRefreshRow.backgroundRefresh.rawValue
|
||||||
|
// {
|
||||||
|
// // Only one row is visible pre-iOS 14.
|
||||||
|
// cell.style = .single
|
||||||
|
// }
|
||||||
|
|
||||||
|
if AppRefreshRow.AllCases().count == 1
|
||||||
{
|
{
|
||||||
// Only one row is visible pre-iOS 14.
|
if let cell = cell as? InsetGroupTableViewCell,
|
||||||
cell.style = .single
|
indexPath.section == Section.appRefresh.rawValue,
|
||||||
|
indexPath.row == AppRefreshRow.backgroundRefresh.rawValue
|
||||||
|
{
|
||||||
|
cell.style = .single
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return cell
|
return cell
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -461,11 +519,13 @@ extension SettingsViewController
|
|||||||
switch row
|
switch row
|
||||||
{
|
{
|
||||||
case .backgroundRefresh: break
|
case .backgroundRefresh: break
|
||||||
|
case .noIdleTimeout: break
|
||||||
case .addToSiri:
|
case .addToSiri:
|
||||||
guard #available(iOS 14, *) else { return }
|
guard #available(iOS 14, *) else { return }
|
||||||
self.addRefreshAppsShortcut()
|
self.addRefreshAppsShortcut()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
case .credits:
|
case .credits:
|
||||||
let row = CreditsRow.allCases[indexPath.row]
|
let row = CreditsRow.allCases[indexPath.row]
|
||||||
switch row
|
switch row
|
||||||
@@ -503,6 +563,52 @@ extension SettingsViewController
|
|||||||
let toastView = ToastView(text: NSLocalizedString("Cannot Send Mail", comment: ""), detailText: nil)
|
let toastView = ToastView(text: NSLocalizedString("Cannot Send Mail", comment: ""), detailText: nil)
|
||||||
toastView.show(in: self)
|
toastView.show(in: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case .clearCache: self.clearCache()
|
||||||
|
|
||||||
|
case .resetPairingFile:
|
||||||
|
let filename = "ALTPairingFile.mobiledevicepairing"
|
||||||
|
let fm = FileManager.default
|
||||||
|
let documentsPath = fm.documentsDirectory.appendingPathComponent("/\(filename)")
|
||||||
|
let alertController = UIAlertController(
|
||||||
|
title: NSLocalizedString("Are you sure to reset the pairing file?", comment: ""),
|
||||||
|
message: NSLocalizedString("You can reset the pairing file when you cannot sideload apps or enable JIT. You need to restart SideStore.", comment: ""),
|
||||||
|
preferredStyle: UIAlertController.Style.actionSheet)
|
||||||
|
|
||||||
|
alertController.addAction(UIAlertAction(title: NSLocalizedString("Delete and Reset", comment: ""), style: .destructive){ _ in
|
||||||
|
if fm.fileExists(atPath: documentsPath.path), let contents = try? String(contentsOf: documentsPath), !contents.isEmpty {
|
||||||
|
try? fm.removeItem(atPath: documentsPath.path)
|
||||||
|
NSLog("Pairing File Reseted")
|
||||||
|
}
|
||||||
|
self.tableView.deselectRow(at: indexPath, animated: true)
|
||||||
|
let dialogMessage = UIAlertController(title: NSLocalizedString("Pairing File Reseted", comment: ""), message: NSLocalizedString("Please restart SideStore", comment: ""), preferredStyle: .alert)
|
||||||
|
self.present(dialogMessage, animated: true, completion: nil)
|
||||||
|
})
|
||||||
|
alertController.addAction(.cancel)
|
||||||
|
//Fix crash on iPad
|
||||||
|
alertController.popoverPresentationController?.sourceView = self.tableView
|
||||||
|
alertController.popoverPresentationController?.sourceRect = self.tableView.rectForRow(at: indexPath)
|
||||||
|
self.present(alertController, animated: true)
|
||||||
|
self.tableView.deselectRow(at: indexPath, animated: true)
|
||||||
|
case .resetAdiPb:
|
||||||
|
let alertController = UIAlertController(
|
||||||
|
title: NSLocalizedString("Are you sure you want to reset the adi.pb file?", comment: ""),
|
||||||
|
message: NSLocalizedString("The adi.pb file is used to generate anisette data, which is required to log into an Apple ID. If you are having issues with account related things, you can try this. However, you will be required to do 2FA again. This will do nothing if you are using an older anisette server.", comment: ""),
|
||||||
|
preferredStyle: UIAlertController.Style.actionSheet)
|
||||||
|
|
||||||
|
alertController.addAction(UIAlertAction(title: NSLocalizedString("Reset adi.pb", comment: ""), style: .destructive){ _ in
|
||||||
|
if Keychain.shared.adiPb != nil {
|
||||||
|
Keychain.shared.adiPb = nil
|
||||||
|
print("Cleared adi.pb from keychain")
|
||||||
|
}
|
||||||
|
self.tableView.deselectRow(at: indexPath, animated: true)
|
||||||
|
})
|
||||||
|
alertController.addAction(.cancel)
|
||||||
|
//Fix crash on iPad
|
||||||
|
alertController.popoverPresentationController?.sourceView = self.tableView
|
||||||
|
alertController.popoverPresentationController?.sourceRect = self.tableView.rectForRow(at: indexPath)
|
||||||
|
self.present(alertController, animated: true)
|
||||||
|
self.tableView.deselectRow(at: indexPath, animated: true)
|
||||||
case .advancedSettings:
|
case .advancedSettings:
|
||||||
// Create the URL that deep links to your app's custom settings.
|
// Create the URL that deep links to your app's custom settings.
|
||||||
if let url = URL(string: UIApplication.openSettingsURLString) {
|
if let url = URL(string: UIApplication.openSettingsURLString) {
|
||||||
@@ -512,6 +618,7 @@ extension SettingsViewController
|
|||||||
ELOG("UIApplication.openSettingsURLString invalid")
|
ELOG("UIApplication.openSettingsURLString invalid")
|
||||||
}
|
}
|
||||||
case .refreshAttempts, .errorLog: break
|
case .refreshAttempts, .errorLog: break
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
default: break
|
default: break
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user