Compare commits

..

2 Commits

Author SHA1 Message Date
Joseph Mattello
93b1c4d834 WireGuard add extra source files
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2023-01-04 09:56:43 -05:00
Joseph Mattello
e96245b9d8 WireGuard extension
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2023-01-04 09:56:22 -05:00
200 changed files with 3664 additions and 10084 deletions

34
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,34 @@
---
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.

View File

@@ -1,40 +0,0 @@
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/RgpFBX3Q3k) 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.

View File

@@ -1,10 +0,0 @@
# force issue template usage
blank_issues_enabled: false
contact_links:
- name: Discord
url: https://discord.gg/RgpFBX3Q3k
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.

View File

@@ -1,33 +0,0 @@
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/RgpFBX3Q3k) 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.

View File

@@ -1,15 +0,0 @@
### 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

View File

@@ -1,22 +0,0 @@
name: Add artifact links to pull request and related issues
on:
workflow_run:
workflows: [Pull Request SideStore build]
types: [completed]
jobs:
artifacts-url-comments:
name: add artifact links to pull request and related issues job
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}
steps:
- name: add artifact links to pull request and related issues step
uses: tonyhallett/artifacts-url-comments@v1.1.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
prefix: Builds for this Pull Request are available at
suffix: Have a nice day.
format: name
addTo: pull
# addTo: pullandissues

View File

@@ -1,12 +1,16 @@
name: Beta SideStore build
on:
push:
tags:
- '[0-9]+.[0-9]+.[0-9]+-beta.[0-9]+' # example: 1.0.0-beta.1
branches:
- develop
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:
@@ -21,18 +25,63 @@ 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: Change version to tag
run: sed -e '/MARKETING_VERSION = .*/s/= .*/= ${{ github.ref_name }}/' -i '' Build.xcconfig
- name: Install rustup
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
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: Create emotional damage
# run: cd Dependencies/em_proxy && cargo build --release --target aarch64-apple-ios
- name: Echo version
run: echo "${{ steps.version.outputs.version }}"
# - 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: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1.4.1
@@ -40,13 +89,39 @@ jobs:
xcode-version: ${{ matrix.version }}
- name: Build SideStore
run: make build | xcpretty && exit ${PIPESTATUS[0]}
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]}
- name: Fakesign app
run: make fakesign
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
- name: Convert to IPA
run: make 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
- name: Get current date
id: date
@@ -56,22 +131,22 @@ jobs:
id: date_altstore
run: echo "date=$(date -u +'%Y-%m-%d')" >> $GITHUB_OUTPUT
- name: Upload to new beta release
uses: softprops/action-gh-release@v1
- name: Upload to beta release
uses: IsaacShelton/update-existing-release@v1.3.1
with:
token: ${{ secrets.GITHUB_TOKEN }}
name: ${{ steps.version.outputs.version }}
tag_name: ${{ github.ref_name }}
draft: true
release: "Beta"
tag: "beta"
prerelease: 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. -->
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
This is an ⚠️ **EXPERIMENTAL** ⚠️ beta build for commit [${{ github.sha }}](https://github.com/${{ github.repository }}/commit/${{ github.sha }}).
- TODO
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").
## Build Info
@@ -79,18 +154,3 @@ 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/

View File

@@ -1,28 +0,0 @@
#!/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

View File

@@ -24,24 +24,63 @@ 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: Cache .nightly-build-num
uses: actions/cache@v3
- name: Install rustup
uses: actions-rs/toolchain@v1
with:
path: .nightly-build-num
key: nightly-build-num
toolchain: stable
override: true
target: aarch64-apple-ios
- name: Increase nightly build number and set as version
run: bash .github/workflows/increase-nightly-build-num.sh
# - 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: Add nightly suffix to version
run: sed -e '/MARKETING_VERSION = .*/s/$/-nightly.${{ github.run_number }}/' -i '' Build.xcconfig
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1.4.1
@@ -49,13 +88,39 @@ jobs:
xcode-version: ${{ matrix.version }}
- name: Build SideStore
run: make build | xcpretty && exit ${PIPESTATUS[0]}
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]}
- name: Fakesign app
run: make fakesign
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
- name: Convert to IPA
run: make 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
- name: Get current date
id: date
@@ -76,9 +141,11 @@ jobs:
body: |
This is an ⚠️ **EXPERIMENTAL** ⚠️ nightly build for commit [${{ github.sha }}](https://github.com/${{ github.repository }}/commit/${{ github.sha }}).
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!**
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**.
If you want to try out new features early but want a lower chance of bugs, you can look at [SideStore Beta](https://github.com/${{ github.repository }}/releases?q=beta).
If you 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").
## Build Info
@@ -86,21 +153,3 @@ 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 }}

View File

@@ -19,20 +19,60 @@ 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: 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: Install rustup
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
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: Create emotional damage
# run: cd Dependencies/em_proxy && cargo build --release --target aarch64-apple-ios
- name: Echo version
run: echo "${{ steps.version.outputs.version }}"
# - name: Build minimuxer
# run: cd Dependencies/minimuxer && cargo build --release --target aarch64-apple-ios
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1.4.1
@@ -40,25 +80,32 @@ jobs:
xcode-version: ${{ matrix.version }}
- name: Build SideStore
run: make build | xcpretty && exit ${PIPESTATUS[0]}
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]}
- name: Fakesign app
run: make fakesign
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
- name: Convert to IPA
run: make 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: Add version to IPA file name
run: mv SideStore.ipa SideStore-${{ steps.version.outputs.version }}.ipa
- name: Upload SideStore.ipa Artifact
- name: Upload 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: SideStore.ipa
path: SideStore.ipa

View File

@@ -2,7 +2,7 @@ name: Stable SideStore build
on:
push:
tags:
- '[0-9]+.[0-9]+.[0-9]+' # example: 1.0.0
- '[0-9]+.[0-9]+.[0-9]+*'
jobs:
build:
@@ -21,18 +21,60 @@ 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: Change version to tag
run: sed -e '/MARKETING_VERSION = .*/s/= .*/= ${{ github.ref_name }}/' -i '' Build.xcconfig
- name: Install rustup
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
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: Create emotional damage
# run: cd Dependencies/em_proxy && cargo build --release --target aarch64-apple-ios
- name: Echo version
run: echo "${{ steps.version.outputs.version }}"
# - name: Build minimuxer
# run: cd Dependencies/minimuxer && cargo build --release --target aarch64-apple-ios
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1.4.1
@@ -40,13 +82,39 @@ jobs:
xcode-version: ${{ matrix.version }}
- name: Build SideStore
run: make build | xcpretty && exit ${PIPESTATUS[0]}
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]}
- name: Fakesign app
run: make fakesign
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
- name: Convert to IPA
run: make 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
- name: Get current date
id: date
@@ -61,11 +129,10 @@ jobs:
with:
token: ${{ secrets.GITHUB_TOKEN }}
name: ${{ steps.version.outputs.version }}
tag_name: ${{ github.ref_name }}
tag_name: ${{ github.ref }}
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
@@ -76,18 +143,3 @@ 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/

11
.gitignore vendored
View File

@@ -34,13 +34,4 @@ xcuserdata
## AppCode specific
.idea/
Payload/
SideStore.ipa
*.dSYM
Dependencies/.*-prebuilt-fetch-*
Dependencies/minimuxer/*
Dependencies/em_proxy/*
!Dependencies/**/.gitkeep
.nightly-build-num
/.build

6
.gitmodules vendored
View File

@@ -13,9 +13,15 @@
[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

View File

@@ -1,3 +1,3 @@
#include "Build.xcconfig"
PRODUCT_BUNDLE_IDENTIFIER = $(PRODUCT_BUNDLE_IDENTIFIER)
PRODUCT_BUNDLE_IDENTIFIER = $(ORG_PREFIX).$(PRODUCT_NAME)

File diff suppressed because it is too large Load Diff

View File

@@ -1,149 +0,0 @@
{
"pins" : [
{
"identity" : "altsign",
"kind" : "remoteSourceControl",
"location" : "https://github.com/SideStore/AltSign",
"state" : {
"branch" : "master",
"revision" : "7e0e7edcf8fbc44ac1e35da3e9030a297aa18b84"
}
},
{
"identity" : "appcenter-sdk-apple",
"kind" : "remoteSourceControl",
"location" : "https://github.com/microsoft/appcenter-sdk-apple.git",
"state" : {
"revision" : "8354a50fe01a7e54e196d3b5493b5ab53dd5866a",
"version" : "4.4.2"
}
},
{
"identity" : "asyncimage",
"kind" : "remoteSourceControl",
"location" : "https://github.com/fabianthdev/AsyncImage",
"state" : {
"branch" : "main",
"revision" : "018a4fffea025066d795ebb025c2769183f3fffb"
}
},
{
"identity" : "expandabletext",
"kind" : "remoteSourceControl",
"location" : "https://github.com/fabianthdev/ExpandableText",
"state" : {
"branch" : "main",
"revision" : "a375f5b8c73f0af69aa7add890378fdf404a29bc"
}
},
{
"identity" : "keychainaccess",
"kind" : "remoteSourceControl",
"location" : "https://github.com/kishikawakatsumi/KeychainAccess.git",
"state" : {
"revision" : "84e546727d66f1adc5439debad16270d0fdd04e7",
"version" : "4.2.2"
}
},
{
"identity" : "launchatlogin",
"kind" : "remoteSourceControl",
"location" : "https://github.com/sindresorhus/LaunchAtLogin.git",
"state" : {
"revision" : "e8171b3e38a2816f579f58f3dac1522aa39efe41",
"version" : "4.2.0"
}
},
{
"identity" : "localconsole",
"kind" : "remoteSourceControl",
"location" : "https://github.com/duraidabdul/LocalConsole.git",
"state" : {
"revision" : "2c5d5e018acd4963fe6dfe858f6d6fecef7cbf2f",
"version" : "1.12.1"
}
},
{
"identity" : "nuke",
"kind" : "remoteSourceControl",
"location" : "https://github.com/kean/Nuke.git",
"state" : {
"revision" : "9318d02a8a6d20af56505c9673261c1fd3b3aebe",
"version" : "7.6.3"
}
},
{
"identity" : "openssl",
"kind" : "remoteSourceControl",
"location" : "https://github.com/krzyzanowskim/OpenSSL",
"state" : {
"revision" : "033fcb41dac96b1b6effa945ca1f9ade002370b2",
"version" : "1.1.1501"
}
},
{
"identity" : "plcrashreporter",
"kind" : "remoteSourceControl",
"location" : "https://github.com/microsoft/PLCrashReporter.git",
"state" : {
"revision" : "6b27393cad517c067dceea85fadf050e70c4ceaa",
"version" : "1.10.1"
}
},
{
"identity" : "reachability.swift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/ashleymills/Reachability.swift",
"state" : {
"branch" : "master",
"revision" : "a81b7367f2c46875f29577e03a60c39cdfad0c8d"
}
},
{
"identity" : "semanticversion",
"kind" : "remoteSourceControl",
"location" : "https://github.com/SwiftPackageIndex/SemanticVersion.git",
"state" : {
"revision" : "fc670910dc0903cc269b3d0b776cda5703979c4e",
"version" : "0.3.5"
}
},
{
"identity" : "sfsafesymbols",
"kind" : "remoteSourceControl",
"location" : "https://github.com/SFSafeSymbols/SFSafeSymbols",
"state" : {
"revision" : "50bc33264e6c0972f905b61af656201cf6091de8",
"version" : "4.0.0"
}
},
{
"identity" : "sparkle",
"kind" : "remoteSourceControl",
"location" : "https://github.com/sparkle-project/Sparkle.git",
"state" : {
"revision" : "286edd1fa22505a9e54d170e9fd07d775ea233f2",
"version" : "2.1.0"
}
},
{
"identity" : "starscream",
"kind" : "remoteSourceControl",
"location" : "https://github.com/daltoniam/Starscream.git",
"state" : {
"revision" : "df8d82047f6654d8e4b655d1b1525c64e1059d21",
"version" : "4.0.4"
}
},
{
"identity" : "stprivilegedtask",
"kind" : "remoteSourceControl",
"location" : "https://github.com/JoeMatt/STPrivilegedTask.git",
"state" : {
"branch" : "master",
"revision" : "10a9150ef32d444af326beba76356ae9af95a3e7"
}
}
],
"version" : 2
}

View File

@@ -4,11 +4,35 @@
<dict>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.developer.associated-domains</key>
<array>
<string>webcredentials:sidestore.io</string>
</array>
<key>com.apple.developer.icloud-container-identifiers</key>
<array/>
<key>com.apple.developer.kernel.increased-memory-limit</key>
<true/>
<key>com.apple.developer.networking.multipath</key>
<true/>
<key>com.apple.developer.networking.vpn.api</key>
<array>
<string>allow-vpn</string>
</array>
<key>com.apple.developer.networking.wifi-info</key>
<true/>
<key>com.apple.developer.shared-with-you</key>
<true/>
<key>com.apple.developer.siri</key>
<true/>
<key>com.apple.developer.ubiquity-kvstore-identifier</key>
<string>$(TeamIdentifierPrefix)$(CFBundleIdentifier)</string>
<key>com.apple.security.application-groups</key>
<array>
<string>group.$(APP_GROUP_IDENTIFIER)</string>
</array>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)$(PRODUCT_BUNDLE_IDENTIFIER)</string>
</array>
</dict>
</plist>

View File

@@ -71,7 +71,7 @@ extension AnalyticsManager
}
}
final class AnalyticsManager
class AnalyticsManager
{
static let shared = AnalyticsManager()

View File

@@ -25,7 +25,7 @@ extension AppContentViewController
}
}
final class AppContentViewController: UITableViewController
class AppContentViewController: UITableViewController
{
var app: StoreApp!

View File

@@ -8,7 +8,7 @@
import UIKit
final class PermissionCollectionViewCell: UICollectionViewCell
class PermissionCollectionViewCell: UICollectionViewCell
{
@IBOutlet var button: UIButton!
@IBOutlet var textLabel: UILabel!
@@ -29,7 +29,7 @@ final class PermissionCollectionViewCell: UICollectionViewCell
}
}
final class AppContentTableViewCell: UITableViewCell
class AppContentTableViewCell: UITableViewCell
{
override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize
{

View File

@@ -13,7 +13,7 @@ import Roxas
import Nuke
final class AppViewController: UIViewController
class AppViewController: UIViewController
{
var app: StoreApp!
@@ -352,7 +352,7 @@ final class AppViewController: UIViewController
extension AppViewController
{
final class func makeAppViewController(app: StoreApp) -> AppViewController
class func makeAppViewController(app: StoreApp) -> AppViewController
{
let storyboard = UIStoryboard(name: "Main", bundle: nil)

View File

@@ -10,7 +10,7 @@ import UIKit
import AltStoreCore
final class PermissionPopoverViewController: UIViewController
class PermissionPopoverViewController: UIViewController
{
var permission: AppPermission!

View File

@@ -11,7 +11,7 @@ import UIKit
import AltStoreCore
import Roxas
final class AppIDsViewController: UICollectionViewController
class AppIDsViewController: UICollectionViewController
{
private lazy var dataSource = self.makeDataSource()

View File

@@ -1,18 +0,0 @@
//
// SideStoreUIApp.swift
// SideStoreUI
//
// Created by Fabian Thies on 18.11.22.
// Copyright © 2022 Fabian Thies. All rights reserved.
//
import SwiftUI
@main
struct SideStoreUIApp: App {
var body: some Scene {
WindowGroup {
RootView()
}
}
}

View File

@@ -30,7 +30,7 @@ extension AppDelegate
}
@UIApplicationMain
final class AppDelegate: UIResponder, UIApplicationDelegate {
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
@@ -58,9 +58,6 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
{
// Copy STDOUT and STDERR to the logging console
_ = OutputCapturer.shared
// Register default settings before doing anything else.
UserDefaults.registerDefaults()

View File

@@ -10,7 +10,7 @@ import UIKit
import AltSign
final class AuthenticationViewController: UIViewController
class AuthenticationViewController: UIViewController
{
var authenticationHandler: ((String, String, @escaping (Result<(ALTAccount, ALTAppleAPISession), Error>) -> Void) -> Void)?
var completionHandler: (((ALTAccount, ALTAppleAPISession, String)?) -> Void)?

View File

@@ -8,7 +8,7 @@
import UIKit
final class InstructionsViewController: UIViewController
class InstructionsViewController: UIViewController
{
var completionHandler: (() -> Void)?

View File

@@ -12,7 +12,7 @@ import AltStoreCore
import AltSign
import Roxas
final class RefreshAltStoreViewController: UIViewController
class RefreshAltStoreViewController: UIViewController
{
var context: AuthenticatedOperationContext!

View File

@@ -14,7 +14,7 @@ import IntentsUI
import AltSign
final class SelectTeamViewController: UITableViewController
class SelectTeamViewController: UITableViewController
{
public var teams: [ALTTeam]?
public var completionHandler: ((Result<ALTTeam, Swift.Error>) -> Void)?

View File

@@ -12,7 +12,7 @@ import Roxas
import Nuke
@objc final class BrowseCollectionViewCell: UICollectionViewCell
@objc class BrowseCollectionViewCell: UICollectionViewCell
{
var imageURLs: [URL] = [] {
didSet {

View File

@@ -8,7 +8,7 @@
import UIKit
final class AppIconImageView: UIImageView
class AppIconImageView: UIImageView
{
override func awakeFromNib()
{

View File

@@ -8,7 +8,7 @@
import AVFoundation
final class BackgroundTaskManager
class BackgroundTaskManager
{
static let shared = BackgroundTaskManager()

View File

@@ -8,7 +8,7 @@
import UIKit
final class BannerCollectionViewCell: UICollectionViewCell
class BannerCollectionViewCell: UICollectionViewCell
{
private(set) var errorBadge: UIView?
@IBOutlet private(set) var bannerView: AppBannerView!

View File

@@ -8,7 +8,7 @@
import UIKit
final class Button: UIButton
class Button: UIButton
{
override var intrinsicContentSize: CGSize {
var size = super.intrinsicContentSize

View File

@@ -8,7 +8,7 @@
import UIKit
final class CollapsingTextView: UITextView
class CollapsingTextView: UITextView
{
var isCollapsed = true {
didSet {

View File

@@ -8,7 +8,7 @@
import UIKit
final class ForwardingNavigationController: UINavigationController
class ForwardingNavigationController: UINavigationController
{
override var childForStatusBarStyle: UIViewController? {
return self.topViewController

View File

@@ -10,7 +10,7 @@ import UIKit
import Roxas
final class NavigationBar: UINavigationBar
class NavigationBar: UINavigationBar
{
@IBInspectable var automaticallyAdjustsItemPositions: Bool = true

View File

@@ -8,7 +8,7 @@
import UIKit
final class PillButton: UIButton
class PillButton: UIButton
{
override var accessibilityValue: String? {
get {

View File

@@ -16,7 +16,7 @@ extension TimeInterval
static let longToastViewDuration = 8.0
}
final class ToastView: RSTToastView
class ToastView: RSTToastView
{
var preferredDuration: TimeInterval

View File

@@ -9,7 +9,7 @@
import Foundation
import OSLog
public let customLog = OSLog(subsystem: "org.sidestore.sidestore",
let customLog = OSLog(subsystem: "org.sidestore.sidestore",
category: "ios")
@@ -18,7 +18,6 @@ public extension OSLog {
/// - Parameters:
/// - message: String or format string
/// - args: optional args for format string
@inlinable
static func error(_ message: StaticString, _ args: CVarArg...) {
os_log(message, log: customLog, type: .error, args)
}
@@ -27,7 +26,6 @@ public extension OSLog {
/// - Parameters:
/// - message: String or format string
/// - args: optional args for format string
@inlinable
static func info(_ message: StaticString, _ args: CVarArg...) {
os_log(message, log: customLog, type: .info, args)
}
@@ -36,7 +34,6 @@ public extension OSLog {
/// - Parameters:
/// - message: String or format string
/// - args: optional args for format string
@inlinable
static func debug(_ message: StaticString, _ args: CVarArg...) {
os_log(message, log: customLog, type: .debug, args)
}
@@ -48,7 +45,6 @@ public extension OSLog {
/// - Parameters:
/// - message: String or format string
/// - args: optional args for format string
@inlinable
public func ELOG(_ message: StaticString, file: StaticString = #file, function: StaticString = #function, line: UInt = #line, _ args: CVarArg...) {
OSLog.error(message, args)
}
@@ -57,7 +53,6 @@ public func ELOG(_ message: StaticString, file: StaticString = #file, function:
/// - Parameters:
/// - message: String or format string
/// - args: optional args for format string
@inlinable
public func ILOG(_ message: StaticString, file: StaticString = #file, function: StaticString = #function, line: UInt = #line, _ args: CVarArg...) {
OSLog.info(message, args)
}

View File

@@ -1,19 +0,0 @@
//
// Source+Trusted.swift
// SideStore
//
// Created by Fabian Thies on 04.02.23.
// Copyright © 2023 SideStore. All rights reserved.
//
import AltStoreCore
extension Source {
var isOfficial: Bool {
self.identifier == Source.altStoreIdentifier
}
var isTrusted: Bool {
UserDefaults.shared.trustedSourceIDs?.contains(self.identifier) ?? false
}
}

View File

@@ -1,17 +0,0 @@
//
// StoreApp+Searchable.swift
// SideStore
//
// Created by Fabian Thies on 01.12.22.
// Copyright © 2022 SideStore. All rights reserved.
//
import AltStoreCore
extension StoreApp: Filterable {
func matches(_ searchText: String) -> Bool {
searchText.isEmpty ||
self.name.lowercased().contains(searchText.lowercased()) ||
self.developerName.lowercased().contains(searchText.lowercased())
}
}

View File

@@ -1,19 +0,0 @@
//
// StoreApp+Trusted.swift
// SideStore
//
// Created by Fabian Thies on 04.02.23.
// Copyright © 2023 SideStore. All rights reserved.
//
import AltStoreCore
extension StoreApp {
var isFromOfficialSource: Bool {
self.source?.isOfficial ?? false
}
var isFromTrustedSource: Bool {
self.source?.isTrusted ?? false
}
}

View File

@@ -1,201 +0,0 @@
// swiftlint:disable all
// Generated using SwiftGen https://github.com/SwiftGen/SwiftGen
#if os(macOS)
import AppKit
#elseif os(iOS)
import UIKit
#elseif os(tvOS) || os(watchOS)
import UIKit
#endif
#if canImport(SwiftUI)
import SwiftUI
#endif
// Deprecated typealiases
@available(*, deprecated, renamed: "ColorAsset.Color", message: "This typealias will be removed in SwiftGen 7.0")
internal typealias AssetColorTypeAlias = ColorAsset.Color
@available(*, deprecated, renamed: "ImageAsset.Image", message: "This typealias will be removed in SwiftGen 7.0")
internal typealias AssetImageTypeAlias = ImageAsset.Image
// swiftlint:disable superfluous_disable_command file_length implicit_return
// MARK: - Asset Catalogs
// swiftlint:disable identifier_name line_length nesting type_body_length type_name
internal enum Asset {
internal static let back = ImageAsset(name: "Back")
internal static let betaBadge = ImageAsset(name: "BetaBadge")
internal static let accentColor = ColorAsset(name: "AccentColor")
internal static let background = ColorAsset(name: "Background")
internal static let blurTint = ColorAsset(name: "BlurTint")
internal static let settingsBackground = ColorAsset(name: "SettingsBackground")
internal static let settingsHighlighted = ColorAsset(name: "SettingsHighlighted")
internal static let next = ImageAsset(name: "Next")
internal static let riley = ImageAsset(name: "Riley")
internal static let shane = ImageAsset(name: "Shane")
internal static let browse = ImageAsset(name: "Browse")
internal static let myApps = ImageAsset(name: "MyApps")
internal static let news = ImageAsset(name: "News")
internal static let settings = ImageAsset(name: "Settings")
}
// swiftlint:enable identifier_name line_length nesting type_body_length type_name
// MARK: - Implementation Details
internal final class ColorAsset {
internal fileprivate(set) var name: String
#if os(macOS)
internal typealias Color = NSColor
#elseif os(iOS) || os(tvOS) || os(watchOS)
internal typealias Color = UIColor
#endif
@available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *)
internal private(set) lazy var color: Color = {
guard let color = Color(asset: self) else {
fatalError("Unable to load color asset named \(name).")
}
return color
}()
#if os(iOS) || os(tvOS)
@available(iOS 11.0, tvOS 11.0, *)
internal func color(compatibleWith traitCollection: UITraitCollection) -> Color {
let bundle = BundleToken.bundle
guard let color = Color(named: name, in: bundle, compatibleWith: traitCollection) else {
fatalError("Unable to load color asset named \(name).")
}
return color
}
#endif
#if canImport(SwiftUI)
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
internal private(set) lazy var swiftUIColor: SwiftUI.Color = {
SwiftUI.Color(asset: self)
}()
#endif
fileprivate init(name: String) {
self.name = name
}
}
internal extension ColorAsset.Color {
@available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *)
convenience init?(asset: ColorAsset) {
let bundle = BundleToken.bundle
#if os(iOS) || os(tvOS)
self.init(named: asset.name, in: bundle, compatibleWith: nil)
#elseif os(macOS)
self.init(named: NSColor.Name(asset.name), bundle: bundle)
#elseif os(watchOS)
self.init(named: asset.name)
#endif
}
}
#if canImport(SwiftUI)
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
internal extension SwiftUI.Color {
init(asset: ColorAsset) {
let bundle = BundleToken.bundle
self.init(asset.name, bundle: bundle)
}
}
#endif
internal struct ImageAsset {
internal fileprivate(set) var name: String
#if os(macOS)
internal typealias Image = NSImage
#elseif os(iOS) || os(tvOS) || os(watchOS)
internal typealias Image = UIImage
#endif
@available(iOS 8.0, tvOS 9.0, watchOS 2.0, macOS 10.7, *)
internal var image: Image {
let bundle = BundleToken.bundle
#if os(iOS) || os(tvOS)
let image = Image(named: name, in: bundle, compatibleWith: nil)
#elseif os(macOS)
let name = NSImage.Name(self.name)
let image = (bundle == .main) ? NSImage(named: name) : bundle.image(forResource: name)
#elseif os(watchOS)
let image = Image(named: name)
#endif
guard let result = image else {
fatalError("Unable to load image asset named \(name).")
}
return result
}
#if os(iOS) || os(tvOS)
@available(iOS 8.0, tvOS 9.0, *)
internal func image(compatibleWith traitCollection: UITraitCollection) -> Image {
let bundle = BundleToken.bundle
guard let result = Image(named: name, in: bundle, compatibleWith: traitCollection) else {
fatalError("Unable to load image asset named \(name).")
}
return result
}
#endif
#if canImport(SwiftUI)
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
internal var swiftUIImage: SwiftUI.Image {
SwiftUI.Image(asset: self)
}
#endif
}
internal extension ImageAsset.Image {
@available(iOS 8.0, tvOS 9.0, watchOS 2.0, *)
@available(macOS, deprecated,
message: "This initializer is unsafe on macOS, please use the ImageAsset.image property")
convenience init?(asset: ImageAsset) {
#if os(iOS) || os(tvOS)
let bundle = BundleToken.bundle
self.init(named: asset.name, in: bundle, compatibleWith: nil)
#elseif os(macOS)
self.init(named: NSImage.Name(asset.name))
#elseif os(watchOS)
self.init(named: asset.name)
#endif
}
}
#if canImport(SwiftUI)
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
internal extension SwiftUI.Image {
init(asset: ImageAsset) {
let bundle = BundleToken.bundle
self.init(asset.name, bundle: bundle)
}
init(asset: ImageAsset, label: Text) {
let bundle = BundleToken.bundle
self.init(asset.name, bundle: bundle, label: label)
}
init(decorative asset: ImageAsset) {
let bundle = BundleToken.bundle
self.init(decorative: asset.name, bundle: bundle)
}
}
#endif
// swiftlint:disable convenience_type
private final class BundleToken {
static let bundle: Bundle = {
#if SWIFT_PACKAGE
return Bundle.module
#else
return Bundle(for: BundleToken.self)
#endif
}()
}
// swiftlint:enable convenience_type

View File

@@ -1,351 +0,0 @@
// swiftlint:disable all
// Generated using SwiftGen https://github.com/SwiftGen/SwiftGen
import Foundation
// swiftlint:disable superfluous_disable_command file_length implicit_return prefer_self_in_static_references
// MARK: - Strings
// swiftlint:disable explicit_type_interface function_parameter_count identifier_name line_length
// swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces
internal enum L10n {
internal enum Action {
/// Close
internal static let close = L10n.tr("Localizable", "Action.close", fallback: "Close")
/// General Actions
internal static let done = L10n.tr("Localizable", "Action.done", fallback: "Done")
}
internal enum AddSourceView {
/// Continue
internal static let `continue` = L10n.tr("Localizable", "AddSourceView.continue", fallback: "Continue")
/// AddSourceView
internal static let sourceURL = L10n.tr("Localizable", "AddSourceView.sourceURL", fallback: "Source URL")
/// Please enter the source url here. Then, tap continue to validate and add the source in the next step.
internal static let sourceWarning = L10n.tr("Localizable", "AddSourceView.sourceWarning", fallback: "Please enter the source url here. Then, tap continue to validate and add the source in the next step.")
/// Be careful with unvalidated third-party sources! Make sure to only add sources that you trust.
internal static let sourceWarningContinued = L10n.tr("Localizable", "AddSourceView.sourceWarningContinued", fallback: "Be careful with unvalidated third-party sources! Make sure to only add sources that you trust.")
/// Add Source
internal static let title = L10n.tr("Localizable", "AddSourceView.title", fallback: "Add Source")
}
internal enum AppAction {
/// Activate
internal static let activate = L10n.tr("Localizable", "AppAction.activate", fallback: "Activate")
/// Backup
internal static let backup = L10n.tr("Localizable", "AppAction.backup", fallback: "Backup")
/// Customize icon
internal static let chooseCustomIcon = L10n.tr("Localizable", "AppAction.chooseCustomIcon", fallback: "Customize icon")
/// Deactivate
internal static let deactivate = L10n.tr("Localizable", "AppAction.deactivate", fallback: "Deactivate")
/// Activate JIT
internal static let enableJIT = L10n.tr("Localizable", "AppAction.enableJIT", fallback: "Activate JIT")
/// Export backup
internal static let exportBackup = L10n.tr("Localizable", "AppAction.exportBackup", fallback: "Export backup")
/// AppAction
internal static let install = L10n.tr("Localizable", "AppAction.install", fallback: "Install")
/// Open
internal static let `open` = L10n.tr("Localizable", "AppAction.open", fallback: "Open")
/// Refresh
internal static let refresh = L10n.tr("Localizable", "AppAction.refresh", fallback: "Refresh")
/// Remove
internal static let remove = L10n.tr("Localizable", "AppAction.remove", fallback: "Remove")
/// Reset icon
internal static let resetIcon = L10n.tr("Localizable", "AppAction.resetIcon", fallback: "Reset icon")
/// Restore backup
internal static let restoreBackup = L10n.tr("Localizable", "AppAction.restoreBackup", fallback: "Restore backup")
}
internal enum AppDetailView {
/// Information
internal static let information = L10n.tr("Localizable", "AppDetailView.information", fallback: "Information")
/// More...
internal static let more = L10n.tr("Localizable", "AppDetailView.more", fallback: "More...")
/// The app requires no permissions.
internal static let noPermissions = L10n.tr("Localizable", "AppDetailView.noPermissions", fallback: "The app requires no permissions.")
/// No screenshots available for this app.
internal static let noScreenshots = L10n.tr("Localizable", "AppDetailView.noScreenshots", fallback: "No screenshots available for this app.")
/// No version information
internal static let noVersionInformation = L10n.tr("Localizable", "AppDetailView.noVersionInformation", fallback: "No version information")
/// Permissions
internal static let permissions = L10n.tr("Localizable", "AppDetailView.permissions", fallback: "Permissions")
/// Ratings & Reviews
internal static let reviews = L10n.tr("Localizable", "AppDetailView.reviews", fallback: "Ratings & Reviews")
/// Version %@
internal static func version(_ p1: Any) -> String {
return L10n.tr("Localizable", "AppDetailView.version", String(describing: p1), fallback: "Version %@")
}
/// What's New
internal static let whatsNew = L10n.tr("Localizable", "AppDetailView.whatsNew", fallback: "What's New")
internal enum Badge {
/// AppDetailView
internal static let official = L10n.tr("Localizable", "AppDetailView.Badge.official", fallback: "Official App")
/// From Trusted Source
internal static let trusted = L10n.tr("Localizable", "AppDetailView.Badge.trusted", fallback: "From Trusted Source")
}
internal enum Information {
/// Compatibility
internal static let compatibility = L10n.tr("Localizable", "AppDetailView.Information.compatibility", fallback: "Compatibility")
/// Requires iOS %@ or higher
internal static func compatibilityAtLeast(_ p1: Any) -> String {
return L10n.tr("Localizable", "AppDetailView.Information.compatibilityAtLeast", String(describing: p1), fallback: "Requires iOS %@ or higher")
}
/// Unknown
internal static let compatibilityCompatible = L10n.tr("Localizable", "AppDetailView.Information.compatibilityCompatible", fallback: "Unknown")
/// Requires iOS %@ or lower
internal static func compatibilityOrLower(_ p1: Any) -> String {
return L10n.tr("Localizable", "AppDetailView.Information.compatibilityOrLower", String(describing: p1), fallback: "Requires iOS %@ or lower")
}
/// Unknown
internal static let compatibilityUnknown = L10n.tr("Localizable", "AppDetailView.Information.compatibilityUnknown", fallback: "Unknown")
/// Developer
internal static let developer = L10n.tr("Localizable", "AppDetailView.Information.developer", fallback: "Developer")
/// Latest Version
internal static let latestVersion = L10n.tr("Localizable", "AppDetailView.Information.latestVersion", fallback: "Latest Version")
/// Size
internal static let size = L10n.tr("Localizable", "AppDetailView.Information.size", fallback: "Size")
/// Source
internal static let source = L10n.tr("Localizable", "AppDetailView.Information.source", fallback: "Source")
}
internal enum Reviews {
/// out of %d
internal static func outOf(_ p1: Int) -> String {
return L10n.tr("Localizable", "AppDetailView.Reviews.outOf", p1, fallback: "out of %d")
}
/// %d Ratings
internal static func ratings(_ p1: Int) -> String {
return L10n.tr("Localizable", "AppDetailView.Reviews.ratings", p1, fallback: "%d Ratings")
}
/// See All
internal static let seeAll = L10n.tr("Localizable", "AppDetailView.Reviews.seeAll", fallback: "See All")
}
internal enum WhatsNew {
/// Show project on GitHub
internal static let showOnGithub = L10n.tr("Localizable", "AppDetailView.WhatsNew.showOnGithub", fallback: "Show project on GitHub")
/// Version History
internal static let versionHistory = L10n.tr("Localizable", "AppDetailView.WhatsNew.versionHistory", fallback: "Version History")
}
}
internal enum AppIDsView {
/// Each app and app extension installed with SideStore must register an App ID with Apple.
///
/// App IDs for paid developer accounts never expire, and there is no limit to how many you can create.
internal static let description = L10n.tr("Localizable", "AppIDsView.description", fallback: "Each app and app extension installed with SideStore must register an App ID with Apple.\n\nApp IDs for paid developer accounts never expire, and there is no limit to how many you can create.")
/// AppIDsView
internal static let title = L10n.tr("Localizable", "AppIDsView.title", fallback: "App IDs")
}
internal enum AppPermissionGrid {
/// AppPermissionGrid
internal static let usageDescription = L10n.tr("Localizable", "AppPermissionGrid.usageDescription", fallback: "Usage Description")
}
internal enum AppPillButton {
/// AppPillButton
internal static let free = L10n.tr("Localizable", "AppPillButton.free", fallback: "Free")
/// Open
internal static let `open` = L10n.tr("Localizable", "AppPillButton.open", fallback: "Open")
}
internal enum AppRowView {
/// AppRowView
internal static let sideloaded = L10n.tr("Localizable", "AppRowView.sideloaded", fallback: "Sideloaded")
}
internal enum BrowseView {
/// Search
internal static let search = L10n.tr("Localizable", "BrowseView.search", fallback: "Search")
/// BrowseView
internal static let title = L10n.tr("Localizable", "BrowseView.title", fallback: "Browse")
internal enum Actions {
/// Sources
internal static let sources = L10n.tr("Localizable", "BrowseView.Actions.sources", fallback: "Sources")
}
internal enum Categories {
/// Games and
/// Emulators
internal static let gamesAndEmulators = L10n.tr("Localizable", "BrowseView.Categories.gamesAndEmulators", fallback: "Games and\nEmulators")
}
internal enum Hints {
internal enum NoApps {
/// Add Source
internal static let addSource = L10n.tr("Localizable", "BrowseView.Hints.NoApps.addSource", fallback: "Add Source")
/// Apps are provided by "sources". The specification for them is an open standard, so everyone can create their own source. To get you started, we have compiled a list of "Trusted Sources" which you can check out by tapping the button below.
internal static let text = L10n.tr("Localizable", "BrowseView.Hints.NoApps.text", fallback: "Apps are provided by \"sources\". The specification for them is an open standard, so everyone can create their own source. To get you started, we have compiled a list of \"Trusted Sources\" which you can check out by tapping the button below.")
/// You don't have any apps yet.
internal static let title = L10n.tr("Localizable", "BrowseView.Hints.NoApps.title", fallback: "You don't have any apps yet.")
}
}
internal enum Section {
internal enum AllApps {
/// All Apps
internal static let title = L10n.tr("Localizable", "BrowseView.Section.AllApps.title", fallback: "All Apps")
}
internal enum PromotedCategories {
/// Show all
internal static let showAll = L10n.tr("Localizable", "BrowseView.Section.PromotedCategories.showAll", fallback: "Show all")
/// Promoted Categories
internal static let title = L10n.tr("Localizable", "BrowseView.Section.PromotedCategories.title", fallback: "Promoted Categories")
}
}
}
internal enum ConfirmAddSourceView {
/// Add Source
internal static let addSource = L10n.tr("Localizable", "ConfirmAddSourceView.addSource", fallback: "Add Source")
/// ConfirmAddSourceView
internal static let apps = L10n.tr("Localizable", "ConfirmAddSourceView.apps", fallback: "Apps")
/// News Items
internal static let newsItems = L10n.tr("Localizable", "ConfirmAddSourceView.newsItems", fallback: "News Items")
/// Source Contents
internal static let sourceContents = L10n.tr("Localizable", "ConfirmAddSourceView.sourceContents", fallback: "Source Contents")
/// Source Identifier
internal static let sourceIdentifier = L10n.tr("Localizable", "ConfirmAddSourceView.sourceIdentifier", fallback: "Source Identifier")
/// Source Information
internal static let sourceInfo = L10n.tr("Localizable", "ConfirmAddSourceView.sourceInfo", fallback: "Source Information")
/// Source URL
internal static let sourceURL = L10n.tr("Localizable", "ConfirmAddSourceView.sourceURL", fallback: "Source URL")
}
internal enum ConnectAppleIDView {
/// Apple ID
internal static let appleID = L10n.tr("Localizable", "ConnectAppleIDView.appleID", fallback: "Apple ID")
/// Cancel
internal static let cancel = L10n.tr("Localizable", "ConnectAppleIDView.cancel", fallback: "Cancel")
/// Connect Your Apple ID
internal static let connectYourAppleID = L10n.tr("Localizable", "ConnectAppleIDView.connectYourAppleID", fallback: "Connect Your Apple ID")
/// Failed to Sign In
internal static let failedToSignIn = L10n.tr("Localizable", "ConnectAppleIDView.failedToSignIn", fallback: "Failed to Sign In")
/// Your Apple ID is used to configure apps so they can be installed on this device. Your credentials will be stored securely in this device's Keychain and sent only to Apple for authentication.
internal static let footer = L10n.tr("Localizable", "ConnectAppleIDView.footer", fallback: "Your Apple ID is used to configure apps so they can be installed on this device. Your credentials will be stored securely in this device's Keychain and sent only to Apple for authentication.")
/// Password
internal static let password = L10n.tr("Localizable", "ConnectAppleIDView.password", fallback: "Password")
/// Sign In
internal static let signIn = L10n.tr("Localizable", "ConnectAppleIDView.signIn", fallback: "Sign In")
/// ConnectAppleIDView
internal static let startWithSignIn = L10n.tr("Localizable", "ConnectAppleIDView.startWithSignIn", fallback: "Sign in with your Apple ID to get started.")
/// Why do we need this?
internal static let whyDoWeNeedThis = L10n.tr("Localizable", "ConnectAppleIDView.whyDoWeNeedThis", fallback: "Why do we need this?")
}
internal enum MyAppsView {
/// MyAppsView
internal static let active = L10n.tr("Localizable", "MyAppsView.active", fallback: "Active")
/// App IDs Remaining
internal static let appIDsRemaining = L10n.tr("Localizable", "MyAppsView.appIDsRemaining", fallback: "App IDs Remaining")
/// apps
internal static let apps = L10n.tr("Localizable", "MyAppsView.apps", fallback: "apps")
/// Failed to refresh
internal static let failedToRefresh = L10n.tr("Localizable", "MyAppsView.failedToRefresh", fallback: "Failed to refresh")
/// My Apps
internal static let myApps = L10n.tr("Localizable", "MyAppsView.myApps", fallback: "My Apps")
/// Refresh All
internal static let refreshAll = L10n.tr("Localizable", "MyAppsView.refreshAll", fallback: "Refresh All")
/// Sideloading in progress...
internal static let sideloading = L10n.tr("Localizable", "MyAppsView.sideloading", fallback: "Sideloading in progress...")
/// Keep this lowercase
internal static let viewAppIDs = L10n.tr("Localizable", "MyAppsView.viewAppIDs", fallback: "View App IDs")
internal enum Hints {
internal enum NoUpdates {
/// Dismiss for now
internal static let dismissForNow = L10n.tr("Localizable", "MyAppsView.Hints.NoUpdates.dismissForNow", fallback: "Dismiss for now")
/// Don't show this again
internal static let dontShowAgain = L10n.tr("Localizable", "MyAppsView.Hints.NoUpdates.dontShowAgain", fallback: "Don't show this again")
/// You will be notified once updates for your apps are available. The updates will then be shown here.
internal static let text = L10n.tr("Localizable", "MyAppsView.Hints.NoUpdates.text", fallback: "You will be notified once updates for your apps are available. The updates will then be shown here.")
/// All Apps are Up To Date
internal static let title = L10n.tr("Localizable", "MyAppsView.Hints.NoUpdates.title", fallback: "All Apps are Up To Date")
}
}
}
internal enum NewsView {
/// NewsView
internal static let title = L10n.tr("Localizable", "NewsView.title", fallback: "News")
internal enum Section {
internal enum FromSources {
/// From your Sources
internal static let title = L10n.tr("Localizable", "NewsView.Section.FromSources.title", fallback: "From your Sources")
}
}
}
internal enum RootView {
/// Browse
internal static let browse = L10n.tr("Localizable", "RootView.browse", fallback: "Browse")
/// My Apps
internal static let myApps = L10n.tr("Localizable", "RootView.myApps", fallback: "My Apps")
/// RootView
internal static let news = L10n.tr("Localizable", "RootView.news", fallback: "News")
/// Settings
internal static let settings = L10n.tr("Localizable", "RootView.settings", fallback: "Settings")
}
internal enum SettingsView {
/// Add to Siri...
internal static let addToSiri = L10n.tr("Localizable", "SettingsView.addToSiri", fallback: "Add to Siri...")
/// Background Refresh
internal static let backgroundRefresh = L10n.tr("Localizable", "SettingsView.backgroundRefresh", fallback: "Background Refresh")
/// Connect your Apple ID
internal static let connectAppleID = L10n.tr("Localizable", "SettingsView.connectAppleID", fallback: "Connect your Apple ID")
/// Credits
internal static let credits = L10n.tr("Localizable", "SettingsView.credits", fallback: "Credits")
/// Debug
internal static let debug = L10n.tr("Localizable", "SettingsView.debug", fallback: "Debug")
/// Refreshing Apps
internal static let refreshingApps = L10n.tr("Localizable", "SettingsView.refreshingApps", fallback: "Refreshing Apps")
/// Enable Background Refresh to automatically refresh apps in the background when connected to WiFi and with Wireguard active.
internal static let refreshingAppsFooter = L10n.tr("Localizable", "SettingsView.refreshingAppsFooter", fallback: "Enable Background Refresh to automatically refresh apps in the background when connected to WiFi and with Wireguard active.")
/// Reset Image Cache
internal static let resetImageCache = L10n.tr("Localizable", "SettingsView.resetImageCache", fallback: "Reset Image Cache")
/// SwiftUI Redesign
internal static let swiftUIRedesign = L10n.tr("Localizable", "SettingsView.swiftUIRedesign", fallback: "SwiftUI Redesign")
/// Switch to UIKit
internal static let switchToUIKit = L10n.tr("Localizable", "SettingsView.switchToUIKit", fallback: "Switch to UIKit")
/// Settings
internal static let title = L10n.tr("Localizable", "SettingsView.title", fallback: "Settings")
internal enum ConnectedAppleID {
/// E-Mail
internal static let eMail = L10n.tr("Localizable", "SettingsView.ConnectedAppleID.eMail", fallback: "E-Mail")
/// SettingsView
internal static let name = L10n.tr("Localizable", "SettingsView.ConnectedAppleID.name", fallback: "Name")
/// Sign Out
internal static let signOut = L10n.tr("Localizable", "SettingsView.ConnectedAppleID.signOut", fallback: "Sign Out")
/// Connected Apple ID
internal static let text = L10n.tr("Localizable", "SettingsView.ConnectedAppleID.text", fallback: "Connected Apple ID")
/// Type
internal static let type = L10n.tr("Localizable", "SettingsView.ConnectedAppleID.type", fallback: "Type")
internal enum Footer {
/// Your Apple ID is required to sign the apps you install with SideStore.
internal static let p1 = L10n.tr("Localizable", "SettingsView.ConnectedAppleID.Footer.p1", fallback: "Your Apple ID is required to sign the apps you install with SideStore.")
/// Your credentials are only sent to Apple's servers and are not accessible by the SideStore Team. Once successfully logged in, the login details are stored securely on your device.
internal static let p2 = L10n.tr("Localizable", "SettingsView.ConnectedAppleID.Footer.p2", fallback: "Your credentials are only sent to Apple's servers and are not accessible by the SideStore Team. Once successfully logged in, the login details are stored securely on your device.")
}
}
}
internal enum SourcesView {
/// Done
internal static let done = L10n.tr("Localizable", "SourcesView.done", fallback: "Done")
/// Remove
internal static let remove = L10n.tr("Localizable", "SourcesView.remove", fallback: "Remove")
/// SideStore has reviewed these sources to make sure they meet our safety standards.
internal static let reviewedText = L10n.tr("Localizable", "SourcesView.reviewedText", fallback: "SideStore has reviewed these sources to make sure they meet our safety standards.")
/// Sources
internal static let sources = L10n.tr("Localizable", "SourcesView.sources", fallback: "Sources")
/// SourcesView
internal static let sourcesDescription = L10n.tr("Localizable", "SourcesView.sourcesDescription", fallback: "Sources control what apps are available to download through SideStore.")
/// Trusted Sources
internal static let trustedSources = L10n.tr("Localizable", "SourcesView.trustedSources", fallback: "Trusted Sources")
}
}
// swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length
// swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces
// MARK: - Implementation Details
extension L10n {
private static func tr(_ table: String, _ key: String, _ args: CVarArg..., fallback value: String) -> String {
let format = BundleToken.bundle.localizedString(forKey: key, value: value, table: table)
return String(format: format, locale: Locale.current, arguments: args)
}
}
// swiftlint:disable convenience_type
private final class BundleToken {
static let bundle: Bundle = {
#if SWIFT_PACKAGE
return Bundle.module
#else
return Bundle(for: BundleToken.self)
#endif
}()
}
// swiftlint:enable convenience_type

View File

@@ -1,72 +0,0 @@
//
// DateFormatterHelper.swift
// SideStore
//
// Created by Fabian Thies on 20.11.22.
// Copyright © 2022 Fabian Thies. All rights reserved.
//
import Foundation
struct DateFormatterHelper {
private static let appExpirationDateFormatter: DateComponentsFormatter = {
let dateComponentsFormatter = DateComponentsFormatter()
dateComponentsFormatter.zeroFormattingBehavior = [.pad]
dateComponentsFormatter.collapsesLargestUnit = false
return dateComponentsFormatter
}()
private static let relativeDateFormatter: RelativeDateTimeFormatter = {
let formatter = RelativeDateTimeFormatter()
formatter.unitsStyle = .abbreviated
return formatter
}()
private static let mediumDateFormatter: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .medium
dateFormatter.timeStyle = .none
dateFormatter.doesRelativeDateFormatting = true
return dateFormatter
}()
private static let timeFormatter: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .none
dateFormatter.timeStyle = .short
return dateFormatter
}()
static func string(forExpirationDate date: Date) -> String {
let startDate = Date()
let interval = date.timeIntervalSince(startDate)
guard interval > 0 else {
return "EXPIRED"
}
if interval < (24 * 60 * 60) {
self.appExpirationDateFormatter.unitsStyle = .positional
self.appExpirationDateFormatter.allowedUnits = [.minute, .second]
} else {
self.appExpirationDateFormatter.unitsStyle = .full
self.appExpirationDateFormatter.allowedUnits = [.day]
}
return self.appExpirationDateFormatter.string(from: startDate, to: date) ?? ""
}
static func string(forRelativeDate date: Date, to referenceDate: Date = Date()) -> String {
self.relativeDateFormatter.localizedString(for: date, relativeTo: referenceDate)
}
static func string(for date: Date) -> String {
self.mediumDateFormatter.string(from: date)
}
static func timeString(for date: Date) -> String {
self.timeFormatter.string(from: date)
}
}

View File

@@ -1,254 +0,0 @@
//
// SideloadingManager.swift
// SideStore
//
// Created by Fabian Thies on 20.12.22.
// Copyright © 2022 SideStore. All rights reserved.
//
import Foundation
import SwiftUI
import CoreData
import AltStoreCore
import CAltSign
import Roxas
// TODO: Move this to the AppManager
class SideloadingManager {
class Context {
var fileURL: URL?
var application: ALTApplication?
var installedApp: InstalledApp? {
didSet {
self.installedAppContext = self.installedApp?.managedObjectContext
}
}
private var installedAppContext: NSManagedObjectContext?
var error: Error?
}
public static let shared = SideloadingManager()
@Published
public var progress: Progress?
private let operationQueue = OperationQueue()
private init() {}
// TODO: Refactor & convert to async
func sideloadApp(at url: URL, completion: @escaping (Result<Void, Error>) -> Void) {
self.progress = Progress.discreteProgress(totalUnitCount: 100)
let temporaryDirectory = FileManager.default.uniqueTemporaryURL()
let unzippedAppDirectory = temporaryDirectory.appendingPathComponent("App")
let context = Context()
let downloadOperation: RSTAsyncBlockOperation?
if url.isFileURL {
downloadOperation = nil
context.fileURL = url
self.progress?.totalUnitCount -= 20
} else {
let downloadProgress = Progress.discreteProgress(totalUnitCount: 100)
downloadOperation = RSTAsyncBlockOperation { (operation) in
let downloadTask = URLSession.shared.downloadTask(with: url) { (fileURL, response, error) in
do
{
let (fileURL, _) = try Result((fileURL, response), error).get()
try FileManager.default.createDirectory(at: temporaryDirectory, withIntermediateDirectories: true, attributes: nil)
let destinationURL = temporaryDirectory.appendingPathComponent("App.ipa")
try FileManager.default.moveItem(at: fileURL, to: destinationURL)
context.fileURL = destinationURL
}
catch
{
context.error = error
}
operation.finish()
}
downloadProgress.addChild(downloadTask.progress, withPendingUnitCount: 100)
downloadTask.resume()
}
self.progress?.addChild(downloadProgress, withPendingUnitCount: 20)
}
let unzipProgress = Progress.discreteProgress(totalUnitCount: 1)
let unzipAppOperation = BlockOperation {
do
{
if let error = context.error
{
throw error
}
guard let fileURL = context.fileURL else { throw OperationError.invalidParameters }
defer {
try? FileManager.default.removeItem(at: fileURL)
}
try FileManager.default.createDirectory(at: unzippedAppDirectory, withIntermediateDirectories: true, attributes: nil)
let unzippedApplicationURL = try FileManager.default.unzipAppBundle(at: fileURL, toDirectory: unzippedAppDirectory)
guard let application = ALTApplication(fileURL: unzippedApplicationURL) else { throw OperationError.invalidApp }
context.application = application
unzipProgress.completedUnitCount = 1
}
catch
{
context.error = error
}
}
self.progress?.addChild(unzipProgress, withPendingUnitCount: 10)
if let downloadOperation = downloadOperation
{
unzipAppOperation.addDependency(downloadOperation)
}
let removeAppExtensionsProgress = Progress.discreteProgress(totalUnitCount: 1)
let removeAppExtensionsOperation = RSTAsyncBlockOperation { [weak self] (operation) in
do
{
if let error = context.error
{
throw error
}
guard let application = context.application else { throw OperationError.invalidParameters }
DispatchQueue.main.async {
self?.removeAppExtensions(from: application) { (result) in
switch result
{
case .success: removeAppExtensionsProgress.completedUnitCount = 1
case .failure(let error): context.error = error
}
operation.finish()
}
}
}
catch
{
context.error = error
operation.finish()
}
}
removeAppExtensionsOperation.addDependency(unzipAppOperation)
self.progress?.addChild(removeAppExtensionsProgress, withPendingUnitCount: 5)
let installProgress = Progress.discreteProgress(totalUnitCount: 100)
let installAppOperation = RSTAsyncBlockOperation { (operation) in
do
{
if let error = context.error
{
throw error
}
guard let application = context.application else { throw OperationError.invalidParameters }
let group = AppManager.shared.install(application, presentingViewController: nil) { (result) in
switch result
{
case .success(let installedApp): context.installedApp = installedApp
case .failure(let error): context.error = error
}
operation.finish()
}
installProgress.addChild(group.progress, withPendingUnitCount: 100)
}
catch
{
context.error = error
operation.finish()
}
}
installAppOperation.completionBlock = {
try? FileManager.default.removeItem(at: temporaryDirectory)
DispatchQueue.main.async {
self.progress = nil
switch Result(context.installedApp, context.error)
{
case .success(let app):
completion(.success(()))
app.managedObjectContext?.perform {
print("Successfully installed app:", app.bundleIdentifier)
}
case .failure(OperationError.cancelled):
completion(.failure((OperationError.cancelled)))
case .failure(let error):
NotificationManager.shared.reportError(error: error)
completion(.failure(error))
}
}
}
self.progress?.addChild(installProgress, withPendingUnitCount: 65)
installAppOperation.addDependency(removeAppExtensionsOperation)
let operations = [downloadOperation, unzipAppOperation, removeAppExtensionsOperation, installAppOperation].compactMap { $0 }
self.operationQueue.addOperations(operations, waitUntilFinished: false)
}
// TODO: Refactor
private func removeAppExtensions(from application: ALTApplication, completion: @escaping (Result<Void, Error>) -> Void)
{
guard !application.appExtensions.isEmpty else { return completion(.success(())) }
let firstSentence: String
if UserDefaults.standard.activeAppLimitIncludesExtensions
{
firstSentence = NSLocalizedString("Non-developer Apple IDs are limited to 3 active apps and app extensions.", comment: "")
}
else
{
firstSentence = NSLocalizedString("Non-developer Apple IDs are limited to creating 10 App IDs per week.", comment: "")
}
let message = firstSentence + " " + NSLocalizedString("Would you like to remove this app's extensions so they don't count towards your limit?", comment: "")
let alertController = UIAlertController(title: NSLocalizedString("App Contains Extensions", comment: ""), message: message, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: UIAlertAction.cancel.title, style: UIAlertAction.cancel.style, handler: { (action) in
completion(.failure(OperationError.cancelled))
}))
alertController.addAction(UIAlertAction(title: NSLocalizedString("Keep App Extensions", comment: ""), style: .default) { (action) in
completion(.success(()))
})
alertController.addAction(UIAlertAction(title: NSLocalizedString("Remove App Extensions", comment: ""), style: .destructive) { (action) in
do
{
for appExtension in application.appExtensions
{
try FileManager.default.removeItem(at: appExtension.fileURL)
}
completion(.success(()))
}
catch
{
completion(.failure(error))
}
})
let rootViewController = UIApplication.shared.keyWindow?.rootViewController
rootViewController?.present(alertController, animated: true, completion: nil)
}
}

View File

@@ -14,7 +14,7 @@
<key>ALTPairingFile</key>
<string>&lt;insert pairing file here&gt;</string>
<key>ALTAnisetteURL</key>
<string>https://ani.sidestore.io</string>
<string>https://sideloadly.io/anisette/irGb3Quww8zrhgqnzmrx</string>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDocumentTypes</key>
@@ -58,7 +58,7 @@
<key>CFBundleURLSchemes</key>
<array>
<string>altstore</string>
<string>sidestore</string>
<string>sidestore</string>
</array>
</dict>
<dict>
@@ -69,7 +69,7 @@
<key>CFBundleURLSchemes</key>
<array>
<string>altstore-com.rileytestut.AltStore</string>
<string>sidestore-com.SideStore.SideStore</string>
<string>sidestore-com.SideStore.SideStore</string>
</array>
</dict>
</array>
@@ -93,6 +93,40 @@
</array>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>127.0.0.1</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
<key>New Exception Domain 1</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
<key>apps.sidestore.io</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
<key>sidestore.io</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
</dict>
</dict>
<key>NSBonjourServices</key>
<array>
<string>_altserver._tcp</string>
@@ -129,6 +163,7 @@
<array>
<string>audio</string>
<string>fetch</string>
<string>processing</string>
<string>remote-notification</string>
</array>
<key>UILaunchStoryboardName</key>
@@ -204,7 +239,5 @@
</dict>
</dict>
</array>
<key>UIFileSharingEnabled</key>
<true/>
</dict>
</plist>

View File

@@ -11,7 +11,7 @@ import Foundation
import AltStoreCore
@available(iOS 14, *)
final class IntentHandler: NSObject, RefreshAllIntentHandling
class IntentHandler: NSObject, RefreshAllIntentHandling
{
private let queue = DispatchQueue(label: "io.altstore.IntentHandler")

View File

@@ -7,7 +7,6 @@
//
import UIKit
import SwiftUI
import Roxas
import EmotionalDamage
import minimuxer
@@ -15,9 +14,7 @@ import minimuxer
import AltStoreCore
import UniformTypeIdentifiers
let pairingFileName = "ALTPairingFile.mobiledevicepairing"
final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDelegate
class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDelegate
{
private var didFinishLaunching = false
@@ -43,40 +40,20 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg
{
defer {
// Create destinationViewController now so view controllers can register for receiving Notifications.
// self.destinationViewController = self.storyboard!.instantiateViewController(withIdentifier: "tabBarController") as! TabBarController
let rootView = RootView()
.environment(\.managedObjectContext, DatabaseManager.shared.viewContext)
self.destinationViewController = UIHostingController(rootView: rootView)
self.destinationViewController = self.storyboard!.instantiateViewController(withIdentifier: "tabBarController") as! TabBarController
}
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
#if !targetEnvironment(simulator)
if !UserDefaults.standard.onboardingComplete {
self.showOnboarding()
return
}
start_em_proxy(bind_addr: Consts.Proxy.serverURL)
guard let pf = fetchPairingFile() else {
self.showOnboarding(enabledSteps: [.pairing])
displayError("Device pairing file not found.")
return
}
start_minimuxer_threads(pf)
#endif
}
func showOnboarding(enabledSteps: [OnboardingStep] = OnboardingStep.allCases) {
let onboardingView = OnboardingView(onDismiss: { self.dismiss(animated: true) }, enabledSteps: enabledSteps)
.environment(\.managedObjectContext, DatabaseManager.shared.viewContext)
let navigationController = UINavigationController(rootViewController: UIHostingController(rootView: onboardingView))
navigationController.isNavigationBarHidden = true
navigationController.isModalInPresentation = true
self.present(navigationController, animated: true)
}
func fetchPairingFile() -> String? {
@@ -100,7 +77,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://wiki.sidestore.io/guides/install#pairing-process", preferredStyle: .alert)
let dialogMessage = UIAlertController(title: "Pairing File", message: "Select the pairing file for your device. For more information, go to https://youtu.be/dQw4w9WgXcQ", preferredStyle: .alert)
// Create OK button with action handler
let ok = UIAlertAction(title: "OK", style: .default, handler: { (action) -> Void in
@@ -146,11 +123,14 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg
}
// Save to a file for next launch
let pairingFile = FileManager.default.documentsDirectory.appendingPathComponent("\(pairingFileName)")
try pairing_string?.write(to: pairingFile, atomically: true, encoding: String.Encoding.utf8)
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)
// Start minimuxer now that we have a file
start_minimuxer_threads(pairing_string!)
} catch {
displayError("Unable to read pairing file")
}
@@ -166,15 +146,12 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg
}
func start_minimuxer_threads(_ pairing_file: String) {
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!")")
set_usbmuxd_socket()
let res = start_minimuxer(pairing_file: pairing_file)
if res != 0 {
displayError("minimuxer failed to start. Incorrect arguments were passed.")
}
start_auto_mounter(documentsDirectory)
auto_mount_dev_image()
}
}

View File

@@ -1,80 +0,0 @@
//
// NotificationManager.swift
// SideStore
//
// Created by Fabian Thies on 21.11.22.
// Copyright © 2022 Fabian Thies. All rights reserved.
//
import SwiftUI
import AltStoreCore
class NotificationManager: ObservableObject {
struct Notification: Identifiable {
let id: UUID
let title: String
let message: String?
}
static let shared = NotificationManager()
@Published
var notifications: [UUID: Notification] = [:]
private init() {}
func reportError(error: Error) {
if case OperationError.cancelled = error {
// Ignore
return
}
var error = error as NSError
var underlyingError = error.underlyingError
if
let unwrappedUnderlyingError = underlyingError,
error.domain == AltServerErrorDomain && error.code == ALTServerError.Code.underlyingError.rawValue
{
// Treat underlyingError as the primary error.
error = unwrappedUnderlyingError as NSError
underlyingError = nil
}
let text: String
let detailText: String?
if let failure = error.localizedFailure
{
text = failure
detailText = error.localizedFailureReason ?? error.localizedRecoverySuggestion ?? underlyingError?.localizedDescription ?? error.localizedDescription
}
else if let reason = error.localizedFailureReason
{
text = reason
detailText = error.localizedRecoverySuggestion ?? underlyingError?.localizedDescription
}
else
{
text = error.localizedDescription
detailText = underlyingError?.localizedDescription ?? error.localizedRecoverySuggestion
}
self.showNotification(title: text, detailText: detailText)
}
func showNotification(title: String, detailText: String? = nil) {
let notificationId = UUID()
DispatchQueue.main.async {
self.notifications[notificationId] = Notification(id: notificationId, title: title, message: detailText)
}
let dismissWorkItem = DispatchWorkItem {
self.notifications.removeValue(forKey: notificationId)
}
DispatchQueue.main.asyncAfter(deadline: .now().advanced(by: .seconds(5)), execute: dismissWorkItem)
}
}

View File

@@ -1,56 +0,0 @@
//
// OutputCapturer.swift
// SideStore
//
// Created by Fabian Thies on 12.02.23.
// Copyright © 2023 SideStore. All rights reserved.
//
import Foundation
import LocalConsole
class OutputCapturer {
public static let shared = OutputCapturer()
private let consoleManager = LCManager.shared
private var inputPipe = Pipe()
private var errorPipe = Pipe()
private var outputPipe = Pipe()
private init() {
// Setup pipe file handlers
self.inputPipe.fileHandleForReading.readabilityHandler = { [weak self] fileHandle in
self?.handle(data: fileHandle.availableData)
}
self.errorPipe.fileHandleForReading.readabilityHandler = { [weak self] fileHandle in
self?.handle(data: fileHandle.availableData, isError: true)
}
// Keep STDOUT
dup2(STDOUT_FILENO, self.outputPipe.fileHandleForWriting.fileDescriptor)
// Intercept STDOUT and STDERR
dup2(self.inputPipe.fileHandleForWriting.fileDescriptor, STDOUT_FILENO)
dup2(self.errorPipe.fileHandleForWriting.fileDescriptor, STDERR_FILENO)
}
deinit {
try? self.inputPipe.fileHandleForReading.close()
try? self.errorPipe.fileHandleForReading.close()
}
private func handle(data: Data, isError: Bool = false) {
// Write output to STDOUT
self.outputPipe.fileHandleForWriting.write(data)
guard let string = String(data: data, encoding: .utf8) else {
return
}
DispatchQueue.main.async {
self.consoleManager.print(string)
}
}
}

View File

@@ -28,7 +28,7 @@ extension AppManager
}
@available(iOS 13, *)
final class AppManagerPublisher: ObservableObject
class AppManagerPublisher: ObservableObject
{
@Published
fileprivate(set) var installationProgress = [String: Progress]()
@@ -42,7 +42,7 @@ private func ==(lhs: OperatingSystemVersion, rhs: OperatingSystemVersion) -> Boo
return (lhs.majorVersion == rhs.majorVersion && lhs.minorVersion == rhs.minorVersion && lhs.patchVersion == rhs.patchVersion)
}
final class AppManager
class AppManager
{
static let shared = AppManager()
@@ -392,8 +392,7 @@ extension AppManager
func fetchAppIDs(completionHandler: @escaping (Result<([AppID], NSManagedObjectContext), Error>) -> Void)
{
let authenticationOperation = self.authenticate(presentingViewController: nil) { (result) in
// 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)
print("Authenticated for fetching App IDs with result:", result)
}
let fetchAppIDsOperation = FetchAppIDsOperation(context: authenticationOperation.context)
@@ -665,7 +664,7 @@ extension AppManager
@available(iOS 14, *)
func enableJIT(for installedApp: InstalledApp, completionHandler: @escaping (Result<Void, Error>) -> Void)
{
final class Context: OperationContext, EnableJITContext
class Context: OperationContext, EnableJITContext
{
var installedApp: InstalledApp?
}
@@ -685,7 +684,7 @@ extension AppManager
@available(iOS 14.0, *)
func patch(resignedApp: ALTApplication, presentingViewController: UIViewController, context authContext: AuthenticatedOperationContext, completionHandler: @escaping (Result<InstalledApp, Error>) -> Void) -> PatchAppOperation
{
final class Context: InstallAppOperationContext, PatchAppContext
class Context: InstallAppOperationContext, PatchAppContext
{
}
@@ -876,9 +875,7 @@ private extension AppManager
if app.certificateSerialNumber != group.context.certificate?.serialNumber ||
uti != nil ||
app.needsResign ||
// We need to reinstall ourselves on refresh to ensure the new provisioning profile is used
app.bundleIdentifier == StoreApp.altstoreAppID
app.needsResign
{
// Resign app instead of just refreshing profiles because either:
// * Refreshing using different certificate

View File

@@ -8,7 +8,7 @@
import UIKit
final class InstalledAppsCollectionHeaderView: UICollectionReusableView
class InstalledAppsCollectionHeaderView: UICollectionReusableView
{
let textLabel: UILabel
let button: UIButton

View File

@@ -9,7 +9,7 @@
import UIKit
import Roxas
final class InstalledAppCollectionViewCell: UICollectionViewCell
class InstalledAppCollectionViewCell: UICollectionViewCell
{
private(set) var deactivateBadge: UIView?
@@ -55,13 +55,13 @@ final class InstalledAppCollectionViewCell: UICollectionViewCell
}
}
final class InstalledAppsCollectionFooterView: UICollectionReusableView
class InstalledAppsCollectionFooterView: UICollectionReusableView
{
@IBOutlet var textLabel: UILabel!
@IBOutlet var button: UIButton!
}
final class NoUpdatesCollectionViewCell: UICollectionViewCell
class NoUpdatesCollectionViewCell: UICollectionViewCell
{
@IBOutlet var blurView: UIVisualEffectView!
@@ -73,7 +73,7 @@ final class NoUpdatesCollectionViewCell: UICollectionViewCell
}
}
final class UpdatesCollectionHeaderView: UICollectionReusableView
class UpdatesCollectionHeaderView: UICollectionReusableView
{
let button = PillButton(type: .system)

View File

@@ -30,7 +30,7 @@ extension MyAppsViewController
}
}
final class MyAppsViewController: UICollectionViewController
class MyAppsViewController: UICollectionViewController
{
private let coordinator = NSFileCoordinator()
private let operationQueue = OperationQueue()

View File

@@ -17,7 +17,7 @@ extension UpdateCollectionViewCell
}
}
@objc final class UpdateCollectionViewCell: UICollectionViewCell
@objc class UpdateCollectionViewCell: UICollectionViewCell
{
var mode: Mode = .expanded {
didSet {

View File

@@ -8,7 +8,7 @@
import UIKit
final class NewsCollectionViewCell: UICollectionViewCell
class NewsCollectionViewCell: UICollectionViewCell
{
@IBOutlet var titleLabel: UILabel!
@IBOutlet var captionLabel: UILabel!

View File

@@ -14,7 +14,7 @@ import Roxas
import Nuke
private final class AppBannerFooterView: UICollectionReusableView
private class AppBannerFooterView: UICollectionReusableView
{
let bannerView = AppBannerView(frame: .zero)
let tapGestureRecognizer = UITapGestureRecognizer(target: nil, action: nil)
@@ -41,7 +41,7 @@ private final class AppBannerFooterView: UICollectionReusableView
}
}
final class NewsViewController: UICollectionViewController
class NewsViewController: UICollectionViewController
{
private lazy var dataSource = self.makeDataSource()
private lazy var placeholderView = RSTPlaceholderView(frame: .zero)

View File

@@ -7,7 +7,6 @@
//
import Foundation
import SwiftUI
import Roxas
import Network
@@ -35,22 +34,22 @@ enum AuthenticationError: LocalizedError
}
@objc(AuthenticationOperation)
final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, ALTAppleAPISession)>
class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, ALTAppleAPISession)>
{
let context: AuthenticatedOperationContext
private weak var presentingViewController: UIViewController?
// private lazy var navigationController: UINavigationController = {
// let navigationController = self.storyboard.instantiateViewController(withIdentifier: "navigationController") as! UINavigationController
// if #available(iOS 13.0, *)
// {
// navigationController.isModalInPresentation = true
// }
// return navigationController
// }()
//
// private lazy var storyboard = UIStoryboard(name: "Authentication", bundle: nil)
private lazy var navigationController: UINavigationController = {
let navigationController = self.storyboard.instantiateViewController(withIdentifier: "navigationController") as! UINavigationController
if #available(iOS 13.0, *)
{
navigationController.isModalInPresentation = true
}
return navigationController
}()
private lazy var storyboard = UIStoryboard(name: "Authentication", bundle: nil)
private var appleIDEmailAddress: String?
private var appleIDPassword: String?
@@ -267,8 +266,7 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A
super.finish(result)
DispatchQueue.main.async {
// self.navigationController.dismiss(animated: true, completion: nil)
self.dismiss()
self.navigationController.dismiss(animated: true, completion: nil)
}
}
}
@@ -278,8 +276,7 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A
super.finish(result)
DispatchQueue.main.async {
// self.navigationController.dismiss(animated: true, completion: nil)
self.dismiss()
self.navigationController.dismiss(animated: true, completion: nil)
}
}
}
@@ -290,33 +287,25 @@ private extension AuthenticationOperation
{
func present(_ viewController: UIViewController) -> Bool
{
UIApplication.shared.keyWindow?.rootViewController?.present(viewController, animated: true)
// guard let presentingViewController = self.presentingViewController else { return false }
//
// self.navigationController.view.tintColor = .white
//
// if self.navigationController.viewControllers.isEmpty
// {
// guard presentingViewController.presentedViewController == nil else { return false }
//
// self.navigationController.setViewControllers([viewController], animated: false)
// presentingViewController.present(self.navigationController, animated: true, completion: nil)
// }
// else
// {
// viewController.navigationItem.leftBarButtonItem = nil
// self.navigationController.pushViewController(viewController, animated: true)
// }
guard let presentingViewController = self.presentingViewController else { return false }
self.navigationController.view.tintColor = .white
if self.navigationController.viewControllers.isEmpty
{
guard presentingViewController.presentedViewController == nil else { return false }
self.navigationController.setViewControllers([viewController], animated: false)
presentingViewController.present(self.navigationController, animated: true, completion: nil)
}
else
{
viewController.navigationItem.leftBarButtonItem = nil
self.navigationController.pushViewController(viewController, animated: true)
}
return true
}
func dismiss() {
if let presentingViewController {
presentingViewController.dismiss(animated: true)
}
// UIApplication.shared.keyWindow?.rootViewController?.presentedViewController?.dismiss(animated: true)
}
}
private extension AuthenticationOperation
@@ -326,29 +315,29 @@ private extension AuthenticationOperation
func authenticate()
{
DispatchQueue.main.async {
let viewController = UIHostingController(rootView: NavigationView {
ConnectAppleIDView { appleID, password, completionHandler in
self.authenticate(appleID: appleID, password: password) { (result) in
completionHandler(result)
}
} completionHandler: { result in
if let (account, session, password) = result
{
// We presented the Auth UI and the user signed in.
// In this case, we'll assume we should show the instructions again.
self.shouldShowInstructions = true
self.appleIDPassword = password
completionHandler(.success((account, session)))
}
else
{
completionHandler(.failure(OperationError.cancelled))
}
let authenticationViewController = self.storyboard.instantiateViewController(withIdentifier: "authenticationViewController") as! AuthenticationViewController
authenticationViewController.authenticationHandler = { (appleID, password, completionHandler) in
self.authenticate(appleID: appleID, password: password) { (result) in
completionHandler(result)
}
})
}
authenticationViewController.completionHandler = { (result) in
if let (account, session, password) = result
{
// We presented the Auth UI and the user signed in.
// In this case, we'll assume we should show the instructions again.
self.shouldShowInstructions = true
self.appleIDPassword = password
completionHandler(.success((account, session)))
}
else
{
completionHandler(.failure(OperationError.cancelled))
}
}
if !self.present(viewController)
if !self.present(authenticationViewController)
{
completionHandler(.failure(OperationError.notAuthenticated))
}
@@ -390,8 +379,8 @@ private extension AuthenticationOperation
case .success(let anisetteData):
let verificationHandler: ((@escaping (String?) -> Void) -> Void)?
// if let presentingViewController = self.presentingViewController
// {
if let presentingViewController = self.presentingViewController
{
verificationHandler = { (completionHandler) in
DispatchQueue.main.async {
let alertController = UIAlertController(title: NSLocalizedString("Please enter the 6-digit verification code that was sent to your Apple devices.", comment: ""), message: nil, preferredStyle: .alert)
@@ -417,22 +406,22 @@ private extension AuthenticationOperation
completionHandler(nil)
})
// if self.navigationController.presentingViewController != nil
// {
// self.navigationController.present(alertController, animated: true, completion: nil)
// }
// else
// {
// presentingViewController.present(alertController, animated: true, completion: nil)
// }
if self.navigationController.presentingViewController != nil
{
self.navigationController.present(alertController, animated: true, completion: nil)
}
else
{
presentingViewController.present(alertController, animated: true, completion: nil)
}
}
}
// }
// else
// {
// // No view controller to present security code alert, so don't provide verificationHandler.
// verificationHandler = nil
// }
}
else
{
// No view controller to present security code alert, so don't provide verificationHandler.
verificationHandler = nil
}
ALTAppleAPI.shared.authenticate(appleID: appleID, password: password, anisetteData: anisetteData,
verificationHandler: verificationHandler) { (account, session, error) in
@@ -463,15 +452,15 @@ private extension AuthenticationOperation
}
} else {
DispatchQueue.main.async {
// let selectTeamViewController = self.storyboard.instantiateViewController(withIdentifier: "selectTeamViewController") as! SelectTeamViewController
//
// selectTeamViewController.teams = teams
// selectTeamViewController.completionHandler = completionHandler
//
// if !self.present(selectTeamViewController)
// {
// return completionHandler(.failure(AuthenticationError.noTeam))
// }
let selectTeamViewController = self.storyboard.instantiateViewController(withIdentifier: "selectTeamViewController") as! SelectTeamViewController
selectTeamViewController.teams = teams
selectTeamViewController.completionHandler = completionHandler
if !self.present(selectTeamViewController)
{
return completionHandler(.failure(AuthenticationError.noTeam))
}
}
}
}
@@ -653,21 +642,20 @@ private extension AuthenticationOperation
func showInstructionsIfNecessary(completionHandler: @escaping (Bool) -> Void)
{
return completionHandler(false)
// guard self.shouldShowInstructions else { return completionHandler(false) }
//
// DispatchQueue.main.async {
// let instructionsViewController = self.storyboard.instantiateViewController(withIdentifier: "instructionsViewController") as! InstructionsViewController
// instructionsViewController.showsBottomButton = true
// instructionsViewController.completionHandler = {
// completionHandler(true)
// }
//
// if !self.present(instructionsViewController)
// {
// completionHandler(false)
// }
// }
guard self.shouldShowInstructions else { return completionHandler(false) }
DispatchQueue.main.async {
let instructionsViewController = self.storyboard.instantiateViewController(withIdentifier: "instructionsViewController") as! InstructionsViewController
instructionsViewController.showsBottomButton = true
instructionsViewController.completionHandler = {
completionHandler(true)
}
if !self.present(instructionsViewController)
{
completionHandler(false)
}
}
}
func showRefreshScreenIfNecessary(signer: ALTSigner, session: ALTAppleAPISession, completionHandler: @escaping (Bool) -> Void)

View File

@@ -11,7 +11,6 @@ import CoreData
import AltStoreCore
import EmotionalDamage
import minimuxer
enum RefreshError: LocalizedError
{
@@ -52,7 +51,7 @@ private let ReceivedApplicationState: @convention(c) (CFNotificationCenter?, Uns
}
@objc(BackgroundRefreshAppsOperation)
final class BackgroundRefreshAppsOperation: ResultOperation<[String: Result<InstalledApp, Error>]>
class BackgroundRefreshAppsOperation: ResultOperation<[String: Result<InstalledApp, Error>]>
{
let installedApps: [InstalledApp]
private let managedObjectContext: NSManagedObjectContext
@@ -98,14 +97,6 @@ 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))
}
start_auto_mounter(documentsDirectory)
self.managedObjectContext.perform {
print("Apps to refresh:", self.installedApps.map(\.bundleIdentifier))

View File

@@ -14,7 +14,7 @@ import Roxas
import minimuxer
@objc(DeactivateAppOperation)
final class DeactivateAppOperation: ResultOperation<InstalledApp>
class DeactivateAppOperation: ResultOperation<InstalledApp>
{
let app: InstalledApp
let context: OperationContext
@@ -44,9 +44,14 @@ final class DeactivateAppOperation: ResultOperation<InstalledApp>
for profile in allIdentifiers {
do {
try remove_provisioning_profile(profile)
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)))
} catch {
return self.finish(.failure(error))
self.finish(.failure(ALTServerError(.unknownResponse)))
}
}

View File

@@ -30,7 +30,7 @@ private extension DownloadAppOperation
}
@objc(DownloadAppOperation)
final class DownloadAppOperation: ResultOperation<ALTApplication>
class DownloadAppOperation: ResultOperation<ALTApplication>
{
let app: AppProtocol
let context: AppOperationContext

View File

@@ -21,7 +21,7 @@ protocol EnableJITContext
}
@available(iOS 14, *)
final class EnableJITOperation<Context: EnableJITContext>: ResultOperation<Void>
class EnableJITOperation<Context: EnableJITContext>: ResultOperation<Void>
{
let context: Context
@@ -45,13 +45,23 @@ 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 {
try debug_app(installedApp.resignedBundleIdentifier)
var x = try debug_app(app_id: installedApp.resignedBundleIdentifier)
switch x {
case .Good:
self.finish(.success(()))
case .Bad(let code):
self.finish(.failure(minimuxer_to_operation(code: code)))
}
} catch Uhoh.Bad(let code) {
self.finish(.failure(minimuxer_to_operation(code: code)))
} catch {
return self.finish(.failure(error))
self.finish(.failure(OperationError.unknown))
}
self.finish(.success(()))
}
}
}

View File

@@ -7,28 +7,15 @@
//
import Foundation
import CommonCrypto
import Starscream
import AltStoreCore
import AltSign
import Roxas
@objc(FetchAnisetteDataOperation)
final class FetchAnisetteDataOperation: ResultOperation<ALTAnisetteData>, WebSocketDelegate
class FetchAnisetteDataOperation: ResultOperation<ALTAnisetteData>
{
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)
{
@@ -45,412 +32,32 @@ final class FetchAnisetteDataOperation: ResultOperation<ALTAnisetteData>, WebSoc
return
}
self.url = AnisetteManager.currentURL
print("Anisette URL: \(self.url!.absoluteString)")
let url = AnisetteManager.currentURL
DLOG("Anisette URL: %@", url.absoluteString)
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") }
}
}
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else { return }
// 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)
// 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))
}
}
} 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: WebSocket) {
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 WebSocket {
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) } }
}

View File

@@ -13,7 +13,7 @@ import AltSign
import Roxas
@objc(FetchAppIDsOperation)
final class FetchAppIDsOperation: ResultOperation<([AppID], NSManagedObjectContext)>
class FetchAppIDsOperation: ResultOperation<([AppID], NSManagedObjectContext)>
{
let context: AuthenticatedOperationContext
let managedObjectContext: NSManagedObjectContext

View File

@@ -13,7 +13,7 @@ import AltSign
import Roxas
@objc(FetchProvisioningProfilesOperation)
final class FetchProvisioningProfilesOperation: ResultOperation<[String: ALTProvisioningProfile]>
class FetchProvisioningProfilesOperation: ResultOperation<[String: ALTProvisioningProfile]>
{
let context: AppOperationContext
@@ -268,17 +268,8 @@ extension FetchProvisioningProfilesOperation
}
}
}
//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: appIDName, bundleIdentifier: bundleIdentifier, team: team, session: session) { (appID, error) in
ALTAppleAPI.shared.addAppID(withName: name, bundleIdentifier: bundleIdentifier, team: team, session: session) { (appID, error) in
do
{
do
@@ -393,39 +384,19 @@ 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] = altStoreAppGroupID
applicationGroups[index] = Bundle.baseAltStoreAppGroupID
}
else
{
applicationGroups.append(altStoreAppGroupID)
applicationGroups.append(Bundle.baseAltStoreAppGroupID)
}
}
print("Application groups: \(applicationGroups)")
// Dispatch onto global queue to prevent appGroupsLock deadlock.
DispatchQueue.global().async {
@@ -507,13 +478,10 @@ extension FetchProvisioningProfilesOperation
ALTAppleAPI.shared.delete(profile, for: team, session: session) { (success, error) in
switch Result(success, error)
{
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))
case .failure(let error): completionHandler(.failure(error))
case .success:
// Fetch new provisioning profile
// Fetch new provisiong profile
ALTAppleAPI.shared.fetchProvisioningProfile(for: appID, deviceType: .iphone, team: team, session: session) { (profile, error) in
completionHandler(Result(profile, error))
}

View File

@@ -13,7 +13,7 @@ import AltStoreCore
import Roxas
@objc(FetchSourceOperation)
final class FetchSourceOperation: ResultOperation<Source>
class FetchSourceOperation: ResultOperation<Source>
{
let sourceURL: URL
let managedObjectContext: NSManagedObjectContext

View File

@@ -32,7 +32,7 @@ extension FetchTrustedSourcesOperation
}
}
final class FetchTrustedSourcesOperation: ResultOperation<[FetchTrustedSourcesOperation.TrustedSource]>
class FetchTrustedSourcesOperation: ResultOperation<[FetchTrustedSourcesOperation.TrustedSource]>
{
override func main()
{

View File

@@ -11,10 +11,9 @@ import Network
import AltStoreCore
import AltSign
import Roxas
import minimuxer
@objc(InstallAppOperation)
final class InstallAppOperation: ResultOperation<InstalledApp>
class InstallAppOperation: ResultOperation<InstalledApp>
{
let context: InstallAppOperationContext
@@ -149,72 +148,17 @@ final class InstallAppOperation: ResultOperation<InstalledApp>
})
}
var installing = true
if installedApp.storeApp?.bundleIdentifier == Bundle.Info.appbundleIdentifier {
// 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 = "To finish refreshing, SideStore must be moved to the background, which it does by opening Safari. Please reopen SideStore after it is done refreshing!"
let notification = UNNotificationRequest(identifier: Bundle.Info.appbundleIdentifier + ".FinishRefreshNotification", content: content, trigger: UNTimeIntervalNotificationTrigger(timeInterval: 2, repeats: false))
UNUserNotificationCenter.current().add(notification)
DispatchQueue.main.async { UIApplication.shared.open(URL(string: "x-web-search://")!) }
break
default:
print("Notifications are not enabled")
let alert = UIAlertController(title: "Finish Refresh", message: "To finish refreshing, SideStore must be moved to the background. To do this, you can either go to the Home Screen or open Safari by pressing Continue. Please reopen SideStore after doing this.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("Continue", comment: ""), style: .default, handler: { _ in
print("Opening Safari")
DispatchQueue.main.async { UIApplication.shared.open(URL(string: "x-web-search://")!) }
}))
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 open Safari")
UIApplication.shared.open(URL(string: "x-web-search://")!)
}
}
break
}
}
}
}
let ns_bundle = NSString(string: installedApp.bundleIdentifier)
let ns_bundle_ptr = UnsafeMutablePointer<CChar>(mutating: ns_bundle.utf8String)
do {
try install_ipa(installedApp.bundleIdentifier)
installing = false
} catch {
installing = false
return self.finish(.failure(error))
let res = minimuxer_install_ipa(ns_bundle_ptr)
if res == 0 {
installedApp.refreshedDate = Date()
self.finish(.success(installedApp))
} else {
self.finish(.failure(minimuxer_to_operation(code: res)))
}
installedApp.refreshedDate = Date()
self.finish(.success(installedApp))
}
}
@@ -230,11 +174,10 @@ 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)
}
}

View File

@@ -38,7 +38,7 @@ class OperationContext
}
}
final class AuthenticatedOperationContext: OperationContext
class AuthenticatedOperationContext: OperationContext
{
var session: ALTAppleAPISession?

View File

@@ -8,7 +8,6 @@
import Foundation
import AltSign
import minimuxer
enum OperationError: LocalizedError
{
@@ -34,9 +33,18 @@ enum OperationError: LocalizedError
case openAppFailed(name: String)
case missingAppGroup
case anisetteV1Error(message: String)
case provisioningError(result: String, message: String?)
case anisetteV3Error(message: String)
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
var failureReason: String? {
switch self {
@@ -53,9 +61,18 @@ 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 .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 .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: "")
}
}
@@ -101,66 +118,49 @@ enum OperationError: LocalizedError
}
}
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:
return NSLocalizedString("Unable to install the app from the staging directory", 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)
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
}
}

View File

@@ -52,7 +52,7 @@ private struct OTAUpdate
}
@available(iOS 14, *)
final class PatchAppOperation: ResultOperation<Void>
class PatchAppOperation: ResultOperation<Void>
{
let context: PatchAppContext

View File

@@ -29,7 +29,7 @@ extension PatchViewController
}
@available(iOS 14.0, *)
final class PatchViewController: UIViewController
class PatchViewController: UIViewController
{
var patchApp: AnyApp?
var installedApp: InstalledApp?

View File

@@ -14,7 +14,7 @@ import Roxas
import minimuxer
@objc(RefreshAppOperation)
final class RefreshAppOperation: ResultOperation<InstalledApp>
class RefreshAppOperation: ResultOperation<InstalledApp>
{
let context: AppOperationContext
@@ -49,12 +49,15 @@ final class RefreshAppOperation: ResultOperation<InstalledApp>
for p in profiles {
do {
let bytes = p.value.data.toRustByteSlice()
try install_provisioning_profile(bytes.forRust())
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 {
return self.finish(.failure(error))
self.finish(.failure(OperationError.unknown))
}
self.progress.completedUnitCount += 1
let predicate = NSPredicate(format: "%K == %@", #keyPath(InstalledApp.bundleIdentifier), app.bundleIdentifier)

View File

@@ -12,7 +12,7 @@ import CoreData
import AltStoreCore
import AltSign
final class RefreshGroup: NSObject
class RefreshGroup: NSObject
{
let context: AuthenticatedOperationContext
let progress = Progress.discreteProgress(totalUnitCount: 0)

View File

@@ -9,7 +9,7 @@
import Foundation
@objc(RemoveAppBackupOperation)
final class RemoveAppBackupOperation: ResultOperation<Void>
class RemoveAppBackupOperation: ResultOperation<Void>
{
let context: InstallAppOperationContext

View File

@@ -12,7 +12,7 @@ import AltStoreCore
import minimuxer
@objc(RemoveAppOperation)
final class RemoveAppOperation: ResultOperation<InstalledApp>
class RemoveAppOperation: ResultOperation<InstalledApp>
{
let context: InstallAppOperationContext
@@ -39,11 +39,15 @@ final class RemoveAppOperation: ResultOperation<InstalledApp>
let resignedBundleIdentifier = installedApp.resignedBundleIdentifier
do {
try remove_app(resignedBundleIdentifier)
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)))
} catch {
return self.finish(.failure(error))
self.finish(.failure(ALTServerError(.appDeletionFailed)))
}
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
self.progress.completedUnitCount += 1

View File

@@ -13,7 +13,7 @@ import AltStoreCore
import AltSign
@objc(ResignAppOperation)
final class ResignAppOperation: ResultOperation<ALTApplication>
class ResignAppOperation: ResultOperation<ALTApplication>
{
let context: InstallAppOperationContext
@@ -61,7 +61,6 @@ 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 }
@@ -148,14 +147,6 @@ 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 {

View File

@@ -9,10 +9,9 @@ import Foundation
import Network
import AltStoreCore
import minimuxer
@objc(SendAppOperation)
final class SendAppOperation: ResultOperation<()>
class SendAppOperation: ResultOperation<()>
{
let context: InstallAppOperationContext
@@ -45,18 +44,24 @@ 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) {
do {
let bytes = Data(data).toRustByteSlice()
try yeet_app_afc(app.bundleIdentifier, bytes.forRust())
} catch {
return self.finish(.failure(error))
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)")
self.progress.completedUnitCount += 1
self.finish(.success(()))
} else {
self.finish(.failure(minimuxer_to_operation(code: res)))
}
self.progress.completedUnitCount += 1
self.finish(.success(()))
} else {
print("IPA doesn't exist????")
self.finish(.failure(ALTServerError(.underlyingError)))
}
}

View File

@@ -30,7 +30,7 @@ extension UpdatePatronsOperation
}
}
final class UpdatePatronsOperation: ResultOperation<Void>
class UpdatePatronsOperation: ResultOperation<Void>
{
let context: NSManagedObjectContext

View File

@@ -55,7 +55,7 @@ enum VerificationError: ALTLocalizedError
}
@objc(VerifyAppOperation)
final class VerifyAppOperation: ResultOperation<Void>
class VerifyAppOperation: ResultOperation<Void>
{
let context: AppOperationContext
var verificationHandler: ((VerificationError) -> Bool)?

View File

@@ -1,23 +0,0 @@
//
// Filterable.swift
// SideStore
//
// Created by Fabian Thies on 01.12.22.
// Copyright © 2022 SideStore. All rights reserved.
//
import Foundation
protocol Filterable {
func matches(_ searchText: String) -> Bool
}
extension Collection where Element: Filterable {
func matches(_ searchText: String) -> Bool {
self.contains(where: { $0.matches(searchText) })
}
func items(matching searchText: String) -> [Element] {
self.filter { $0.matches(searchText) }
}
}

View File

@@ -1,22 +0,0 @@
//
// NavigationTab.swift
// SideStoreUI
//
// Created by Fabian Thies on 18.11.22.
// Copyright © 2022 Fabian Thies. All rights reserved.
//
import Foundation
import SFSafeSymbols
protocol NavigationTab: RawRepresentable, Identifiable, CaseIterable, Hashable where RawValue == Int {
static var defaultTab: Self { get }
var displaySymbol: SFSymbol { get }
var displayName: String { get }
}
extension NavigationTab {
var id: Int {
self.rawValue
}
}

View File

@@ -1,11 +0,0 @@
//
// ViewModel.swift
// SideStoreUI
//
// Created by Fabian Thies on 18.11.22.
// Copyright © 2022 Fabian Thies. All rights reserved.
//
import SwiftUI
protocol ViewModel: ObservableObject {}

Binary file not shown.

View File

@@ -1,20 +0,0 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xFA",
"green" : "0x05",
"red" : "0xA4"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,269 @@
{
"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 games 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 dont 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"
}
}

View File

@@ -1,166 +0,0 @@
/*
Localizable.strings
AltStore
Created by Fabian Thies on 22.12.22.
Copyright © 2022 SideStore. All rights reserved.
*/
/* General Actions */
"Action.done" = "Done";
"Action.close" = "Close";
/* NewsView */
"NewsView.title" = "News";
"NewsView.Section.FromSources.title" = "From your Sources";
/* BrowseView */
"BrowseView.title" = "Browse";
"BrowseView.search" = "Search";
"BrowseView.Section.PromotedCategories.title" = "Promoted Categories";
"BrowseView.Section.PromotedCategories.showAll" = "Show all";
"BrowseView.Section.AllApps.title" = "All Apps";
"BrowseView.Hints.NoApps.title" = "You don't have any apps yet.";
"BrowseView.Hints.NoApps.text" = "Apps are provided by \"sources\". The specification for them is an open standard, so everyone can create their own source. To get you started, we have compiled a list of \"Trusted Sources\" which you can check out by tapping the button below.";
"BrowseView.Hints.NoApps.addSource" = "Add Source";
"BrowseView.Actions.sources" = "Sources";
"BrowseView.Categories.gamesAndEmulators" = "Games and\nEmulators";
/* AppRowView */
"AppRowView.sideloaded" = "Sideloaded";
/* AppPillButton */
"AppPillButton.free" = "Free";
"AppPillButton.open" = "Open";
/* RootView */
"RootView.news" = "News";
"RootView.browse" = "Browse";
"RootView.myApps" = "My Apps";
"RootView.settings" = "Settings";
/* SettingsView */
"SettingsView.ConnectedAppleID.name" = "Name";
"SettingsView.ConnectedAppleID.eMail" = "E-Mail";
"SettingsView.ConnectedAppleID.type" = "Type";
"SettingsView.ConnectedAppleID.text" = "Connected Apple ID";
"SettingsView.ConnectedAppleID.signOut" = "Sign Out";
"SettingsView.ConnectedAppleID.Footer.p1" = "Your Apple ID is required to sign the apps you install with SideStore.";
"SettingsView.ConnectedAppleID.Footer.p2" = "Your credentials are only sent to Apple's servers and are not accessible by the SideStore Team. Once successfully logged in, the login details are stored securely on your device.";
"SettingsView.connectAppleID" = "Connect your Apple ID";
"SettingsView.backgroundRefresh" = "Background Refresh";
"SettingsView.addToSiri" = "Add to Siri...";
"SettingsView.refreshingApps" = "Refreshing Apps";
"SettingsView.switchToUIKit" = "Switch to UIKit";
"SettingsView.resetImageCache" = "Reset Image Cache";
"SettingsView.debug" = "Debug";
"SettingsView.swiftUIRedesign" = "SwiftUI Redesign";
"SettingsView.credits" = "Credits";
"SettingsView.title" = "Settings";
"SettingsView.refreshingAppsFooter" = "Enable Background Refresh to automatically refresh apps in the background when connected to WiFi and with Wireguard active.";
/* ConnectAppleIDView */
"ConnectAppleIDView.startWithSignIn" = "Sign in with your Apple ID to get started.";
"ConnectAppleIDView.signIn" = "Sign In";
"ConnectAppleIDView.appleID" = "Apple ID";
"ConnectAppleIDView.password" = "Password";
"ConnectAppleIDView.whyDoWeNeedThis" = "Why do we need this?";
"ConnectAppleIDView.footer" = "Your Apple ID is used to configure apps so they can be installed on this device. Your credentials will be stored securely in this device's Keychain and sent only to Apple for authentication.";
"ConnectAppleIDView.connectYourAppleID" = "Connect Your Apple ID";
"ConnectAppleIDView.cancel" = "Cancel";
"ConnectAppleIDView.failedToSignIn" = "Failed to Sign In";
/* SourcesView */
"SourcesView.sourcesDescription" = "Sources control what apps are available to download through SideStore.";
"SourcesView.remove" = "Remove";
"SourcesView.trustedSources" = "Trusted Sources";
"SourcesView.reviewedText" = "SideStore has reviewed these sources to make sure they meet our safety standards.";
"SourcesView.sources" = "Sources";
"SourcesView.done" = "Done";
/* AddSourceView */
"AddSourceView.sourceURL" = "Source URL";
"AddSourceView.sourceWarning" = "Please enter the source url here. Then, tap continue to validate and add the source in the next step.";
"AddSourceView.sourceWarningContinued" = "Be careful with unvalidated third-party sources! Make sure to only add sources that you trust.";
"AddSourceView.continue" = "Continue";
"AddSourceView.title" = "Add Source";
/* ConfirmAddSourceView */
"ConfirmAddSourceView.apps" = "Apps";
"ConfirmAddSourceView.newsItems" = "News Items";
"ConfirmAddSourceView.sourceContents" = "Source Contents";
"ConfirmAddSourceView.sourceIdentifier" = "Source Identifier";
"ConfirmAddSourceView.sourceURL" = "Source URL";
"ConfirmAddSourceView.sourceInfo" = "Source Information";
"ConfirmAddSourceView.addSource" = "Add Source";
/* AppPillButton */
"AppPillButton.free" = "Free";
"AppPillButton.open" = "Open";
/* AppAction */
"AppAction.install" = "Install";
"AppAction.open" = "Open";
"AppAction.refresh" = "Refresh";
"AppAction.activate" = "Activate";
"AppAction.deactivate" = "Deactivate";
"AppAction.remove" = "Remove";
"AppAction.enableJIT" = "Activate JIT";
"AppAction.backup" = "Backup";
"AppAction.exportBackup" = "Export backup";
"AppAction.restoreBackup" = "Restore backup";
"AppAction.chooseCustomIcon" = "Customize icon";
"AppAction.resetIcon" = "Reset icon";
/* AppDetailView*/
"AppDetailView.Badge.official" = "Official App";
"AppDetailView.Badge.trusted" = "From Trusted Source";
"AppDetailView.noScreenshots" = "No screenshots available for this app.";
"AppDetailView.more" = "More...";
"AppDetailView.whatsNew" = "What's New";
"AppDetailView.WhatsNew.versionHistory" = "Version History";
"AppDetailView.WhatsNew.showOnGithub" = "Show project on GitHub";
"AppDetailView.reviews" = "Ratings & Reviews";
"AppDetailView.Reviews.outOf" = "out of %d";
"AppDetailView.Reviews.ratings" = "%d Ratings";
"AppDetailView.Reviews.seeAll" = "See All";
"AppDetailView.version" = "Version %@";
"AppDetailView.noVersionInformation" = "No version information";
"AppDetailView.noPermissions" = "The app requires no permissions.";
"AppDetailView.permissions" = "Permissions";
"AppDetailView.information" = "Information";
"AppDetailView.Information.source" = "Source";
"AppDetailView.Information.developer" = "Developer";
"AppDetailView.Information.size" = "Size";
"AppDetailView.Information.latestVersion" = "Latest Version";
"AppDetailView.Information.compatibility" = "Compatibility";
"AppDetailView.Information.compatibilityCompatible" = "Unknown";
"AppDetailView.Information.compatibilityUnknown" = "Unknown";
"AppDetailView.Information.compatibilityAtLeast" = "Requires iOS %@ or higher";
"AppDetailView.Information.compatibilityOrLower" = "Requires iOS %@ or lower";
/* AppPermissionGrid */
"AppPermissionGrid.usageDescription" = "Usage Description";
/* MyAppsView */
"MyAppsView.active" = "Active";
"MyAppsView.sideloading" = "Sideloading in progress...";
"MyAppsView.refreshAll" = "Refresh All";
"MyAppsView.appIDsRemaining" = "App IDs Remaining";
"MyAppsView.failedToRefresh" = "Failed to refresh";
"MyAppsView.apps" = "apps"; /* Keep this lowercase */
"MyAppsView.viewAppIDs" = "View App IDs";
"MyAppsView.myApps" = "My Apps";
"MyAppsView.Hints.NoUpdates.title" = "All Apps are Up To Date";
"MyAppsView.Hints.NoUpdates.text" = "You will be notified once updates for your apps are available. The updates will then be shown here.";
"MyAppsView.Hints.NoUpdates.dismissForNow" = "Dismiss for now";
"MyAppsView.Hints.NoUpdates.dontShowAgain" = "Don't show this again";
/* AppIDsView */
"AppIDsView.title" = "App IDs";
"AppIDsView.description" = "Each app and app extension installed with SideStore must register an App ID with Apple.\n\nApp IDs for paid developer accounts never expire, and there is no limit to how many you can create.";

View File

@@ -1,124 +0,0 @@
/*
Localizable.strings
AltStore
Created by Joshua Laymon on 23.12.22.
Updated by Gabriel Morazán on 25.12.22.
Copyright © 2022 SideStore. All rights reserved.
*/
/* NewsView */
"NewsView.title" = "Noticias";
"NewsView.Section.FromSources.title" = "De tus fuentes";
/* BrowseView */
"BrowseView.title" = "Navegar";
"BrowseView.search" = "Buscar";
"BrowseView.Section.PromotedCategories.title" = "Categorías promocionadas";
"BrowseView.Section.PromotedCategories.showAll" = "Mostrar todo";
"BrowseView.Section.AllApps.title" = "Todas las aplicaciones";
"BrowseView.Actions.sources" = "Fuentes";
"BrowseView.Categories.gamesAndEmulators" = "Juegos y\nemuladores";
/* RootView */
"RootView.news" = "Noticias";
"RootView.browse" = "Navegar";
"RootView.myApps" = "Mis apps";
"RootView.settings" = "Configuración";
/* SettingsView */
"SettingsView.ConnectedAppleID.name" = "Nombre";
"SettingsView.ConnectedAppleID.eMail" = "Correo electrónico";
"SettingsView.ConnectedAppleID.type" = "Tipo";
"SettingsView.ConnectedAppleID.text" = "Apple ID conectado";
"SettingsView.ConnectedAppleID.signOut" = "Cerrar sesión";
"SettingsView.ConnectedAppleID.Footer.p1" = "Se requiere de su Apple ID para firmar las aplicaciones que se instalan con SideStore.";
"SettingsView.ConnectedAppleID.Footer.p2" = "Sus datos son enviados solamente a Apple, SideStore Team no tiene acceso alguno a sus credenciales. Los detalles del inicio de sesión serán guardadas de forma segura en su dispositivo después de haber iniciado sesión exitosamente.";
"SettingsView.connectAppleID" = "Inicia sesión con un Apple ID";
"SettingsView.backgroundRefresh" = "Refrescar en segundo plano";
"SettingsView.addToSiri" = "Agregar a Siri...";
"SettingsView.refreshingApps" = "Refrescando las aplicaciones";
"SettingsView.switchToUIKit" = "Cambiar a UIKit";
"SettingsView.resetImageCache" = "Borrar el caché de imagenes";
"SettingsView.debug" = "Depurar";
"SettingsView.swiftUIRedesign" = "Rediseño de SwiftUI";
"SettingsView.credits" = "Créditos";
"SettingsView.title" = "Configuración";
"SettingsView.refreshingAppsFooter" = "Active el refresco en segundo plano para que las aplicaciones sean refrescadas automáticamente cuando estés conectado a una red Wi-Fi y tengas a WireGuard activado.";
/* ConnectAppleIDView */
"ConnectAppleIDView.appleID" = "Apple ID";
"ConnectAppleIDView.startWithSignIn" = "Inicie sesión con su Apple ID para comenzar.";
"ConnectAppleIDView.signIn" = "Iniciar sesión";
"ConnectAppleIDView.password" = "Contraseña";
"ConnectAppleIDView.whyDoWeNeedThis" = "¿Por qué necesitamos esto?";
"ConnectAppleIDView.footer" = "Su Apple ID es usado para configurar las aplicaciones que se instalarán en su dispositivo. Sus credenciales serán guardadas de forma segura en su dispositivo y seran enviados solamente a Apple para ser autenticados.";
"ConnectAppleIDView.connectYourAppleID" = "Conectar su Apple ID";
"ConnectAppleIDView.cancel" = "Cancelar";
"ConnectAppleIDView.failedToSignIn" = "No se pudo iniciar sesión";
/* SourcesView */
"SourcesView.sourcesDescription" = "Las fuentes determinan que aplicaciones estarán disponibles a través del SideStore.";
"SourcesView.remove" = "Eliminar";
"SourcesView.trustedSources" = "Fuentes confiables";
"SourcesView.reviewedText" = "SideStore Team ha verificado que estas fuentes son seguras de usar.";
"SourcesView.sources" = "Fuentes";
"SourcesView.done" = "Listo";
/* AddSourceView */
"AddSourceView.sourceURL" = "URL de fuente";
"AddSourceView.sourceWarning" = "Introduce el URL de una fuente aquí. Luego, toca \"Continuar\" para validar y agregar la fuente en el siguiente paso.";
"AddSourceView.sourceWarningContinued" = "¡Ten cuidado con las fuentes de terceros! Asegurate de solo agregar las fuentes en las que confíes.";
"AddSourceView.continue" = "Continuar";
"AddSourceView.title" = "Agregar fuente";
/* ConfirmAddSourceView */
"ConfirmAddSourceView.apps" = "Aplicaciones";
"ConfirmAddSourceView.newsItems" = "Noticias";
"ConfirmAddSourceView.sourceContents" = "Contenidos de la fuente";
"ConfirmAddSourceView.sourceIdentifier" = "Identificador de la fuente";
"ConfirmAddSourceView.sourceURL" = "URL de la fuente";
"ConfirmAddSourceView.sourceInfo" = "Información sobre la fuente";
"ConfirmAddSourceView.addSource" = "Agregar una fuente";
/* AppPillButton */
"AppPillButton.free" = "Gratis";
"AppPillButton.open" = "Abrir";
/* AppAction */
"AppAction.install" = "Instalar";
"AppAction.open" = "Abrir";
"AppAction.refresh" = "Refrescar";
"AppAction.activate" = "Activar";
"AppAction.deactivate" = "Desactivar";
"AppAction.remove" = "Eliminar";
"AppAction.enableJIT" = "Activar JIT";
"AppAction.backup" = "Respaldar";
"AppAction.exportBackup" = "Exportar respaldo";
"AppAction.restoreBackup" = "Restablecer respaldo";
"AppAction.chooseCustomIcon" = "Personalizar icono";
"AppAction.resetIcon" = "Reiniciar icono";
/* AppDetailView*/
"AppDetailView.more" = "Más...";
"AppDetailView.whatsNew" = "Novedades";
"AppDetailView.version" = "Versión";
"AppDetailView.noVersionInformation" = "Sin información de la versión";
"AppDetailView.noPermissions" = "Esta aplicación no requiere de permisos.";
"AppDetailView.permissions" = "Los permisos";
/* AppPermissionGrid */
"AppPermissionGrid.usageDescription" = "Descripciones de uso";
/* MyAppsView */
"MyAppsView.active" = "Activo";
"MyAppsView.sideloading" = "Instalación en progreso...";
"MyAppsView.refreshAll" = "Refrescar todo";
"MyAppsView.appIDsRemaining" = "IDs de aplicaciones restantes";
"MyAppsView.noUpdatesAvailable" = "No hay actualizaciones disponibles";
"MyAppsView.failedToRefresh" = "No se pudo refrescar";
"MyAppsView.apps" = "aplicaciones"; /* Keep this lowercase */
"MyAppsView.viewAppIDs" = "Ver IDs de las aplicaciones";
"MyAppsView.myApps" = "Mis aplicaciones";

View File

@@ -1,112 +0,0 @@
/* NewsView */
"NewsView.title" = "Actualités";
"BrowseView.search" = "Rechercher";
"BrowseView.Actions.sources" = "Sources";
"AppAction.chooseCustomIcon" = "Personaliser l'icone";
/* AppPermissionGrid */
"AppPermissionGrid.usageDescription" = "Description d'usage";
"MyAppsView.noUpdatesAvailable" = "Aucunes mises à jour disponibles";
/* BrowseView */
"BrowseView.title" = "Parcourir";
"BrowseView.Section.PromotedCategories.showAll" = "Montrer tout";
"BrowseView.Section.AllApps.title" = "Toutes les applications";
"BrowseView.Section.PromotedCategories.title" = "Catégories promus";
"ConnectAppleIDView.whyDoWeNeedThis" = "Pourquoi avons-nous besoin de cela?";
/* RootView */
"RootView.news" = "Actualités";
"RootView.browse" = "Parcourir";
"RootView.myApps" = "Mes applications";
"RootView.settings" = "Réglages";
/* SettingsView */
"SettingsView.ConnectedAppleID.name" = "Nom";
"SettingsView.ConnectedAppleID.eMail" = "Adresse courriel";
"SettingsView.ConnectedAppleID.type" = "Type";
"SettingsView.ConnectedAppleID.text" = "Identifiant Apple connecté";
"SettingsView.ConnectedAppleID.signOut" = "Déconnexion";
"SettingsView.connectAppleID" = "Connecter votre identifiant Apple";
"SettingsView.backgroundRefresh" = "Rafraîchissement en arrière plan";
"SettingsView.switchToUIKit" = "Changer vers UIKit";
"SettingsView.addToSiri" = "Ajouter à Siri...";
"SettingsView.debug" = "Déboguer";
"SettingsView.swiftUIRedesign" = "Refonte SwiftUI";
"SettingsView.credits" = "Crédits";
"SettingsView.title" = "Réglages";
"SettingsView.refreshingAppsFooter" = "Active la rafraîchissement dans l'arrière plan pour que SideStore soit capable de rafraîchir vos applications dans l'arrière plan lorsque vous êtes connecté au Wi-Fi avec Wireguard activé.";
/* ConnectAppleIDView */
"ConnectAppleIDView.startWithSignIn" = "Connectez-vous avec votre identifiant Apple pour commencer.";
"ConnectAppleIDView.signIn" = "Connection";
"ConnectAppleIDView.appleID" = "Identifiant Apple";
"ConnectAppleIDView.password" = "Mot de passe";
"ConnectAppleIDView.connectYourAppleID" = "Connecter votre identifiant Apple";
"ConnectAppleIDView.cancel" = "Annuler";
"ConnectAppleIDView.failedToSignIn" = "Tentative de connexion échouée";
/* SourcesView */
"SourcesView.sourcesDescription" = "Les sources contiennent des applications qui peuvent être installés avec SideStore.";
"SourcesView.remove" = "Enlever";
"SourcesView.trustedSources" = "Sources Fiables";
"SourcesView.reviewedText" = "L'équipe de SideStore a inspecté ces sources pour vérifier s'ils répondes à nos normes de sécurité.";
"SourcesView.sources" = "Sources";
"SourcesView.done" = "OK";
/* AddSourceView */
"AddSourceView.sourceURL" = "URL du Source";
"AddSourceView.continue" = "Continuer";
"AddSourceView.title" = "Ajouter une source";
"AppAction.exportBackup" = "Exporter la sauvegarde";
"AppAction.restoreBackup" = "Restaurer la sauvegarde";
"AppAction.resetIcon" = "Réinitialiser l'icone";
/* AppDetailView*/
"AppDetailView.more" = "Plus...";
"AppDetailView.whatsNew" = "Quoi de neuf";
"AppDetailView.version" = "Version";
"AppDetailView.noPermissions" = "Cette application nécessite aucune permissions.";
"AppDetailView.permissions" = "Permissions";
/* MyAppsView */
"MyAppsView.active" = "Active";
"MyAppsView.sideloading" = "Installation en cours...";
"MyAppsView.refreshAll" = "Rafraîchir tout";
"NewsView.Section.FromSources.title" = "De vos Sources";
"ConnectAppleIDView.footer" = "Votre identifiant Apple est utilisé pour configurer les applications pour qu'il peuvent être installé sur votre appareil. Les informations de votre compte vont être gardés en sécurité sur votre appareil et sont envoyés uniquement à Apple pour t'authentiquer.";
"AppDetailView.noVersionInformation" = "Aucune information sur la version";
"SettingsView.ConnectedAppleID.Footer.p1" = "Votre identifiant Apple est requis pour signer les applications que vous installez avec SideStore.";
"SettingsView.ConnectedAppleID.Footer.p2" = "Vos informations sont envoyés uniquement aux servers d'Apple et ne sont pas accessibles par l'équipe de SideStore. Les informations de votre compte vont être gardés uniquement sur votre appareil.";
"AddSourceView.sourceWarning" = "Entrez l'URL du source ici. En suite, appuyez \"Continuer\" pour valider le source et l'ajouter.";
"AddSourceView.sourceWarningContinued" = "Soyez prudent avec des sources non officiels! Ajoutez seulement des sources que vous jugez fiables.";
"ConfirmAddSourceView.sourceURL" = "URL du source";
"ConfirmAddSourceView.sourceInfo" = "Informations du source";
"ConfirmAddSourceView.addSource" = "Ajouter une source";
"AppPillButton.open" = "Ouvrir";
/* AppAction */
"AppAction.install" = "Installer";
"AppAction.open" = "Ouvrir";
"AppAction.refresh" = "Rafraîchir";
"AppAction.activate" = "Activer";
"AppAction.deactivate" = "Désactiver";
"AppAction.remove" = "Enlever";
"AppAction.enableJIT" = "Activer JIT";
"AppAction.backup" = "Sauvegarde";
"MyAppsView.failedToRefresh" = "Rafraîchissement échoué";
"MyAppsView.myApps" = "Mes Applications";
"BrowseView.Categories.gamesAndEmulators" = "Jeux et\némulateurs";
"MyAppsView.appIDsRemaining" = "Identifiants d'applications restantes";
"MyAppsView.apps" = "apps"; /* Keep this lowercase */
"MyAppsView.viewAppIDs" = "Voir les identifiants d'applications";
/* ConfirmAddSourceView */
"ConfirmAddSourceView.apps" = "Applications";
/* AppPillButton */
"AppPillButton.free" = "Gratuit";

View File

@@ -1,128 +0,0 @@
/*
Localizable.strings
AltStore
Created by Fabian Thies on 22.12.22.
Modified by mindfreakdev on 26.12.22
Copyright © 2022 SideStore. All rights reserved.
*/
/* NewsView */
"NewsView.title" = "समाचार";
"NewsView.Section.FromSources.title" = "अपने सूत्रों से";
/* BrowseView */
"BrowseView.title" = "ब्राउज़";
"BrowseView.search" = "खोज";
"BrowseView.Section.PromotedCategories.title" = "प्रचारित श्रेणियां";
"BrowseView.Section.PromotedCategories.showAll" = "सब दिखाएं";
"BrowseView.Section.AllApps.title" = "सभी एप्लीकेशन";
"BrowseView.Actions.sources" = "सूत्रों का कहना है";
"BrowseView.Categories.gamesAndEmulators" = "गेम्स और\nएम्युलेटर्स";
/* RootView */
"RootView.news" = "समाचार";
"RootView.browse" = "ब्राउज़";
"RootView.myApps" = "मेरी एप्प्स";
"RootView.settings" = "समायोजन";
/* SettingsView */
"SettingsView.ConnectedAppleID.name" = "नाम";
"SettingsView.ConnectedAppleID.eMail" = "ईमेल";
"SettingsView.ConnectedAppleID.type" = "टाइप";
"SettingsView.ConnectedAppleID.text" = "कनेक्टेड ऐप्पल आईडी";
"SettingsView.ConnectedAppleID.signOut" = "प्रस्थान करें";
"SettingsView.ConnectedAppleID.Footer.p1" = "आपके द्वारा SideStore के साथ इंस्टॉल किए गए ऐप्स पर हस्ताक्षर करने के लिए आपकी ऐप्पल आईडी आवश्यक है।";
"SettingsView.ConnectedAppleID.Footer.p2" = "आपके क्रेडेंशियल्स केवल Apple के सर्वरों को भेजे जाते हैं और SideStore टीम द्वारा एक्सेस नहीं किए जा सकते हैं। एक बार सफलतापूर्वक लॉग इन करने के बाद, लॉगिन विवरण आपके डिवाइस पर सुरक्षित रूप से संग्रहीत हो जाते हैं।";
"SettingsView.connectAppleID" = "अपनी ऐप्पल आईडी कनेक्ट करें";
"SettingsView.backgroundRefresh" = "बैकग्राउंड रिफ्रेश";
"SettingsView.addToSiri" = "सिरी में जोड़ें...";
"SettingsView.refreshingApps" = "ताज़ा करने वाले ऐप्स";
"SettingsView.switchToUIKit" = "यूआईकिट पर स्विच करें";
"SettingsView.resetImageCache" = "छवि कैश रीसेट करें";
"SettingsView.debug" = "डिबग";
"SettingsView.swiftUIRedesign" = "स्विफ्टयूआई रिडिजाइन";
"SettingsView.credits" = "क्रेडिट";
"SettingsView.title" = "समायोजन";
"SettingsView.refreshingAppsFooter" = "वाई-फाई से कनेक्ट होने और वायरगार्ड सक्रिय होने पर बैकग्राउंड में ऐप्स को स्वचालित रूप से रीफ्रेश करने के लिए बैकग्राउंड रिफ्रेश सक्षम करें।";
/* ConnectAppleIDView */
"ConnectAppleIDView.startWithSignIn" = "आरंभ करने के लिए अपने Apple ID से साइन इन करें।";
"ConnectAppleIDView.signIn" = "साइन इन करें";
"ConnectAppleIDView.appleID" = "ऐप्पल आईडी";
"ConnectAppleIDView.password" = "कुंजिका";
"ConnectAppleIDView.whyDoWeNeedThis" = "हमें इसकी ज़रूरत क्यों है?";
"ConnectAppleIDView.footer" = "आपकी Apple ID का उपयोग ऐप्स को कॉन्फ़िगर करने के लिए किया जाता है ताकि उन्हें इस डिवाइस पर इंस्टॉल किया जा सके। आपके क्रेडेंशियल इस डिवाइस के कीचेन में सुरक्षित रूप से स्टोर किए जाएंगे और प्रमाणीकरण के लिए केवल Apple को भेजे जाएंगे।";
"ConnectAppleIDView.connectYourAppleID" = "अपनी ऐप्पल आईडी कनेक्ट करें";
"ConnectAppleIDView.cancel" = "रद्द करना";
"ConnectAppleIDView.failedToSignIn" = "साइन इन करने में विफल";
/* SourcesView */
"SourcesView.sourcesDescription" = "स्रोत नियंत्रित करते हैं कि कौन से ऐप्स SideStore के माध्यम से डाउनलोड करने के लिए उपलब्ध हैं।";
"SourcesView.remove" = "हटाना";
"SourcesView.trustedSources" = "विश्वसनीय स्रोत";
"SourcesView.reviewedText" = "SideStore ने यह सुनिश्चित करने के लिए इन स्रोतों की समीक्षा की है कि वे हमारे सुरक्षा मानकों को पूरा करते हैं।";
"SourcesView.sources" = "सूत्रों का कहना है";
"SourcesView.done" = "पूर्ण";
/* AddSourceView */
"AddSourceView.sourceURL" = "स्रोत यूआरएल";
"AddSourceView.sourceWarning" = "कृपया यहां स्रोत url दर्ज करें। फिर, अगले चरण में स्रोत को सत्यापित करने और जोड़ने के लिए जारी रखें पर टैप करें।";
"AddSourceView.sourceWarningContinued" = "अमान्य तृतीय पक्ष स्रोतों से सावधान रहें! केवल उन स्रोतों को जोड़ना सुनिश्चित करें जिन पर आप भरोसा करते हैं।";
"AddSourceView.continue" = "जारी रखना";
"AddSourceView.title" = "स्रोत जोड़ें";
/* ConfirmAddSourceView */
"ConfirmAddSourceView.apps" = "ऐप्स";
"ConfirmAddSourceView.newsItems" = "समाचार आइटम";
"ConfirmAddSourceView.sourceContents" = "स्रोत सामग्री";
"ConfirmAddSourceView.sourceIdentifier" = "स्रोत पहचानकर्ता";
"ConfirmAddSourceView.sourceURL" = "स्रोत यूआरएल";
"ConfirmAddSourceView.sourceInfo" = "स्रोत की जानकारी";
"ConfirmAddSourceView.addSource" = "स्रोत जोड़ें";
/* AppPillButton */
"AppPillButton.free" = "नि शुल्क";
"AppPillButton.open" = "खुला";
/* AppAction */
"AppAction.install" = "स्थापित करना";
"AppAction.open" = "खुला";
"AppAction.refresh" = "ताज़ा करना";
"AppAction.activate" = "सक्रिय";
"AppAction.deactivate" = "निष्क्रिय करें";
"AppAction.remove" = "हटाना";
"AppAction.enableJIT" = "सक्रिय JIT";
"AppAction.backup" = "बैकअप";
"AppAction.exportBackup" = "बैकअप निर्यात करें";
"AppAction.restoreBackup" = "बैकअप बहाल";
"AppAction.chooseCustomIcon" = "आइकन अनुकूलित करें";
"AppAction.resetIcon" = "रीसेट आइकन";
/* AppDetailView*/
"AppDetailView.more" = "अधिक...";
"AppDetailView.whatsNew" = "नया क्या है";
"AppDetailView.version" = "संस्करण";
"AppDetailView.noVersionInformation" = "कोई संस्करण जानकारी नहीं";
"AppDetailView.noPermissions" = "ऐप को किसी अनुमति की आवश्यकता नहीं है।";
"AppDetailView.permissions" = "अनुमतियां";
/* AppPermissionGrid */
"AppPermissionGrid.usageDescription" = "उपयोग विवरण";
/* MyAppsView */
"MyAppsView.active" = "सक्रिय";
"MyAppsView.sideloading" = "साइडलोडिंग प्रगति पर है...";
"MyAppsView.refreshAll" = "सभी को रीफ्रेश करें";
"MyAppsView.remainingAppID" = "शेष ऐप आईडी";
"MyAppsView.appIDsRemaining" = "ऐप आईडी शेष";
"MyAppsView.noUpdatesAvailable" = "कोई अपडेट उपलब्ध नहीं";
"MyAppsView.failedToRefresh" = "रीफ़्रेश करने में विफल";
"MyAppsView.apps" = "ऐप्स"; /* Keep this lowercase */
"MyAppsView.viewAppIDs" = "ऐप आईडी देखें";
"MyAppsView.myApps" = "मेरी एप्प्स";

View File

@@ -1,117 +0,0 @@
"AppAction.enableJIT" = "JIT 사용";
"AppAction.backup" = "백업";
/* BrowseView */
"BrowseView.title" = "탐럼하기";
"BrowseView.search" = "검색";
"BrowseView.Section.PromotedCategories.title" = "추천된 앱 종류";
"BrowseView.Section.PromotedCategories.showAll" = "모두 보기";
"BrowseView.Section.AllApps.title" = "모두 보기";
"BrowseView.Actions.sources" = "소스";
"BrowseView.Categories.gamesAndEmulators" = "게임과\n에뮬레이터";
/* RootView */
"RootView.news" = "뉴즈";
"RootView.browse" = "탐험하기";
"RootView.myApps" = "설치된 앱";
"RootView.settings" = "설정";
"SettingsView.ConnectedAppleID.eMail" = "현재 연동된 Apple ID의 이매일";
"SettingsView.ConnectedAppleID.type" = "종류";
"SettingsView.ConnectedAppleID.text" = "현재 연동된 Apple ID";
"SettingsView.ConnectedAppleID.signOut" = "Apple ID에서 로그아웃하기";
"ConfirmAddSourceView.addSource" = "소스추가";
/* AppPillButton */
"AppPillButton.free" = "무료";
/* AppAction */
"AppAction.install" = "설치";
"AppAction.open" = "열기";
"AppPillButton.open" = "열기";
"AppAction.refresh" = "재검색";
"AppAction.activate" = "활성화";
"AppAction.deactivate" = "비활성화";
"AppAction.remove" = "지우기";
"ConnectAppleIDView.appleID" = "Apple ID 이메일";
"SettingsView.connectAppleID" = "Apple ID와 연동하기";
"SettingsView.backgroundRefresh" = "백그라운드 사인";
"SettingsView.refreshingApps" = "앱 다시 사인 하는중";
"SettingsView.addToSiri" = "시리에 추가하기...";
"SettingsView.switchToUIKit" = "UIkit을 사용하기";
"SettingsView.resetImageCache" = "사진 다시 연동하기";
"SettingsView.debug" = "디버그";
"SettingsView.swiftUIRedesign" = "SwiftUI 리디다인";
"SettingsView.credits" = "크래딧";
"SettingsView.title" = "설정";
"ConnectAppleIDView.signIn" = "로그인";
"ConnectAppleIDView.password" = "비밀번호";
"ConnectAppleIDView.whyDoWeNeedThis" = "정보수집을 왜하지?";
"ConnectAppleIDView.connectYourAppleID" = "Apple ID 연동하기";
"ConnectAppleIDView.cancel" = "취소";
"ConnectAppleIDView.failedToSignIn" = "로그인 실패";
"SourcesView.remove" = "삭제";
/* SourcesView */
"SourcesView.sourcesDescription" = "소스는 SideStore에서 설치 가능한 앱을 설정합니다.";
"SourcesView.trustedSources" = "검증소스";
"SourcesView.reviewedText" = "이 소스는 SideStore에서 점검해 안전한 앱이 있습니다.";
"SourcesView.sources" = "소스";
"SourcesView.done" = "완료";
/* AddSourceView */
"AddSourceView.sourceURL" = "소스 URL";
"AddSourceView.sourceWarningContinued" = "미검증 소스를 함부로 추가하지 마세요.";
"AddSourceView.sourceWarning" = "소스 URL을 입력하고, 작동 검증한 뒤 추가해주세요.";
"AddSourceView.continue" = "추가";
"AddSourceView.title" = "소스 추고";
/* ConfirmAddSourceView */
"ConfirmAddSourceView.apps" = "앱";
"ConfirmAddSourceView.newsItems" = "뉴스";
"ConfirmAddSourceView.sourceContents" = "소스 내용물";
"ConfirmAddSourceView.sourceIdentifier" = "소스 식별";
"ConfirmAddSourceView.sourceURL" = "소스 URL";
"ConfirmAddSourceView.sourceInfo" = "소스 정보";
/* NewsView */
"NewsView.title" = "뉴스";
"NewsView.Section.FromSources.title" = "소스에서";
/* SettingsView */
"SettingsView.ConnectedAppleID.name" = "현재 연동된 Apple ID의 이름";
"SettingsView.ConnectedAppleID.Footer.p1" = "이 앱을 사용하려면, Apple ID에 로그인을 해서 앱을 인증을 할 수 있습니다.";
"AppAction.exportBackup" = "백업 내보내기";
"ConnectAppleIDView.footer" = "당신의 Apple ID는 앱을 설정하여 기기에 설치가 가능하게 합니다. 당신의 개인정보는 디바이스의 Keychain에만 저장되며 애플외 전송되지 않습니다.";
"SettingsView.ConnectedAppleID.Footer.p2" = "당신의 개인정보는 애플 외에는 전송되지 않으며, 로그인이 완료된 후에 보안이 강하게 저장됨니다.";
"AppAction.restoreBackup" = "백업 받기";
"AppAction.chooseCustomIcon" = "아이콘 바꾸기";
"SettingsView.refreshingAppsFooter" = "백그라운드 앱 샤로고침 키면 와이파이와 Wireguard가켜진상태에 앱을 라프래쉬합니다.";
"AppAction.resetIcon" = "유저 아이콘 삭재";
/* ConnectAppleIDView */
"ConnectAppleIDView.startWithSignIn" = "Apple ID에 로그인을 하여 시작합시다.";
"AppDetailView.whatsNew" = "무엇이 새로운가";
"AppDetailView.version" = "버전";
"AppDetailView.noVersionInformation" = "버전정보 없움";
"AppDetailView.noPermissions" = "앱은 추가 기능이 필요 없다.";
"AppDetailView.permissions" = "추가 기능";
/* AppPermissionGrid */
"AppPermissionGrid.usageDescription" = "사용량";
/* MyAppsView */
"MyAppsView.active" = "활성화";
"MyAppsView.noUpdatesAvailable" = "업데이트 없움";
"MyAppsView.failedToRefresh" = "업대이트 실패";
/* AppDetailView*/
"AppDetailView.more" = "더 보기...";
"MyAppsView.refreshAll" = "모두 다시 검색";
"MyAppsView.appIDsRemaining" = "사용 가능한 App ID 개수";
"MyAppsView.apps" = "apps"; /* Keep this lowercase */
"MyAppsView.viewAppIDs" = "사용한 App ID보기";
"MyAppsView.myApps" = "나의 앱";
"MyAppsView.sideloading" = "설치 중...";

View File

@@ -1,128 +0,0 @@
/*
Localizable.strings
AltStore
Created by Fabian Thies on 22.12.22.
Modified by mindfreakdev on 25.12.22
Copyright © 2022 SideStore. All rights reserved.
*/
/* NewsView */
"NewsView.title" = "Nieuws";
"NewsView.Section.FromSources.title" = "Van je bronnen";
/* BrowseView */
"BrowseView.title" = "Bladeren";
"BrowseView.search" = "Zoekopdracht";
"BrowseView.Section.PromotedCategories.title" = "Gepromoveerde categorieën";
"BrowseView.Section.PromotedCategories.showAll" = "Toon alles";
"BrowseView.Section.AllApps.title" = "Alle Apps";
"BrowseView.Actions.sources" = "Bronnen";
"BrowseView.Categories.gamesAndEmulators" = "Spellen en\nEmulators";
/* RootView */
"RootView.news" = "Nieuws";
"RootView.browse" = "Bladeren";
"RootView.myApps" = "Mijn apps";
"RootView.settings" = "Instellingen";
/* SettingsView */
"SettingsView.ConnectedAppleID.name" = "Naam";
"SettingsView.ConnectedAppleID.eMail" = "E-mailen";
"SettingsView.ConnectedAppleID.type" = "Type";
"SettingsView.ConnectedAppleID.text" = "Verbonden Apple ID";
"SettingsView.ConnectedAppleID.signOut" = "Afmelden";
"SettingsView.ConnectedAppleID.Footer.p1" = "Uw Apple ID is vereist om de apps te ondertekenen die u met SideStore installeert.";
"SettingsView.ConnectedAppleID.Footer.p2" = "Uw inloggegevens worden alleen naar de servers van Apple verzonden en zijn niet toegankelijk voor het SideStore-team. Nadat u succesvol bent ingelogd, worden de inloggegevens veilig op uw apparaat opgeslagen.";
"SettingsView.connectAppleID" = "Koppel uw Apple ID";
"SettingsView.backgroundRefresh" = "Achtergrond Vernieuwen";
"SettingsView.addToSiri" = "Toevoegen aan Siri...";
"SettingsView.refreshingApps" = "Verfrissende Apps";
"SettingsView.switchToUIKit" = "Schakel over naar UIKit";
"SettingsView.resetImageCache" = "Stel De Afbeeldingscache Opnieuw In";
"SettingsView.debug" = "Debuggen";
"SettingsView.swiftUIRedesign" = "SwiftUI Herontwerp";
"SettingsView.credits" = "Credits";
"SettingsView.title" = "Instellingen";
"SettingsView.refreshingAppsFooter" = "Schakel Achtergrondvernieuwing in om apps op de achtergrond automatisch te vernieuwen wanneer ze verbonden zijn met wifi en Wireguard actief is.";
/* ConnectAppleIDView */
"ConnectAppleIDView.startWithSignIn" = "Aanmelden met uw Apple ID om aan de slag te gaan.";
"ConnectAppleIDView.signIn" = "Aanmelden";
"ConnectAppleIDView.appleID" = "Apple ID";
"ConnectAppleIDView.password" = "Wachtwoord";
"ConnectAppleIDView.whyDoWeNeedThis" = "Waarom hebben we dit nodig?";
"ConnectAppleIDView.footer" = "Uw Apple ID wordt gebruikt om apps te configureren zodat ze op dit apparaat kunnen worden geïnstalleerd. Uw inloggegevens worden veilig opgeslagen in de sleutelhanger van dit apparaat en alleen naar Apple verzonden voor authenticatie.";
"ConnectAppleIDView.connectYourAppleID" = "Verbind uw Apple ID";
"ConnectAppleIDView.cancel" = "Annuleren";
"ConnectAppleIDView.failedToSignIn" = "Aanmelden mislukt";
/* SourcesView */
"SourcesView.sourcesDescription" = "Bronnen bepalen welke apps beschikbaar zijn om te downloaden via SideStore.";
"SourcesView.remove" = "Verwijderen";
"SourcesView.trustedSources" = "Vertrouwde Bronnen";
"SourcesView.reviewedText" = "SideStore heeft deze bronnen beoordeeld om er zeker van te zijn dat ze voldoen aan onze veiligheidsnormen.";
"SourcesView.sources" = "Bronnen";
"SourcesView.done" = "Gedaan";
/* AddSourceView */
"AddSourceView.sourceURL" = "Bron URL";
"AddSourceView.sourceWarning" = "Voer hier de bron-URL in. Tik vervolgens op doorgaan om te valideren en voeg de bron toe in de volgende stap.";
"AddSourceView.sourceWarningContinued" = "Wees voorzichtig met niet-gevalideerde bronnen van derden! Zorg ervoor dat u alleen bronnen toevoegt die u vertrouwt.";
"AddSourceView.continue" = "Doorgaan";
"AddSourceView.title" = "Bron Toevoegen";
/* ConfirmAddSourceView */
"ConfirmAddSourceView.apps" = "Apps";
"ConfirmAddSourceView.newsItems" = "Nieuwsberichten";
"ConfirmAddSourceView.sourceContents" = "Bron Inhoud";
"ConfirmAddSourceView.sourceIdentifier" = "Bron Identificatie";
"ConfirmAddSourceView.sourceURL" = "Bron URL";
"ConfirmAddSourceView.sourceInfo" = "Bron Informatie";
"ConfirmAddSourceView.addSource" = "Bron Toevoegen";
/* AppPillButton */
"AppPillButton.free" = "Vrij";
"AppPillButton.open" = "Open";
/* AppAction */
"AppAction.install" = "Installeren";
"AppAction.open" = "Open";
"AppAction.refresh" = "Vernieuwen";
"AppAction.activate" = "Activeren";
"AppAction.deactivate" = "Deactiveren";
"AppAction.remove" = "Verwijderen";
"AppAction.enableJIT" = "Activeer JIT";
"AppAction.backup" = "Backup";
"AppAction.exportBackup" = "Backup exporteren";
"AppAction.restoreBackup" = "Backup terugzetten";
"AppAction.chooseCustomIcon" = "Pictogram aanpassen";
"AppAction.resetIcon" = "Pictogram resetten";
/* AppDetailView*/
"AppDetailView.more" = "Meer...";
"AppDetailView.whatsNew" = "Wat Is Er Nieuw";
"AppDetailView.version" = "Versie";
"AppDetailView.noVersionInformation" = "Geen versie informatie";
"AppDetailView.noPermissions" = "De app vereist geen machtigingen.";
"AppDetailView.permissions" = "Rechten";
/* AppPermissionGrid */
"AppPermissionGrid.usageDescription" = "Gebruik Beschrijving";
/* MyAppsView */
"MyAppsView.active" = "Actief";
"MyAppsView.sideloading" = "Bezig met sideloaden...";
"MyAppsView.refreshAll" = "Ververs Alles";
"MyAppsView.remainingAppID" = "Resterende App IDs";
"MyAppsView.appIDsRemaining" = "App IDs Resterende";
"MyAppsView.noUpdatesAvailable" = "Geen Updates Beschikbaar";
"MyAppsView.failedToRefresh" = "Kan niet vernieuwen";
"MyAppsView.apps" = "apps"; /* Keep this lowercase */
"MyAppsView.viewAppIDs" = "App IDs Bekijken";
"MyAppsView.myApps" = "Mijn Apps";

View File

@@ -3,18 +3,18 @@
<plist version="1.0">
<dict>
<key>application-identifier</key>
<string>XYZ0123456.com.SideStore.SideStore</string>
<string>A72ZC8AJ5X.com.SideStore.AltStore</string>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.developer.siri</key>
<true/>
<key>com.apple.developer.team-identifier</key>
<string>XYZ0123456</string>
<string>A72ZC8AJ5X</string>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.SideStore.SideStore</string>
<string>group.com.SideStore.AltStore</string>
</array>
<key>get-task-allow</key>
<true/>
</dict>
</plist>
</plist>

View File

@@ -1,128 +0,0 @@
/*
Localizable.strings
AltStore
Created by Fabian Thies on 22.12.22.
Modified by mindfreakdev on 25.12.22
Copyright © 2022 SideStore. All rights reserved.
*/
/* NewsView */
"NewsView.title" = "Новини";
"NewsView.Section.FromSources.title" = "З ваших джерел";
/* BrowseView */
"BrowseView.title" = "переглядати";
"BrowseView.search" = "Пошук";
"BrowseView.Section.PromotedCategories.title" = "Розширені категорії";
"BrowseView.Section.PromotedCategories.showAll" = "Показати все";
"BrowseView.Section.AllApps.title" = "Усі додатки";
"BrowseView.Actions.sources" = "Джерела";
"BrowseView.Categories.gamesAndEmulators" = "Ігри та\nемулятори";
/* RootView */
"RootView.news" = "Новини";
"RootView.browse" = "переглядати";
"RootView.myApps" = "Мої програми";
"RootView.settings" = "Налаштування";
/* SettingsView */
"SettingsView.ConnectedAppleID.name" = "Ім'я";
"SettingsView.ConnectedAppleID.eMail" = "Електронна пошта";
"SettingsView.ConnectedAppleID.type" = "Тип";
"SettingsView.ConnectedAppleID.text" = "Підключений Apple ID";
"SettingsView.ConnectedAppleID.signOut" = "Вийти з аккаунта";
"SettingsView.ConnectedAppleID.Footer.p1" = "Ваш Apple ID потрібен для підпису програм, які ви встановлюєте за допомогою SideStore.";
"SettingsView.ConnectedAppleID.Footer.p2" = "Ваші облікові дані надсилаються лише на сервери Apple і недоступні для команди SideStore. Після успішного входу дані для входу надійно зберігаються на вашому пристрої.";
"SettingsView.connectAppleID" = "Підключіть свій Apple ID";
"SettingsView.backgroundRefresh" = "Фонове оновлення";
"SettingsView.addToSiri" = "Додати до Siri...";
"SettingsView.refreshingApps" = "Оновлення програм";
"SettingsView.switchToUIKit" = "Перейдіть на UIKit";
"SettingsView.resetImageCache" = "Скинути кеш зображень";
"SettingsView.debug" = "Відлагоджувати";
"SettingsView.swiftUIRedesign" = "Редизайн SwiftUI";
"SettingsView.credits" = "Кредити";
"SettingsView.title" = "Налаштування";
"SettingsView.refreshingAppsFooter" = "Увімкніть Background Refresh, щоб автоматично оновлювати програми у фоновому режимі при підключенні до Wi-Fi і Wireguard.";
/* ConnectAppleIDView */
"ConnectAppleIDView.startWithSignIn" = "Щоб почати, увійдіть за допомогою свого Apple ID.";
"ConnectAppleIDView.signIn" = "Увійти";
"ConnectAppleIDView.appleID" = "Apple ID";
"ConnectAppleIDView.password" = "Пароль";
"ConnectAppleIDView.whyDoWeNeedThis" = "Навіщо нам це?";
"ConnectAppleIDView.footer" = "Ваш Apple ID використовується для налаштування додатків, щоб їх можна було встановити на цьому пристрої. Ваші облікові дані будуть безпечно зберігатися в Keychain цього пристрою та надсилатися лише в Apple для автентифікації.";
"ConnectAppleIDView.connectYourAppleID" = "Підключіть свій Apple ID";
"ConnectAppleIDView.cancel" = "Скасувати";
"ConnectAppleIDView.failedToSignIn" = "Не вдалося ввійти";
/* SourcesView */
"SourcesView.sourcesDescription" = "Джерела контролюють, які програми доступні для завантаження через SideStore.";
"SourcesView.remove" = "видалити";
"SourcesView.trustedSources" = "Надійні джерела";
"SourcesView.reviewedText" = "SideStore перевірив ці джерела, щоб переконатися, що вони відповідають нашим стандартам безпеки.";
"SourcesView.sources" = "Джерела";
"SourcesView.done" = "Готово";
/* AddSourceView */
"AddSourceView.sourceURL" = "Вихідна URL-адреса";
"AddSourceView.sourceWarning" = "Введіть URL-адресу джерела тут. Потім натисніть «Продовжити», щоб перевірити та додати джерело на наступному кроці.";
"AddSourceView.sourceWarningContinued" = "Будьте обережні з неперевіреними сторонніми джерелами! Додавайте лише ті джерела, яким довіряєте.";
"AddSourceView.continue" = "Продовжити";
"AddSourceView.title" = "Додати джерело";
/* ConfirmAddSourceView */
"ConfirmAddSourceView.apps" = "програми";
"ConfirmAddSourceView.newsItems" = "Новини";
"ConfirmAddSourceView.sourceContents" = "Зміст джерела";
"ConfirmAddSourceView.sourceIdentifier" = "Ідентифікатор джерела";
"ConfirmAddSourceView.sourceURL" = "Вихідна URL-адреса";
"ConfirmAddSourceView.sourceInfo" = "Вихідна інформація";
"ConfirmAddSourceView.addSource" = "Додати джерело";
/* AppPillButton */
"AppPillButton.free" = "безкоштовно";
"AppPillButton.open" = "ВІДЧИНЕНО";
/* AppAction */
"AppAction.install" = "встановити";
"AppAction.open" = "ВІДЧИНЕНО";
"AppAction.refresh" = "Оновити";
"AppAction.activate" = "активувати";
"AppAction.deactivate" = "Дезактивувати";
"AppAction.remove" = "видалити";
"AppAction.enableJIT" = "Активуйте JIT";
"AppAction.backup" = "Резервне копіювання";
"AppAction.exportBackup" = "Експорт резервної копії";
"AppAction.restoreBackup" = "Відновлення резервної копії";
"AppAction.chooseCustomIcon" = "Налаштувати значок";
"AppAction.resetIcon" = "Значок скидання";
/* AppDetailView*/
"AppDetailView.more" = "більше...";
"AppDetailView.whatsNew" = "Що нового";
"AppDetailView.version" = "Версія";
"AppDetailView.noVersionInformation" = "Немає інформації про версію";
"AppDetailView.noPermissions" = "Програма не потребує дозволів.";
"AppDetailView.permissions" = "Дозволи";
/* AppPermissionGrid */
"AppPermissionGrid.usageDescription" = "Опис використання";
/* MyAppsView */
"MyAppsView.active" = "Активний";
"MyAppsView.sideloading" = "Виконується стороннє завантаження...";
"MyAppsView.refreshAll" = "Оновити все";
"MyAppsView.remainingAppID" = "Решта ідентифікаторів програм";
"MyAppsView.appIDsRemaining" = "Залишилося ідентифікаторів програм";
"MyAppsView.noUpdatesAvailable" = "Немає оновлень";
"MyAppsView.failedToRefresh" = "Не вдалося оновити";
"MyAppsView.apps" = "програми"; /* Keep this lowercase */
"MyAppsView.viewAppIDs" = "Перегляньте ідентифікатори програм";
"MyAppsView.myApps" = "Мої програми";

View File

@@ -11,7 +11,7 @@ import AltStoreCore
import EmotionalDamage
@available(iOS 13, *)
final class SceneDelegate: UIResponder, UIWindowSceneDelegate
class SceneDelegate: UIResponder, UIWindowSceneDelegate
{
var window: UIWindow?

View File

@@ -16,13 +16,14 @@
<key>Key</key>
<string>customAnisetteURL</string>
<key>DefaultValue</key>
<string>https://ani.sidestore.io</string>
<string>http://191.101.206.188:6969</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>
@@ -30,10 +31,11 @@
</array>
<key>Values</key>
<array>
<string>https://ani.sidestore.io</string>
<string>http://us1.sternserv.tech</string>
<string>http://de1.sternserv.tech</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>

View File

@@ -9,7 +9,7 @@
import UIKit
@objc(ErrorLogTableViewCell)
final class ErrorLogTableViewCell: UITableViewCell
class ErrorLogTableViewCell: UITableViewCell
{
@IBOutlet var appIconImageView: AppIconImageView!

View File

@@ -14,9 +14,7 @@ import Roxas
import Nuke
import QuickLook
final class ErrorLogViewController: UITableViewController
class ErrorLogViewController: UITableViewController
{
private lazy var dataSource = self.makeDataSource()
private var expandedErrorIDs = Set<NSManagedObjectID>()
@@ -178,15 +176,6 @@ private extension ErrorLogViewController
}
}
@IBAction func showMinimuxerLogs(_ sender: UIBarButtonItem)
{
// Show minimuxer.log
let previewController = QLPreviewController()
previewController.dataSource = self
let navigationController = UINavigationController(rootViewController: previewController)
present(navigationController, animated: true, completion: nil)
}
@IBAction func clearLoggedErrors(_ sender: UIBarButtonItem)
{
let alertController = UIAlertController(title: NSLocalizedString("Are you sure you want to clear the error log?", comment: ""), message: nil, preferredStyle: .actionSheet)
@@ -310,14 +299,3 @@ extension ErrorLogViewController
}
}
}
extension ErrorLogViewController: QLPreviewControllerDataSource {
func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
return 1
}
func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
let fileURL = FileManager.default.documentsDirectory.appendingPathComponent("minimuxer.log")
return fileURL as QLPreviewItem
}
}

Some files were not shown because too many files have changed in this diff Show More