mirror of
https://github.com/SideStore/SideStore.git
synced 2026-05-11 20:05:40 +02:00
Compare commits
36 Commits
1c8541fdd4
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a0d3c5ed4a | ||
|
|
0d8a2df802 | ||
|
|
9223da751d | ||
|
|
e744ed8b67 | ||
|
|
20714673b2 | ||
|
|
7f2be4cd58 | ||
|
|
f055f33bec | ||
|
|
4deda9229c | ||
|
|
c1bcadbad5 | ||
|
|
f8e199b3d4 | ||
|
|
5efed9df08 | ||
|
|
ae380e48dd | ||
|
|
db5fb89d7c | ||
|
|
d0324a2021 | ||
|
|
195dda6bb6 | ||
|
|
e8983570a9 | ||
|
|
0b072e358a | ||
|
|
cc74be4b34 | ||
|
|
eabf9dbaaa | ||
|
|
bd75d62c7b | ||
|
|
75edfad132 | ||
|
|
84c5bf40ca | ||
|
|
01e73328f8 | ||
|
|
a1f71a8149 | ||
|
|
8624a8e919 | ||
|
|
dfdac41a70 | ||
|
|
62ad755920 | ||
|
|
baf1aa7fff | ||
|
|
395d2bb657 | ||
|
|
e32f4fbeae | ||
|
|
b643f6ae01 | ||
|
|
2f0fb3cb71 | ||
|
|
0f90f9bf2d | ||
|
|
6e9e0aee0a | ||
|
|
2b71c36ace | ||
|
|
4aca1dfa43 |
20
.github/workflows/alpha.yml
vendored
20
.github/workflows/alpha.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
UPSTREAM_CHANNEL: "nightly"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
@@ -54,13 +54,13 @@ jobs:
|
||||
echo "MARKETING_VERSION=$NORMALIZED_VERSION" | tee -a $GITHUB_ENV
|
||||
|
||||
- name: Setup Xcode
|
||||
uses: maxim-lobanov/setup-xcode@v1.6.0
|
||||
uses: maxim-lobanov/setup-xcode@v1.7.0
|
||||
with:
|
||||
xcode-version: "26.2"
|
||||
|
||||
- name: Restore Cache (exact)
|
||||
id: xcode-cache-exact
|
||||
uses: actions/cache/restore@v3
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: |
|
||||
~/Library/Developer/Xcode/DerivedData
|
||||
@@ -70,7 +70,7 @@ jobs:
|
||||
- name: Restore Cache (last)
|
||||
if: steps.xcode-cache-exact.outputs.cache-hit != 'true'
|
||||
id: xcode-cache-fallback
|
||||
uses: actions/cache/restore@v3
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: |
|
||||
~/Library/Developer/Xcode/DerivedData
|
||||
@@ -119,7 +119,7 @@ jobs:
|
||||
|
||||
- name: Save Cache
|
||||
if: ${{ steps.xcode-cache-fallback.outputs.cache-hit != 'true' }}
|
||||
uses: actions/cache/save@v3
|
||||
uses: actions/cache/save@v5
|
||||
with:
|
||||
path: |
|
||||
~/Library/Developer/Xcode/DerivedData
|
||||
@@ -141,12 +141,12 @@ jobs:
|
||||
# --------------------------------------------------
|
||||
# artifacts
|
||||
# --------------------------------------------------
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: build-logs-${{ env.MARKETING_VERSION }}.zip
|
||||
path: build-logs.zip
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v7
|
||||
if: >
|
||||
vars.ENABLE_TESTS == '1' &&
|
||||
vars.ENABLE_TESTS_BUILD == '1'
|
||||
@@ -154,7 +154,7 @@ jobs:
|
||||
name: tests-build-logs-${{ env.SHORT_COMMIT }}.zip
|
||||
path: tests-build-logs.zip
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v7
|
||||
if: >
|
||||
vars.ENABLE_TESTS == '1' &&
|
||||
vars.ENABLE_TESTS_RUN == '1'
|
||||
@@ -166,12 +166,12 @@ jobs:
|
||||
with:
|
||||
name: SideStore-${{ env.MARKETING_VERSION }}.ipa
|
||||
path: SideStore.ipa
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: SideStore-${{ env.MARKETING_VERSION }}-dSYMs.zip
|
||||
path: SideStore.dSYMs.zip
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v7
|
||||
if: env.DEPLOY_KEY != ''
|
||||
with:
|
||||
repository: "SideStore/apps-v2.json"
|
||||
|
||||
2
.github/workflows/attach_build_products.yml
vendored
2
.github/workflows/attach_build_products.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v6
|
||||
- uses: actions/github-script@v9
|
||||
with:
|
||||
# This snippet is public-domain, taken from
|
||||
# https://github.com/oprypin/nightly.link/blob/master/.github/workflows/pr-comment.yml
|
||||
|
||||
24
.github/workflows/nightly.yml
vendored
24
.github/workflows/nightly.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
UPSTREAM_CHANNEL: ""
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
@@ -78,14 +78,14 @@ jobs:
|
||||
|
||||
- name: Setup Xcode
|
||||
if: steps.build_gate.outputs.should_skip != 'true'
|
||||
uses: maxim-lobanov/setup-xcode@v1.6.0
|
||||
uses: maxim-lobanov/setup-xcode@v1.7.0
|
||||
with:
|
||||
xcode-version: "26.2"
|
||||
xcode-version: "26.4"
|
||||
|
||||
- name: Restore Cache (exact)
|
||||
if: steps.build_gate.outputs.should_skip != 'true'
|
||||
id: xcode-cache-exact
|
||||
uses: actions/cache/restore@v3
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: |
|
||||
~/Library/Developer/Xcode/DerivedData
|
||||
@@ -97,7 +97,7 @@ jobs:
|
||||
steps.build_gate.outputs.should_skip != 'true' &&
|
||||
steps.xcode-cache-exact.outputs.cache-hit != 'true'
|
||||
id: xcode-cache-fallback
|
||||
uses: actions/cache/restore@v3
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: |
|
||||
~/Library/Developer/Xcode/DerivedData
|
||||
@@ -151,7 +151,7 @@ jobs:
|
||||
if: >
|
||||
steps.build_gate.outputs.should_skip != 'true' &&
|
||||
steps.xcode-cache-fallback.outputs.cache-hit != 'true'
|
||||
uses: actions/cache/save@v3
|
||||
uses: actions/cache/save@v5
|
||||
with:
|
||||
path: |
|
||||
~/Library/Developer/Xcode/DerivedData
|
||||
@@ -174,13 +174,13 @@ jobs:
|
||||
# --------------------------------------------------
|
||||
# artifacts
|
||||
# --------------------------------------------------
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v7
|
||||
if: steps.build_gate.outputs.should_skip != 'true'
|
||||
with:
|
||||
name: build-logs-${{ env.MARKETING_VERSION }}.zip
|
||||
path: build-logs.zip
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v7
|
||||
if: >
|
||||
steps.build_gate.outputs.should_skip != 'true' &&
|
||||
vars.ENABLE_TESTS == '1' &&
|
||||
@@ -189,7 +189,7 @@ jobs:
|
||||
name: tests-build-logs-${{ env.SHORT_COMMIT }}.zip
|
||||
path: tests-build-logs.zip
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v7
|
||||
if: >
|
||||
steps.build_gate.outputs.should_skip != 'true' &&
|
||||
vars.ENABLE_TESTS == '1' &&
|
||||
@@ -198,18 +198,18 @@ jobs:
|
||||
name: tests-run-logs-${{ env.SHORT_COMMIT }}.zip
|
||||
path: tests-run-logs.zip
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v7
|
||||
if: steps.build_gate.outputs.should_skip != 'true'
|
||||
with:
|
||||
name: SideStore-${{ env.MARKETING_VERSION }}.ipa
|
||||
path: SideStore.ipa
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v7
|
||||
if: steps.build_gate.outputs.should_skip != 'true'
|
||||
with:
|
||||
name: SideStore-${{ env.MARKETING_VERSION }}-dSYMs.zip
|
||||
path: SideStore.dSYMs.zip
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
if: steps.build_gate.outputs.should_skip != 'true' && env.DEPLOY_KEY != ''
|
||||
with:
|
||||
repository: "SideStore/apps-v2.json"
|
||||
|
||||
16
.github/workflows/pr.yml
vendored
16
.github/workflows/pr.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
runs-on: macos-26
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 1 # shallow clone just for PR
|
||||
@@ -33,13 +33,13 @@ jobs:
|
||||
echo "MARKETING_VERSION=$NORMALIZED_VERSION" | tee -a $GITHUB_ENV
|
||||
|
||||
- name: Setup Xcode
|
||||
uses: maxim-lobanov/setup-xcode@v1.6.0
|
||||
uses: maxim-lobanov/setup-xcode@v1.7.0
|
||||
with:
|
||||
xcode-version: "26.2"
|
||||
|
||||
- name: Restore Cache (exact)
|
||||
id: xcode-cache-exact
|
||||
uses: actions/cache/restore@v3
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: |
|
||||
~/Library/Developer/Xcode/DerivedData
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
- name: Restore Cache (last)
|
||||
if: steps.xcode-cache-exact.outputs.cache-hit != 'true'
|
||||
id: xcode-cache-fallback
|
||||
uses: actions/cache/restore@v3
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: |
|
||||
~/Library/Developer/Xcode/DerivedData
|
||||
@@ -67,24 +67,24 @@ jobs:
|
||||
|
||||
- name: Save Cache
|
||||
if: ${{ steps.xcode-cache-fallback.outputs.cache-hit != 'true' }}
|
||||
uses: actions/cache/save@v3
|
||||
uses: actions/cache/save@v5
|
||||
with:
|
||||
path: |
|
||||
~/Library/Developer/Xcode/DerivedData
|
||||
~/Library/Caches/org.swift.swiftpm
|
||||
key: xcode-build-cache-${{ github.ref_name }}-${{ github.sha }}
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: build-logs-${{ env.MARKETING_VERSION }}.zip
|
||||
path: build-logs.zip
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: SideStore-${{ env.MARKETING_VERSION }}.ipa
|
||||
path: SideStore-${{ env.MARKETING_VERSION }}.ipa
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: SideStore-${{ env.MARKETING_VERSION }}-dSYMs.zip
|
||||
path: SideStore.dSYMs.zip
|
||||
|
||||
18
.github/workflows/stable.yml
vendored
18
.github/workflows/stable.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
UPSTREAM_CHANNEL: ""
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
@@ -50,13 +50,13 @@ jobs:
|
||||
echo "SHORT_COMMIT=$SHORT_COMMIT" | tee -a $GITHUB_ENV
|
||||
|
||||
- name: Setup Xcode
|
||||
uses: maxim-lobanov/setup-xcode@v1.6.0
|
||||
uses: maxim-lobanov/setup-xcode@v1.7.0
|
||||
with:
|
||||
xcode-version: "26.0"
|
||||
xcode-version: "26.4"
|
||||
|
||||
- name: Restore Cache (exact)
|
||||
id: xcode-cache-exact
|
||||
uses: actions/cache/restore@v3
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: |
|
||||
~/Library/Developer/Xcode/DerivedData
|
||||
@@ -66,7 +66,7 @@ jobs:
|
||||
- name: Restore Cache (last)
|
||||
if: steps.xcode-cache-exact.outputs.cache-hit != 'true'
|
||||
id: xcode-cache-fallback
|
||||
uses: actions/cache/restore@v3
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: |
|
||||
~/Library/Developer/Xcode/DerivedData
|
||||
@@ -84,24 +84,24 @@ jobs:
|
||||
|
||||
- name: Save Cache
|
||||
if: ${{ steps.xcode-cache-fallback.outputs.cache-hit != 'true' }}
|
||||
uses: actions/cache/save@v3
|
||||
uses: actions/cache/save@v5
|
||||
with:
|
||||
path: |
|
||||
~/Library/Developer/Xcode/DerivedData
|
||||
~/Library/Caches/org.swift.swiftpm
|
||||
key: xcode-build-cache-stable-${{ github.sha }}
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: build-logs-${{ env.MARKETING_VERSION }}.zip
|
||||
path: build-logs.zip
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: SideStore-${{ env.MARKETING_VERSION }}.ipa
|
||||
path: SideStore.ipa
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: SideStore-${{ env.MARKETING_VERSION }}-dSYMs.zip
|
||||
path: SideStore.dSYMs.zip
|
||||
|
||||
4
.gitmodules
vendored
4
.gitmodules
vendored
@@ -54,10 +54,6 @@
|
||||
path = Dependencies/em_proxy
|
||||
url = https://github.com/SideStore/em_proxy
|
||||
branch = master
|
||||
[submodule "Dependencies/libfragmentzip"]
|
||||
path = Dependencies/libfragmentzip
|
||||
url = https://github.com/SideStore/libfragmentzip
|
||||
branch = master
|
||||
[submodule "Dependencies/apps-v2.json"]
|
||||
path = Dependencies/apps-v2.json
|
||||
url = https://github.com/SideStore/apps-v2.json
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
178FB6C72F80B8BD00C92806 /* NSAttributedString+Markdown.m in Sources */ = {isa = PBXBuildFile; fileRef = A8BD2FA82F54401E0045335F /* NSAttributedString+Markdown.m */; };
|
||||
A8036E332F545D5400097AF1 /* userpref.h in Headers */ = {isa = PBXBuildFile; fileRef = A8BD30332F5440300045335F /* userpref.h */; };
|
||||
A8036E342F545D6000097AF1 /* userpref.c in Sources */ = {isa = PBXBuildFile; fileRef = A8BD30342F5440300045335F /* userpref.c */; };
|
||||
A8036E352F545E3800097AF1 /* idevice.h in Headers */ = {isa = PBXBuildFile; fileRef = A8BD30A42F5440300045335F /* idevice.h */; };
|
||||
@@ -93,11 +94,9 @@
|
||||
A8B646012D70C23E00125819 /* MarkdownKit in Frameworks */ = {isa = PBXBuildFile; productRef = A8B646002D70C23E00125819 /* MarkdownKit */; };
|
||||
A8C2260E2EC9039A00047C0D /* Nuke in Frameworks */ = {isa = PBXBuildFile; productRef = A8C2260D2EC9039A00047C0D /* Nuke */; };
|
||||
A8EB89C22F5448B20094BC01 /* em_proxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8BD2F652F543FD40045335F /* em_proxy.swift */; };
|
||||
A8EB89CA2F54519C0094BC01 /* libfragmentzip.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A8EB89C92F54519C0094BC01 /* libfragmentzip.a */; };
|
||||
A8EB89CC2F5451AF0094BC01 /* Minimuxer in Frameworks */ = {isa = PBXBuildFile; productRef = A8EB89CB2F5451AF0094BC01 /* Minimuxer */; };
|
||||
A8EB89CE2F5451B50094BC01 /* SemanticVersion in Frameworks */ = {isa = PBXBuildFile; productRef = A8EB89CD2F5451B50094BC01 /* SemanticVersion */; };
|
||||
A8EB89D02F5451C20094BC01 /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = A8EB89CF2F5451C20094BC01 /* KeychainAccess */; };
|
||||
A8EB89D12F5451E90094BC01 /* libcurl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A8BD2F6D2F543FFF0045335F /* libcurl.a */; };
|
||||
A8EB955C2F5455B30094BC01 /* debug.c in Sources */ = {isa = PBXBuildFile; fileRef = A8BD30312F5440300045335F /* debug.c */; };
|
||||
A8EB95C92F5455FE0094BC01 /* debug.h in Headers */ = {isa = PBXBuildFile; fileRef = A8BD30302F5440300045335F /* debug.h */; };
|
||||
A8EB96042F54561E0094BC01 /* afc.c in Sources */ = {isa = PBXBuildFile; fileRef = A8BD30932F5440300045335F /* afc.c */; };
|
||||
@@ -231,41 +230,6 @@
|
||||
remoteGlobalIDString = CA60C44C93D7916DE57E6EBD;
|
||||
remoteInfo = "em_proxy-staticlib";
|
||||
};
|
||||
A8EB8CC32F5453CD0094BC01 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = A8BD2FFB2F5440300045335F /* libfragmentzip.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = 87B8C3401E0E9C37002F817D;
|
||||
remoteInfo = "fragmentzip-cli-macOS";
|
||||
};
|
||||
A8EB8CC52F5453CD0094BC01 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = A8BD2FFB2F5440300045335F /* libfragmentzip.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = B315FDB02866CCF8002E243C;
|
||||
remoteInfo = "fragmentzip-cli-iOS";
|
||||
};
|
||||
A8EB8CC72F5453CD0094BC01 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = A8BD2FFB2F5440300045335F /* libfragmentzip.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = B315FDB52866CD91002E243C;
|
||||
remoteInfo = "fragmentzip-macOS";
|
||||
};
|
||||
A8EB8CC92F5453CD0094BC01 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = A8BD2FFB2F5440300045335F /* libfragmentzip.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = B315FDCE2866CDD3002E243C;
|
||||
remoteInfo = "fragmentzip-iOS";
|
||||
};
|
||||
A8EB8CCE2F5453CD0094BC01 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = A8BD2FD72F5440300045335F /* libgeneral.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = 87977F6F227C4B71004F31DA;
|
||||
remoteInfo = libgeneral;
|
||||
};
|
||||
BF66EE832501AE50007EE018 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = BFD247622284B9A500981D42 /* Project object */;
|
||||
@@ -361,6 +325,10 @@
|
||||
A8036E7F2F54606400097AF1 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
|
||||
A8036EA72F54642D00097AF1 /* libimobiledevice.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libimobiledevice.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
A8037A0D2F54664300097AF1 /* libem_proxy_swift.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libem_proxy_swift.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
A84596F12F7DC4C90000B8CD /* .gitignore */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitignore; sourceTree = "<group>"; };
|
||||
A84596F22F7DC4C90000B8CD /* .gitmodules */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitmodules; sourceTree = "<group>"; };
|
||||
A84596F32F7DC4C90000B8CD /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
|
||||
A84596F42F7DC4C90000B8CD /* trustedapps.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = trustedapps.json; sourceTree = "<group>"; };
|
||||
A8635D052F4CF16D00E66784 /* OpenSSL.xcframework */ = {isa = PBXFileReference; expectedSignature = "AppleDeveloperProgram:67RAULRX93:Marcin Krzyzanowski"; lastKnownFileType = wrapper.xcframework; name = OpenSSL.xcframework; path = Dependencies/AltSign/Dependencies/OpenSSL.xcframework; sourceTree = "<group>"; };
|
||||
A8945AA52D059B6100D86CBE /* Roxas.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Roxas.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
A8BD20292F543FD40045335F /* client_privatekey */ = {isa = PBXFileReference; lastKnownFileType = text; path = client_privatekey; sourceTree = "<group>"; };
|
||||
@@ -381,26 +349,12 @@
|
||||
A8BD2F692F543FD40045335F /* Makefile */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = "<group>"; };
|
||||
A8BD2F6A2F543FD40045335F /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = "<group>"; };
|
||||
A8BD2F6B2F543FD40045335F /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
|
||||
A8BD2F6D2F543FFF0045335F /* libcurl.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libcurl.a; sourceTree = "<group>"; };
|
||||
A8BD2FA62F54401E0045335F /* .gitignore */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitignore; sourceTree = "<group>"; };
|
||||
A8BD2FA72F54401E0045335F /* NSAttributedString+Markdown.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSAttributedString+Markdown.h"; sourceTree = "<group>"; };
|
||||
A8BD2FA82F54401E0045335F /* NSAttributedString+Markdown.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSAttributedString+Markdown.m"; sourceTree = "<group>"; };
|
||||
A8BD2FA92F54401E0045335F /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
|
||||
A8BD2FBD2F5440300045335F /* em_proxy.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = em_proxy.xcodeproj; sourceTree = "<group>"; };
|
||||
A8BD2FBE2F5440300045335F /* fetch-prebuilt.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = "fetch-prebuilt.sh"; sourceTree = "<group>"; };
|
||||
A8BD2FD72F5440300045335F /* libgeneral.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = libgeneral.xcodeproj; sourceTree = "<group>"; };
|
||||
A8BD2FDB2F5440300045335F /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
|
||||
A8BD2FE02F5440300045335F /* curl.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = curl.h; sourceTree = "<group>"; };
|
||||
A8BD2FE12F5440300045335F /* curlbuild.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = curlbuild.h; sourceTree = "<group>"; };
|
||||
A8BD2FE42F5440300045335F /* curlrules.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = curlrules.h; sourceTree = "<group>"; };
|
||||
A8BD2FE52F5440300045335F /* curlver.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = curlver.h; sourceTree = "<group>"; };
|
||||
A8BD2FE62F5440300045335F /* easy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = easy.h; sourceTree = "<group>"; };
|
||||
A8BD2FE82F5440300045335F /* mprintf.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = mprintf.h; sourceTree = "<group>"; };
|
||||
A8BD2FE92F5440300045335F /* multi.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = multi.h; sourceTree = "<group>"; };
|
||||
A8BD2FEA2F5440300045335F /* stdcheaders.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = stdcheaders.h; sourceTree = "<group>"; };
|
||||
A8BD2FEB2F5440300045335F /* typecheck-gcc.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "typecheck-gcc.h"; sourceTree = "<group>"; };
|
||||
A8BD2FFB2F5440300045335F /* libfragmentzip.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = libfragmentzip.xcodeproj; sourceTree = "<group>"; };
|
||||
A8BD30012F5440300045335F /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
|
||||
A8BD30062F5440300045335F /* add_scalar.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = add_scalar.c; sourceTree = "<group>"; };
|
||||
A8BD30072F5440300045335F /* ed25519.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ed25519.h; sourceTree = "<group>"; };
|
||||
A8BD30082F5440300045335F /* fe.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = fe.h; sourceTree = "<group>"; };
|
||||
@@ -568,8 +522,6 @@
|
||||
A8BD325F2F5440300045335F /* minimuxer */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = minimuxer; sourceTree = "<group>"; };
|
||||
A8BD32D02F5440300045335F /* Roxas.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = Roxas.xcodeproj; sourceTree = "<group>"; };
|
||||
A8BD32FA2F54444D0045335F /* AltSign */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = AltSign; sourceTree = "<group>"; };
|
||||
A8EB89C72F5451970094BC01 /* libfragmentzip.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libfragmentzip.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
A8EB89C92F54519C0094BC01 /* libfragmentzip.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libfragmentzip.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
B3C39606284F4C8400DA9E2F /* CodeSigning.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = CodeSigning.xcconfig; sourceTree = "<group>"; };
|
||||
B3C39607284F4C8400DA9E2F /* Build.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Build.xcconfig; sourceTree = "<group>"; };
|
||||
B3C39608284F4C8400DA9E2F /* CodeSigning.xcconfig.sample */ = {isa = PBXFileReference; lastKnownFileType = text; path = CodeSigning.xcconfig.sample; sourceTree = "<group>"; };
|
||||
@@ -644,7 +596,6 @@
|
||||
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||
membershipExceptions = (
|
||||
Info.plist,
|
||||
"Operations/Patch App/ALTAppPatcher.m",
|
||||
Resources/ReleaseEntitlements.plist,
|
||||
);
|
||||
target = BFD247692284B9A500981D42 /* SideStore */;
|
||||
@@ -686,17 +637,16 @@
|
||||
);
|
||||
target = BF989166250AABF3002ACF50 /* AltWidgetExtension */;
|
||||
};
|
||||
A8FAC1FE2F4B52F40061A851 /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
|
||||
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||
membershipExceptions = (
|
||||
"Operations/Patch App/ALTAppPatcher.h",
|
||||
"Operations/Patch App/ALTAppPatcher.m",
|
||||
);
|
||||
target = BF66EE7D2501AE50007EE018 /* AltStoreCore */;
|
||||
};
|
||||
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||
|
||||
/* Begin PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet section */
|
||||
174F75012F8B4F4900A53376 /* PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet */ = {
|
||||
isa = PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet;
|
||||
buildPhase = BFD247682284B9A500981D42 /* Resources */;
|
||||
membershipExceptions = (
|
||||
Intents/ViewApp.intentdefinition,
|
||||
);
|
||||
};
|
||||
A8EEC8CC2F4B146B00F2436D /* PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet */ = {
|
||||
isa = PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet;
|
||||
buildPhase = BFD247682284B9A500981D42 /* Resources */;
|
||||
@@ -708,12 +658,12 @@
|
||||
|
||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||
A8BD20242F543FAD0045335F /* apps-v2.json */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = "apps-v2.json"; sourceTree = "<group>"; };
|
||||
A8EEC0502F4AF7FB00F2436D /* AltStoreCore */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (A8EEC0BF2F4AF7FB00F2436D /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = AltStoreCore; sourceTree = "<group>"; };
|
||||
A8EEC0502F4AF7FB00F2436D /* AltStoreCore */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (174F75012F8B4F4900A53376 /* PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet */, A8EEC0BF2F4AF7FB00F2436D /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = AltStoreCore; sourceTree = "<group>"; };
|
||||
A8EEC3482F4B0D8600F2436D /* Shared */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (A8EEC36A2F4B0D8700F2436D /* PBXFileSystemSynchronizedBuildFileExceptionSet */, A8EEC36C2F4B0D8700F2436D /* PBXFileSystemSynchronizedBuildFileExceptionSet */, A8EEC36B2F4B0D8700F2436D /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = Shared; sourceTree = "<group>"; };
|
||||
A8EEC3B92F4B0EFC00F2436D /* AltWidget */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (A8EEC3CA2F4B0EFC00F2436D /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = AltWidget; sourceTree = "<group>"; };
|
||||
A8EEC3D92F4B0FC800F2436D /* AltBackup */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (A8EEC3E22F4B0FC800F2436D /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = AltBackup; sourceTree = "<group>"; };
|
||||
A8EEC71D2F4B10D900F2436D /* xcconfigs */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = xcconfigs; sourceTree = "<group>"; };
|
||||
A8EEC8412F4B146A00F2436D /* AltStore */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (A8EEC8CB2F4B146B00F2436D /* PBXFileSystemSynchronizedBuildFileExceptionSet */, A8EEC8CC2F4B146B00F2436D /* PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet */, A8FAC1FE2F4B52F40061A851 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, A8EEC8CD2F4B146B00F2436D /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = AltStore; sourceTree = "<group>"; };
|
||||
A8EEC8412F4B146A00F2436D /* AltStore */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (A8EEC8CB2F4B146B00F2436D /* PBXFileSystemSynchronizedBuildFileExceptionSet */, A8EEC8CC2F4B146B00F2436D /* PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet */, A8EEC8CD2F4B146B00F2436D /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = AltStore; sourceTree = "<group>"; };
|
||||
A8EECF2A2F4B195000F2436D /* SideStore */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (A8EECF492F4B195000F2436D /* PBXFileSystemSynchronizedBuildFileExceptionSet */, A8EECF4A2F4B195000F2436D /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = SideStore; sourceTree = "<group>"; };
|
||||
/* End PBXFileSystemSynchronizedRootGroup section */
|
||||
|
||||
@@ -769,9 +719,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
A8EB89D12F5451E90094BC01 /* libcurl.a in Frameworks */,
|
||||
A8EB89CC2F5451AF0094BC01 /* Minimuxer in Frameworks */,
|
||||
A8EB89CA2F54519C0094BC01 /* libfragmentzip.a in Frameworks */,
|
||||
A8037A012F54661200097AF1 /* libimobiledevice.a in Frameworks */,
|
||||
A8B646012D70C23E00125819 /* MarkdownKit in Frameworks */,
|
||||
A8037A0E2F54664300097AF1 /* libem_proxy_swift.a in Frameworks */,
|
||||
@@ -848,8 +796,6 @@
|
||||
A8BD32FA2F54444D0045335F /* AltSign */,
|
||||
A8BD325F2F5440300045335F /* minimuxer */,
|
||||
A8BD20242F543FAD0045335F /* apps-v2.json */,
|
||||
A8BD2F6E2F543FFF0045335F /* libcurl */,
|
||||
A8BD30022F5440300045335F /* libfragmentzip */,
|
||||
A8BD325E2F5440300045335F /* libusbmuxd */,
|
||||
A8BD30F02F5440300045335F /* libimobiledevice */,
|
||||
A8036E802F54606400097AF1 /* libimobiledevice-glue */,
|
||||
@@ -904,14 +850,6 @@
|
||||
path = em_proxy;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A8BD2F6E2F543FFF0045335F /* libcurl */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A8BD2F6D2F543FFF0045335F /* libcurl.a */,
|
||||
);
|
||||
path = libcurl;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A8BD2FAA2F54401E0045335F /* MarkdownAttributedString */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -923,58 +861,6 @@
|
||||
path = MarkdownAttributedString;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A8BD2FDC2F5440300045335F /* libgeneral */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A8BD2FD72F5440300045335F /* libgeneral.xcodeproj */,
|
||||
A8BD2FDB2F5440300045335F /* README.md */,
|
||||
);
|
||||
path = libgeneral;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A8BD2FDD2F5440300045335F /* dependencies */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A8BD2FDC2F5440300045335F /* libgeneral */,
|
||||
);
|
||||
path = dependencies;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A8BD2FEC2F5440300045335F /* curl */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A8BD2FE02F5440300045335F /* curl.h */,
|
||||
A8BD2FE12F5440300045335F /* curlbuild.h */,
|
||||
A8BD2FE42F5440300045335F /* curlrules.h */,
|
||||
A8BD2FE52F5440300045335F /* curlver.h */,
|
||||
A8BD2FE62F5440300045335F /* easy.h */,
|
||||
A8BD2FE82F5440300045335F /* mprintf.h */,
|
||||
A8BD2FE92F5440300045335F /* multi.h */,
|
||||
A8BD2FEA2F5440300045335F /* stdcheaders.h */,
|
||||
A8BD2FEB2F5440300045335F /* typecheck-gcc.h */,
|
||||
);
|
||||
path = curl;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A8BD2FF02F5440300045335F /* include */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A8BD2FEC2F5440300045335F /* curl */,
|
||||
);
|
||||
path = include;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A8BD30022F5440300045335F /* libfragmentzip */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A8BD2FDD2F5440300045335F /* dependencies */,
|
||||
A8BD2FF02F5440300045335F /* include */,
|
||||
A8BD2FFB2F5440300045335F /* libfragmentzip.xcodeproj */,
|
||||
A8BD30012F5440300045335F /* README.md */,
|
||||
);
|
||||
path = libfragmentzip;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A8BD301A2F5440300045335F /* ed25519 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -1290,25 +1176,6 @@
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A8EB8CBD2F5453CD0094BC01 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A8EB8CC42F5453CD0094BC01 /* libfragmentzip */,
|
||||
A8EB8CC62F5453CD0094BC01 /* libfragmentzip */,
|
||||
A8EB8CC82F5453CD0094BC01 /* libfragmentzip.a */,
|
||||
A8EB8CCA2F5453CD0094BC01 /* libfragmentzip.a */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A8EB8CCB2F5453CD0094BC01 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A8EB8CCF2F5453CD0094BC01 /* libgeneral */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BFD247612284B9A500981D42 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -1325,6 +1192,10 @@
|
||||
B3C39607284F4C8400DA9E2F /* Build.xcconfig */,
|
||||
B3C39606284F4C8400DA9E2F /* CodeSigning.xcconfig */,
|
||||
B3C39608284F4C8400DA9E2F /* CodeSigning.xcconfig.sample */,
|
||||
A84596F12F7DC4C90000B8CD /* .gitignore */,
|
||||
A84596F22F7DC4C90000B8CD /* .gitmodules */,
|
||||
A84596F32F7DC4C90000B8CD /* README.md */,
|
||||
A84596F42F7DC4C90000B8CD /* trustedapps.json */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@@ -1344,8 +1215,6 @@
|
||||
BFD247852284BB3300981D42 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A8EB89C92F54519C0094BC01 /* libfragmentzip.a */,
|
||||
A8EB89C72F5451970094BC01 /* libfragmentzip.a */,
|
||||
A8635D052F4CF16D00E66784 /* OpenSSL.xcframework */,
|
||||
BF580497246A3D19008AE704 /* UIKit.framework */,
|
||||
BFD247862284BB3B00981D42 /* Roxas.framework */,
|
||||
@@ -1624,14 +1493,6 @@
|
||||
ProductGroup = A8EB8CB82F5453CD0094BC01 /* Products */;
|
||||
ProjectRef = A8BD2FBD2F5440300045335F /* em_proxy.xcodeproj */;
|
||||
},
|
||||
{
|
||||
ProductGroup = A8EB8CBD2F5453CD0094BC01 /* Products */;
|
||||
ProjectRef = A8BD2FFB2F5440300045335F /* libfragmentzip.xcodeproj */;
|
||||
},
|
||||
{
|
||||
ProductGroup = A8EB8CCB2F5453CD0094BC01 /* Products */;
|
||||
ProjectRef = A8BD2FD72F5440300045335F /* libgeneral.xcodeproj */;
|
||||
},
|
||||
{
|
||||
ProductGroup = A8EB8CAD2F5453CD0094BC01 /* Products */;
|
||||
ProjectRef = A8BD32D02F5440300045335F /* Roxas.xcodeproj */;
|
||||
@@ -1678,41 +1539,6 @@
|
||||
remoteRef = A8EB8CBB2F5453CD0094BC01 /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
A8EB8CC42F5453CD0094BC01 /* libfragmentzip */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = "compiled.mach-o.executable";
|
||||
path = libfragmentzip;
|
||||
remoteRef = A8EB8CC32F5453CD0094BC01 /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
A8EB8CC62F5453CD0094BC01 /* libfragmentzip */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = "compiled.mach-o.executable";
|
||||
path = libfragmentzip;
|
||||
remoteRef = A8EB8CC52F5453CD0094BC01 /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
A8EB8CC82F5453CD0094BC01 /* libfragmentzip.a */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = archive.ar;
|
||||
path = libfragmentzip.a;
|
||||
remoteRef = A8EB8CC72F5453CD0094BC01 /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
A8EB8CCA2F5453CD0094BC01 /* libfragmentzip.a */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = archive.ar;
|
||||
path = libfragmentzip.a;
|
||||
remoteRef = A8EB8CC92F5453CD0094BC01 /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
A8EB8CCF2F5453CD0094BC01 /* libgeneral */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = "compiled.mach-o.executable";
|
||||
path = libgeneral;
|
||||
remoteRef = A8EB8CCE2F5453CD0094BC01 /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
/* End PBXReferenceProxy section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
@@ -1896,6 +1722,7 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
178FB6C72F80B8BD00C92806 /* NSAttributedString+Markdown.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -2453,8 +2280,6 @@
|
||||
LD_WARN_UNUSED_DYLIBS = YES;
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Dependencies/libfragmentzip",
|
||||
"$(PROJECT_DIR)/Dependencies/libcurl",
|
||||
"$(PROJECT_DIR)/Dependencies/minimuxer/Sources/RustBridge/lib",
|
||||
);
|
||||
LLVM_LTO = YES_THIN;
|
||||
@@ -2502,8 +2327,6 @@
|
||||
LD_WARN_UNUSED_DYLIBS = YES;
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Dependencies/libfragmentzip",
|
||||
"$(PROJECT_DIR)/Dependencies/libcurl",
|
||||
"$(PROJECT_DIR)/Dependencies/minimuxer/Sources/RustBridge/lib",
|
||||
);
|
||||
LLVM_LTO = YES_THIN;
|
||||
@@ -2604,8 +2427,8 @@
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/SideStore/AltSign";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 0.1.0;
|
||||
branch = master;
|
||||
kind = branch;
|
||||
};
|
||||
};
|
||||
A82067C22D03E0DE00645C0D /* XCRemoteSwiftPackageReference "SemanticVersion" */ = {
|
||||
|
||||
16
AltStore.xcworkspace/contents.xcworkspacedata
generated
16
AltStore.xcworkspace/contents.xcworkspacedata
generated
@@ -1,16 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:AltStore.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Dependencies/AltSign">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Dependencies/minimuxer">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Dependencies/Roxas/Roxas.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -3,6 +3,3 @@
|
||||
//
|
||||
|
||||
#import "NSAttributedString+Markdown.h"
|
||||
#import "ALTAppPatcher.h"
|
||||
|
||||
#include "fragmentzip.h"
|
||||
|
||||
@@ -203,10 +203,14 @@ class HeaderContentViewController<Header: UIView, Content: ScrollableContentView
|
||||
self.navigationBarButton = PillButton(type: .system)
|
||||
self.navigationBarButton.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 9000), for: .horizontal) // Prioritize over title length.
|
||||
|
||||
// Embed navigationBarButton in container view with Auto Layout to ensure it can automatically update its size.
|
||||
let buttonContainerView = UIView()
|
||||
buttonContainerView.addSubview(self.navigationBarButton, pinningEdgesWith: .zero)
|
||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: buttonContainerView)
|
||||
if #available(iOS 26.0, *) {
|
||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: self.navigationBarButton)
|
||||
} else {
|
||||
// Embed navigationBarButton in container view with Auto Layout to ensure it can automatically update its size.
|
||||
let buttonContainerView = UIView()
|
||||
buttonContainerView.addSubview(self.navigationBarButton, pinningEdgesWith: .zero)
|
||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: buttonContainerView)
|
||||
}
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
self.navigationBarIconView.widthAnchor.constraint(equalToConstant: 35),
|
||||
|
||||
@@ -242,6 +242,7 @@
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>mobiledevicepairing</string>
|
||||
<string>mobiledevicepair</string>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
|
||||
@@ -942,70 +942,6 @@ extension AppManager
|
||||
self.run([enableJITOperation], context: context, requiresSerialQueue: true)
|
||||
}
|
||||
|
||||
func patch(resignedApp: ALTApplication, presentingViewController: UIViewController, context authContext: AuthenticatedOperationContext, completionHandler: @escaping (Result<InstalledApp, Error>) -> Void) -> PatchAppOperation
|
||||
{
|
||||
final class Context: InstallAppOperationContext, PatchAppContext
|
||||
{
|
||||
}
|
||||
|
||||
guard let originalBundleID = resignedApp.bundle.infoDictionary?[Bundle.Info.altBundleID] as? String else {
|
||||
let context = Context(bundleIdentifier: resignedApp.bundleIdentifier, authenticatedContext: authContext)
|
||||
completionHandler(.failure(OperationError.invalidApp))
|
||||
|
||||
return PatchAppOperation(context: context)
|
||||
}
|
||||
|
||||
let context = Context(bundleIdentifier: originalBundleID, authenticatedContext: authContext)
|
||||
context.resignedApp = resignedApp
|
||||
|
||||
let patchAppOperation = PatchAppOperation(context: context)
|
||||
let sendAppOperation = SendAppOperation(context: context)
|
||||
let installOperation = InstallAppOperation(context: context)
|
||||
|
||||
let installationProgress = Progress.discreteProgress(totalUnitCount: 100)
|
||||
installationProgress.addChild(sendAppOperation.progress, withPendingUnitCount: 40)
|
||||
installationProgress.addChild(installOperation.progress, withPendingUnitCount: 60)
|
||||
|
||||
/* Patch */
|
||||
patchAppOperation.resultHandler = { [weak patchAppOperation] (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error):
|
||||
context.error = error
|
||||
case .success:
|
||||
// Kinda hacky that we're calling patchAppOperation's progressHandler manually, but YOLO.
|
||||
patchAppOperation?.progressHandler?(installationProgress, NSLocalizedString("Patching placeholder app...", comment: ""))
|
||||
}
|
||||
}
|
||||
|
||||
/* Send */
|
||||
sendAppOperation.resultHandler = { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error):
|
||||
context.error = error
|
||||
completionHandler(.failure(error))
|
||||
case .success(_): print("App sent over AFC")
|
||||
}
|
||||
}
|
||||
sendAppOperation.addDependency(patchAppOperation)
|
||||
|
||||
|
||||
/* Install */
|
||||
installOperation.resultHandler = { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): completionHandler(.failure(error))
|
||||
case .success(let installedApp): completionHandler(.success(installedApp))
|
||||
}
|
||||
//UIApplication.shared.open(shortcutURLon, options: [:], completionHandler: nil)
|
||||
}
|
||||
installOperation.addDependency(sendAppOperation)
|
||||
|
||||
self.run([patchAppOperation, sendAppOperation, installOperation], context: context.authenticatedContext)
|
||||
return patchAppOperation
|
||||
}
|
||||
|
||||
func installationProgress(for app: AppProtocol) -> Progress?
|
||||
{
|
||||
os_unfair_lock_lock(self.progressLock)
|
||||
@@ -1392,80 +1328,6 @@ private extension AppManager
|
||||
}
|
||||
}
|
||||
deactivateAppsOperation.addDependency(fetchProvisioningProfilesOperation)
|
||||
|
||||
/* Patch App */
|
||||
let patchAppOperation = RSTAsyncBlockOperation { operation in
|
||||
do
|
||||
{
|
||||
// Only attempt to patch app if we're installing a new app, not refreshing existing app.
|
||||
// Post reboot, we install the correct jailbreak app by refreshing the patched app,
|
||||
// so this check avoids infinite recursion.
|
||||
guard case .install = appOperation else {
|
||||
operation.finish()
|
||||
return
|
||||
}
|
||||
|
||||
guard let presentingViewController = context.presentingViewController else { return operation.finish() }
|
||||
|
||||
if let error = context.error
|
||||
{
|
||||
throw error
|
||||
}
|
||||
|
||||
guard let app = context.app else {
|
||||
throw OperationError.invalidParameters("AppManager._install.patchAppOperation: context.app is nil")
|
||||
}
|
||||
|
||||
guard let isUntetherRequired = app.bundle.infoDictionary?[Bundle.Info.untetherRequired] as? Bool,
|
||||
let minimumiOSVersionString = app.bundle.infoDictionary?[Bundle.Info.untetherMinimumiOSVersion] as? String,
|
||||
let maximumiOSVersionString = app.bundle.infoDictionary?[Bundle.Info.untetherMaximumiOSVersion] as? String,
|
||||
case let minimumiOSVersion = OperatingSystemVersion(string: minimumiOSVersionString),
|
||||
case let maximumiOSVersion = OperatingSystemVersion(string: maximumiOSVersionString)
|
||||
else { return operation.finish() }
|
||||
|
||||
let iOSVersion = ProcessInfo.processInfo.operatingSystemVersion
|
||||
let iOSVersionSupported = ProcessInfo.processInfo.isOperatingSystemAtLeast(minimumiOSVersion) &&
|
||||
(!ProcessInfo.processInfo.isOperatingSystemAtLeast(maximumiOSVersion) || maximumiOSVersion == iOSVersion)
|
||||
|
||||
guard isUntetherRequired, iOSVersionSupported, UIDevice.current.supportsFugu14 else { return operation.finish() }
|
||||
|
||||
guard let patchAppLink = app.bundle.infoDictionary?[Bundle.Info.untetherURL] as? String,
|
||||
let patchAppURL = URL(string: patchAppLink)
|
||||
else { throw OperationError.invalidApp }
|
||||
|
||||
let patchApp = AnyApp(name: app.name, bundleIdentifier: context.bundleIdentifier, url: patchAppURL, storeApp: nil)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
let storyboard = UIStoryboard(name: "PatchApp", bundle: nil)
|
||||
let navigationController = storyboard.instantiateInitialViewController() as! UINavigationController
|
||||
|
||||
let patchViewController = navigationController.topViewController as! PatchViewController
|
||||
patchViewController.patchApp = patchApp
|
||||
patchViewController.completionHandler = { [weak presentingViewController] (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(OperationError.cancelled): break // Ignore
|
||||
case .failure(let error): group.context.error = error
|
||||
case .success: group.context.error = OperationError.cancelled
|
||||
}
|
||||
|
||||
operation.finish()
|
||||
|
||||
DispatchQueue.main.async {
|
||||
presentingViewController?.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
presentingViewController.present(navigationController, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
group.context.error = error
|
||||
operation.finish()
|
||||
}
|
||||
}
|
||||
patchAppOperation.addDependency(deactivateAppsOperation)
|
||||
|
||||
|
||||
let modifyAppExBundleIdOperation = RSTAsyncBlockOperation { operation in
|
||||
if !context.useMainProfile {
|
||||
@@ -1498,7 +1360,7 @@ private extension AppManager
|
||||
self.exportResginedAppsToDocsDir(resignedApp)
|
||||
}
|
||||
}
|
||||
resignAppOperation.addDependency(patchAppOperation)
|
||||
resignAppOperation.addDependency(deactivateAppsOperation)
|
||||
resignAppOperation.addDependency(modifyAppExBundleIdOperation)
|
||||
progress.addChild(resignAppOperation.progress, withPendingUnitCount: 20)
|
||||
|
||||
@@ -1549,7 +1411,6 @@ private extension AppManager
|
||||
verifyOperation,
|
||||
removeAppExtensionsOperation,
|
||||
deactivateAppsOperation,
|
||||
patchAppOperation,
|
||||
refreshAnisetteDataOperation,
|
||||
fetchProvisioningProfilesOperation,
|
||||
modifyAppExBundleIdOperation,
|
||||
|
||||
@@ -214,13 +214,11 @@ final class InstallAppOperation: ResultOperation<InstalledApp>
|
||||
alert.addAction(UIAlertAction(title: NSLocalizedString("Continue", comment: ""), style: .default, handler: { _ in
|
||||
print("Going home")
|
||||
// Cell Shortcut
|
||||
|
||||
DispatchQueue.main.async{
|
||||
// UIApplication.shared.open(shortcutURLonDelay, options: [:]) { _ in
|
||||
// print("Cell OFF Shortcut finished execution.")
|
||||
// }
|
||||
UIApplication.shared.perform(#selector(NSXPCConnection.suspend))
|
||||
if self.context.shouldTurnOffData {
|
||||
UIApplication.shared.open(shortcutURLonDelay, options: [:]) { _ in
|
||||
print("Cell OFF Shortcut finished execution.")}
|
||||
}
|
||||
UIApplication.shared.perform(#selector(NSXPCConnection.suspend))
|
||||
}))
|
||||
|
||||
DispatchQueue.main.async {
|
||||
@@ -236,14 +234,11 @@ final class InstallAppOperation: ResultOperation<InstalledApp>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
// Cell Shortcut
|
||||
// UIApplication.shared.open(shortcutURLonDelay, options: [:]) { _ in
|
||||
// print("Cell OFF Shortcut finished execution.")
|
||||
// }
|
||||
UIApplication.shared.perform(#selector(NSXPCConnection.suspend))
|
||||
// Cell Shortcut
|
||||
if self.context.shouldTurnOffData {
|
||||
UIApplication.shared.open(shortcutURLonDelay, options: [:]) { _ in print("Cell OFF Shortcut finished execution.")}
|
||||
}
|
||||
UIApplication.shared.perform(#selector(NSXPCConnection.suspend))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -123,6 +123,8 @@ class InstallAppOperationContext: AppOperationContext
|
||||
|
||||
var alternateIconURL: URL?
|
||||
|
||||
var shouldTurnOffData: Bool = false
|
||||
|
||||
// Non-nil when installing from a source.
|
||||
@AsyncManaged
|
||||
var appVersion: AppVersion?
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
//
|
||||
// ALTAppPatcher.h
|
||||
// AltStore
|
||||
//
|
||||
// Created by Riley Testut on 10/18/21.
|
||||
// Copyright © 2021 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ALTAppPatcher : NSObject
|
||||
|
||||
- (BOOL)patchAppBinaryAtURL:(NSURL *)appFileURL withBinaryAtURL:(NSURL *)patchFileURL error:(NSError *_Nullable *)error;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,143 +0,0 @@
|
||||
//
|
||||
// ALTAppPatcher.m
|
||||
// AltStore
|
||||
//
|
||||
// Created by Riley Testut on 10/18/21.
|
||||
// Copied with minor modifications from sample code provided by Linus Henze.
|
||||
//
|
||||
|
||||
#import "ALTAppPatcher.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
@import Roxas;
|
||||
|
||||
#define CPU_SUBTYPE_PAC 0x80000000
|
||||
#define FAT_MAGIC 0xcafebabe
|
||||
|
||||
#define ROUND_TO_PAGE(val) (((val % 0x4000) == 0) ? val : (val + (0x4000 - (val & 0x3FFF))))
|
||||
|
||||
typedef struct {
|
||||
uint32_t magic;
|
||||
uint32_t cpuType;
|
||||
uint32_t cpuSubType;
|
||||
// Incomplete, we don't need anything else
|
||||
} MachOHeader;
|
||||
|
||||
typedef struct {
|
||||
uint32_t cpuType;
|
||||
uint32_t cpuSubType;
|
||||
uint32_t fileOffset;
|
||||
uint32_t size;
|
||||
uint32_t alignment;
|
||||
} FatArch;
|
||||
|
||||
typedef struct {
|
||||
uint32_t magic;
|
||||
uint32_t archCount;
|
||||
FatArch archs[0];
|
||||
} FatHeader;
|
||||
|
||||
// Given two MachO files, return a FAT file with the following properties:
|
||||
// 1. installd will still see the original MachO and validate it's code signature
|
||||
// 2. The kernel will only see the injected MachO instead
|
||||
//
|
||||
// Only arm64e for now
|
||||
void *injectApp(void *originalApp, size_t originalAppSize, void *appToInject, size_t appToInjectSize, size_t *outputSize) {
|
||||
*outputSize = 0;
|
||||
|
||||
// First validate the App to inject: It must be an arm64e application
|
||||
if (appToInjectSize < sizeof(MachOHeader)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
MachOHeader *injectedHeader = (MachOHeader*) appToInject;
|
||||
if (injectedHeader->cpuType != CPU_TYPE_ARM64) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (injectedHeader->cpuSubType != (CPU_SUBTYPE_ARM64E | CPU_SUBTYPE_PAC)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Ok, the App to inject is ok
|
||||
// Now build a fat header
|
||||
size_t originalAppSizeRounded = ROUND_TO_PAGE(originalAppSize);
|
||||
size_t appToInjectSizeRounded = ROUND_TO_PAGE(appToInjectSize);
|
||||
size_t totalSize = 0x4000 /* Fat Header + Alignment */ + originalAppSizeRounded + appToInjectSizeRounded;
|
||||
|
||||
void *fatBuf = malloc(totalSize);
|
||||
if (fatBuf == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bzero(fatBuf, totalSize);
|
||||
|
||||
FatHeader *fatHeader = (FatHeader*) fatBuf;
|
||||
fatHeader->magic = htonl(FAT_MAGIC);
|
||||
fatHeader->archCount = htonl(2);
|
||||
|
||||
// Write first arch (original app)
|
||||
fatHeader->archs[0].cpuType = htonl(CPU_TYPE_ARM64);
|
||||
fatHeader->archs[0].cpuSubType = htonl(CPU_SUBTYPE_ARM64E); /* Note that this is not a valid cpu subtype */
|
||||
fatHeader->archs[0].fileOffset = htonl(0x4000);
|
||||
fatHeader->archs[0].size = htonl(originalAppSize);
|
||||
fatHeader->archs[0].alignment = htonl(0xE);
|
||||
|
||||
// Write second arch (injected app)
|
||||
fatHeader->archs[1].cpuType = htonl(CPU_TYPE_ARM64);
|
||||
fatHeader->archs[1].cpuSubType = htonl(CPU_SUBTYPE_ARM64E | CPU_SUBTYPE_PAC);
|
||||
fatHeader->archs[1].fileOffset = htonl(0x4000 + originalAppSizeRounded);
|
||||
fatHeader->archs[1].size = htonl(appToInjectSize);
|
||||
fatHeader->archs[1].alignment = htonl(0xE);
|
||||
|
||||
// Ok, now write the MachOs
|
||||
memcpy(fatBuf + 0x4000, originalApp, originalAppSize);
|
||||
memcpy(fatBuf + 0x4000 + originalAppSizeRounded, appToInject, appToInjectSize);
|
||||
|
||||
// We're done!
|
||||
*outputSize = totalSize;
|
||||
return fatBuf;
|
||||
}
|
||||
|
||||
@implementation ALTAppPatcher
|
||||
|
||||
- (BOOL)patchAppBinaryAtURL:(NSURL *)appFileURL withBinaryAtURL:(NSURL *)patchFileURL error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
NSMutableData *originalApp = [NSMutableData dataWithContentsOfURL:appFileURL options:0 error:error];
|
||||
if (originalApp == nil)
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSMutableData *injectedApp = [NSMutableData dataWithContentsOfURL:patchFileURL options:0 error:error];
|
||||
if (injectedApp == nil)
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
size_t outputSize = 0;
|
||||
void *output = injectApp(originalApp.mutableBytes, originalApp.length, injectedApp.mutableBytes, injectedApp.length, &outputSize);
|
||||
if (output == NULL)
|
||||
{
|
||||
if (error)
|
||||
{
|
||||
// If injectApp fails, it means the patch app is in the wrong format.
|
||||
*error = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileReadCorruptFileError userInfo:@{NSURLErrorKey: patchFileURL}];
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSData *outputData = [NSData dataWithBytesNoCopy:output length:outputSize freeWhenDone:YES];
|
||||
if (![outputData writeToURL:appFileURL options:NSDataWritingAtomic error:error])
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,124 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="19162" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="WBb-E1-bN8">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19144"/>
|
||||
<capability name="Named colors" minToolsVersion="9.0"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Navigation Controller-->
|
||||
<scene sceneID="dx2-fp-qDX">
|
||||
<objects>
|
||||
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="WBb-E1-bN8" sceneMemberID="viewController">
|
||||
<toolbarItems/>
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" barStyle="black" id="cVa-8m-fW6" customClass="NavigationBar" customModule="AltStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="44" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<color key="barTintColor" name="SettingsBackground"/>
|
||||
</navigationBar>
|
||||
<nil name="viewControllers"/>
|
||||
<connections>
|
||||
<segue destination="idH-XF-rK8" kind="relationship" relationship="rootViewController" id="hSJ-tL-4nB"/>
|
||||
</connections>
|
||||
</navigationController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="i7K-pi-SRe" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="137.68115942028987" y="137.94642857142856"/>
|
||||
</scene>
|
||||
<!--Patch View Controller-->
|
||||
<scene sceneID="gJ4-4F-79r">
|
||||
<objects>
|
||||
<viewController id="idH-XF-rK8" customClass="PatchViewController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" id="4bV-S5-z7S">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="RHK-C5-7wu" customClass="RSTPlaceholderView">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||
</view>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="PjB-Dc-9n3">
|
||||
<rect key="frame" x="20" y="736.5" width="374" height="117.5"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text=" " textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="GQF-6P-Fit">
|
||||
<rect key="frame" x="0.0" y="0.0" width="374" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" name="Text"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="SNn-Ad-ICf" customClass="PillButton" customModule="AltStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="28.5" width="374" height="51"/>
|
||||
<color key="backgroundColor" name="SettingsHighlighted"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="51" id="s4X-uf-nl9"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="19"/>
|
||||
<color key="tintColor" name="SettingsHighlighted"/>
|
||||
<state key="normal" title="Install Untethered Jailbreak">
|
||||
<color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</state>
|
||||
<connections>
|
||||
<action selector="performButtonAction" destination="idH-XF-rK8" eventType="primaryActionTriggered" id="FxO-1Y-IML"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="leJ-09-giz">
|
||||
<rect key="frame" x="0.0" y="87.5" width="374" height="30"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
|
||||
<state key="normal" title="Install Without Untethering">
|
||||
<color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</state>
|
||||
<connections>
|
||||
<action selector="installRegularJailbreak" destination="idH-XF-rK8" eventType="primaryActionTriggered" id="1XB-11-Kdn"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="H6g-xA-DdL"/>
|
||||
<color key="backgroundColor" name="SettingsBackground"/>
|
||||
<constraints>
|
||||
<constraint firstItem="RHK-C5-7wu" firstAttribute="top" secondItem="4bV-S5-z7S" secondAttribute="top" id="CVK-6E-iA6"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="PjB-Dc-9n3" secondAttribute="trailing" id="GOg-JU-LIP"/>
|
||||
<constraint firstItem="RHK-C5-7wu" firstAttribute="bottom" secondItem="4bV-S5-z7S" secondAttribute="bottom" id="LPh-J8-IVx"/>
|
||||
<constraint firstItem="PjB-Dc-9n3" firstAttribute="leading" secondItem="4bV-S5-z7S" secondAttribute="leadingMargin" id="Rlg-PC-5ZN"/>
|
||||
<constraint firstItem="RHK-C5-7wu" firstAttribute="trailing" secondItem="H6g-xA-DdL" secondAttribute="trailing" id="XdZ-36-6yS"/>
|
||||
<constraint firstAttribute="bottomMargin" secondItem="PjB-Dc-9n3" secondAttribute="bottom" id="hTS-nX-0xv"/>
|
||||
<constraint firstItem="RHK-C5-7wu" firstAttribute="leading" secondItem="H6g-xA-DdL" secondAttribute="leading" id="lzV-fG-Xv6"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<navigationItem key="navigationItem" largeTitleDisplayMode="always" id="0J1-80-RD8">
|
||||
<barButtonItem key="leftBarButtonItem" systemItem="cancel" id="vtw-PQ-Dk1">
|
||||
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<connections>
|
||||
<action selector="cancel" destination="idH-XF-rK8" id="4Wk-dv-RYG"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
</navigationItem>
|
||||
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" prompted="NO"/>
|
||||
<connections>
|
||||
<outlet property="cancelBarButtonItem" destination="vtw-PQ-Dk1" id="8Mh-GU-KD5"/>
|
||||
<outlet property="cancelButton" destination="leJ-09-giz" id="BNh-I3-vXc"/>
|
||||
<outlet property="pillButton" destination="SNn-Ad-ICf" id="iJg-TC-p8q"/>
|
||||
<outlet property="placeholderView" destination="RHK-C5-7wu" id="5x0-sg-HAH"/>
|
||||
<outlet property="taskDescriptionLabel" destination="GQF-6P-Fit" id="C4c-xy-kvU"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="8ev-19-qsi" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1001" y="138"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<namedColor name="SettingsBackground">
|
||||
<color red="0.0039215686274509803" green="0.50196078431372548" blue="0.51764705882352946" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</namedColor>
|
||||
<namedColor name="SettingsHighlighted">
|
||||
<color red="0.0080000003799796104" green="0.32199999690055847" blue="0.40400001406669617" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</namedColor>
|
||||
<namedColor name="Text">
|
||||
<color red="1" green="1" blue="1" alpha="0.75" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</namedColor>
|
||||
</resources>
|
||||
</document>
|
||||
@@ -1,256 +0,0 @@
|
||||
//
|
||||
// PatchAppOperation.swift
|
||||
// AltStore
|
||||
//
|
||||
// Created by Riley Testut on 10/13/21.
|
||||
// Copyright © 2021 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Combine
|
||||
import AppleArchive
|
||||
import System
|
||||
import AltStoreCore
|
||||
import AltSign
|
||||
import Roxas
|
||||
|
||||
protocol PatchAppContext
|
||||
{
|
||||
var bundleIdentifier: String { get }
|
||||
var temporaryDirectory: URL { get }
|
||||
|
||||
var resignedApp: ALTApplication? { get }
|
||||
var error: Error? { get }
|
||||
}
|
||||
|
||||
extension PatchAppError
|
||||
{
|
||||
enum Code: Int, ALTErrorCode, CaseIterable {
|
||||
typealias Error = PatchAppError
|
||||
|
||||
case unsupportedOperatingSystemVersion
|
||||
}
|
||||
|
||||
static func unsupportedOperatingSystemVersion(_ osVersion: OperatingSystemVersion) -> PatchAppError {
|
||||
PatchAppError(code: .unsupportedOperatingSystemVersion, osVersion: osVersion)
|
||||
}
|
||||
}
|
||||
|
||||
struct PatchAppError: ALTLocalizedError {
|
||||
let code: Code
|
||||
|
||||
var errorTitle: String?
|
||||
var errorFailure: String?
|
||||
|
||||
var osVersion: OperatingSystemVersion?
|
||||
|
||||
var errorFailureReason: String {
|
||||
switch self.code {
|
||||
case .unsupportedOperatingSystemVersion:
|
||||
let osVersionString: String
|
||||
|
||||
if let osVersion = self.osVersion?.stringValue {
|
||||
osVersionString = NSLocalizedString("iOS", comment: "") + " " + osVersion
|
||||
} else {
|
||||
osVersionString = NSLocalizedString("your device's iOS version", comment: "")
|
||||
}
|
||||
return String(format: NSLocalizedString("The OTA download URL for %@ could not be determined.", comment: ""), osVersionString)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct OTAUpdate
|
||||
{
|
||||
var url: URL
|
||||
var archivePath: String
|
||||
}
|
||||
|
||||
@available(iOS 14, *)
|
||||
final class PatchAppOperation: ResultOperation<Void>
|
||||
{
|
||||
let context: PatchAppContext
|
||||
|
||||
var progressHandler: ((Progress, String) -> Void)?
|
||||
|
||||
private let appPatcher = ALTAppPatcher()
|
||||
private lazy var patchDirectory: URL = self.context.temporaryDirectory.appendingPathComponent("Patch", isDirectory: true)
|
||||
|
||||
private var cancellable: AnyCancellable?
|
||||
|
||||
init(context: PatchAppContext)
|
||||
{
|
||||
self.context = context
|
||||
|
||||
super.init()
|
||||
|
||||
self.progress.totalUnitCount = 100
|
||||
}
|
||||
|
||||
override func main()
|
||||
{
|
||||
super.main()
|
||||
|
||||
if let error = self.context.error
|
||||
{
|
||||
self.finish(.failure(error))
|
||||
return
|
||||
}
|
||||
|
||||
guard let resignedApp = self.context.resignedApp else {
|
||||
return self.finish(.failure(OperationError.invalidParameters("PatchAppOperation.main: self.context.resignedApp is nil")))
|
||||
}
|
||||
|
||||
self.progressHandler?(self.progress, NSLocalizedString("Downloading iOS firmware...", comment: ""))
|
||||
|
||||
self.cancellable = self.fetchOTAUpdate()
|
||||
.flatMap { self.downloadArchive(from: $0) }
|
||||
.flatMap { self.extractSpotlightFromArchive(at: $0) }
|
||||
.flatMap { self.patch(resignedApp, withBinaryAt: $0) }
|
||||
.tryMap { try FileManager.default.zipAppBundle(at: $0) }
|
||||
.tryMap { (fileURL) in
|
||||
let app = AnyApp(name: resignedApp.name, bundleIdentifier: self.context.bundleIdentifier, url: resignedApp.fileURL, storeApp: nil)
|
||||
|
||||
let destinationURL = InstalledApp.refreshedIPAURL(for: app)
|
||||
try FileManager.default.copyItem(at: fileURL, to: destinationURL, shouldReplace: true)
|
||||
}
|
||||
.receive(on: RunLoop.main)
|
||||
.sink { completion in
|
||||
switch completion
|
||||
{
|
||||
case .failure(let error): self.finish(.failure(error))
|
||||
case .finished: self.finish(.success(()))
|
||||
}
|
||||
} receiveValue: { _ in }
|
||||
}
|
||||
|
||||
override func cancel()
|
||||
{
|
||||
super.cancel()
|
||||
|
||||
self.cancellable?.cancel()
|
||||
self.cancellable = nil
|
||||
}
|
||||
}
|
||||
|
||||
private let ALTFragmentZipCallback: @convention(c) (UInt32) -> Void = { (percentageComplete) in
|
||||
guard let progress = Progress.current() else { return }
|
||||
|
||||
if percentageComplete == 100 && progress.completedUnitCount == 0
|
||||
{
|
||||
// Ignore first percentageComplete, which is always 100.
|
||||
return
|
||||
}
|
||||
|
||||
progress.completedUnitCount = Int64(percentageComplete)
|
||||
}
|
||||
|
||||
private extension PatchAppOperation
|
||||
{
|
||||
func fetchOTAUpdate() -> AnyPublisher<OTAUpdate, Error>
|
||||
{
|
||||
Just(()).tryMap {
|
||||
let osVersion = ProcessInfo.processInfo.operatingSystemVersion
|
||||
switch (osVersion.majorVersion, osVersion.minorVersion)
|
||||
{
|
||||
case (14, 3):
|
||||
return OTAUpdate(url: URL(string: "https://updates.cdn-apple.com/2020WinterFCS/patches/001-87330/99E29969-F6B6-422A-B946-70DE2E2D73BE/com_apple_MobileAsset_SoftwareUpdate/67f9e42f5e57a20e0a87eaf81b69dd2a61311d3f.zip")!,
|
||||
archivePath: "AssetData/payloadv2/payload.042")
|
||||
|
||||
case (14, 4):
|
||||
return OTAUpdate(url: URL(string: "https://updates.cdn-apple.com/2021WinterFCS/patches/001-98606/43AF99A1-F286-43B1-A101-F9F856EA395A/com_apple_MobileAsset_SoftwareUpdate/c4985c32c344beb7b49c61919b4e39d1fd336c90.zip")!,
|
||||
archivePath: "AssetData/payloadv2/payload.042")
|
||||
|
||||
case (14, 5):
|
||||
return OTAUpdate(url: URL(string: "https://updates.cdn-apple.com/2021SpringFCS/patches/061-84483/AB525139-066E-46F8-8E85-DCE802C03BA8/com_apple_MobileAsset_SoftwareUpdate/788573ae93113881db04269acedeecabbaa643e3.zip")!,
|
||||
archivePath: "AssetData/payloadv2/payload.043")
|
||||
|
||||
default: throw PatchAppError.unsupportedOperatingSystemVersion(osVersion)
|
||||
}
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func downloadArchive(from update: OTAUpdate) -> AnyPublisher<URL, Error>
|
||||
{
|
||||
Just(()).tryMap {
|
||||
#if targetEnvironment(simulator)
|
||||
throw PatchAppError.unsupportedOperatingSystemVersion(ProcessInfo.processInfo.operatingSystemVersion)
|
||||
#else
|
||||
|
||||
try FileManager.default.createDirectory(at: self.patchDirectory, withIntermediateDirectories: true, attributes: nil)
|
||||
|
||||
let archiveURL = self.patchDirectory.appendingPathComponent("ota.archive")
|
||||
try archiveURL.withUnsafeFileSystemRepresentation { archivePath in
|
||||
guard let fz = fragmentzip_open((update.url.absoluteString as NSString).utf8String!) else {
|
||||
throw URLError(.cannotConnectToHost, userInfo: [NSLocalizedDescriptionKey: NSLocalizedString("The connection failed because a connection cannot be made to the host.", comment: ""),
|
||||
NSURLErrorKey: update.url])
|
||||
}
|
||||
defer { fragmentzip_close(fz) }
|
||||
|
||||
self.progress.becomeCurrent(withPendingUnitCount: 100)
|
||||
defer { self.progress.resignCurrent() }
|
||||
|
||||
guard fragmentzip_download_file(fz, update.archivePath, archivePath!, ALTFragmentZipCallback) == 0 else {
|
||||
throw URLError(.networkConnectionLost, userInfo: [NSLocalizedDescriptionKey: NSLocalizedString("The connection failed because the network connection was lost.", comment: ""),
|
||||
NSURLErrorKey: update.url])
|
||||
}
|
||||
}
|
||||
|
||||
Logger.fugu14.notice("Downloaded iOS OTA archive.")
|
||||
return archiveURL
|
||||
|
||||
#endif
|
||||
}
|
||||
.mapError { ($0 as NSError).withLocalizedFailure(NSLocalizedString("Could not download OTA archive.", comment: "")) }
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func extractSpotlightFromArchive(at archiveURL: URL) -> AnyPublisher<URL, Error>
|
||||
{
|
||||
Just(()).tryMap {
|
||||
#if targetEnvironment(simulator)
|
||||
throw PatchAppError.unsupportedOperatingSystemVersion(ProcessInfo.processInfo.operatingSystemVersion)
|
||||
#else
|
||||
let spotlightPath = "Applications/Spotlight.app/Spotlight"
|
||||
let spotlightFileURL = self.patchDirectory.appendingPathComponent(spotlightPath)
|
||||
|
||||
guard let readFileStream = ArchiveByteStream.fileStream(path: FilePath(archiveURL.path), mode: .readOnly, options: [], permissions: FilePermissions(rawValue: 0o644)),
|
||||
let decompressStream = ArchiveByteStream.decompressionStream(readingFrom: readFileStream),
|
||||
let decodeStream = ArchiveStream.decodeStream(readingFrom: decompressStream),
|
||||
let readStream = ArchiveStream.extractStream(extractingTo: FilePath(self.patchDirectory.path))
|
||||
else { throw CocoaError(.fileReadCorruptFile, userInfo: [NSURLErrorKey: archiveURL]) }
|
||||
|
||||
_ = try ArchiveStream.process(readingFrom: decodeStream, writingTo: readStream) { message, filePath, data in
|
||||
guard filePath == FilePath(spotlightPath) else { return .skip }
|
||||
return .ok
|
||||
}
|
||||
|
||||
Logger.fugu14.notice("Extracted Spotlight from OTA archive.")
|
||||
return spotlightFileURL
|
||||
|
||||
#endif
|
||||
}
|
||||
.mapError { ($0 as NSError).withLocalizedFailure(NSLocalizedString("Could not extract Spotlight from OTA archive.", comment: "")) }
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func patch(_ app: ALTApplication, withBinaryAt patchFileURL: URL) -> AnyPublisher<URL, Error>
|
||||
{
|
||||
Just(()).tryMap {
|
||||
// executableURL may be nil, so use infoDictionary instead to determine executable name.
|
||||
// guard let appName = app.bundle.executableURL?.lastPathComponent else { throw OperationError.invalidApp }
|
||||
guard let appName = app.bundle.infoDictionary?[kCFBundleExecutableKey as String] as? String else { throw OperationError.invalidApp }
|
||||
|
||||
let temporaryAppURL = self.patchDirectory.appendingPathComponent("Patched.app", isDirectory: true)
|
||||
try FileManager.default.copyItem(at: app.fileURL, to: temporaryAppURL)
|
||||
|
||||
let appBinaryURL = temporaryAppURL.appendingPathComponent(appName, isDirectory: false)
|
||||
try self.appPatcher.patchAppBinary(at: appBinaryURL, withBinaryAt: patchFileURL)
|
||||
|
||||
Logger.fugu14.notice("Patched \(app.name, privacy: .public)!")
|
||||
return temporaryAppURL
|
||||
}
|
||||
.mapError { ($0 as NSError).withLocalizedFailure(String(format: NSLocalizedString("Could not patch %@ placeholder.", comment: ""), app.name)) }
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
}
|
||||
@@ -1,494 +0,0 @@
|
||||
//
|
||||
// PatchViewController.swift
|
||||
// AltStore
|
||||
//
|
||||
// Created by Riley Testut on 10/20/21.
|
||||
// Copyright © 2021 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Combine
|
||||
import AltStoreCore
|
||||
import AltSign
|
||||
import Roxas
|
||||
|
||||
extension PatchViewController
|
||||
{
|
||||
enum Step
|
||||
{
|
||||
case confirm
|
||||
case install
|
||||
case openApp
|
||||
case patchApp
|
||||
case reboot
|
||||
case refresh
|
||||
case finish
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
final class PatchViewController: UIViewController
|
||||
{
|
||||
var patchApp: AnyApp?
|
||||
var installedApp: InstalledApp?
|
||||
|
||||
var completionHandler: ((Result<Void, Error>) -> Void)?
|
||||
|
||||
private let context = AuthenticatedOperationContext()
|
||||
|
||||
private var currentStep: Step = .confirm {
|
||||
didSet {
|
||||
DispatchQueue.main.async {
|
||||
self.update()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var buttonHandler: (() -> Void)?
|
||||
private var resignedApp: ALTApplication?
|
||||
|
||||
private lazy var temporaryDirectory: URL = FileManager.default.uniqueTemporaryURL()
|
||||
|
||||
private var didEnterBackgroundObservation: NSObjectProtocol?
|
||||
private weak var cancellableProgress: Progress?
|
||||
|
||||
@IBOutlet private var placeholderView: RSTPlaceholderView!
|
||||
@IBOutlet private var taskDescriptionLabel: UILabel!
|
||||
@IBOutlet private var pillButton: PillButton!
|
||||
@IBOutlet private var cancelBarButtonItem: UIBarButtonItem!
|
||||
@IBOutlet private var cancelButton: UIButton!
|
||||
|
||||
override func viewDidLoad()
|
||||
{
|
||||
super.viewDidLoad()
|
||||
|
||||
self.isModalInPresentation = true
|
||||
|
||||
self.placeholderView.stackView.spacing = 20
|
||||
self.placeholderView.textLabel.textColor = .white
|
||||
|
||||
self.placeholderView.detailTextLabel.textAlignment = .left
|
||||
self.placeholderView.detailTextLabel.textColor = UIColor.white.withAlphaComponent(0.6)
|
||||
|
||||
self.buttonHandler = { [weak self] in
|
||||
self?.startProcess()
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
try FileManager.default.createDirectory(at: self.temporaryDirectory, withIntermediateDirectories: true, attributes: nil)
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger.fugu14.error("Failed to create temporary directory \(self.temporaryDirectory.lastPathComponent, privacy: .public). \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
|
||||
self.update()
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool)
|
||||
{
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
if self.installedApp != nil
|
||||
{
|
||||
self.refreshApp()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension PatchViewController
|
||||
{
|
||||
func update()
|
||||
{
|
||||
self.cancelButton.alpha = 0.0
|
||||
|
||||
switch self.currentStep
|
||||
{
|
||||
case .confirm:
|
||||
guard let app = self.patchApp else { break }
|
||||
|
||||
if UIDevice.current.isUntetheredJailbreakRequired
|
||||
{
|
||||
self.placeholderView.textLabel.text = NSLocalizedString("Jailbreak Requires Untethering", comment: "")
|
||||
self.placeholderView.detailTextLabel.text = String(format: NSLocalizedString("This jailbreak is untethered, which means %@ will never expire — even after 7 days or rebooting the device.\n\nInstalling an untethered jailbreak requires a few extra steps, but SideStore will walk you through the process.\n\nWould you like to continue? ", comment: ""), app.name)
|
||||
}
|
||||
else
|
||||
{
|
||||
self.placeholderView.textLabel.text = NSLocalizedString("Jailbreak Supports Untethering", comment: "")
|
||||
self.placeholderView.detailTextLabel.text = String(format: NSLocalizedString("This jailbreak has an untethered version, which means %@ will never expire — even after 7 days or rebooting the device.\n\nInstalling an untethered jailbreak requires a few extra steps, but SideStore will walk you through the process.\n\nWould you like to continue? ", comment: ""), app.name)
|
||||
}
|
||||
|
||||
self.pillButton.setTitle(NSLocalizedString("Install Untethered Jailbreak", comment: ""), for: .normal)
|
||||
|
||||
self.cancelButton.alpha = 1.0
|
||||
|
||||
case .install:
|
||||
guard let app = self.patchApp else { break }
|
||||
|
||||
self.placeholderView.textLabel.text = String(format: NSLocalizedString("Installing %@ placeholder…", comment: ""), app.name)
|
||||
self.placeholderView.detailTextLabel.text = NSLocalizedString("A placeholder app needs to be installed in order to prepare your device for untethering.\n\nThis may take a few moments.", comment: "")
|
||||
|
||||
case .openApp:
|
||||
self.placeholderView.textLabel.text = NSLocalizedString("Continue in App", comment: "")
|
||||
self.placeholderView.detailTextLabel.text = NSLocalizedString("Please open the placeholder app and follow the instructions to continue jailbreaking your device.", comment: "")
|
||||
|
||||
self.pillButton.setTitle(NSLocalizedString("Open Placeholder", comment: ""), for: .normal)
|
||||
|
||||
case .patchApp:
|
||||
guard let app = self.patchApp else { break }
|
||||
|
||||
self.placeholderView.textLabel.text = String(format: NSLocalizedString("Patching %@ placeholder…", comment: ""), app.name)
|
||||
self.placeholderView.detailTextLabel.text = NSLocalizedString("This will take a few moments. Please do not turn off the screen or leave the app until patching is complete.", comment: "")
|
||||
|
||||
self.pillButton.setTitle(NSLocalizedString("Patch Placeholder", comment: ""), for: .normal)
|
||||
|
||||
case .reboot:
|
||||
self.placeholderView.textLabel.text = NSLocalizedString("Continue in App", comment: "")
|
||||
self.placeholderView.detailTextLabel.text = NSLocalizedString("Please open the placeholder app and follow the instructions to continue jailbreaking your device.", comment: "")
|
||||
|
||||
self.pillButton.setTitle(NSLocalizedString("Open Placeholder", comment: ""), for: .normal)
|
||||
|
||||
case .refresh:
|
||||
guard let installedApp = self.installedApp else { break }
|
||||
|
||||
self.placeholderView.textLabel.text = String(format: NSLocalizedString("Finish installing %@?", comment: ""), installedApp.name)
|
||||
self.placeholderView.detailTextLabel.text = String(format: NSLocalizedString("In order to finish jailbreaking this device, you need to install %@ then follow the instructions in the app.", comment: ""), installedApp.name)
|
||||
|
||||
self.pillButton.setTitle(String(format: NSLocalizedString("Install %@", comment: ""), installedApp.name), for: .normal)
|
||||
|
||||
case .finish:
|
||||
guard let installedApp = self.installedApp else { break }
|
||||
|
||||
self.placeholderView.textLabel.text = String(format: NSLocalizedString("Finish in %@", comment: ""), installedApp.name)
|
||||
self.placeholderView.detailTextLabel.text = String(format: NSLocalizedString("Follow the instructions in %@ to finish jailbreaking this device.", comment: ""), installedApp.name)
|
||||
|
||||
self.pillButton.setTitle(String(format: NSLocalizedString("Open %@", comment: ""), installedApp.name), for: .normal)
|
||||
}
|
||||
}
|
||||
|
||||
func present(_ error: Error, title: String)
|
||||
{
|
||||
DispatchQueue.main.async {
|
||||
let nsError = error as NSError
|
||||
|
||||
let alertController = UIAlertController(title: nsError.localizedFailure ?? title, message: error.localizedDescription, preferredStyle: .alert)
|
||||
alertController.addAction(.ok)
|
||||
self.present(alertController, animated: true, completion: nil)
|
||||
|
||||
self.setProgress(nil, description: nil)
|
||||
}
|
||||
}
|
||||
|
||||
func setProgress(_ progress: Progress?, description: String?)
|
||||
{
|
||||
DispatchQueue.main.async {
|
||||
self.pillButton.progress = progress
|
||||
self.taskDescriptionLabel.text = description ?? " " // Use non-empty string to prevent label resizing itself.
|
||||
}
|
||||
}
|
||||
|
||||
func finish(with result: Result<Void, Error>)
|
||||
{
|
||||
do
|
||||
{
|
||||
try FileManager.default.removeItem(at: self.temporaryDirectory)
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger.fugu14.error("Failed to remove temporary directory \(self.temporaryDirectory.lastPathComponent, privacy: .public). \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
|
||||
if let observation = self.didEnterBackgroundObservation
|
||||
{
|
||||
NotificationCenter.default.removeObserver(observation)
|
||||
}
|
||||
|
||||
self.completionHandler?(result)
|
||||
self.completionHandler = nil
|
||||
}
|
||||
}
|
||||
|
||||
private extension PatchViewController
|
||||
{
|
||||
@IBAction func performButtonAction()
|
||||
{
|
||||
self.buttonHandler?()
|
||||
}
|
||||
|
||||
@IBAction func cancel()
|
||||
{
|
||||
self.finish(with: .success(()))
|
||||
|
||||
self.cancellableProgress?.cancel()
|
||||
}
|
||||
|
||||
@IBAction func installRegularJailbreak()
|
||||
{
|
||||
guard let app = self.patchApp else { return }
|
||||
|
||||
let title: String
|
||||
let message: String
|
||||
|
||||
if UIDevice.current.isUntetheredJailbreakRequired
|
||||
{
|
||||
title = NSLocalizedString("Untethering Required", comment: "")
|
||||
message = String(format: NSLocalizedString("%@ can not jailbreak this device unless you untether it first. Are you sure you want to install without untethering?", comment: ""), app.name)
|
||||
}
|
||||
else
|
||||
{
|
||||
title = NSLocalizedString("Untethering Recommended", comment: "")
|
||||
message = String(format: NSLocalizedString("Untethering this jailbreak will prevent %@ from expiring, even after 7 days or rebooting the device. Are you sure you want to install without untethering?", comment: ""), app.name)
|
||||
}
|
||||
|
||||
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Install Without Untethering", comment: ""), style: .default) { _ in
|
||||
self.finish(with: .failure(OperationError.cancelled))
|
||||
})
|
||||
alertController.addAction(.cancel)
|
||||
self.present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
private extension PatchViewController
|
||||
{
|
||||
func startProcess()
|
||||
{
|
||||
guard let patchApp = self.patchApp else { return }
|
||||
|
||||
self.currentStep = .install
|
||||
|
||||
if let progress = AppManager.shared.installationProgress(for: patchApp)
|
||||
{
|
||||
// Cancel pending jailbreak app installation so we can start a new one.
|
||||
progress.cancel()
|
||||
}
|
||||
|
||||
let appURL = InstalledApp.fileURL(for: patchApp)
|
||||
let cachedAppURL = self.temporaryDirectory.appendingPathComponent("Cached.app")
|
||||
|
||||
do
|
||||
{
|
||||
// Make copy of original app, so we can replace the cached patch app with it later.
|
||||
try FileManager.default.copyItem(at: appURL, to: cachedAppURL, shouldReplace: true)
|
||||
}
|
||||
catch
|
||||
{
|
||||
self.present(error, title: NSLocalizedString("Could not back up jailbreak app.", comment: ""))
|
||||
return
|
||||
}
|
||||
|
||||
var unzippingError: Error?
|
||||
let refreshGroup = AppManager.shared.install(patchApp, presentingViewController: self, context: self.context) { result in
|
||||
do
|
||||
{
|
||||
_ = try result.get()
|
||||
|
||||
if let unzippingError = unzippingError
|
||||
{
|
||||
throw unzippingError
|
||||
}
|
||||
|
||||
// Replace cached patch app with original app so we can resume installing it post-reboot.
|
||||
try FileManager.default.copyItem(at: cachedAppURL, to: appURL, shouldReplace: true)
|
||||
|
||||
self.openApp()
|
||||
}
|
||||
catch
|
||||
{
|
||||
self.present(error, title: String(format: NSLocalizedString("Could not install %@ placeholder.", comment: ""), patchApp.name))
|
||||
}
|
||||
}
|
||||
refreshGroup.beginInstallationHandler = { (installedApp) in
|
||||
do
|
||||
{
|
||||
// Replace patch app name with correct name.
|
||||
installedApp.name = patchApp.name
|
||||
|
||||
let ipaURL = installedApp.refreshedIPAURL
|
||||
let resignedAppURL = try FileManager.default.unzipAppBundle(at: ipaURL, toDirectory: self.temporaryDirectory)
|
||||
|
||||
self.resignedApp = ALTApplication(fileURL: resignedAppURL)
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger.fugu14.error("Error unzipping app bundle: \(error.localizedDescription, privacy: .public)")
|
||||
unzippingError = error
|
||||
}
|
||||
}
|
||||
self.setProgress(refreshGroup.progress, description: nil)
|
||||
|
||||
self.cancellableProgress = refreshGroup.progress
|
||||
}
|
||||
|
||||
func openApp()
|
||||
{
|
||||
guard let patchApp = self.patchApp else { return }
|
||||
|
||||
self.setProgress(nil, description: nil)
|
||||
self.currentStep = .openApp
|
||||
|
||||
// This observation is willEnterForeground because patching starts immediately upon return.
|
||||
self.didEnterBackgroundObservation = NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: .main) { (notification) in
|
||||
self.didEnterBackgroundObservation.map { NotificationCenter.default.removeObserver($0) }
|
||||
self.patchApplication()
|
||||
}
|
||||
|
||||
self.buttonHandler = { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
#if !targetEnvironment(simulator)
|
||||
|
||||
let openURL = InstalledApp.openAppURL(for: patchApp)
|
||||
UIApplication.shared.open(openURL) { success in
|
||||
guard !success else { return }
|
||||
self.present(OperationError.openAppFailed(name: patchApp.name), title: String(format: NSLocalizedString("Could not open %@ placeholder.", comment: ""), patchApp.name))
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
func patchApplication()
|
||||
{
|
||||
guard let resignedApp = self.resignedApp else { return }
|
||||
|
||||
self.currentStep = .patchApp
|
||||
|
||||
self.buttonHandler = { [weak self] in
|
||||
self?.patchApplication()
|
||||
}
|
||||
|
||||
let patchAppOperation = AppManager.shared.patch(resignedApp: resignedApp, presentingViewController: self, context: self.context) { result in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): self.present(error, title: String(format: NSLocalizedString("Could not patch %@ placeholder.", comment: ""), resignedApp.name))
|
||||
case .success: self.rebootDevice()
|
||||
}
|
||||
}
|
||||
patchAppOperation.progressHandler = { (progress, description) in
|
||||
self.setProgress(progress, description: description)
|
||||
}
|
||||
self.cancellableProgress = patchAppOperation.progress
|
||||
}
|
||||
|
||||
func rebootDevice()
|
||||
{
|
||||
guard let patchApp = self.patchApp else { return }
|
||||
|
||||
self.setProgress(nil, description: nil)
|
||||
self.currentStep = .reboot
|
||||
|
||||
self.didEnterBackgroundObservation = NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: .main) { (notification) in
|
||||
self.didEnterBackgroundObservation.map { NotificationCenter.default.removeObserver($0) }
|
||||
|
||||
var patchedApps = UserDefaults.standard.patchedApps ?? []
|
||||
if !patchedApps.contains(patchApp.bundleIdentifier)
|
||||
{
|
||||
patchedApps.append(patchApp.bundleIdentifier)
|
||||
UserDefaults.standard.patchedApps = patchedApps
|
||||
}
|
||||
|
||||
self.finish(with: .success(()))
|
||||
}
|
||||
|
||||
self.buttonHandler = { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
#if !targetEnvironment(simulator)
|
||||
|
||||
let openURL = InstalledApp.openAppURL(for: patchApp)
|
||||
UIApplication.shared.open(openURL) { success in
|
||||
guard !success else { return }
|
||||
self.present(OperationError.openAppFailed(name: patchApp.name), title: String(format: NSLocalizedString("Could not open %@ placeholder.", comment: ""), patchApp.name))
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
func refreshApp()
|
||||
{
|
||||
guard let installedApp = self.installedApp else { return }
|
||||
|
||||
self.currentStep = .refresh
|
||||
|
||||
self.buttonHandler = { [weak self] in
|
||||
guard let self = self else { return }
|
||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { context in
|
||||
let tempApp = context.object(with: installedApp.objectID) as! InstalledApp
|
||||
tempApp.needsResign = true
|
||||
|
||||
let errorTitle = String(format: NSLocalizedString("Could not install %@.", comment: ""), tempApp.name)
|
||||
|
||||
do
|
||||
{
|
||||
try context.save()
|
||||
|
||||
installedApp.managedObjectContext?.perform {
|
||||
// Refreshing ensures we don't attempt to patch the app again,
|
||||
// since that is only checked when installing a new app.
|
||||
let refreshGroup = AppManager.shared.refresh([installedApp], presentingViewController: self, group: nil)
|
||||
refreshGroup.completionHandler = { [weak refreshGroup, weak self] (results) in
|
||||
guard let self = self else { return }
|
||||
|
||||
do
|
||||
{
|
||||
guard let (bundleIdentifier, result) = results.first else { throw refreshGroup?.context.error ?? OperationError.unknown() }
|
||||
_ = try result.get()
|
||||
|
||||
if var patchedApps = UserDefaults.standard.patchedApps, let index = patchedApps.firstIndex(of: bundleIdentifier)
|
||||
{
|
||||
patchedApps.remove(at: index)
|
||||
UserDefaults.standard.patchedApps = patchedApps
|
||||
}
|
||||
|
||||
self.finish()
|
||||
}
|
||||
catch
|
||||
{
|
||||
self.present(error, title: errorTitle)
|
||||
}
|
||||
}
|
||||
self.setProgress(refreshGroup.progress, description: String(format: NSLocalizedString("Installing %@...", comment: ""), installedApp.name))
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
self.present(error, title: errorTitle)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func finish()
|
||||
{
|
||||
guard let installedApp = self.installedApp else { return }
|
||||
|
||||
self.setProgress(nil, description: nil)
|
||||
self.currentStep = .finish
|
||||
|
||||
self.didEnterBackgroundObservation = NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: .main) { (notification) in
|
||||
self.didEnterBackgroundObservation.map { NotificationCenter.default.removeObserver($0) }
|
||||
self.finish(with: .success(()))
|
||||
}
|
||||
|
||||
installedApp.managedObjectContext?.perform {
|
||||
let appName = installedApp.name
|
||||
let openURL = installedApp.openAppURL
|
||||
|
||||
self.buttonHandler = { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
#if !targetEnvironment(simulator)
|
||||
|
||||
UIApplication.shared.open(openURL) { success in
|
||||
guard !success else { return }
|
||||
self.present(OperationError.openAppFailed(name: appName), title: String(format: NSLocalizedString("Could not open %@.", comment: ""), appName))
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
//
|
||||
// fragmentzip.h
|
||||
// AltStore
|
||||
//
|
||||
// Created by Riley Testut on 10/25/21.
|
||||
// Copyright © 2021 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef fragmentzip_h
|
||||
#define fragmentzip_h
|
||||
|
||||
typedef void fragmentzip_t;
|
||||
typedef void (*fragmentzip_process_callback_t)(unsigned int progress);
|
||||
fragmentzip_t *fragmentzip_open(const char *url);
|
||||
int fragmentzip_download_file(fragmentzip_t *info, const char *remotepath, const char *savepath, fragmentzip_process_callback_t callback);
|
||||
void fragmentzip_close(fragmentzip_t *info);
|
||||
|
||||
#endif /* fragmentzip_h */
|
||||
@@ -10,7 +10,6 @@ import Network
|
||||
import AltStoreCore
|
||||
import Minimuxer
|
||||
|
||||
|
||||
@objc(SendAppOperation)
|
||||
final class SendAppOperation: ResultOperation<()>
|
||||
{
|
||||
@@ -44,15 +43,28 @@ final class SendAppOperation: ResultOperation<()>
|
||||
let fileURL = InstalledApp.refreshedIPAURL(for: app)
|
||||
print("AFC App `fileURL`: \(fileURL.absoluteString)")
|
||||
|
||||
// Wait for Shortcut to Finish Before Proceeding
|
||||
DispatchQueue.main.async {
|
||||
// UIApplication.shared.open(shortcutURLoff, options: [:]) { _ in
|
||||
// print("Shortcut finished execution. Proceeding with file transfer.")
|
||||
// only when minimuxer is not ready and below 26.4 should we turn off data
|
||||
if #available(iOS 26.4, *) {
|
||||
context.shouldTurnOffData = false
|
||||
} else if !isMinimuxerReady {
|
||||
context.shouldTurnOffData = true
|
||||
} else {
|
||||
context.shouldTurnOffData = false
|
||||
}
|
||||
|
||||
if context.shouldTurnOffData {
|
||||
// Wait for Shortcut to Finish Before Proceeding
|
||||
UIApplication.shared.open(shortcutURLoff, options: [:]) { _ in
|
||||
print("Shortcut finished execution. Proceeding with file transfer.")
|
||||
|
||||
DispatchQueue.global().async {
|
||||
self.processFile(at: fileURL, for: app.bundleIdentifier)
|
||||
}
|
||||
// }
|
||||
}
|
||||
} else {
|
||||
DispatchQueue.global().async {
|
||||
self.processFile(at: fileURL, for: app.bundleIdentifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
1
AltStore/Resources/AltBackup.ipa
Symbolic link
1
AltStore/Resources/AltBackup.ipa
Symbolic link
@@ -0,0 +1 @@
|
||||
../../build/AltBackup.ipa
|
||||
@@ -37,11 +37,11 @@ struct VPNConfigurationView: View {
|
||||
editable: false
|
||||
)
|
||||
} header: {
|
||||
Text("Override Configuration")
|
||||
Text("User Configuration")
|
||||
} footer: {
|
||||
HStack(alignment: .top, spacing: 0) {
|
||||
Text("Note: ")
|
||||
Text("if override configuration is invalid or unusable SideStore may use auto-discovered config as fallback.")
|
||||
Text("'Device IP' is mandatory and should match exactly as in the VPN's configuration")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -56,7 +56,6 @@ struct VPNConfigurationView: View {
|
||||
}
|
||||
|
||||
private func commitChanges() {
|
||||
TunnelConfig.shared.commitFakeIP()
|
||||
bindTunnelConfig()
|
||||
}
|
||||
|
||||
@@ -99,7 +98,7 @@ final class TunnelConfig: ObservableObject {
|
||||
static let shared = TunnelConfig()
|
||||
|
||||
private static let defaultOverrideIP: String = {
|
||||
if #available(iOS 26.4, *) { return "192.168.1.50" }
|
||||
// if #available(iOS 26.4, *) { return "192.168.1.50" }
|
||||
return "10.7.0.1"
|
||||
}()
|
||||
|
||||
@@ -117,8 +116,4 @@ final class TunnelConfig: ObservableObject {
|
||||
}
|
||||
|
||||
var overrideActive: String { overrideEffective ? "Yes" : "No" }
|
||||
|
||||
func commitFakeIP() {
|
||||
fakeIP = overrideFakeIP
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,35 +61,6 @@ final class TabBarController: UITabBarController
|
||||
self.initialSegue = nil
|
||||
self.performSegue(withIdentifier: identifier, sender: sender)
|
||||
}
|
||||
else if let patchedApps = UserDefaults.standard.patchedApps, !patchedApps.isEmpty
|
||||
{
|
||||
// Check if we need to finish installing untethered jailbreak.
|
||||
let activeApps = InstalledApp.fetchActiveApps(in: DatabaseManager.shared.viewContext)
|
||||
guard let patchedApp = activeApps.first(where: { patchedApps.contains($0.bundleIdentifier) }) else { return }
|
||||
|
||||
self.performSegue(withIdentifier: "finishJailbreak", sender: patchedApp)
|
||||
}
|
||||
}
|
||||
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
|
||||
{
|
||||
guard let identifier = segue.identifier else { return }
|
||||
|
||||
switch identifier
|
||||
{
|
||||
case "finishJailbreak":
|
||||
guard let installedApp = sender as? InstalledApp else { return }
|
||||
|
||||
let navigationController = segue.destination as! UINavigationController
|
||||
|
||||
let patchViewController = navigationController.viewControllers.first as! PatchViewController
|
||||
patchViewController.installedApp = installedApp
|
||||
patchViewController.completionHandler = { [weak self] _ in
|
||||
self?.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
||||
override func performSegue(withIdentifier identifier: String, sender: Any?)
|
||||
|
||||
@@ -85,12 +85,12 @@ extension ProcessInfo {
|
||||
}
|
||||
|
||||
public var sparseRestorePatched: Bool {
|
||||
if operatingSystemVersion < OperatingSystemVersion(majorVersion: 18, minorVersion: 1, patchVersion: 0) { false }
|
||||
else if operatingSystemVersion > OperatingSystemVersion(majorVersion: 18, minorVersion: 1, patchVersion: 1) { true }
|
||||
else if operatingSystemVersion >= OperatingSystemVersion(majorVersion: 18, minorVersion: 1, patchVersion: 0),
|
||||
let currentBuild = BuildVersion(operatingSystemBuild),
|
||||
let targetBuild = BuildVersion("22B5054e") {
|
||||
currentBuild >= targetBuild
|
||||
} else { false }
|
||||
// only true if we are 18.7.2<=26 || >=26.0.2
|
||||
if (OperatingSystemVersion(majorVersion: 18, minorVersion: 7, patchVersion: 2) <= operatingSystemVersion && operatingSystemVersion.majorVersion == 18) || operatingSystemVersion >= OperatingSystemVersion(majorVersion: 26, minorVersion: 0, patchVersion: 2) { true }
|
||||
// we are 26.0<26.0.2
|
||||
else if operatingSystemVersion < OperatingSystemVersion(majorVersion: 26, minorVersion: 0, patchVersion: 2) { false }
|
||||
// we are <18.7.2
|
||||
else if operatingSystemVersion < OperatingSystemVersion(majorVersion: 18, minorVersion: 7, patchVersion: 2) { false }
|
||||
else { true }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import WidgetKit
|
||||
|
||||
extension View
|
||||
{
|
||||
@@ -78,4 +79,59 @@ extension View
|
||||
}
|
||||
}
|
||||
|
||||
/// Opts this view into the widget accent group on iOS 16+, which lets the
|
||||
/// system tint it with the user's chosen colour in tinted (accented) mode.
|
||||
/// No-op on older OS versions where the API does not exist.
|
||||
@ViewBuilder
|
||||
func widgetAccentableIfAvailable() -> some View
|
||||
{
|
||||
if #available(iOSApplicationExtension 16, *)
|
||||
{
|
||||
self.widgetAccentable()
|
||||
}
|
||||
else
|
||||
{
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Applies `luminanceToAlpha()` only when the widget is rendering in
|
||||
/// accented (tinted) mode on iOS 16+. This converts the view's pixel
|
||||
/// brightness into opacity so the system can overlay the user's chosen
|
||||
/// tint colour correctly — without it, images appear as white rectangles
|
||||
/// in tinted mode. No-op in fullColor/dark/light mode and on older OS.
|
||||
@ViewBuilder
|
||||
func luminanceToAlphaInAccentedMode() -> some View
|
||||
{
|
||||
if #available(iOSApplicationExtension 16, *)
|
||||
{
|
||||
LuminanceToAlphaWrapper(content: self)
|
||||
}
|
||||
else
|
||||
{
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Helper view that reads widgetRenderingMode (iOS 16+) and conditionally
|
||||
/// applies luminanceToAlpha(). Kept separate so the environment read is
|
||||
/// cleanly scoped behind the @available gate.
|
||||
@available(iOSApplicationExtension 16, *)
|
||||
private struct LuminanceToAlphaWrapper<Content: View>: View
|
||||
{
|
||||
let content: Content
|
||||
@Environment(\.widgetRenderingMode) private var renderingMode
|
||||
|
||||
var body: some View {
|
||||
if renderingMode == .accented
|
||||
{
|
||||
content.luminanceToAlpha()
|
||||
}
|
||||
else
|
||||
{
|
||||
content
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
73
AltWidget/Intents/ViewAppIntent.swift
Normal file
73
AltWidget/Intents/ViewAppIntent.swift
Normal file
@@ -0,0 +1,73 @@
|
||||
//
|
||||
// ViewAppIntent.swift
|
||||
// AltWidgetExtension
|
||||
//
|
||||
// Replaces the legacy SiriKit ViewAppIntent (ViewApp.intentdefinition) with a
|
||||
// modern AppIntents-based intent. Required because IntentConfiguration does not
|
||||
// support containerBackground on iOS 17+, causing the blank-widget bug.
|
||||
//
|
||||
|
||||
import AppIntents
|
||||
import WidgetKit
|
||||
import AltStoreCore
|
||||
|
||||
// Represents one installed app in the picker list.
|
||||
@available(iOSApplicationExtension 17, *)
|
||||
struct InstalledAppEntity: AppEntity
|
||||
{
|
||||
// Disambiguates from the AppEntity name used in AppIntents framework.
|
||||
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Installed App"
|
||||
static var defaultQuery = InstalledAppQuery()
|
||||
|
||||
var id: String // bundle identifier
|
||||
var name: String
|
||||
|
||||
var displayRepresentation: DisplayRepresentation {
|
||||
DisplayRepresentation(title: "\(name)")
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOSApplicationExtension 17, *)
|
||||
struct InstalledAppQuery: EntityQuery
|
||||
{
|
||||
func entities(for identifiers: [String]) async throws -> [InstalledAppEntity]
|
||||
{
|
||||
try await DatabaseManager.shared.start()
|
||||
let context = DatabaseManager.shared.persistentContainer.newBackgroundContext()
|
||||
return try await context.performAsync {
|
||||
let fetchRequest = InstalledApp.fetchRequest()
|
||||
fetchRequest.predicate = NSPredicate(
|
||||
format: "%K IN %@",
|
||||
#keyPath(InstalledApp.bundleIdentifier),
|
||||
identifiers
|
||||
)
|
||||
fetchRequest.returnsObjectsAsFaults = false
|
||||
let apps = try context.fetch(fetchRequest)
|
||||
return apps.map { InstalledAppEntity(id: $0.bundleIdentifier, name: $0.name) }
|
||||
}
|
||||
}
|
||||
|
||||
func suggestedEntities() async throws -> [InstalledAppEntity]
|
||||
{
|
||||
try await DatabaseManager.shared.start()
|
||||
let context = DatabaseManager.shared.persistentContainer.newBackgroundContext()
|
||||
return try await context.performAsync {
|
||||
InstalledApp.all(in: context)
|
||||
.map { InstalledAppEntity(id: $0.bundleIdentifier, name: $0.name) }
|
||||
.sorted { $0.name < $1.name }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOSApplicationExtension 17, *)
|
||||
struct SelectAppIntent: WidgetConfigurationIntent
|
||||
{
|
||||
static var title: LocalizedStringResource = "Select App"
|
||||
static var description = IntentDescription("Choose which app to display.")
|
||||
|
||||
@Parameter(title: "App")
|
||||
var app: InstalledAppEntity?
|
||||
|
||||
// WidgetConfigurationIntent requires perform() — no-op for configuration intents.
|
||||
func perform() async throws -> some IntentResult { .result() }
|
||||
}
|
||||
@@ -221,3 +221,33 @@ class AppsTimelineProvider: AppsTimelineProviderBase<Intent>, IntentTimelineProv
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Modern AppIntents-based provider for AppDetailWidget on iOS 17+.
|
||||
// Replaces AppsTimelineProvider (IntentTimelineProvider) which uses the legacy
|
||||
// SiriKit Intents framework that breaks containerBackground on iOS 17+.
|
||||
@available(iOSApplicationExtension 17, *)
|
||||
class SelectAppTimelineProvider: AppsTimelineProviderBase<SelectAppIntent>, AppIntentTimelineProvider
|
||||
{
|
||||
typealias Intent = SelectAppIntent
|
||||
|
||||
func snapshot(for intent: SelectAppIntent, in context: Context) async -> AppsEntry<SelectAppIntent>
|
||||
{
|
||||
let bundleID = await resolvedBundleID(for: intent)
|
||||
return await self.snapshot(for: [bundleID], in: intent)
|
||||
}
|
||||
|
||||
func timeline(for intent: SelectAppIntent, in context: Context) async -> Timeline<AppsEntry<SelectAppIntent>>
|
||||
{
|
||||
let bundleID = await resolvedBundleID(for: intent)
|
||||
return await self.timeline(for: [bundleID], in: intent)
|
||||
}
|
||||
|
||||
// If the user hasn't picked an app yet, fall back to the first active app
|
||||
// rather than a hardcoded bundle ID that may not exist in the database.
|
||||
private func resolvedBundleID(for intent: SelectAppIntent) async -> String
|
||||
{
|
||||
if let id = intent.app?.id { return id }
|
||||
let activeIDs = await self.fetchActiveAppBundleIDs()
|
||||
return activeIDs.first ?? StoreApp.altstoreAppID
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +74,9 @@ private struct ActiveAppsWidgetView: View
|
||||
|
||||
@Environment(\.colorScheme)
|
||||
private var colorScheme
|
||||
|
||||
@Environment(\.widgetRenderingMode)
|
||||
private var renderingMode
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
@@ -92,6 +95,12 @@ private struct ActiveAppsWidgetView: View
|
||||
{
|
||||
LinearGradient(colors: [.altGradientDark, .altGradientExtraDark], startPoint: .top, endPoint: .bottom)
|
||||
}
|
||||
else if renderingMode == .accented
|
||||
{
|
||||
// Plain dark background in tinted mode so the system's
|
||||
// accent colour composites cleanly over it.
|
||||
Color.black
|
||||
}
|
||||
else
|
||||
{
|
||||
LinearGradient(colors: [.altGradientLight, .altGradientDark], startPoint: .top, endPoint: .bottom)
|
||||
@@ -128,11 +137,16 @@ private struct ActiveAppsWidgetView: View
|
||||
let daysRemaining = app.expirationDate.numberOfCalendarDays(since: entry.date)
|
||||
|
||||
HStack(spacing: 10) {
|
||||
// In tinted (accented) mode, luminanceToAlpha() converts the icon's
|
||||
// brightness into opacity so the system can tint it with the user's
|
||||
// chosen accent colour. widgetAccentable() opts the view into that
|
||||
// accent group. In fullColor mode both are no-ops (via the helpers).
|
||||
Image(uiImage: resizedIcon)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.cornerRadius(cornerRadius)
|
||||
|
||||
.luminanceToAlphaInAccentedMode()
|
||||
.mask(RoundedRectangle(cornerRadius: cornerRadius, style: .continuous))
|
||||
.widgetAccentableIfAvailable()
|
||||
|
||||
VStack(alignment: .leading, spacing: 1) {
|
||||
Text(app.name)
|
||||
@@ -151,6 +165,7 @@ private struct ActiveAppsWidgetView: View
|
||||
.font(.system(size: 13, weight: .semibold, design: .rounded))
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
.widgetAccentableIfAvailable()
|
||||
|
||||
Spacer()
|
||||
|
||||
@@ -167,6 +182,7 @@ private struct ActiveAppsWidgetView: View
|
||||
.activatesRefreshAllAppsIntent()
|
||||
// this modifier invalidates the view (disables user interaction and shows a blinking effect)
|
||||
.invalidatableContent()
|
||||
.widgetAccentableIfAvailable()
|
||||
|
||||
}
|
||||
.frame(height: rowHeight)
|
||||
|
||||
@@ -15,36 +15,52 @@ struct AppDetailWidget: Widget
|
||||
private let kind: String = "AppDetail"
|
||||
|
||||
public var body: some WidgetConfiguration {
|
||||
let configuration = IntentConfiguration(kind: kind,
|
||||
intent: ViewAppIntent.self,
|
||||
provider: AppsTimelineProvider()) { (entry) in
|
||||
AppDetailWidgetView(entry: entry)
|
||||
}
|
||||
.supportedFamilies([.systemSmall])
|
||||
.configurationDisplayName("App Status")
|
||||
.description("View remaining days until your sideloaded apps expire. Tap the countdown timer to refresh them in the background.")
|
||||
|
||||
if #available(iOS 17, *)
|
||||
// On iOS 16+ use AppIntentConfiguration — it correctly supports
|
||||
// containerBackground and contentMarginsDisabled(), unlike the legacy
|
||||
// IntentConfiguration which breaks on iOS 17+ with the
|
||||
// "Please adopt containerBackground" error.
|
||||
if #available(iOSApplicationExtension 17, *)
|
||||
{
|
||||
return configuration
|
||||
.contentMarginsDisabled()
|
||||
return AppIntentConfiguration(
|
||||
kind: kind,
|
||||
intent: SelectAppIntent.self,
|
||||
provider: SelectAppTimelineProvider()
|
||||
) { entry in
|
||||
AppDetailWidgetView(apps: entry.apps, date: entry.date, isPlaceholder: entry.isPlaceholder)
|
||||
}
|
||||
.supportedFamilies([.systemSmall])
|
||||
.configurationDisplayName("App Status")
|
||||
.description("View remaining days until your sideloaded apps expire. Tap the countdown timer to refresh them in the background.")
|
||||
.contentMarginsDisabled()
|
||||
}
|
||||
else
|
||||
{
|
||||
return configuration
|
||||
// Legacy path for iOS 15.
|
||||
return IntentConfiguration(
|
||||
kind: kind,
|
||||
intent: ViewAppIntent.self,
|
||||
provider: AppsTimelineProvider()
|
||||
) { entry in
|
||||
AppDetailWidgetView(apps: entry.apps, date: entry.date, isPlaceholder: entry.isPlaceholder)
|
||||
}
|
||||
.supportedFamilies([.systemSmall])
|
||||
.configurationDisplayName("App Status")
|
||||
.description("View remaining days until your sideloaded apps expire. Tap the countdown timer to refresh them in the background.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct AppDetailWidgetView: View
|
||||
{
|
||||
var entry: AppsEntry<Intent>
|
||||
let apps: [AppSnapshot]
|
||||
let date: Date
|
||||
let isPlaceholder: Bool
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
if let app = self.entry.apps.first
|
||||
if let app = apps.first
|
||||
{
|
||||
let daysRemaining = app.expirationDate.numberOfCalendarDays(since: self.entry.date)
|
||||
let daysRemaining = app.expirationDate.numberOfCalendarDays(since: date)
|
||||
|
||||
GeometryReader { (geometry) in
|
||||
Group {
|
||||
@@ -52,11 +68,7 @@ private struct AppDetailWidgetView: View
|
||||
VStack(alignment: .leading, spacing: 5) {
|
||||
let imageHeight = geometry.size.height * 0.4
|
||||
|
||||
Image(uiImage: app.icon ?? UIImage())
|
||||
.resizable()
|
||||
.aspectRatio(CGSize(width: 1, height: 1), contentMode: .fit)
|
||||
.frame(height: imageHeight)
|
||||
.mask(RoundedRectangle(cornerRadius: imageHeight / 5.0, style: .continuous))
|
||||
AppIconView(icon: app.icon, imageHeight: imageHeight)
|
||||
|
||||
Text(app.name.uppercased())
|
||||
.font(.system(size: 12, weight: .semibold, design: .rounded))
|
||||
@@ -65,6 +77,7 @@ private struct AppDetailWidgetView: View
|
||||
.minimumScaleFactor(0.5)
|
||||
}
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.widgetAccentableIfAvailable()
|
||||
|
||||
Spacer(minLength: 0)
|
||||
|
||||
@@ -97,7 +110,7 @@ private struct AppDetailWidgetView: View
|
||||
{
|
||||
Countdown(startDate: app.refreshedDate,
|
||||
endDate: app.expirationDate,
|
||||
currentDate: self.entry.date)
|
||||
currentDate: date)
|
||||
.font(.system(size: 20, weight: .semibold, design: .rounded))
|
||||
.foregroundColor(Color.white)
|
||||
.opacity(0.8)
|
||||
@@ -108,6 +121,7 @@ private struct AppDetailWidgetView: View
|
||||
}
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.activatesRefreshAllAppsIntent()
|
||||
.widgetAccentableIfAvailable()
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
@@ -118,7 +132,7 @@ private struct AppDetailWidgetView: View
|
||||
VStack {
|
||||
// Put conditional inside VStack, or else an empty view will be returned
|
||||
// if isPlaceholder == false, which messes up layout.
|
||||
if !entry.isPlaceholder
|
||||
if !isPlaceholder
|
||||
{
|
||||
Text("App Not Found")
|
||||
.font(.system(.body, design: .rounded))
|
||||
@@ -131,15 +145,12 @@ private struct AppDetailWidgetView: View
|
||||
}
|
||||
.widgetBackground(
|
||||
backgroundView(
|
||||
icon: entry.apps.first?.icon,
|
||||
tintColor: entry.apps.first?.tintColor
|
||||
icon: apps.first?.icon,
|
||||
tintColor: apps.first?.tintColor
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private extension AppDetailWidgetView
|
||||
{
|
||||
func backgroundView(icon: UIImage? = nil, tintColor: UIColor? = nil) -> some View
|
||||
{
|
||||
let icon = icon ?? UIImage(named: "SideStore")!
|
||||
@@ -173,12 +184,6 @@ private extension AppDetailWidgetView
|
||||
.saturation(saturation)
|
||||
.blur(radius: blurRadius, opaque: true)
|
||||
.scaleEffect(geometry.size.width / imageHeight, anchor: .center)
|
||||
// .onAppear {
|
||||
// print("Geometry size: \(geometry.size)")
|
||||
// print("Image height: \(imageHeight), Geometry width: \(geometry.size.width)")
|
||||
// print("Icon size: \(icon.size)")
|
||||
// }
|
||||
|
||||
|
||||
Color(tintColor)
|
||||
.opacity(tintOpacity)
|
||||
@@ -193,6 +198,26 @@ private extension AppDetailWidgetView
|
||||
}
|
||||
}
|
||||
|
||||
// In tinted/clear mode: luminanceToAlpha converts pixel brightness → opacity so
|
||||
// the system can overlay the accent colour. Must come BEFORE the mask so the
|
||||
// squircle corners are clipped after conversion (reverse order = corner bleed).
|
||||
// widgetAccentable() opts the result into the accent group.
|
||||
private struct AppIconView: View
|
||||
{
|
||||
let icon: UIImage?
|
||||
let imageHeight: CGFloat
|
||||
|
||||
var body: some View {
|
||||
Image(uiImage: icon ?? UIImage())
|
||||
.resizable()
|
||||
.aspectRatio(CGSize(width: 1, height: 1), contentMode: .fit)
|
||||
.frame(height: imageHeight)
|
||||
.luminanceToAlphaInAccentedMode()
|
||||
.mask(RoundedRectangle(cornerRadius: imageHeight / 5.0, style: .continuous))
|
||||
.widgetAccentableIfAvailable()
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 17, *)
|
||||
#Preview(as: .systemSmall) {
|
||||
AppDetailWidget()
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// Configuration settings file format documentation can be found at:
|
||||
// https://help.apple.com/xcode/#/dev745c5c974
|
||||
|
||||
MARKETING_VERSION = 0.6.3
|
||||
CURRENT_PROJECT_VERSION = 0603
|
||||
MARKETING_VERSION = 0.6.4
|
||||
CURRENT_PROJECT_VERSION = 0604
|
||||
|
||||
// Vars to be overwritten by `CodeSigning.xcconfig` if exists
|
||||
DEVELOPMENT_TEAM = S32Z3HMYVQ
|
||||
|
||||
2
Dependencies/em_proxy
vendored
2
Dependencies/em_proxy
vendored
Submodule Dependencies/em_proxy updated: fcaa590843...816dc73350
BIN
Dependencies/libcurl/libcurl.a
vendored
BIN
Dependencies/libcurl/libcurl.a
vendored
Binary file not shown.
1
Dependencies/libfragmentzip
vendored
1
Dependencies/libfragmentzip
vendored
Submodule Dependencies/libfragmentzip deleted from b7f9272acf
2
Dependencies/minimuxer
vendored
2
Dependencies/minimuxer
vendored
Submodule Dependencies/minimuxer updated: c9236c844a...e3614068c7
7
Makefile
7
Makefile
@@ -169,7 +169,7 @@ MARKETING_VERSION ?=
|
||||
BUNDLE_ID_SUFFIX ?=
|
||||
# Common build settings for xcodebuild
|
||||
COMMON_BUILD_SETTINGS = \
|
||||
-workspace AltStore.xcworkspace \
|
||||
-project AltStore.xcodeproj \
|
||||
-scheme SideStore \
|
||||
-sdk iphoneos \
|
||||
-configuration $(BUILD_CONFIG) \
|
||||
@@ -247,7 +247,7 @@ sim-boot-check:
|
||||
|
||||
clean-build:
|
||||
@echo "Cleaning build artifacts..."
|
||||
@xcodebuild clean -workspace AltStore.xcworkspace -scheme SideStore
|
||||
@xcodebuild clean -project AltStore.xcodeproj -scheme SideStore
|
||||
|
||||
fakesign-apps:
|
||||
rm -rf SideStore.xcarchive/Products/Applications/SideStore.app/Frameworks/AltStoreCore.framework/Frameworks/
|
||||
@@ -374,8 +374,7 @@ ipa-altbackup: checkPaths copy-altbackup
|
||||
@echo " Copying from $(ALT_APP_SRC) into $(ALT_APP_PAYLOAD_DST)"
|
||||
@cp -R -f "$(ALT_APP_SRC)/." "$(ALT_APP_PAYLOAD_DST)/$(TARGET_NAME)"
|
||||
@pushd "$(ALT_APP_DST_ARCHIVE)" && zip -r "../../$(ALT_APP_IPA_DST)" Payload || popd
|
||||
@cp -f "$(ALT_APP_IPA_DST)" AltStore/Resources
|
||||
@echo " IPA created: AltStore/Resources/AltBackup.ipa"
|
||||
@echo " IPA created: build/AltBackup.ipa"
|
||||
|
||||
clean-altbackup:
|
||||
@echo ""
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
},
|
||||
{
|
||||
"identifier": "com.livecontainer.source",
|
||||
"sourceURL": "https://raw.githubusercontent.com/LiveContainer/LiveContainer/refs/heads/main/apps.json"
|
||||
"sourceURL": "https://github.com/LiveContainer/LiveContainer/releases/download/1.0/apps.json"
|
||||
},
|
||||
{
|
||||
"identifier": "com.aoshuang.manicemu",
|
||||
@@ -75,6 +75,10 @@
|
||||
{
|
||||
"identifier": "thatstel.la.altsource",
|
||||
"sourceURL": "https://alt.thatstel.la/"
|
||||
},
|
||||
{
|
||||
"identifier": "com.deliacheminot.mona",
|
||||
"sourceURL": "https://raw.githubusercontent.com/delia-cheminot/mona-hrt/refs/heads/main/ios_source.json"
|
||||
}
|
||||
],
|
||||
"sources": [
|
||||
@@ -91,7 +95,7 @@
|
||||
},
|
||||
{
|
||||
"identifier": "com.livecontainer.source",
|
||||
"sourceURL": "https://raw.githubusercontent.com/LiveContainer/LiveContainer/refs/heads/main/apps.json"
|
||||
"sourceURL": "https://github.com/LiveContainer/LiveContainer/releases/download/1.0/apps.json"
|
||||
},
|
||||
{
|
||||
"identifier": "com.aoshuang.manicemu",
|
||||
@@ -148,6 +152,10 @@
|
||||
{
|
||||
"identifier": "thatstel.la.altsource",
|
||||
"sourceURL": "https://alt.thatstel.la/"
|
||||
},
|
||||
{
|
||||
"identifier": "com.deliacheminot.mona",
|
||||
"sourceURL": "https://raw.githubusercontent.com/delia-cheminot/mona-hrt/refs/heads/main/ios_source.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user