Compare commits
178 Commits
fabianthde
...
ny/chore/m
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0b0c87d564 | ||
|
|
4a00955461 | ||
|
|
86f804391a | ||
|
|
be75d8b4d9 | ||
|
|
864e03cd4a | ||
|
|
be5e84537a | ||
|
|
1ae8d336be | ||
|
|
989b8e0010 | ||
|
|
983355d356 | ||
|
|
a6ff416067 | ||
|
|
bbf712a822 | ||
|
|
dd0436511a | ||
|
|
4667841c8d | ||
|
|
d2f0409452 | ||
|
|
d718d2155f | ||
|
|
d0b424f408 | ||
|
|
3e6ca7c673 | ||
|
|
31a3323ef2 | ||
|
|
7edb5716b7 | ||
|
|
78bf461c5b | ||
|
|
8cfe8a1ac0 | ||
|
|
9b7c0387cf | ||
|
|
1a36726361 | ||
|
|
e2a5e263f4 | ||
|
|
16d13c430c | ||
|
|
b024e67fee | ||
|
|
4365ba0f1a | ||
|
|
36ceec3ae7 | ||
|
|
99652aae65 | ||
|
|
6c8a400aec | ||
|
|
c24de874e6 | ||
|
|
775167415a | ||
|
|
459e378522 | ||
|
|
83ece72ae1 | ||
|
|
d60bcc49e1 | ||
|
|
bc9c37adda | ||
|
|
2583c7f617 | ||
|
|
fea5229e02 | ||
|
|
68be615057 | ||
|
|
370cafcba0 | ||
|
|
f923c1602e | ||
|
|
50a85be872 | ||
|
|
aae4725a3c | ||
|
|
9d76ee9f19 | ||
|
|
34a101b796 | ||
|
|
49b1fd751c | ||
|
|
4c5bf7bb7d | ||
|
|
2d71631d93 | ||
|
|
fa0d933956 | ||
|
|
b5d6384a07 | ||
|
|
d39644a4c9 | ||
|
|
a2feb34dc1 | ||
|
|
7e5fe64153 | ||
|
|
44175d071c | ||
|
|
bae26de444 | ||
|
|
b78707808d | ||
|
|
d41518581a | ||
|
|
4abbfe6142 | ||
|
|
dae813d80c | ||
|
|
af89b178ad | ||
|
|
8c269207fd | ||
|
|
42ecd38517 | ||
|
|
9f7d4dee49 | ||
|
|
458b8e491e | ||
|
|
495e621e69 | ||
|
|
c986512b5f | ||
|
|
d277754ae5 | ||
|
|
2ef2e2f26b | ||
|
|
23a53034fa | ||
|
|
ce57d72a78 | ||
|
|
502b89d890 | ||
|
|
5f0015fad0 | ||
|
|
c81236957b | ||
|
|
970ab38b27 | ||
|
|
8a5c31b81d | ||
|
|
8508fe79b5 | ||
|
|
3859e98801 | ||
|
|
a759c7be9e | ||
|
|
12fc6cf6e2 | ||
|
|
580db6530e | ||
|
|
9c67c237ee | ||
|
|
357d85a72e | ||
|
|
88ad828ce0 | ||
|
|
a95625a34a | ||
|
|
95e00d81f5 | ||
|
|
c2e386a5c5 | ||
|
|
a76aade4ff | ||
|
|
65c9986103 | ||
|
|
9e2b9b6639 | ||
|
|
cf373634d7 | ||
|
|
b3d5d976b4 | ||
|
|
c3c31995ce | ||
|
|
7e92e17429 | ||
|
|
88ab8fa8d7 | ||
|
|
ebe78932bf | ||
|
|
2e613e6d15 | ||
|
|
35ee92db12 | ||
|
|
04d9f760ad | ||
|
|
4f52743be8 | ||
|
|
32cae7a5b2 | ||
|
|
c2c0e3b790 | ||
|
|
6d36a30787 | ||
|
|
48a86ec6de | ||
|
|
5cff914ff3 | ||
|
|
70ea725ce3 | ||
|
|
78f12e45f9 | ||
|
|
e5061acc20 | ||
|
|
2d7bc51d30 | ||
|
|
9128b67ee8 | ||
|
|
551c004476 | ||
|
|
ed6a8d1379 | ||
|
|
766fb89e0b | ||
|
|
c5b8cb4459 | ||
|
|
0deae92829 | ||
|
|
cc5d2f1813 | ||
|
|
41151d0d49 | ||
|
|
52702264a3 | ||
|
|
6e297e1278 | ||
|
|
e3bb9b425f | ||
|
|
79255be79c | ||
|
|
7c836f5ba1 | ||
|
|
938bcd14ad | ||
|
|
229d79fc05 | ||
|
|
2d3dac2e1d | ||
|
|
e23f5e7894 | ||
|
|
571d27c814 | ||
|
|
dde6bd4fe3 | ||
|
|
6e6dbd9329 | ||
|
|
258268f5ef | ||
|
|
9ae49977fb | ||
|
|
d61c54fa60 | ||
|
|
980699af6f | ||
|
|
cc5c280882 | ||
|
|
090456bba1 | ||
|
|
5354d4eb76 | ||
|
|
b986fae611 | ||
|
|
cfcfc3e928 | ||
|
|
f97548fc3a | ||
|
|
36913b425c | ||
|
|
822ea08d89 | ||
|
|
98dd6f3fe7 | ||
|
|
b3f0dbb155 | ||
|
|
6904d931c3 | ||
|
|
529466a9f7 | ||
|
|
77dc695ba1 | ||
|
|
e17776f651 | ||
|
|
0d2f355a74 | ||
|
|
2ce1576016 | ||
|
|
0f3be3c494 | ||
|
|
8c1ca8503a | ||
|
|
32a59c17f4 | ||
|
|
b4b4ceab0b | ||
|
|
be1f27bb9e | ||
|
|
ed10ddb1cb | ||
|
|
dbdb4b0f32 | ||
|
|
59e537362e | ||
|
|
6d96bf414f | ||
|
|
e7ba778a5f | ||
|
|
933d349cd5 | ||
|
|
3de24dcfce | ||
|
|
3275d16b8b | ||
|
|
5bb4cd1dad | ||
|
|
16b14441fa | ||
|
|
93a6272d30 | ||
|
|
0dc526f778 | ||
|
|
183e185812 | ||
|
|
e02453598c | ||
|
|
24af1b5b5f | ||
|
|
5864c283f6 | ||
|
|
be78fa4b91 | ||
|
|
b3abf69a02 | ||
|
|
c530dc11ae | ||
|
|
d368ddbd11 | ||
|
|
e5c6521a15 | ||
|
|
898a59768e | ||
|
|
a85bc93142 | ||
|
|
c6c1f9faa0 | ||
|
|
0eea19c9cc |
2
.github/CODEOWNERS
vendored
@@ -1 +1 @@
|
||||
* @JoeMatt @lonkelle
|
||||
* @JoeMatt @lonkelle @nythepegasus @Spidy123222 @SternXD
|
||||
|
||||
5
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -2,15 +2,14 @@ name: Bug Report
|
||||
description: Report a bug
|
||||
title: "[BUG] "
|
||||
labels: ["bug"]
|
||||
assignees:
|
||||
- naturecodevoid
|
||||
assignees: []
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this bug report! Before you continue filling out the report, please **[search in GitHub Issues](https://github.com/SideStore/SideStore/issues?q=is%3Aissue+is%3Aopen) for the bug you are experiencing** in case it has already been reported.
|
||||
|
||||
**Please use [Discord](https://discord.gg/RgpFBX3Q3k) or [GitHub Discussions](https://github.com/SideStore/SideStore/discussions) for support.**
|
||||
**Please use [Discord](https://discord.gg/sidestore-949183273383395328) or [GitHub Discussions](https://github.com/SideStore/SideStore/discussions) for support.**
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -3,7 +3,7 @@ blank_issues_enabled: false
|
||||
|
||||
contact_links:
|
||||
- name: Discord
|
||||
url: https://discord.gg/RgpFBX3Q3k
|
||||
url: https://discord.gg/sidestore-949183273383395328
|
||||
about: If you need support, please go here first instead of making an issue!
|
||||
- name: GitHub Discussions
|
||||
url: https://github.com/SideStore/SideStore/discussions
|
||||
|
||||
5
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@@ -2,15 +2,14 @@ name: Feature Request
|
||||
description: Suggest a feature
|
||||
title: "[FEATURE REQUEST] "
|
||||
labels: ["enhancement"]
|
||||
assignees:
|
||||
- naturecodevoid
|
||||
assignees: []
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this feature request! Before you continue filling out the form, please **[search in GitHub Issues](https://github.com/SideStore/SideStore/issues?q=is%3Aissue+is%3Aopen) for the feature you are suggestion** in case it has already been suggested.
|
||||
|
||||
**Please use [Discord](https://discord.gg/RgpFBX3Q3k) or [GitHub Discussions](https://github.com/SideStore/SideStore/discussions) for support.**
|
||||
**Please use [Discord](https://discord.gg/sidestore-949183273383395328) or [GitHub Discussions](https://github.com/SideStore/SideStore/discussions) for support.**
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
|
||||
2
.gitmodules
vendored
@@ -9,7 +9,7 @@
|
||||
url = https://github.com/libimobiledevice/libusbmuxd.git
|
||||
[submodule "Dependencies/libplist"]
|
||||
path = Dependencies/libplist
|
||||
url = https://github.com/libimobiledevice/libplist.git
|
||||
url = https://github.com/SideStore/libplist.git
|
||||
[submodule "Dependencies/MarkdownAttributedString"]
|
||||
path = Dependencies/MarkdownAttributedString
|
||||
url = https://github.com/chockenberry/MarkdownAttributedString.git
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.518",
|
||||
"green" : "0.502",
|
||||
"red" : "0.004"
|
||||
"blue" : "175",
|
||||
"green" : "4",
|
||||
"red" : "115"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
@@ -23,9 +23,9 @@
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.404",
|
||||
"green" : "0.322",
|
||||
"red" : "0.008"
|
||||
"blue" : "150",
|
||||
"green" : "3",
|
||||
"red" : "99"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
|
||||
@@ -3,19 +3,54 @@
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 52;
|
||||
objectVersion = 54;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
03F06CD52942C27E001C4D68 /* Bundle+AltStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1E314122A05D4C00370A3C /* Bundle+AltStore.swift */; };
|
||||
0E05025A2BEC83C500879B5C /* OperatingSystemVersion+Comparable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0502592BEC83C500879B5C /* OperatingSystemVersion+Comparable.swift */; };
|
||||
0E05025C2BEC947000879B5C /* String+SideStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E05025B2BEC947000879B5C /* String+SideStore.swift */; };
|
||||
0E1A1F912AE36A9700364CAD /* bytearray.c in Sources */ = {isa = PBXBuildFile; fileRef = 0E1A1F902AE36A9600364CAD /* bytearray.c */; };
|
||||
0E764E172ADFF5740043DD4E /* AltBackup.ipa in Resources */ = {isa = PBXBuildFile; fileRef = 0E764E162ADFF5740043DD4E /* AltBackup.ipa */; };
|
||||
0EA1665B2ADFE0D2003015C1 /* out-limd.c in Sources */ = {isa = PBXBuildFile; fileRef = 0EA166472ADFE0D1003015C1 /* out-limd.c */; };
|
||||
0EA1665C2ADFE0D2003015C1 /* out-default.c in Sources */ = {isa = PBXBuildFile; fileRef = 0EA166522ADFE0D2003015C1 /* out-default.c */; };
|
||||
0EA1665D2ADFE0D2003015C1 /* out-plutil.c in Sources */ = {isa = PBXBuildFile; fileRef = 0EA166552ADFE0D2003015C1 /* out-plutil.c */; };
|
||||
0EA1665E2ADFE0D2003015C1 /* oplist.c in Sources */ = {isa = PBXBuildFile; fileRef = 0EA166562ADFE0D2003015C1 /* oplist.c */; };
|
||||
0EA166682ADFE122003015C1 /* jsmn.h in Headers */ = {isa = PBXBuildFile; fileRef = 0EA166632ADFE122003015C1 /* jsmn.h */; };
|
||||
0EA166692ADFE140003015C1 /* Array.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0EA166452ADFE0D1003015C1 /* Array.cpp */; };
|
||||
0EA1666A2ADFE140003015C1 /* String.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0EA166492ADFE0D1003015C1 /* String.cpp */; };
|
||||
0EA1666B2ADFE140003015C1 /* Boolean.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0EA1664E2ADFE0D1003015C1 /* Boolean.cpp */; };
|
||||
0EA1666C2ADFE140003015C1 /* Integer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0EA166532ADFE0D2003015C1 /* Integer.cpp */; };
|
||||
0EA1666D2ADFE140003015C1 /* Data.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0EA166432ADFE0D1003015C1 /* Data.cpp */; };
|
||||
0EA1666E2ADFE140003015C1 /* ptrarray.c in Sources */ = {isa = PBXBuildFile; fileRef = 0EA166512ADFE0D2003015C1 /* ptrarray.c */; };
|
||||
0EA1666F2ADFE140003015C1 /* hashtable.c in Sources */ = {isa = PBXBuildFile; fileRef = 0EA1664F2ADFE0D1003015C1 /* hashtable.c */; };
|
||||
0EA166702ADFE140003015C1 /* Node.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0EA166502ADFE0D2003015C1 /* Node.cpp */; };
|
||||
0EA166712ADFE140003015C1 /* Key.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0EA166582ADFE0D2003015C1 /* Key.cpp */; };
|
||||
0EA166732ADFE140003015C1 /* Dictionary.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0EA166462ADFE0D1003015C1 /* Dictionary.cpp */; };
|
||||
0EA166742ADFE140003015C1 /* Date.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0EA166422ADFE0D1003015C1 /* Date.cpp */; };
|
||||
0EA166752ADFE140003015C1 /* Real.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0EA166542ADFE0D2003015C1 /* Real.cpp */; };
|
||||
0EA166762ADFE140003015C1 /* base64.c in Sources */ = {isa = PBXBuildFile; fileRef = 0EA1664A2ADFE0D1003015C1 /* base64.c */; };
|
||||
0EA166772ADFE140003015C1 /* jplist.c in Sources */ = {isa = PBXBuildFile; fileRef = 0EA166412ADFE0D1003015C1 /* jplist.c */; };
|
||||
0EA166782ADFE140003015C1 /* jsmn.c in Sources */ = {isa = PBXBuildFile; fileRef = 0EA166572ADFE0D2003015C1 /* jsmn.c */; };
|
||||
0EA166792ADFE140003015C1 /* bplist.c in Sources */ = {isa = PBXBuildFile; fileRef = 0EA166442ADFE0D1003015C1 /* bplist.c */; };
|
||||
0EA1667A2ADFE140003015C1 /* Uid.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0EA1664B2ADFE0D1003015C1 /* Uid.cpp */; };
|
||||
0EA1667B2ADFE140003015C1 /* Structure.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0EA1665A2ADFE0D2003015C1 /* Structure.cpp */; };
|
||||
0EA1667D2ADFE140003015C1 /* xplist.c in Sources */ = {isa = PBXBuildFile; fileRef = 0EA166592ADFE0D2003015C1 /* xplist.c */; };
|
||||
0EA1667E2ADFE140003015C1 /* time64.c in Sources */ = {isa = PBXBuildFile; fileRef = 0EA1664C2ADFE0D1003015C1 /* time64.c */; };
|
||||
0EA4263A2C2230150026D7FB /* AnisetteServerList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA426392C2230150026D7FB /* AnisetteServerList.swift */; };
|
||||
0EA4B9BC2AE4A414009209CE /* plist.c in Sources */ = {isa = PBXBuildFile; fileRef = 0EA4B9BB2AE4A3F6009209CE /* plist.c */; };
|
||||
0EE7FDC42BE8BC7900D1E390 /* ALTLocalizedError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE7FDC32BE8BC7900D1E390 /* ALTLocalizedError.swift */; };
|
||||
0EE7FDC62BE8CEA300D1E390 /* ALTLocalizedError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE7FDC32BE8BC7900D1E390 /* ALTLocalizedError.swift */; };
|
||||
0EE7FDC72BE8CF4100D1E390 /* ALTWrappedError.m in Sources */ = {isa = PBXBuildFile; fileRef = 0EE7FDC02BE8BC2100D1E390 /* ALTWrappedError.m */; };
|
||||
0EE7FDC82BE8CF4800D1E390 /* ALTWrappedError.h in Headers */ = {isa = PBXBuildFile; fileRef = 0EE7FDC22BE8BC4200D1E390 /* ALTWrappedError.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
0EE7FDC92BE8D07400D1E390 /* NSError+AltStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6C336124197D700034FD24 /* NSError+AltStore.swift */; };
|
||||
0EE7FDCB2BE8D12B00D1E390 /* ALTLocalizedError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE7FDC32BE8BC7900D1E390 /* ALTLocalizedError.swift */; };
|
||||
0EE7FDCD2BE9124400D1E390 /* ErrorDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE7FDCC2BE9124400D1E390 /* ErrorDetailsViewController.swift */; };
|
||||
19104D952909BAEA00C49C7B /* libimobiledevice.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BF45872B2298D31600BD7491 /* libimobiledevice.a */; };
|
||||
19104DB52909C06D00C49C7B /* EmotionalDamage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19104DB42909C06D00C49C7B /* EmotionalDamage.swift */; };
|
||||
19104DBC2909C4E500C49C7B /* libEmotionalDamage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 19104DB22909C06C00C49C7B /* libEmotionalDamage.a */; };
|
||||
191E5FB4290A5DA0001A3B7C /* libminimuxer.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 191E5FAB290A5D92001A3B7C /* libminimuxer.a */; };
|
||||
191E5FDC290AFA5C001A3B7C /* OpenSSL in Frameworks */ = {isa = PBXBuildFile; productRef = 191E5FDB290AFA5C001A3B7C /* OpenSSL */; };
|
||||
191E607D290B2EA5001A3B7C /* jsmn.c in Sources */ = {isa = PBXBuildFile; fileRef = 191E5FD0290A651D001A3B7C /* jsmn.c */; };
|
||||
191E607E290B2EA7001A3B7C /* jplist.c in Sources */ = {isa = PBXBuildFile; fileRef = 191E5FCF290A651D001A3B7C /* jplist.c */; };
|
||||
1920B04F2924AC8300744F60 /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 1920B04E2924AC8300744F60 /* Settings.bundle */; };
|
||||
19B9B7452845E6DF0076EF69 /* SelectTeamViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19B9B7442845E6DF0076EF69 /* SelectTeamViewController.swift */; };
|
||||
4879A95F2861046500FC1BBD /* AltSign in Frameworks */ = {isa = PBXBuildFile; productRef = 4879A95E2861046500FC1BBD /* AltSign */; };
|
||||
4879A9622861049C00FC1BBD /* OpenSSL in Frameworks */ = {isa = PBXBuildFile; productRef = 4879A9612861049C00FC1BBD /* OpenSSL */; };
|
||||
@@ -25,7 +60,6 @@
|
||||
99F87D1829D8E4C900B40039 /* SwiftBridgeCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99F87D1629D8E4C900B40039 /* SwiftBridgeCore.swift */; };
|
||||
99F87D1929D8E4C900B40039 /* minimuxer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99F87D1729D8E4C900B40039 /* minimuxer.swift */; };
|
||||
B3146ED2284F581E00BBC3FD /* Roxas.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B3146ECD284F580500BBC3FD /* Roxas.framework */; };
|
||||
B3146ED3284F581E00BBC3FD /* Roxas.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B3146ECD284F580500BBC3FD /* Roxas.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
B33FFBA8295F8E98002259E6 /* libfragmentzip.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B343F894295F7F9B002B1159 /* libfragmentzip.a */; };
|
||||
B33FFBAA295F8F78002259E6 /* preboard.c in Sources */ = {isa = PBXBuildFile; fileRef = B33FFBA9295F8F78002259E6 /* preboard.c */; };
|
||||
B33FFBAC295F8F98002259E6 /* companion_proxy.c in Sources */ = {isa = PBXBuildFile; fileRef = B33FFBAB295F8F98002259E6 /* companion_proxy.c */; };
|
||||
@@ -74,7 +108,6 @@
|
||||
BF41B808233433C100C593A3 /* LoadingState.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF41B807233433C100C593A3 /* LoadingState.swift */; };
|
||||
BF42345A25101C35006D1EB2 /* WidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF42345825101C1D006D1EB2 /* WidgetView.swift */; };
|
||||
BF44EEF0246B08BA002A52F2 /* BackupController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF44EEEF246B08BA002A52F2 /* BackupController.swift */; };
|
||||
BF44EEF3246B3A17002A52F2 /* AltBackup.ipa in Resources */ = {isa = PBXBuildFile; fileRef = BF44EEF2246B3A17002A52F2 /* AltBackup.ipa */; };
|
||||
BF44EEFC246B4550002A52F2 /* RemoveAppOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF44EEFB246B4550002A52F2 /* RemoveAppOperation.swift */; };
|
||||
BF4587F82298D3AB00BD7491 /* service.h in Headers */ = {isa = PBXBuildFile; fileRef = BF4587C82298D3A800BD7491 /* service.h */; };
|
||||
BF4587F92298D3AB00BD7491 /* diagnostics_relay.c in Sources */ = {isa = PBXBuildFile; fileRef = BF4587C92298D3A800BD7491 /* diagnostics_relay.c */; };
|
||||
@@ -138,7 +171,6 @@
|
||||
BF58048A246A28F9008AE704 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BF580488246A28F9008AE704 /* LaunchScreen.storyboard */; };
|
||||
BF580496246A3CB5008AE704 /* UIColor+AltBackup.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF580495246A3CB5008AE704 /* UIColor+AltBackup.swift */; };
|
||||
BF580498246A3D19008AE704 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF580497246A3D19008AE704 /* UIKit.framework */; };
|
||||
BF58049B246A432D008AE704 /* NSError+AltStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6C336124197D700034FD24 /* NSError+AltStore.swift */; };
|
||||
BF663C4F2433ED8200DAA738 /* FileManager+DirectorySize.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF663C4E2433ED8200DAA738 /* FileManager+DirectorySize.swift */; };
|
||||
BF66EE822501AE50007EE018 /* AltStoreCore.h in Headers */ = {isa = PBXBuildFile; fileRef = BF66EE802501AE50007EE018 /* AltStoreCore.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
BF66EE852501AE50007EE018 /* AltStoreCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF66EE7E2501AE50007EE018 /* AltStoreCore.framework */; };
|
||||
@@ -184,7 +216,6 @@
|
||||
BF66EEE92501AED0007EE018 /* JSONDecoder+Properties.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66EEE52501AED0007EE018 /* JSONDecoder+Properties.swift */; };
|
||||
BF66EEEA2501AED0007EE018 /* UIColor+Hex.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66EEE62501AED0007EE018 /* UIColor+Hex.swift */; };
|
||||
BF66EEEB2501AED0007EE018 /* UIApplication+AppExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66EEE72501AED0007EE018 /* UIApplication+AppExtension.swift */; };
|
||||
BF6C336224197D700034FD24 /* NSError+AltStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6C336124197D700034FD24 /* NSError+AltStore.swift */; };
|
||||
BF6C8FAC242935ED00125131 /* NSAttributedString+Markdown.m in Sources */ = {isa = PBXBuildFile; fileRef = BF6C8FAA242935ED00125131 /* NSAttributedString+Markdown.m */; };
|
||||
BF6C8FAE2429597900125131 /* BannerCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6C8FAD2429597900125131 /* BannerCollectionViewCell.swift */; };
|
||||
BF6C8FB02429599900125131 /* TextCollectionReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6C8FAF2429599900125131 /* TextCollectionReusableView.swift */; };
|
||||
@@ -215,7 +246,7 @@
|
||||
BF9ABA4B22DD1380008935CF /* NavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF9ABA4A22DD137F008935CF /* NavigationBar.swift */; };
|
||||
BF9ABA4D22DD16DE008935CF /* PillButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF9ABA4C22DD16DE008935CF /* PillButton.swift */; };
|
||||
BFA8172B23C5633D001B5953 /* FetchAnisetteDataOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFA8172A23C5633D001B5953 /* FetchAnisetteDataOperation.swift */; };
|
||||
BFAECC522501B0A400528F27 /* CodableServerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD44605241188C300EAB90A /* CodableServerError.swift */; };
|
||||
BFAECC522501B0A400528F27 /* CodableError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD44605241188C300EAB90A /* CodableError.swift */; };
|
||||
BFAECC532501B0A400528F27 /* ServerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1E3128229F474900370A3C /* ServerProtocol.swift */; };
|
||||
BFAECC542501B0A400528F27 /* NSError+ALTServerError.m in Sources */ = {isa = PBXBuildFile; fileRef = BF1E314922A060F400370A3C /* NSError+ALTServerError.m */; };
|
||||
BFAECC552501B0A400528F27 /* Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF18BFF624858BDE00DD5981 /* Connection.swift */; };
|
||||
@@ -254,34 +285,6 @@
|
||||
BFD2477A2284B9A700981D42 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BFD247782284B9A700981D42 /* LaunchScreen.storyboard */; };
|
||||
BFD2478C2284C4C300981D42 /* AppIconImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD2478B2284C4C300981D42 /* AppIconImageView.swift */; };
|
||||
BFD2478F2284C8F900981D42 /* Button.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD2478E2284C8F900981D42 /* Button.swift */; };
|
||||
BFD52C0122A1A9CB000B7ED1 /* ptrarray.c in Sources */ = {isa = PBXBuildFile; fileRef = BFD52BE522A1A9CA000B7ED1 /* ptrarray.c */; };
|
||||
BFD52C0222A1A9CB000B7ED1 /* base64.c in Sources */ = {isa = PBXBuildFile; fileRef = BFD52BE622A1A9CA000B7ED1 /* base64.c */; };
|
||||
BFD52C0322A1A9CB000B7ED1 /* hashtable.c in Sources */ = {isa = PBXBuildFile; fileRef = BFD52BE722A1A9CA000B7ED1 /* hashtable.c */; };
|
||||
BFD52C0422A1A9CB000B7ED1 /* Dictionary.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BFD52BE822A1A9CA000B7ED1 /* Dictionary.cpp */; };
|
||||
BFD52C0522A1A9CB000B7ED1 /* ptrarray.h in Headers */ = {isa = PBXBuildFile; fileRef = BFD52BE922A1A9CA000B7ED1 /* ptrarray.h */; };
|
||||
BFD52C0622A1A9CB000B7ED1 /* bplist.c in Sources */ = {isa = PBXBuildFile; fileRef = BFD52BEA22A1A9CA000B7ED1 /* bplist.c */; };
|
||||
BFD52C0722A1A9CB000B7ED1 /* String.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BFD52BEB22A1A9CA000B7ED1 /* String.cpp */; };
|
||||
BFD52C0822A1A9CB000B7ED1 /* time64.c in Sources */ = {isa = PBXBuildFile; fileRef = BFD52BEC22A1A9CA000B7ED1 /* time64.c */; };
|
||||
BFD52C0922A1A9CB000B7ED1 /* plist.h in Headers */ = {isa = PBXBuildFile; fileRef = BFD52BED22A1A9CA000B7ED1 /* plist.h */; };
|
||||
BFD52C0A22A1A9CB000B7ED1 /* plist.c in Sources */ = {isa = PBXBuildFile; fileRef = BFD52BEE22A1A9CA000B7ED1 /* plist.c */; };
|
||||
BFD52C0B22A1A9CB000B7ED1 /* hashtable.h in Headers */ = {isa = PBXBuildFile; fileRef = BFD52BEF22A1A9CA000B7ED1 /* hashtable.h */; };
|
||||
BFD52C0C22A1A9CB000B7ED1 /* Date.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BFD52BF022A1A9CA000B7ED1 /* Date.cpp */; };
|
||||
BFD52C0D22A1A9CB000B7ED1 /* Uid.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BFD52BF122A1A9CA000B7ED1 /* Uid.cpp */; };
|
||||
BFD52C0E22A1A9CB000B7ED1 /* Boolean.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BFD52BF222A1A9CA000B7ED1 /* Boolean.cpp */; };
|
||||
BFD52C0F22A1A9CB000B7ED1 /* Real.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BFD52BF322A1A9CA000B7ED1 /* Real.cpp */; };
|
||||
BFD52C1022A1A9CB000B7ED1 /* strbuf.h in Headers */ = {isa = PBXBuildFile; fileRef = BFD52BF422A1A9CA000B7ED1 /* strbuf.h */; };
|
||||
BFD52C1122A1A9CB000B7ED1 /* bytearray.c in Sources */ = {isa = PBXBuildFile; fileRef = BFD52BF522A1A9CA000B7ED1 /* bytearray.c */; };
|
||||
BFD52C1222A1A9CB000B7ED1 /* base64.h in Headers */ = {isa = PBXBuildFile; fileRef = BFD52BF622A1A9CA000B7ED1 /* base64.h */; };
|
||||
BFD52C1322A1A9CB000B7ED1 /* Data.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BFD52BF722A1A9CA000B7ED1 /* Data.cpp */; };
|
||||
BFD52C1422A1A9CB000B7ED1 /* Array.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BFD52BF822A1A9CB000B7ED1 /* Array.cpp */; };
|
||||
BFD52C1522A1A9CB000B7ED1 /* Node.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BFD52BF922A1A9CB000B7ED1 /* Node.cpp */; };
|
||||
BFD52C1622A1A9CB000B7ED1 /* bytearray.h in Headers */ = {isa = PBXBuildFile; fileRef = BFD52BFA22A1A9CB000B7ED1 /* bytearray.h */; };
|
||||
BFD52C1722A1A9CB000B7ED1 /* Key.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BFD52BFB22A1A9CB000B7ED1 /* Key.cpp */; };
|
||||
BFD52C1822A1A9CB000B7ED1 /* Integer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BFD52BFC22A1A9CB000B7ED1 /* Integer.cpp */; };
|
||||
BFD52C1922A1A9CB000B7ED1 /* Structure.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BFD52BFD22A1A9CB000B7ED1 /* Structure.cpp */; };
|
||||
BFD52C1A22A1A9CB000B7ED1 /* time64_limits.h in Headers */ = {isa = PBXBuildFile; fileRef = BFD52BFE22A1A9CB000B7ED1 /* time64_limits.h */; };
|
||||
BFD52C1B22A1A9CB000B7ED1 /* time64.h in Headers */ = {isa = PBXBuildFile; fileRef = BFD52BFF22A1A9CB000B7ED1 /* time64.h */; };
|
||||
BFD52C1C22A1A9CB000B7ED1 /* xplist.c in Sources */ = {isa = PBXBuildFile; fileRef = BFD52C0022A1A9CB000B7ED1 /* xplist.c */; };
|
||||
BFD52C2022A1A9EC000B7ED1 /* node.c in Sources */ = {isa = PBXBuildFile; fileRef = BFD52C1D22A1A9EC000B7ED1 /* node.c */; };
|
||||
BFD52C2122A1A9EC000B7ED1 /* node_list.c in Sources */ = {isa = PBXBuildFile; fileRef = BFD52C1E22A1A9EC000B7ED1 /* node_list.c */; };
|
||||
BFD52C2222A1A9EC000B7ED1 /* cnary.c in Sources */ = {isa = PBXBuildFile; fileRef = BFD52C1F22A1A9EC000B7ED1 /* cnary.c */; };
|
||||
@@ -302,7 +305,7 @@
|
||||
BFE60742231B07E6002B0E8E /* SettingsHeaderFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE60741231B07E6002B0E8E /* SettingsHeaderFooterView.swift */; };
|
||||
BFE6325A22A83BEB00F30809 /* Authentication.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BFE6325922A83BEB00F30809 /* Authentication.storyboard */; };
|
||||
BFE6326C22A86FF300F30809 /* AuthenticationOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE6326B22A86FF300F30809 /* AuthenticationOperation.swift */; };
|
||||
BFECAC8824FD950E0077C41F /* CodableServerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD44605241188C300EAB90A /* CodableServerError.swift */; };
|
||||
BFECAC8824FD950E0077C41F /* CodableError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD44605241188C300EAB90A /* CodableError.swift */; };
|
||||
BFECAC8924FD950E0077C41F /* ConnectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF18BFF22485828200DD5981 /* ConnectionManager.swift */; };
|
||||
BFECAC8A24FD950E0077C41F /* ALTServerError+Conveniences.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF767CB2489AB5C0097E58C /* ALTServerError+Conveniences.swift */; };
|
||||
BFECAC8B24FD950E0077C41F /* ServerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1E3128229F474900370A3C /* ServerProtocol.swift */; };
|
||||
@@ -336,6 +339,7 @@
|
||||
D57FE84428C7DB7100216002 /* ErrorLogViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57FE84328C7DB7100216002 /* ErrorLogViewController.swift */; };
|
||||
D58916FE28C7C55C00E39C8B /* LoggedError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D58916FD28C7C55C00E39C8B /* LoggedError.swift */; };
|
||||
D593F1942717749A006E82DE /* PatchAppOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D593F1932717749A006E82DE /* PatchAppOperation.swift */; };
|
||||
D5ACE84528E3B8450021CAB9 /* ClearAppCacheOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5ACE84428E3B8450021CAB9 /* ClearAppCacheOperation.swift */; };
|
||||
D5CA0C4B280E141900469595 /* ManagedPatron.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5CA0C4A280E141900469595 /* ManagedPatron.swift */; };
|
||||
D5CA0C4E280E249E00469595 /* AltStore9ToAltStore10.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = D5CA0C4D280E249E00469595 /* AltStore9ToAltStore10.xcmappingmodel */; };
|
||||
D5DAE0942804B0B80034D8D4 /* ScreenshotProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5DAE0932804B0B80034D8D4 /* ScreenshotProcessor.swift */; };
|
||||
@@ -482,7 +486,6 @@
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
B3146ED3284F581E00BBC3FD /* Roxas.framework in Embed Frameworks */,
|
||||
BF1614F2250822F100767AEA /* Roxas.framework in Embed Frameworks */,
|
||||
BF66EE862501AE50007EE018 /* AltStoreCore.framework in Embed Frameworks */,
|
||||
);
|
||||
@@ -503,13 +506,52 @@
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
0E0502592BEC83C500879B5C /* OperatingSystemVersion+Comparable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OperatingSystemVersion+Comparable.swift"; sourceTree = "<group>"; };
|
||||
0E05025B2BEC947000879B5C /* String+SideStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+SideStore.swift"; sourceTree = "<group>"; };
|
||||
0E1A1F902AE36A9600364CAD /* bytearray.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = bytearray.c; path = src/bytearray.c; sourceTree = "<group>"; };
|
||||
0E764E162ADFF5740043DD4E /* AltBackup.ipa */ = {isa = PBXFileReference; lastKnownFileType = file; name = AltBackup.ipa; path = AltStore/Resources/AltBackup.ipa; sourceTree = SOURCE_ROOT; };
|
||||
0EA166412ADFE0D1003015C1 /* jplist.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = jplist.c; path = Dependencies/libplist/src/jplist.c; sourceTree = SOURCE_ROOT; };
|
||||
0EA166422ADFE0D1003015C1 /* Date.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Date.cpp; path = Dependencies/libplist/src/Date.cpp; sourceTree = SOURCE_ROOT; };
|
||||
0EA166432ADFE0D1003015C1 /* Data.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Data.cpp; path = Dependencies/libplist/src/Data.cpp; sourceTree = SOURCE_ROOT; };
|
||||
0EA166442ADFE0D1003015C1 /* bplist.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = bplist.c; path = Dependencies/libplist/src/bplist.c; sourceTree = SOURCE_ROOT; };
|
||||
0EA166452ADFE0D1003015C1 /* Array.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Array.cpp; path = Dependencies/libplist/src/Array.cpp; sourceTree = SOURCE_ROOT; };
|
||||
0EA166462ADFE0D1003015C1 /* Dictionary.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Dictionary.cpp; path = Dependencies/libplist/src/Dictionary.cpp; sourceTree = SOURCE_ROOT; };
|
||||
0EA166472ADFE0D1003015C1 /* out-limd.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = "out-limd.c"; path = "Dependencies/libplist/src/out-limd.c"; sourceTree = SOURCE_ROOT; };
|
||||
0EA166492ADFE0D1003015C1 /* String.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = String.cpp; path = Dependencies/libplist/src/String.cpp; sourceTree = SOURCE_ROOT; };
|
||||
0EA1664A2ADFE0D1003015C1 /* base64.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = base64.c; path = Dependencies/libplist/src/base64.c; sourceTree = SOURCE_ROOT; };
|
||||
0EA1664B2ADFE0D1003015C1 /* Uid.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Uid.cpp; path = Dependencies/libplist/src/Uid.cpp; sourceTree = SOURCE_ROOT; };
|
||||
0EA1664C2ADFE0D1003015C1 /* time64.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = time64.c; path = Dependencies/libplist/src/time64.c; sourceTree = SOURCE_ROOT; };
|
||||
0EA1664E2ADFE0D1003015C1 /* Boolean.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Boolean.cpp; path = Dependencies/libplist/src/Boolean.cpp; sourceTree = SOURCE_ROOT; };
|
||||
0EA1664F2ADFE0D1003015C1 /* hashtable.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = hashtable.c; path = Dependencies/libplist/src/hashtable.c; sourceTree = SOURCE_ROOT; };
|
||||
0EA166502ADFE0D2003015C1 /* Node.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Node.cpp; path = Dependencies/libplist/src/Node.cpp; sourceTree = SOURCE_ROOT; };
|
||||
0EA166512ADFE0D2003015C1 /* ptrarray.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = ptrarray.c; path = Dependencies/libplist/src/ptrarray.c; sourceTree = SOURCE_ROOT; };
|
||||
0EA166522ADFE0D2003015C1 /* out-default.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = "out-default.c"; path = "Dependencies/libplist/src/out-default.c"; sourceTree = SOURCE_ROOT; };
|
||||
0EA166532ADFE0D2003015C1 /* Integer.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Integer.cpp; path = Dependencies/libplist/src/Integer.cpp; sourceTree = SOURCE_ROOT; };
|
||||
0EA166542ADFE0D2003015C1 /* Real.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Real.cpp; path = Dependencies/libplist/src/Real.cpp; sourceTree = SOURCE_ROOT; };
|
||||
0EA166552ADFE0D2003015C1 /* out-plutil.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = "out-plutil.c"; path = "Dependencies/libplist/src/out-plutil.c"; sourceTree = SOURCE_ROOT; };
|
||||
0EA166562ADFE0D2003015C1 /* oplist.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = oplist.c; path = Dependencies/libplist/src/oplist.c; sourceTree = SOURCE_ROOT; };
|
||||
0EA166572ADFE0D2003015C1 /* jsmn.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = jsmn.c; path = Dependencies/libplist/src/jsmn.c; sourceTree = SOURCE_ROOT; };
|
||||
0EA166582ADFE0D2003015C1 /* Key.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Key.cpp; path = Dependencies/libplist/src/Key.cpp; sourceTree = SOURCE_ROOT; };
|
||||
0EA166592ADFE0D2003015C1 /* xplist.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = xplist.c; path = Dependencies/libplist/src/xplist.c; sourceTree = SOURCE_ROOT; };
|
||||
0EA1665A2ADFE0D2003015C1 /* Structure.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Structure.cpp; path = Dependencies/libplist/src/Structure.cpp; sourceTree = SOURCE_ROOT; };
|
||||
0EA1665F2ADFE122003015C1 /* time64_limits.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = time64_limits.h; path = Dependencies/libplist/src/time64_limits.h; sourceTree = SOURCE_ROOT; };
|
||||
0EA166602ADFE122003015C1 /* time64.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = time64.h; path = Dependencies/libplist/src/time64.h; sourceTree = SOURCE_ROOT; };
|
||||
0EA166612ADFE122003015C1 /* bytearray.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = bytearray.h; path = Dependencies/libplist/src/bytearray.h; sourceTree = SOURCE_ROOT; };
|
||||
0EA166622ADFE122003015C1 /* ptrarray.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ptrarray.h; path = Dependencies/libplist/src/ptrarray.h; sourceTree = SOURCE_ROOT; };
|
||||
0EA166632ADFE122003015C1 /* jsmn.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = jsmn.h; path = Dependencies/libplist/src/jsmn.h; sourceTree = SOURCE_ROOT; };
|
||||
0EA166642ADFE122003015C1 /* plist.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = plist.h; path = Dependencies/libplist/src/plist.h; sourceTree = SOURCE_ROOT; };
|
||||
0EA166652ADFE122003015C1 /* hashtable.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = hashtable.h; path = Dependencies/libplist/src/hashtable.h; sourceTree = SOURCE_ROOT; };
|
||||
0EA166662ADFE122003015C1 /* base64.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = base64.h; path = Dependencies/libplist/src/base64.h; sourceTree = SOURCE_ROOT; };
|
||||
0EA166672ADFE122003015C1 /* strbuf.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = strbuf.h; path = Dependencies/libplist/src/strbuf.h; sourceTree = SOURCE_ROOT; };
|
||||
0EA426392C2230150026D7FB /* AnisetteServerList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnisetteServerList.swift; sourceTree = "<group>"; };
|
||||
0EA4B9BB2AE4A3F6009209CE /* plist.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = plist.c; path = Dependencies/libplist/src/plist.c; sourceTree = SOURCE_ROOT; };
|
||||
0EE7FDC02BE8BC2100D1E390 /* ALTWrappedError.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ALTWrappedError.m; sourceTree = "<group>"; };
|
||||
0EE7FDC22BE8BC4200D1E390 /* ALTWrappedError.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ALTWrappedError.h; sourceTree = "<group>"; };
|
||||
0EE7FDC32BE8BC7900D1E390 /* ALTLocalizedError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ALTLocalizedError.swift; sourceTree = "<group>"; };
|
||||
0EE7FDCC2BE9124400D1E390 /* ErrorDetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorDetailsViewController.swift; sourceTree = "<group>"; };
|
||||
19104DB22909C06C00C49C7B /* libEmotionalDamage.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libEmotionalDamage.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
19104DB42909C06D00C49C7B /* EmotionalDamage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmotionalDamage.swift; sourceTree = "<group>"; };
|
||||
191E5FAB290A5D92001A3B7C /* libminimuxer.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libminimuxer.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
191E5FCF290A651D001A3B7C /* jplist.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = jplist.c; path = Dependencies/libplist/src/jplist.c; sourceTree = SOURCE_ROOT; };
|
||||
191E5FD0290A651D001A3B7C /* jsmn.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = jsmn.c; path = Dependencies/libplist/src/jsmn.c; sourceTree = SOURCE_ROOT; };
|
||||
191E5FD1290A651D001A3B7C /* jsmn.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = jsmn.h; path = Dependencies/libplist/src/jsmn.h; sourceTree = SOURCE_ROOT; };
|
||||
1920B04E2924AC8300744F60 /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = "<group>"; };
|
||||
19B9B7442845E6DF0076EF69 /* SelectTeamViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectTeamViewController.swift; sourceTree = "<group>"; };
|
||||
9961EC2D29BE9F2E00AF2C6F /* minimuxer-helpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "minimuxer-helpers.swift"; path = "Dependencies/minimuxer/minimuxer-helpers.swift"; sourceTree = SOURCE_ROOT; };
|
||||
99F87D1629D8E4C900B40039 /* SwiftBridgeCore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SwiftBridgeCore.swift; path = Dependencies/minimuxer/SwiftBridgeCore.swift; sourceTree = SOURCE_ROOT; };
|
||||
@@ -573,7 +615,6 @@
|
||||
BF41B807233433C100C593A3 /* LoadingState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingState.swift; sourceTree = "<group>"; };
|
||||
BF42345825101C1D006D1EB2 /* WidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetView.swift; sourceTree = "<group>"; };
|
||||
BF44EEEF246B08BA002A52F2 /* BackupController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupController.swift; sourceTree = "<group>"; };
|
||||
BF44EEF2246B3A17002A52F2 /* AltBackup.ipa */ = {isa = PBXFileReference; lastKnownFileType = file; path = AltBackup.ipa; sourceTree = "<group>"; };
|
||||
BF44EEFB246B4550002A52F2 /* RemoveAppOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveAppOperation.swift; sourceTree = "<group>"; };
|
||||
BF45872B2298D31600BD7491 /* libimobiledevice.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libimobiledevice.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
BF4587C82298D3A800BD7491 /* service.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = service.h; path = Dependencies/libimobiledevice/src/service.h; sourceTree = SOURCE_ROOT; };
|
||||
@@ -757,36 +798,8 @@
|
||||
BFD2478B2284C4C300981D42 /* AppIconImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIconImageView.swift; sourceTree = "<group>"; };
|
||||
BFD2478E2284C8F900981D42 /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = "<group>"; };
|
||||
BFD2479E2284FBD000981D42 /* UIColor+AltStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+AltStore.swift"; sourceTree = "<group>"; };
|
||||
BFD44605241188C300EAB90A /* CodableServerError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodableServerError.swift; sourceTree = "<group>"; };
|
||||
BFD44605241188C300EAB90A /* CodableError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodableError.swift; sourceTree = "<group>"; };
|
||||
BFD52BD222A06EFB000B7ED1 /* ALTConstants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ALTConstants.h; sourceTree = "<group>"; };
|
||||
BFD52BE522A1A9CA000B7ED1 /* ptrarray.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = ptrarray.c; path = Dependencies/libplist/src/ptrarray.c; sourceTree = SOURCE_ROOT; };
|
||||
BFD52BE622A1A9CA000B7ED1 /* base64.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = base64.c; path = Dependencies/libplist/src/base64.c; sourceTree = SOURCE_ROOT; };
|
||||
BFD52BE722A1A9CA000B7ED1 /* hashtable.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = hashtable.c; path = Dependencies/libplist/src/hashtable.c; sourceTree = SOURCE_ROOT; };
|
||||
BFD52BE822A1A9CA000B7ED1 /* Dictionary.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Dictionary.cpp; path = Dependencies/libplist/src/Dictionary.cpp; sourceTree = SOURCE_ROOT; };
|
||||
BFD52BE922A1A9CA000B7ED1 /* ptrarray.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ptrarray.h; path = Dependencies/libplist/src/ptrarray.h; sourceTree = SOURCE_ROOT; };
|
||||
BFD52BEA22A1A9CA000B7ED1 /* bplist.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = bplist.c; path = Dependencies/libplist/src/bplist.c; sourceTree = SOURCE_ROOT; };
|
||||
BFD52BEB22A1A9CA000B7ED1 /* String.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = String.cpp; path = Dependencies/libplist/src/String.cpp; sourceTree = SOURCE_ROOT; };
|
||||
BFD52BEC22A1A9CA000B7ED1 /* time64.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = time64.c; path = Dependencies/libplist/src/time64.c; sourceTree = SOURCE_ROOT; };
|
||||
BFD52BED22A1A9CA000B7ED1 /* plist.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = plist.h; path = Dependencies/libplist/src/plist.h; sourceTree = SOURCE_ROOT; };
|
||||
BFD52BEE22A1A9CA000B7ED1 /* plist.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = plist.c; path = Dependencies/libplist/src/plist.c; sourceTree = SOURCE_ROOT; };
|
||||
BFD52BEF22A1A9CA000B7ED1 /* hashtable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = hashtable.h; path = Dependencies/libplist/src/hashtable.h; sourceTree = SOURCE_ROOT; };
|
||||
BFD52BF022A1A9CA000B7ED1 /* Date.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Date.cpp; path = Dependencies/libplist/src/Date.cpp; sourceTree = SOURCE_ROOT; };
|
||||
BFD52BF122A1A9CA000B7ED1 /* Uid.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Uid.cpp; path = Dependencies/libplist/src/Uid.cpp; sourceTree = SOURCE_ROOT; };
|
||||
BFD52BF222A1A9CA000B7ED1 /* Boolean.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Boolean.cpp; path = Dependencies/libplist/src/Boolean.cpp; sourceTree = SOURCE_ROOT; };
|
||||
BFD52BF322A1A9CA000B7ED1 /* Real.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Real.cpp; path = Dependencies/libplist/src/Real.cpp; sourceTree = SOURCE_ROOT; };
|
||||
BFD52BF422A1A9CA000B7ED1 /* strbuf.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = strbuf.h; path = Dependencies/libplist/src/strbuf.h; sourceTree = SOURCE_ROOT; };
|
||||
BFD52BF522A1A9CA000B7ED1 /* bytearray.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = bytearray.c; path = Dependencies/libplist/src/bytearray.c; sourceTree = SOURCE_ROOT; };
|
||||
BFD52BF622A1A9CA000B7ED1 /* base64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = base64.h; path = Dependencies/libplist/src/base64.h; sourceTree = SOURCE_ROOT; };
|
||||
BFD52BF722A1A9CA000B7ED1 /* Data.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Data.cpp; path = Dependencies/libplist/src/Data.cpp; sourceTree = SOURCE_ROOT; };
|
||||
BFD52BF822A1A9CB000B7ED1 /* Array.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Array.cpp; path = Dependencies/libplist/src/Array.cpp; sourceTree = SOURCE_ROOT; };
|
||||
BFD52BF922A1A9CB000B7ED1 /* Node.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Node.cpp; path = Dependencies/libplist/src/Node.cpp; sourceTree = SOURCE_ROOT; };
|
||||
BFD52BFA22A1A9CB000B7ED1 /* bytearray.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = bytearray.h; path = Dependencies/libplist/src/bytearray.h; sourceTree = SOURCE_ROOT; };
|
||||
BFD52BFB22A1A9CB000B7ED1 /* Key.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Key.cpp; path = Dependencies/libplist/src/Key.cpp; sourceTree = SOURCE_ROOT; };
|
||||
BFD52BFC22A1A9CB000B7ED1 /* Integer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Integer.cpp; path = Dependencies/libplist/src/Integer.cpp; sourceTree = SOURCE_ROOT; };
|
||||
BFD52BFD22A1A9CB000B7ED1 /* Structure.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Structure.cpp; path = Dependencies/libplist/src/Structure.cpp; sourceTree = SOURCE_ROOT; };
|
||||
BFD52BFE22A1A9CB000B7ED1 /* time64_limits.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = time64_limits.h; path = Dependencies/libplist/src/time64_limits.h; sourceTree = SOURCE_ROOT; };
|
||||
BFD52BFF22A1A9CB000B7ED1 /* time64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = time64.h; path = Dependencies/libplist/src/time64.h; sourceTree = SOURCE_ROOT; };
|
||||
BFD52C0022A1A9CB000B7ED1 /* xplist.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = xplist.c; path = Dependencies/libplist/src/xplist.c; sourceTree = SOURCE_ROOT; };
|
||||
BFD52C1D22A1A9EC000B7ED1 /* node.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = node.c; path = Dependencies/libplist/libcnary/node.c; sourceTree = SOURCE_ROOT; };
|
||||
BFD52C1E22A1A9EC000B7ED1 /* node_list.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = node_list.c; path = Dependencies/libplist/libcnary/node_list.c; sourceTree = SOURCE_ROOT; };
|
||||
BFD52C1F22A1A9EC000B7ED1 /* cnary.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = cnary.c; path = Dependencies/libplist/libcnary/cnary.c; sourceTree = SOURCE_ROOT; };
|
||||
@@ -840,6 +853,7 @@
|
||||
D57FE84328C7DB7100216002 /* ErrorLogViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorLogViewController.swift; sourceTree = "<group>"; };
|
||||
D58916FD28C7C55C00E39C8B /* LoggedError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggedError.swift; sourceTree = "<group>"; };
|
||||
D593F1932717749A006E82DE /* PatchAppOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatchAppOperation.swift; sourceTree = "<group>"; };
|
||||
D5ACE84428E3B8450021CAB9 /* ClearAppCacheOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClearAppCacheOperation.swift; sourceTree = "<group>"; };
|
||||
D5CA0C4A280E141900469595 /* ManagedPatron.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagedPatron.swift; sourceTree = "<group>"; };
|
||||
D5CA0C4C280E242500469595 /* AltStore 10.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "AltStore 10.xcdatamodel"; sourceTree = "<group>"; };
|
||||
D5CA0C4D280E249E00469595 /* AltStore9ToAltStore10.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = AltStore9ToAltStore10.xcmappingmodel; sourceTree = "<group>"; };
|
||||
@@ -935,6 +949,16 @@
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
0EE7FDBF2BE8BBBF00D1E390 /* Errors */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0EE7FDC32BE8BC7900D1E390 /* ALTLocalizedError.swift */,
|
||||
0EE7FDC02BE8BC2100D1E390 /* ALTWrappedError.m */,
|
||||
0EE7FDC22BE8BC4200D1E390 /* ALTWrappedError.h */,
|
||||
);
|
||||
path = Errors;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
19104DB32909C06D00C49C7B /* EmotionalDamage */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -1060,7 +1084,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BF1E3128229F474900370A3C /* ServerProtocol.swift */,
|
||||
BFD44605241188C300EAB90A /* CodableServerError.swift */,
|
||||
BFD44605241188C300EAB90A /* CodableError.swift */,
|
||||
);
|
||||
path = "Server Protocol";
|
||||
sourceTree = "<group>";
|
||||
@@ -1073,6 +1097,7 @@
|
||||
BF18BFFF2485A75F00DD5981 /* Server Protocol */,
|
||||
BFF767CF2489AC240097E58C /* Connections */,
|
||||
BFF7C92D2578464D00E55F36 /* XPC */,
|
||||
0EE7FDBF2BE8BBBF00D1E390 /* Errors */,
|
||||
BFF767C32489A6800097E58C /* Extensions */,
|
||||
BFF767C42489A6980097E58C /* Categories */,
|
||||
);
|
||||
@@ -1192,37 +1217,41 @@
|
||||
BF4588562298DC6D00BD7491 /* libplist */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BFD52BE622A1A9CA000B7ED1 /* base64.c */,
|
||||
BFD52BEA22A1A9CA000B7ED1 /* bplist.c */,
|
||||
BFD52BF522A1A9CA000B7ED1 /* bytearray.c */,
|
||||
BFD52BE722A1A9CA000B7ED1 /* hashtable.c */,
|
||||
191E5FCF290A651D001A3B7C /* jplist.c */,
|
||||
191E5FD0290A651D001A3B7C /* jsmn.c */,
|
||||
BFD52BEE22A1A9CA000B7ED1 /* plist.c */,
|
||||
BFD52BE522A1A9CA000B7ED1 /* ptrarray.c */,
|
||||
BFD52BEC22A1A9CA000B7ED1 /* time64.c */,
|
||||
BFD52C0022A1A9CB000B7ED1 /* xplist.c */,
|
||||
BFD52BF822A1A9CB000B7ED1 /* Array.cpp */,
|
||||
BFD52BF222A1A9CA000B7ED1 /* Boolean.cpp */,
|
||||
BFD52BF722A1A9CA000B7ED1 /* Data.cpp */,
|
||||
BFD52BF022A1A9CA000B7ED1 /* Date.cpp */,
|
||||
BFD52BE822A1A9CA000B7ED1 /* Dictionary.cpp */,
|
||||
BFD52BFC22A1A9CB000B7ED1 /* Integer.cpp */,
|
||||
BFD52BFB22A1A9CB000B7ED1 /* Key.cpp */,
|
||||
BFD52BF922A1A9CB000B7ED1 /* Node.cpp */,
|
||||
BFD52BF322A1A9CA000B7ED1 /* Real.cpp */,
|
||||
BFD52BEB22A1A9CA000B7ED1 /* String.cpp */,
|
||||
BFD52BFD22A1A9CB000B7ED1 /* Structure.cpp */,
|
||||
BFD52BF122A1A9CA000B7ED1 /* Uid.cpp */,
|
||||
BFD52BF622A1A9CA000B7ED1 /* base64.h */,
|
||||
BFD52BFA22A1A9CB000B7ED1 /* bytearray.h */,
|
||||
BFD52BEF22A1A9CA000B7ED1 /* hashtable.h */,
|
||||
191E5FD1290A651D001A3B7C /* jsmn.h */,
|
||||
BFD52BED22A1A9CA000B7ED1 /* plist.h */,
|
||||
BFD52BE922A1A9CA000B7ED1 /* ptrarray.h */,
|
||||
BFD52BF422A1A9CA000B7ED1 /* strbuf.h */,
|
||||
BFD52BFE22A1A9CB000B7ED1 /* time64_limits.h */,
|
||||
BFD52BFF22A1A9CB000B7ED1 /* time64.h */,
|
||||
0EA166462ADFE0D1003015C1 /* Dictionary.cpp */,
|
||||
0E1A1F902AE36A9600364CAD /* bytearray.c */,
|
||||
0EA166442ADFE0D1003015C1 /* bplist.c */,
|
||||
0EA166432ADFE0D1003015C1 /* Data.cpp */,
|
||||
0EA166422ADFE0D1003015C1 /* Date.cpp */,
|
||||
0EA1664F2ADFE0D1003015C1 /* hashtable.c */,
|
||||
0EA166532ADFE0D2003015C1 /* Integer.cpp */,
|
||||
0EA166562ADFE0D2003015C1 /* oplist.c */,
|
||||
0EA166522ADFE0D2003015C1 /* out-default.c */,
|
||||
0EA166472ADFE0D1003015C1 /* out-limd.c */,
|
||||
0EA166552ADFE0D2003015C1 /* out-plutil.c */,
|
||||
0EA166492ADFE0D1003015C1 /* String.cpp */,
|
||||
0EA1665A2ADFE0D2003015C1 /* Structure.cpp */,
|
||||
0EA166452ADFE0D1003015C1 /* Array.cpp */,
|
||||
0EA1664A2ADFE0D1003015C1 /* base64.c */,
|
||||
0EA166572ADFE0D2003015C1 /* jsmn.c */,
|
||||
0EA1664E2ADFE0D1003015C1 /* Boolean.cpp */,
|
||||
0EA4B9BB2AE4A3F6009209CE /* plist.c */,
|
||||
0EA166412ADFE0D1003015C1 /* jplist.c */,
|
||||
0EA166582ADFE0D2003015C1 /* Key.cpp */,
|
||||
0EA166512ADFE0D2003015C1 /* ptrarray.c */,
|
||||
0EA1664C2ADFE0D1003015C1 /* time64.c */,
|
||||
0EA166502ADFE0D2003015C1 /* Node.cpp */,
|
||||
0EA166542ADFE0D2003015C1 /* Real.cpp */,
|
||||
0EA1664B2ADFE0D1003015C1 /* Uid.cpp */,
|
||||
0EA166592ADFE0D2003015C1 /* xplist.c */,
|
||||
0EA166662ADFE122003015C1 /* base64.h */,
|
||||
0EA166652ADFE122003015C1 /* hashtable.h */,
|
||||
0EA166632ADFE122003015C1 /* jsmn.h */,
|
||||
0EA166642ADFE122003015C1 /* plist.h */,
|
||||
0EA166612ADFE122003015C1 /* bytearray.h */,
|
||||
0EA166622ADFE122003015C1 /* ptrarray.h */,
|
||||
0EA166672ADFE122003015C1 /* strbuf.h */,
|
||||
0EA1665F2ADFE122003015C1 /* time64_limits.h */,
|
||||
0EA166602ADFE122003015C1 /* time64.h */,
|
||||
BF4588892298DDEA00BD7491 /* libcnary */,
|
||||
);
|
||||
path = libplist;
|
||||
@@ -1391,6 +1420,8 @@
|
||||
BF66EEE62501AED0007EE018 /* UIColor+Hex.swift */,
|
||||
BF66EEE42501AED0007EE018 /* UserDefaults+AltStore.swift */,
|
||||
BF6A531F246DC1B0004F59C8 /* FileManager+SharedDirectories.swift */,
|
||||
0E0502592BEC83C500879B5C /* OperatingSystemVersion+Comparable.swift */,
|
||||
0E05025B2BEC947000879B5C /* String+SideStore.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
@@ -1570,7 +1601,6 @@
|
||||
BFD247962284D7C100981D42 /* Resources */,
|
||||
BF6C8FA8242935CA00125131 /* Dependencies */,
|
||||
BFD247972284D7D800981D42 /* Supporting Files */,
|
||||
1920B04E2924AC8300744F60 /* Settings.bundle */,
|
||||
);
|
||||
path = AltStore;
|
||||
sourceTree = "<group>";
|
||||
@@ -1619,7 +1649,7 @@
|
||||
BFD247962284D7C100981D42 /* Resources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BF44EEF2246B3A17002A52F2 /* AltBackup.ipa */,
|
||||
0E764E162ADFF5740043DD4E /* AltBackup.ipa */,
|
||||
BFD247762284B9A700981D42 /* Assets.xcassets */,
|
||||
BF770E6822BD57DD002A40FE /* Silence.m4a */,
|
||||
);
|
||||
@@ -1654,6 +1684,7 @@
|
||||
children = (
|
||||
BFE60737231ADF49002B0E8E /* Settings.storyboard */,
|
||||
BFE60739231ADF82002B0E8E /* SettingsViewController.swift */,
|
||||
0EA426392C2230150026D7FB /* AnisetteServerList.swift */,
|
||||
BFE6073F231AFD2A002B0E8E /* InsetGroupTableViewCell.swift */,
|
||||
BFE60741231B07E6002B0E8E /* SettingsHeaderFooterView.swift */,
|
||||
BFE6073B231AE1E7002B0E8E /* SettingsHeaderFooterView.xib */,
|
||||
@@ -1694,6 +1725,7 @@
|
||||
D57F2C9026E0070200B9FA39 /* EnableJITOperation.swift */,
|
||||
D5DAE0952804DF430034D8D4 /* UpdatePatronsOperation.swift */,
|
||||
D5E1E7C028077DE90016FC96 /* FetchTrustedSourcesOperation.swift */,
|
||||
D5ACE84428E3B8450021CAB9 /* ClearAppCacheOperation.swift */,
|
||||
BF7B44062725A4B8005288A4 /* Patch App */,
|
||||
);
|
||||
path = Operations;
|
||||
@@ -1769,6 +1801,7 @@
|
||||
children = (
|
||||
D57FE84328C7DB7100216002 /* ErrorLogViewController.swift */,
|
||||
D54DED1328CBC44B008B27A0 /* ErrorLogTableViewCell.swift */,
|
||||
0EE7FDCC2BE9124400D1E390 /* ErrorDetailsViewController.swift */,
|
||||
);
|
||||
path = "Error Log";
|
||||
sourceTree = "<group>";
|
||||
@@ -1780,32 +1813,26 @@
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
0EA166682ADFE122003015C1 /* jsmn.h in Headers */,
|
||||
BF4588112298D3AB00BD7491 /* misagent.h in Headers */,
|
||||
BF4588042298D3AB00BD7491 /* lockdown.h in Headers */,
|
||||
BF45880B2298D3AB00BD7491 /* mobilesync.h in Headers */,
|
||||
BF4588002298D3AB00BD7491 /* restore.h in Headers */,
|
||||
BF4588152298D3AB00BD7491 /* mobilebackup.h in Headers */,
|
||||
BF4588182298D3AB00BD7491 /* syslog_relay.h in Headers */,
|
||||
BFD52C1022A1A9CB000B7ED1 /* strbuf.h in Headers */,
|
||||
BF45881D2298D3AB00BD7491 /* file_relay.h in Headers */,
|
||||
BFD52C0922A1A9CB000B7ED1 /* plist.h in Headers */,
|
||||
BF4587FD2298D3AB00BD7491 /* sbservices.h in Headers */,
|
||||
BF4588362298D3C100BD7491 /* debug.h in Headers */,
|
||||
BF4588202298D3AB00BD7491 /* mobile_image_mounter.h in Headers */,
|
||||
BF4588122298D3AB00BD7491 /* house_arrest.h in Headers */,
|
||||
BF45881F2298D3AB00BD7491 /* device_link_service.h in Headers */,
|
||||
BFD52C1A22A1A9CB000B7ED1 /* time64_limits.h in Headers */,
|
||||
BF45880E2298D3AB00BD7491 /* debugserver.h in Headers */,
|
||||
BF4588102298D3AB00BD7491 /* heartbeat.h in Headers */,
|
||||
BF4587FA2298D3AB00BD7491 /* diagnostics_relay.h in Headers */,
|
||||
BFD52C1622A1A9CB000B7ED1 /* bytearray.h in Headers */,
|
||||
BFD52C1222A1A9CB000B7ED1 /* base64.h in Headers */,
|
||||
BF4588192298D3AB00BD7491 /* webinspector.h in Headers */,
|
||||
BF4588342298D3C100BD7491 /* userpref.h in Headers */,
|
||||
BF45880A2298D3AB00BD7491 /* screenshotr.h in Headers */,
|
||||
BFD52C0B22A1A9CB000B7ED1 /* hashtable.h in Headers */,
|
||||
BF4587FE2298D3AB00BD7491 /* mobilebackup2.h in Headers */,
|
||||
BFD52C0522A1A9CB000B7ED1 /* ptrarray.h in Headers */,
|
||||
BF45881C2298D3AB00BD7491 /* afc.h in Headers */,
|
||||
BF45881A2298D3AB00BD7491 /* mobileactivation.h in Headers */,
|
||||
BF4588052298D3AB00BD7491 /* idevice.h in Headers */,
|
||||
@@ -1813,7 +1840,6 @@
|
||||
BF4587F82298D3AB00BD7491 /* service.h in Headers */,
|
||||
BF4588252298D3AB00BD7491 /* property_list_service.h in Headers */,
|
||||
BF4588132298D3AB00BD7491 /* notification_proxy.h in Headers */,
|
||||
BFD52C1B22A1A9CB000B7ED1 /* time64.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -1828,6 +1854,7 @@
|
||||
BFAECC5D2501B0BF00528F27 /* ALTConnection.h in Headers */,
|
||||
BF66EE942501AEBC007EE018 /* ALTAppPermission.h in Headers */,
|
||||
BFAECC602501B0BF00528F27 /* NSError+ALTServerError.h in Headers */,
|
||||
0EE7FDC82BE8CF4800D1E390 /* ALTWrappedError.h in Headers */,
|
||||
BFAECC5E2501B0BF00528F27 /* CFNotificationName+AltStore.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -2209,14 +2236,13 @@
|
||||
BFE60738231ADF49002B0E8E /* Settings.storyboard in Resources */,
|
||||
D57DF638271E32F000677701 /* PatchApp.storyboard in Resources */,
|
||||
BFD2477A2284B9A700981D42 /* LaunchScreen.storyboard in Resources */,
|
||||
BF44EEF3246B3A17002A52F2 /* AltBackup.ipa in Resources */,
|
||||
BF770E6922BD57DD002A40FE /* Silence.m4a in Resources */,
|
||||
BFD247772284B9A700981D42 /* Assets.xcassets in Resources */,
|
||||
1920B04F2924AC8300744F60 /* Settings.bundle in Resources */,
|
||||
BFF0B6922321A305007A79E1 /* AboutPatreonHeaderView.xib in Resources */,
|
||||
BFB6B22423187A3D0022A802 /* NewsCollectionViewCell.xib in Resources */,
|
||||
BFD247752284B9A500981D42 /* Main.storyboard in Resources */,
|
||||
BFDB5B2622EFBBEA00F74113 /* BrowseCollectionViewCell.xib in Resources */,
|
||||
0E764E172ADFF5740043DD4E /* AltBackup.ipa in Resources */,
|
||||
BFE6073C231AE1E7002B0E8E /* SettingsHeaderFooterView.xib in Resources */,
|
||||
BF29012F2318F6B100D88A45 /* AppBannerView.xib in Resources */,
|
||||
BFE6325A22A83BEB00F30809 /* Authentication.storyboard in Resources */,
|
||||
@@ -2272,7 +2298,7 @@
|
||||
BF1FE358251A9FB000C3CE09 /* NSXPCConnection+MachServices.swift in Sources */,
|
||||
BFECAC8F24FD950E0077C41F /* Result+Conveniences.swift in Sources */,
|
||||
BF8CAE472489E772004D6CCE /* DaemonRequestHandler.swift in Sources */,
|
||||
BFECAC8824FD950E0077C41F /* CodableServerError.swift in Sources */,
|
||||
BFECAC8824FD950E0077C41F /* CodableError.swift in Sources */,
|
||||
BFC712C32512D5F100AB5EBE /* XPCConnection.swift in Sources */,
|
||||
BFC712C52512D5F100AB5EBE /* XPCConnectionHandler.swift in Sources */,
|
||||
BFECAC8A24FD950E0077C41F /* ALTServerError+Conveniences.swift in Sources */,
|
||||
@@ -2292,69 +2318,73 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
0E1A1F912AE36A9700364CAD /* bytearray.c in Sources */,
|
||||
0EA1666E2ADFE140003015C1 /* ptrarray.c in Sources */,
|
||||
0EA1665B2ADFE0D2003015C1 /* out-limd.c in Sources */,
|
||||
0EA166742ADFE140003015C1 /* Date.cpp in Sources */,
|
||||
0EA166712ADFE140003015C1 /* Key.cpp in Sources */,
|
||||
0EA1665C2ADFE0D2003015C1 /* out-default.c in Sources */,
|
||||
0EA1666A2ADFE140003015C1 /* String.cpp in Sources */,
|
||||
0EA166732ADFE140003015C1 /* Dictionary.cpp in Sources */,
|
||||
0EA1665D2ADFE0D2003015C1 /* out-plutil.c in Sources */,
|
||||
0EA1665E2ADFE0D2003015C1 /* oplist.c in Sources */,
|
||||
0EA166702ADFE140003015C1 /* Node.cpp in Sources */,
|
||||
0EA166752ADFE140003015C1 /* Real.cpp in Sources */,
|
||||
0EA166762ADFE140003015C1 /* base64.c in Sources */,
|
||||
0EA1666D2ADFE140003015C1 /* Data.cpp in Sources */,
|
||||
BF45881B2298D3AB00BD7491 /* house_arrest.c in Sources */,
|
||||
BFD52C0622A1A9CB000B7ED1 /* bplist.c in Sources */,
|
||||
0EA1666F2ADFE140003015C1 /* hashtable.c in Sources */,
|
||||
BF4588232298D3AB00BD7491 /* mobilesync.c in Sources */,
|
||||
BF4588072298D3AB00BD7491 /* afc.c in Sources */,
|
||||
191E607D290B2EA5001A3B7C /* jsmn.c in Sources */,
|
||||
191E607E290B2EA7001A3B7C /* jplist.c in Sources */,
|
||||
BF4588082298D3AB00BD7491 /* mobile_image_mounter.c in Sources */,
|
||||
BFD52C1122A1A9CB000B7ED1 /* bytearray.c in Sources */,
|
||||
BF4588022298D3AB00BD7491 /* file_relay.c in Sources */,
|
||||
BF45880F2298D3AB00BD7491 /* debugserver.c in Sources */,
|
||||
0EA166792ADFE140003015C1 /* bplist.c in Sources */,
|
||||
0EA166772ADFE140003015C1 /* jplist.c in Sources */,
|
||||
BF4588162298D3AB00BD7491 /* restore.c in Sources */,
|
||||
BFD52C0422A1A9CB000B7ED1 /* Dictionary.cpp in Sources */,
|
||||
BFD52C0222A1A9CB000B7ED1 /* base64.c in Sources */,
|
||||
BFD52C2022A1A9EC000B7ED1 /* node.c in Sources */,
|
||||
BF4588092298D3AB00BD7491 /* installation_proxy.c in Sources */,
|
||||
0EA1666B2ADFE140003015C1 /* Boolean.cpp in Sources */,
|
||||
0EA1667E2ADFE140003015C1 /* time64.c in Sources */,
|
||||
BF4587FF2298D3AB00BD7491 /* heartbeat.c in Sources */,
|
||||
BF4588222298D3AB00BD7491 /* mobileactivation.c in Sources */,
|
||||
BFD52C1822A1A9CB000B7ED1 /* Integer.cpp in Sources */,
|
||||
BF4588212298D3AB00BD7491 /* idevice.c in Sources */,
|
||||
B343F885295F7C5D002B1159 /* tlv.c in Sources */,
|
||||
BFD52C1C22A1A9CB000B7ED1 /* xplist.c in Sources */,
|
||||
BF4587F92298D3AB00BD7491 /* diagnostics_relay.c in Sources */,
|
||||
B343F87D295F7C5D002B1159 /* cbuf.c in Sources */,
|
||||
BF4588062298D3AB00BD7491 /* webinspector.c in Sources */,
|
||||
BFD52C1722A1A9CB000B7ED1 /* Key.cpp in Sources */,
|
||||
B343F883295F7C5D002B1159 /* thread.c in Sources */,
|
||||
BF45880D2298D3AB00BD7491 /* mobilebackup.c in Sources */,
|
||||
BFD52C0C22A1A9CB000B7ED1 /* Date.cpp in Sources */,
|
||||
BFD52C0A22A1A9CB000B7ED1 /* plist.c in Sources */,
|
||||
BFD52C1322A1A9CB000B7ED1 /* Data.cpp in Sources */,
|
||||
BF45883A2298D3C100BD7491 /* debug.c in Sources */,
|
||||
B343F881295F7C5D002B1159 /* termcolors.c in Sources */,
|
||||
0EA1667D2ADFE140003015C1 /* xplist.c in Sources */,
|
||||
B343F87E295F7C5D002B1159 /* collection.c in Sources */,
|
||||
BFD52C0F22A1A9CB000B7ED1 /* Real.cpp in Sources */,
|
||||
B33FFBAA295F8F78002259E6 /* preboard.c in Sources */,
|
||||
B33FFBAC295F8F98002259E6 /* companion_proxy.c in Sources */,
|
||||
BF4587FB2298D3AB00BD7491 /* notification_proxy.c in Sources */,
|
||||
BF4588352298D3C100BD7491 /* userpref.c in Sources */,
|
||||
BFD52C0122A1A9CB000B7ED1 /* ptrarray.c in Sources */,
|
||||
0EA1667A2ADFE140003015C1 /* Uid.cpp in Sources */,
|
||||
B343F87C295F7C5D002B1159 /* opack.c in Sources */,
|
||||
BFD52C0E22A1A9CB000B7ED1 /* Boolean.cpp in Sources */,
|
||||
BFD52C0822A1A9CB000B7ED1 /* time64.c in Sources */,
|
||||
B343F884295F7C5D002B1159 /* utils.c in Sources */,
|
||||
BFD52C2122A1A9EC000B7ED1 /* node_list.c in Sources */,
|
||||
B343F87F295F7C5D002B1159 /* glue.c in Sources */,
|
||||
BFD52C1422A1A9CB000B7ED1 /* Array.cpp in Sources */,
|
||||
BF4588242298D3AB00BD7491 /* property_list_service.c in Sources */,
|
||||
BF45881E2298D3AB00BD7491 /* misagent.c in Sources */,
|
||||
0EA166692ADFE140003015C1 /* Array.cpp in Sources */,
|
||||
B343F880295F7C5D002B1159 /* socket.c in Sources */,
|
||||
BF4587FC2298D3AB00BD7491 /* sbservices.c in Sources */,
|
||||
BFD52C1522A1A9CB000B7ED1 /* Node.cpp in Sources */,
|
||||
0EA166782ADFE140003015C1 /* jsmn.c in Sources */,
|
||||
BF4588142298D3AB00BD7491 /* device_link_service.c in Sources */,
|
||||
BF4588172298D3AB00BD7491 /* screenshotr.c in Sources */,
|
||||
BFD52C0D22A1A9CB000B7ED1 /* Uid.cpp in Sources */,
|
||||
BFD52C0322A1A9CB000B7ED1 /* hashtable.c in Sources */,
|
||||
BF4588432298D40000BD7491 /* libusbmuxd.c in Sources */,
|
||||
0EA1667B2ADFE140003015C1 /* Structure.cpp in Sources */,
|
||||
0EA1666C2ADFE140003015C1 /* Integer.cpp in Sources */,
|
||||
BF4588032298D3AB00BD7491 /* syslog_relay.c in Sources */,
|
||||
BF4588272298D3AB00BD7491 /* service.c in Sources */,
|
||||
BFD52C0722A1A9CB000B7ED1 /* String.cpp in Sources */,
|
||||
BF4588262298D3AB00BD7491 /* lockdown.c in Sources */,
|
||||
BFD52C2222A1A9EC000B7ED1 /* cnary.c in Sources */,
|
||||
BF45880C2298D3AB00BD7491 /* mobilebackup2.c in Sources */,
|
||||
BFD52C1922A1A9CB000B7ED1 /* Structure.cpp in Sources */,
|
||||
0EA4B9BC2AE4A414009209CE /* plist.c in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -2365,8 +2395,8 @@
|
||||
BF580496246A3CB5008AE704 /* UIColor+AltBackup.swift in Sources */,
|
||||
BF580482246A28F7008AE704 /* ViewController.swift in Sources */,
|
||||
BF44EEF0246B08BA002A52F2 /* BackupController.swift in Sources */,
|
||||
0EE7FDC62BE8CEA300D1E390 /* ALTLocalizedError.swift in Sources */,
|
||||
03F06CD52942C27E001C4D68 /* Bundle+AltStore.swift in Sources */,
|
||||
BF58049B246A432D008AE704 /* NSError+AltStore.swift in Sources */,
|
||||
BF58047E246A28F7008AE704 /* AppDelegate.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -2382,7 +2412,7 @@
|
||||
BF66EECD2501AECA007EE018 /* StoreAppPolicy.swift in Sources */,
|
||||
BF66EEE82501AED0007EE018 /* UserDefaults+AltStore.swift in Sources */,
|
||||
BF340E9A250AD39500A192CB /* ViewApp.intentdefinition in Sources */,
|
||||
BFAECC522501B0A400528F27 /* CodableServerError.swift in Sources */,
|
||||
BFAECC522501B0A400528F27 /* CodableError.swift in Sources */,
|
||||
BF66EE9E2501AEC1007EE018 /* Fetchable.swift in Sources */,
|
||||
BF66EEDF2501AECA007EE018 /* PatreonAccount.swift in Sources */,
|
||||
BFAECC532501B0A400528F27 /* ServerProtocol.swift in Sources */,
|
||||
@@ -2390,11 +2420,14 @@
|
||||
BF66EE9D2501AEC1007EE018 /* AppProtocol.swift in Sources */,
|
||||
BFC712C42512D5F100AB5EBE /* XPCConnection.swift in Sources */,
|
||||
D5CA0C4B280E141900469595 /* ManagedPatron.swift in Sources */,
|
||||
0E05025A2BEC83C500879B5C /* OperatingSystemVersion+Comparable.swift in Sources */,
|
||||
BF66EE8C2501AEB2007EE018 /* Keychain.swift in Sources */,
|
||||
BF66EED42501AECA007EE018 /* AltStore5ToAltStore6.xcmappingmodel in Sources */,
|
||||
BF66EE972501AEBC007EE018 /* ALTAppPermission.m in Sources */,
|
||||
BFAECC552501B0A400528F27 /* Connection.swift in Sources */,
|
||||
BF66EEDA2501AECA007EE018 /* RefreshAttempt.swift in Sources */,
|
||||
0E05025C2BEC947000879B5C /* String+SideStore.swift in Sources */,
|
||||
0EE7FDCB2BE8D12B00D1E390 /* ALTLocalizedError.swift in Sources */,
|
||||
BF66EEA92501AEC5007EE018 /* Tier.swift in Sources */,
|
||||
BF66EEDB2501AECA007EE018 /* StoreApp.swift in Sources */,
|
||||
BF66EEDE2501AECA007EE018 /* AppID.swift in Sources */,
|
||||
@@ -2403,6 +2436,7 @@
|
||||
BF66EEDD2501AECA007EE018 /* AppPermission.swift in Sources */,
|
||||
D58916FE28C7C55C00E39C8B /* LoggedError.swift in Sources */,
|
||||
BFBF331B2526762200B7B8C9 /* AltStore8ToAltStore9.xcmappingmodel in Sources */,
|
||||
0EE7FDC72BE8CF4100D1E390 /* ALTWrappedError.m in Sources */,
|
||||
D5CA0C4E280E249E00469595 /* AltStore9ToAltStore10.xcmappingmodel in Sources */,
|
||||
BF989184250AACFC002ACF50 /* Date+RelativeDate.swift in Sources */,
|
||||
BF66EE962501AEBC007EE018 /* ALTPatreonBenefitType.m in Sources */,
|
||||
@@ -2427,6 +2461,7 @@
|
||||
BF66EEEA2501AED0007EE018 /* UIColor+Hex.swift in Sources */,
|
||||
BF66EECC2501AECA007EE018 /* Source.swift in Sources */,
|
||||
BF66EED72501AECA007EE018 /* InstalledApp.swift in Sources */,
|
||||
0EE7FDC92BE8D07400D1E390 /* NSError+AltStore.swift in Sources */,
|
||||
BF66EECE2501AECA007EE018 /* InstalledAppPolicy.swift in Sources */,
|
||||
BF1FE359251A9FB000C3CE09 /* NSXPCConnection+MachServices.swift in Sources */,
|
||||
BF66EEA62501AEC5007EE018 /* PatreonAPI.swift in Sources */,
|
||||
@@ -2482,7 +2517,6 @@
|
||||
BF8F69C422E662D300049BA1 /* AppViewController.swift in Sources */,
|
||||
BFF0B68E23219520007A79E1 /* PatreonViewController.swift in Sources */,
|
||||
BFF00D302501BD7D00746320 /* Intents.intentdefinition in Sources */,
|
||||
BF6C336224197D700034FD24 /* NSError+AltStore.swift in Sources */,
|
||||
D5DAE0942804B0B80034D8D4 /* ScreenshotProcessor.swift in Sources */,
|
||||
BFD2476E2284B9A500981D42 /* AppDelegate.swift in Sources */,
|
||||
BF41B806233423AE00C593A3 /* TabBarController.swift in Sources */,
|
||||
@@ -2504,6 +2538,8 @@
|
||||
BFD6B03322DFF20800B86064 /* MyAppsComponents.swift in Sources */,
|
||||
BF41B808233433C100C593A3 /* LoadingState.swift in Sources */,
|
||||
BFF0B69A2322D7D0007A79E1 /* UIScreen+CompactHeight.swift in Sources */,
|
||||
D5ACE84528E3B8450021CAB9 /* ClearAppCacheOperation.swift in Sources */,
|
||||
0EA4263A2C2230150026D7FB /* AnisetteServerList.swift in Sources */,
|
||||
D5F2F6A92720B7C20081CCF5 /* PatchViewController.swift in Sources */,
|
||||
B39F16132918D7C5002E9404 /* Consts.swift in Sources */,
|
||||
BF8F69C222E659F700049BA1 /* AppContentViewController.swift in Sources */,
|
||||
@@ -2529,9 +2565,11 @@
|
||||
BFF00D322501BDA100746320 /* BackgroundRefreshAppsOperation.swift in Sources */,
|
||||
BF0C4EBD22A1BD8B009A2DD7 /* AppManager.swift in Sources */,
|
||||
BF2901312318F7A800D88A45 /* AppBannerView.swift in Sources */,
|
||||
0EE7FDC42BE8BC7900D1E390 /* ALTLocalizedError.swift in Sources */,
|
||||
BFF00D342501BDCF00746320 /* IntentHandler.swift in Sources */,
|
||||
BFDBBD80246CB84F004ED2F3 /* RemoveAppBackupOperation.swift in Sources */,
|
||||
BFF0B6942321CB85007A79E1 /* AuthenticationViewController.swift in Sources */,
|
||||
0EE7FDCD2BE9124400D1E390 /* ErrorDetailsViewController.swift in Sources */,
|
||||
BF3432FB246B894F0052F4A1 /* BackupAppOperation.swift in Sources */,
|
||||
BF9ABA4922DD0742008935CF /* ScreenshotCollectionViewCell.swift in Sources */,
|
||||
BF9ABA4D22DD16DE008935CF /* PillButton.swift in Sources */,
|
||||
@@ -3221,9 +3259,10 @@
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Dependencies/fragmentzip",
|
||||
"$(PROJECT_DIR)/Dependencies/libfragmentzip",
|
||||
"$(PROJECT_DIR)/Dependencies/libcurl",
|
||||
);
|
||||
OTHER_LDFLAGS = "";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@@ -3255,9 +3294,10 @@
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Dependencies/fragmentzip",
|
||||
"$(PROJECT_DIR)/Dependencies/libfragmentzip",
|
||||
"$(PROJECT_DIR)/Dependencies/libcurl",
|
||||
);
|
||||
OTHER_LDFLAGS = "";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
{
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "altsign",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/SideStore/AltSign",
|
||||
"state" : {
|
||||
"branch" : "master",
|
||||
"revision" : "7e0e7edcf8fbc44ac1e35da3e9030a297aa18b84"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "appcenter-sdk-apple",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/microsoft/appcenter-sdk-apple.git",
|
||||
"state" : {
|
||||
"revision" : "8354a50fe01a7e54e196d3b5493b5ab53dd5866a",
|
||||
"version" : "4.4.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "keychainaccess",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/kishikawakatsumi/KeychainAccess.git",
|
||||
"state" : {
|
||||
"revision" : "84e546727d66f1adc5439debad16270d0fdd04e7",
|
||||
"version" : "4.2.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "launchatlogin",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/sindresorhus/LaunchAtLogin.git",
|
||||
"state" : {
|
||||
"revision" : "e8171b3e38a2816f579f58f3dac1522aa39efe41",
|
||||
"version" : "4.2.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "nuke",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/kean/Nuke.git",
|
||||
"state" : {
|
||||
"revision" : "9318d02a8a6d20af56505c9673261c1fd3b3aebe",
|
||||
"version" : "7.6.3"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "openssl",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/krzyzanowskim/OpenSSL",
|
||||
"state" : {
|
||||
"revision" : "033fcb41dac96b1b6effa945ca1f9ade002370b2",
|
||||
"version" : "1.1.1501"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "plcrashreporter",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/microsoft/PLCrashReporter.git",
|
||||
"state" : {
|
||||
"revision" : "6b27393cad517c067dceea85fadf050e70c4ceaa",
|
||||
"version" : "1.10.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "semanticversion",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/SwiftPackageIndex/SemanticVersion.git",
|
||||
"state" : {
|
||||
"revision" : "fc670910dc0903cc269b3d0b776cda5703979c4e",
|
||||
"version" : "0.3.5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "sparkle",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/sparkle-project/Sparkle.git",
|
||||
"state" : {
|
||||
"revision" : "286edd1fa22505a9e54d170e9fd07d775ea233f2",
|
||||
"version" : "2.1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "starscream",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/daltoniam/Starscream.git",
|
||||
"state" : {
|
||||
"revision" : "df8d82047f6654d8e4b655d1b1525c64e1059d21",
|
||||
"version" : "4.0.4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "stprivilegedtask",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/JoeMatt/STPrivilegedTask.git",
|
||||
"state" : {
|
||||
"branch" : "master",
|
||||
"revision" : "10a9150ef32d444af326beba76356ae9af95a3e7"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 2
|
||||
}
|
||||
@@ -81,7 +81,7 @@ final class AppContentViewController: UITableViewController
|
||||
self.subtitleLabel.text = self.app.subtitle
|
||||
self.descriptionTextView.text = self.app.localizedDescription
|
||||
|
||||
if let version = self.app.latestVersion
|
||||
if let version = self.app.latestAvailableVersion
|
||||
{
|
||||
self.versionDescriptionTextView.text = version.localizedDescription
|
||||
self.versionLabel.text = String(format: NSLocalizedString("Version %@", comment: ""), version.version)
|
||||
|
||||
@@ -217,8 +217,8 @@ final class AppViewController: UIViewController
|
||||
|
||||
self._shouldResetLayout = false
|
||||
}
|
||||
|
||||
let statusBarHeight = UIApplication.shared.statusBarFrame.height
|
||||
|
||||
let statusBarHeight = self.view.window?.windowScene?.statusBarManager?.statusBarFrame.height ?? 0
|
||||
let cornerRadius = self.contentViewControllerShadowView.layer.cornerRadius
|
||||
|
||||
let inset = 12 as CGFloat
|
||||
@@ -323,7 +323,7 @@ final class AppViewController: UIViewController
|
||||
|
||||
self.backButtonContainerView.layer.cornerRadius = self.backButtonContainerView.bounds.midY
|
||||
|
||||
self.scrollView.scrollIndicatorInsets.top = statusBarHeight
|
||||
self.scrollView.verticalScrollIndicatorInsets.top = statusBarHeight
|
||||
|
||||
// Adjust content offset + size.
|
||||
let contentOffset = self.scrollView.contentOffset
|
||||
@@ -384,7 +384,7 @@ private extension AppViewController
|
||||
button.progress = progress
|
||||
}
|
||||
|
||||
if let versionDate = self.app.latestVersion?.date, versionDate > Date()
|
||||
if let versionDate = self.app.latestAvailableVersion?.date, versionDate > Date()
|
||||
{
|
||||
self.bannerView.button.countdownDate = versionDate
|
||||
self.navigationBarDownloadButton.countdownDate = versionDate
|
||||
@@ -510,7 +510,7 @@ extension AppViewController
|
||||
catch
|
||||
{
|
||||
DispatchQueue.main.async {
|
||||
let toastView = ToastView(error: error)
|
||||
let toastView = ToastView(error: error, opensLog: true)
|
||||
toastView.show(in: self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,14 +90,21 @@ private extension AppIDsViewController
|
||||
cell.bannerView.button.isUserInteractionEnabled = false
|
||||
|
||||
cell.bannerView.buttonLabel.isHidden = false
|
||||
|
||||
|
||||
let currentDate = Date()
|
||||
|
||||
let numberOfDays = expirationDate.numberOfCalendarDays(since: currentDate)
|
||||
let numberOfDaysText = (numberOfDays == 1) ? NSLocalizedString("1 day", comment: "") : String(format: NSLocalizedString("%@ days", comment: ""), NSNumber(value: numberOfDays))
|
||||
cell.bannerView.button.setTitle(numberOfDaysText.uppercased(), for: .normal)
|
||||
let formatter = DateComponentsFormatter()
|
||||
formatter.unitsStyle = .full
|
||||
formatter.includesApproximationPhrase = false
|
||||
formatter.includesTimeRemainingPhrase = false
|
||||
formatter.allowedUnits = [.minute, .hour, .day]
|
||||
formatter.maximumUnitCount = 1
|
||||
|
||||
attributedAccessibilityLabel.mutableString.append(String(format: NSLocalizedString("Expires in %@.", comment: ""), numberOfDaysText) + " ")
|
||||
cell.bannerView.button.setTitle((formatter.string(from: currentDate, to: expirationDate) ?? NSLocalizedString("Unknown", comment: "")).uppercased(), for: .normal)
|
||||
|
||||
// formatter.includesTimeRemainingPhrase = true
|
||||
|
||||
// attributedAccessibilityLabel.mutableString.append((formatter.string(from: currentDate, to: expirationDate) ?? NSLocalizedString("Unknown", comment: "")) + " ")
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -61,6 +61,8 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
// Register default settings before doing anything else.
|
||||
UserDefaults.registerDefaults()
|
||||
|
||||
|
||||
|
||||
DatabaseManager.shared.start { (error) in
|
||||
if let error = error
|
||||
{
|
||||
@@ -380,7 +382,7 @@ private extension AppDelegate
|
||||
for update in updates
|
||||
{
|
||||
guard !previousUpdates.contains(where: { $0[#keyPath(InstalledApp.bundleIdentifier)] == update.bundleIdentifier }) else { continue }
|
||||
guard let storeApp = update.storeApp, let version = storeApp.version else { continue }
|
||||
guard let storeApp = update.storeApp, let version = storeApp.latestSupportedVersion else { continue }
|
||||
|
||||
let content = UNMutableNotificationContent()
|
||||
content.title = NSLocalizedString("New Update Available", comment: "")
|
||||
|
||||
@@ -108,11 +108,9 @@ private extension AuthenticationViewController
|
||||
|
||||
case .failure(let error as NSError):
|
||||
DispatchQueue.main.async {
|
||||
let error = error.withLocalizedFailure(NSLocalizedString("Failed to Log In", comment: ""))
|
||||
|
||||
let error = error.withLocalizedTitle(NSLocalizedString("Failed to Log In", comment: ""))
|
||||
|
||||
let toastView = ToastView(error: error)
|
||||
toastView.textLabel.textColor = .altPink
|
||||
toastView.detailTextLabel.textColor = .altPink
|
||||
toastView.show(in: self)
|
||||
self.toastView = toastView
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21223" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="wKh-xq-NuP">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="wKh-xq-NuP">
|
||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21204"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22684"/>
|
||||
<capability name="Named colors" minToolsVersion="9.0"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
@@ -356,8 +356,8 @@
|
||||
<stackView opaque="NO" contentMode="scaleToFill" distribution="equalSpacing" translatesAutoresizingMaskIntoConstraints="NO" id="ewH-gi-pyW">
|
||||
<rect key="frame" x="0.0" y="30.5" width="335" height="17"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="751" text="Version 4.4.2" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="7E0-TV-G4l">
|
||||
<rect key="frame" x="0.0" y="0.0" width="84.5" height="17"/>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="751" text="Version 0.5.6" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="7E0-TV-G4l">
|
||||
<rect key="frame" x="0.0" y="0.0" width="84" height="17"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
@@ -596,7 +596,7 @@ World</string>
|
||||
<tabBarItem key="tabBarItem" title="Browse" image="Browse" id="Uwh-Bg-Ymq"/>
|
||||
<toolbarItems/>
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="dIv-qd-9L5" customClass="NavigationBar" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="96"/>
|
||||
<rect key="frame" x="0.0" y="20" width="375" height="96"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<color key="tintColor" name="Primary"/>
|
||||
</navigationBar>
|
||||
@@ -626,7 +626,7 @@ World</string>
|
||||
</tabBarItem>
|
||||
<toolbarItems/>
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="CzO-Kt-BlZ" customClass="NavigationBar" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="96"/>
|
||||
<rect key="frame" x="0.0" y="20" width="375" height="96"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</navigationBar>
|
||||
<nil name="viewControllers"/>
|
||||
@@ -883,7 +883,7 @@ World</string>
|
||||
<navigationItem key="navigationItem" title="App IDs" id="3Co-uv-Fhb">
|
||||
<barButtonItem key="leftBarButtonItem" style="plain" id="Aqs-QK-Ups">
|
||||
<view key="customView" contentMode="scaleToFill" id="p0q-Fg-3Ba">
|
||||
<rect key="frame" x="16" y="1" width="83" height="42"/>
|
||||
<rect key="frame" x="16" y="7" width="83" height="42"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
</view>
|
||||
</barButtonItem>
|
||||
@@ -909,7 +909,7 @@ World</string>
|
||||
<tabBarItem key="tabBarItem" title="News" image="News" id="fVN-ed-uO1"/>
|
||||
<toolbarItems/>
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="525-jF-uDK" customClass="NavigationBar" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="96"/>
|
||||
<rect key="frame" x="0.0" y="20" width="375" height="96"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<edgeInsets key="layoutMargins" top="8" left="20" bottom="8" right="8"/>
|
||||
</navigationBar>
|
||||
@@ -928,7 +928,7 @@ World</string>
|
||||
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="IXk-qg-mFJ" sceneMemberID="viewController">
|
||||
<toolbarItems/>
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="9sB-f3-Fnk">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="96"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="108"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</navigationBar>
|
||||
<nil name="viewControllers"/>
|
||||
@@ -1070,7 +1070,7 @@ World</string>
|
||||
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="Qo4-72-Hmr" sceneMemberID="viewController">
|
||||
<toolbarItems/>
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="mcx-oR-qPe">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="96"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="108"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</navigationBar>
|
||||
<nil name="viewControllers"/>
|
||||
@@ -1095,13 +1095,13 @@ World</string>
|
||||
<image name="News" width="19" height="20"/>
|
||||
<image name="Settings" width="20" height="20"/>
|
||||
<namedColor name="Background">
|
||||
<color red="0.6431" green="0.0196" blue="0.9804" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color red="0.45098039215686275" green="0.015686274509803921" blue="0.68627450980392157" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</namedColor>
|
||||
<namedColor name="BlurTint">
|
||||
<color red="1" green="1" blue="1" alpha="0.3" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color red="1" green="1" blue="1" alpha="0.30000001192092896" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</namedColor>
|
||||
<namedColor name="Primary">
|
||||
<color red="0.6431" green="0.0196" blue="0.9804" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color red="0.64313725490196083" green="0.019607843137254902" blue="0.98039215686274506" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</namedColor>
|
||||
<systemColor name="systemBackgroundColor">
|
||||
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
import minimuxer
|
||||
import AltStoreCore
|
||||
import Roxas
|
||||
|
||||
@@ -113,9 +114,9 @@ private extension BrowseViewController
|
||||
let progress = AppManager.shared.installationProgress(for: app)
|
||||
cell.bannerView.button.progress = progress
|
||||
|
||||
if let versionDate = app.latestVersion?.date, versionDate > Date()
|
||||
if let versionDate = app.latestSupportedVersion?.date, versionDate > Date()
|
||||
{
|
||||
cell.bannerView.button.countdownDate = app.versionDate
|
||||
cell.bannerView.button.countdownDate = versionDate
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -264,14 +265,20 @@ private extension BrowseViewController
|
||||
previousProgress?.cancel()
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if !minimuxer.ready() {
|
||||
let toastView = ToastView(error: MinimuxerError.NoConnection)
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
|
||||
_ = AppManager.shared.install(app, presentingViewController: self) { (result) in
|
||||
DispatchQueue.main.async {
|
||||
switch result
|
||||
{
|
||||
case .failure(OperationError.cancelled): break // Ignore
|
||||
case .failure(let error):
|
||||
let toastView = ToastView(error: error)
|
||||
let toastView = ToastView(error: error, opensLog: true)
|
||||
toastView.show(in: self)
|
||||
|
||||
case .success: print("Installed app:", app.bundleIdentifier)
|
||||
|
||||
@@ -22,7 +22,7 @@ final class CollapsingTextView: UITextView
|
||||
}
|
||||
}
|
||||
|
||||
var lineSpacing: CGFloat = 2 {
|
||||
var lineSpacing: Double = 2 {
|
||||
didSet {
|
||||
self.setNeedsLayout()
|
||||
}
|
||||
@@ -34,7 +34,19 @@ final class CollapsingTextView: UITextView
|
||||
{
|
||||
super.awakeFromNib()
|
||||
|
||||
self.layoutManager.delegate = self
|
||||
self.initialize()
|
||||
}
|
||||
|
||||
private func initialize()
|
||||
{
|
||||
if #available(iOS 16, *)
|
||||
{
|
||||
self.updateText()
|
||||
}
|
||||
else
|
||||
{
|
||||
self.layoutManager.delegate = self
|
||||
}
|
||||
|
||||
self.textContainerInset = .zero
|
||||
self.textContainer.lineFragmentPadding = 0
|
||||
@@ -108,6 +120,25 @@ private extension CollapsingTextView
|
||||
{
|
||||
self.isCollapsed.toggle()
|
||||
}
|
||||
|
||||
@available(iOS 16, *)
|
||||
func updateText()
|
||||
{
|
||||
do
|
||||
{
|
||||
let style = NSMutableParagraphStyle()
|
||||
style.lineSpacing = self.lineSpacing
|
||||
|
||||
var attributedText = try AttributedString(self.attributedText, including: \.uiKit)
|
||||
attributedText[AttributeScopes.UIKitAttributes.ParagraphStyleAttribute.self] = style
|
||||
|
||||
self.attributedText = NSAttributedString(attributedText)
|
||||
}
|
||||
catch
|
||||
{
|
||||
print("[ALTLog] Failed to update CollapsingTextView line spacing:", error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension CollapsingTextView: NSLayoutManagerDelegate
|
||||
|
||||
@@ -8,6 +8,12 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
extension PillButton
|
||||
{
|
||||
static let minimumSize = CGSize(width: 77, height: 31)
|
||||
static let contentInsets = NSDirectionalEdgeInsets(top: 7, leading: 13, bottom: 7, trailing: 13)
|
||||
}
|
||||
|
||||
final class PillButton: UIButton
|
||||
{
|
||||
override var accessibilityValue: String? {
|
||||
@@ -70,9 +76,7 @@ final class PillButton: UIButton
|
||||
}()
|
||||
|
||||
override var intrinsicContentSize: CGSize {
|
||||
var size = super.intrinsicContentSize
|
||||
size.width += 26
|
||||
size.height += 3
|
||||
let size = self.sizeThatFits(CGSize(width: Double.infinity, height: .infinity))
|
||||
return size
|
||||
}
|
||||
|
||||
@@ -88,6 +92,8 @@ final class PillButton: UIButton
|
||||
self.layer.masksToBounds = true
|
||||
self.accessibilityTraits.formUnion([.updatesFrequently, .button])
|
||||
|
||||
self.contentEdgeInsets = UIEdgeInsets(top: Self.contentInsets.top, left: Self.contentInsets.leading, bottom: Self.contentInsets.bottom, right: Self.contentInsets.trailing)
|
||||
|
||||
self.activityIndicatorView.style = .medium
|
||||
self.activityIndicatorView.isUserInteractionEnabled = false
|
||||
|
||||
@@ -119,6 +125,15 @@ final class PillButton: UIButton
|
||||
|
||||
self.update()
|
||||
}
|
||||
|
||||
override func sizeThatFits(_ size: CGSize) -> CGSize
|
||||
{
|
||||
var size = super.sizeThatFits(size)
|
||||
size.width = max(size.width, PillButton.minimumSize.width)
|
||||
size.height = max(size.height, PillButton.minimumSize.height)
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
private extension PillButton
|
||||
|
||||
@@ -18,8 +18,17 @@ extension TimeInterval
|
||||
|
||||
final class ToastView: RSTToastView
|
||||
{
|
||||
static let openErrorLogNotification = Notification.Name("ALTOpenErrorLogNotification")
|
||||
|
||||
var preferredDuration: TimeInterval
|
||||
|
||||
|
||||
var opensErrorLog: Bool = false
|
||||
|
||||
convenience init(text: String, detailText: String?, opensLog: Bool = false) {
|
||||
self.init(text: text, detailText: detailText)
|
||||
self.opensErrorLog = opensLog
|
||||
}
|
||||
|
||||
override init(text: String, detailText detailedText: String?)
|
||||
{
|
||||
if detailedText == nil
|
||||
@@ -43,53 +52,43 @@ final class ToastView: RSTToastView
|
||||
// RSTToastView does not expose stack view containing labels,
|
||||
// so we access it indirectly as the labels' superview.
|
||||
stackView.spacing = (detailedText != nil) ? 4.0 : 0.0
|
||||
stackView.alignment = .leading
|
||||
}
|
||||
self.addTarget(self, action: #selector(ToastView.showErrorLog), for: .touchUpInside)
|
||||
}
|
||||
|
||||
|
||||
convenience init(error: Error, opensLog: Bool = false) {
|
||||
self.init(error: error)
|
||||
self.opensErrorLog = opensLog
|
||||
}
|
||||
|
||||
convenience init(error: Error)
|
||||
{
|
||||
var error = error as NSError
|
||||
var underlyingError = error.underlyingError
|
||||
|
||||
var preferredDuration: TimeInterval?
|
||||
|
||||
if
|
||||
let unwrappedUnderlyingError = underlyingError,
|
||||
error.domain == AltServerErrorDomain && error.code == ALTServerError.Code.underlyingError.rawValue
|
||||
{
|
||||
// Treat underlyingError as the primary error.
|
||||
|
||||
// Treat underlyingError as the primary error, but keep localized title + failure.
|
||||
let nsError = error as NSError
|
||||
error = unwrappedUnderlyingError as NSError
|
||||
|
||||
if let localizedTitle = nsError.localizedTitle {
|
||||
error = error.withLocalizedTitle(localizedTitle)
|
||||
}
|
||||
if let localizedFailure = nsError.localizedFailure {
|
||||
error = error.withLocalizedFailure(localizedFailure)
|
||||
}
|
||||
|
||||
underlyingError = nil
|
||||
|
||||
preferredDuration = .longToastViewDuration
|
||||
}
|
||||
|
||||
let text: String
|
||||
let detailText: String?
|
||||
|
||||
if let failure = error.localizedFailure
|
||||
{
|
||||
text = failure
|
||||
detailText = error.localizedFailureReason ?? error.localizedRecoverySuggestion ?? underlyingError?.localizedDescription ?? error.localizedDescription
|
||||
}
|
||||
else if let reason = error.localizedFailureReason
|
||||
{
|
||||
text = reason
|
||||
detailText = error.localizedRecoverySuggestion ?? underlyingError?.localizedDescription
|
||||
}
|
||||
else
|
||||
{
|
||||
text = error.localizedDescription
|
||||
detailText = underlyingError?.localizedDescription ?? error.localizedRecoverySuggestion
|
||||
}
|
||||
|
||||
let text = error.localizedTitle ?? NSLocalizedString("Operation Failed", comment: "")
|
||||
let detailText = error.localizedDescription
|
||||
|
||||
|
||||
self.init(text: text, detailText: detailText)
|
||||
|
||||
if let preferredDuration = preferredDuration
|
||||
{
|
||||
self.preferredDuration = preferredDuration
|
||||
}
|
||||
}
|
||||
|
||||
required init(coder aDecoder: NSCoder) {
|
||||
@@ -112,6 +111,18 @@ final class ToastView: RSTToastView
|
||||
|
||||
override func show(in view: UIView, duration: TimeInterval)
|
||||
{
|
||||
if opensErrorLog, #available(iOS 13.0, *), case let configuration = UIImage.SymbolConfiguration(font: self.textLabel.font),
|
||||
let icon = UIImage(systemName: "chevron.right.circle", withConfiguration: configuration) {
|
||||
let tintedIcon = icon.withTintColor(.white, renderingMode: .alwaysOriginal)
|
||||
let moreIconImageView = UIImageView(image: tintedIcon)
|
||||
moreIconImageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
self.addSubview(moreIconImageView)
|
||||
NSLayoutConstraint.activate([
|
||||
moreIconImageView.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -self.layoutMargins.right),
|
||||
moreIconImageView.centerYAnchor.constraint(equalTo: self.textLabel.centerYAnchor),
|
||||
moreIconImageView.leadingAnchor.constraint(greaterThanOrEqualToSystemSpacingAfter: self.textLabel.trailingAnchor, multiplier: 1.0)
|
||||
])
|
||||
}
|
||||
super.show(in: view, duration: duration)
|
||||
|
||||
let announcement = (self.textLabel.text ?? "") + ". " + (self.detailTextLabel.text ?? "")
|
||||
@@ -127,4 +138,10 @@ final class ToastView: RSTToastView
|
||||
{
|
||||
self.show(in: view, duration: self.preferredDuration)
|
||||
}
|
||||
|
||||
@objc
|
||||
func showErrorLog() {
|
||||
guard self.opensErrorLog else { return }
|
||||
NotificationCenter.default.post(name: ToastView.openErrorLogNotification, object: self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,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>ALTAnisetteURL</key>
|
||||
<string>https://ani.sidestore.io</string>
|
||||
<key>ALTAppGroups</key>
|
||||
<array>
|
||||
<string>group.$(APP_GROUP_IDENTIFIER)</string>
|
||||
@@ -9,12 +11,10 @@
|
||||
</array>
|
||||
<key>ALTDeviceID</key>
|
||||
<string>00008101-000129D63698001E</string>
|
||||
<key>ALTServerID</key>
|
||||
<string>1F7D5B55-79CE-4546-A029-D4DDC4AF3B6D</string>
|
||||
<key>ALTPairingFile</key>
|
||||
<string><insert pairing file here></string>
|
||||
<key>ALTAnisetteURL</key>
|
||||
<string>https://ani.sidestore.io</string>
|
||||
<key>ALTServerID</key>
|
||||
<string>1F7D5B55-79CE-4546-A029-D4DDC4AF3B6D</string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDocumentTypes</key>
|
||||
@@ -44,8 +44,6 @@
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||
<true/>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
@@ -93,6 +91,13 @@
|
||||
</array>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||
<true/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>NSBonjourServices</key>
|
||||
<array>
|
||||
<string>_altserver._tcp</string>
|
||||
@@ -131,13 +136,10 @@
|
||||
<string>fetch</string>
|
||||
<string>remote-notification</string>
|
||||
</array>
|
||||
<key>UIFileSharingEnabled</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
@@ -204,7 +206,5 @@
|
||||
</dict>
|
||||
</dict>
|
||||
</array>
|
||||
<key>UIFileSharingEnabled</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
import minimuxer
|
||||
import AltStoreCore
|
||||
|
||||
@available(iOS 14, *)
|
||||
@@ -39,8 +40,12 @@ final class IntentHandler: NSObject, RefreshAllIntentHandling
|
||||
|
||||
// Give ourselves 9 extra seconds before starting handle() timeout timer.
|
||||
// 10 seconds or longer results in timeout regardless.
|
||||
self.queue.asyncAfter(deadline: .now() + 9.0) {
|
||||
self.finish(intent, response: RefreshAllIntentResponse(code: .ready, userActivity: nil))
|
||||
self.queue.asyncAfter(deadline: .now() + 8.0) {
|
||||
if minimuxer.ready() {
|
||||
self.finish(intent, response: RefreshAllIntentResponse(code: .success, userActivity: nil))
|
||||
} else {
|
||||
self.finish(intent, response: RefreshAllIntentResponse(code: .failure, userActivity: nil))
|
||||
}
|
||||
}
|
||||
|
||||
if !DatabaseManager.shared.isStarted
|
||||
@@ -52,12 +57,14 @@ final class IntentHandler: NSObject, RefreshAllIntentHandling
|
||||
}
|
||||
else
|
||||
{
|
||||
self.finish(intent, response: RefreshAllIntentResponse(code: .ready, userActivity: nil))
|
||||
self.refreshApps(intent: intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
self.finish(intent, response: RefreshAllIntentResponse(code: .ready, userActivity: nil))
|
||||
self.refreshApps(intent: intent)
|
||||
}
|
||||
}
|
||||
@@ -83,6 +90,11 @@ final class IntentHandler: NSObject, RefreshAllIntentHandling
|
||||
// We took too long to finish and return the final result,
|
||||
// so we'll now present a normal notification when finished.
|
||||
operation.presentsFinishedNotification = true
|
||||
if minimuxer.ready() {
|
||||
self.finish(intent, response: RefreshAllIntentResponse(code: .success, userActivity: nil))
|
||||
} else {
|
||||
self.finish(intent, response: RefreshAllIntentResponse(code: .failure, userActivity: nil))
|
||||
}
|
||||
}
|
||||
|
||||
self.finish(intent, response: RefreshAllIntentResponse(code: .inProgress, userActivity: nil))
|
||||
@@ -106,6 +118,8 @@ private extension IntentHandler
|
||||
{
|
||||
// Queue response in case refreshing finishes after confirm() but before handle().
|
||||
self.queuedResponses[intent] = response
|
||||
|
||||
UIApplication.shared.perform(#selector(NSXPCConnection.suspend))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -126,10 +140,12 @@ private extension IntentHandler
|
||||
}
|
||||
|
||||
self.finish(intent, response: RefreshAllIntentResponse(code: .success, userActivity: nil))
|
||||
UIApplication.shared.perform(#selector(NSXPCConnection.suspend))
|
||||
}
|
||||
catch RefreshError.noInstalledApps
|
||||
catch ~RefreshErrorCode.noInstalledApps
|
||||
{
|
||||
self.finish(intent, response: RefreshAllIntentResponse(code: .success, userActivity: nil))
|
||||
UIApplication.shared.perform(#selector(NSXPCConnection.suspend))
|
||||
}
|
||||
catch let error as NSError
|
||||
{
|
||||
|
||||
@@ -49,6 +49,43 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(true)
|
||||
if #available(iOS 17, *), !UserDefaults.standard.sidejitenable {
|
||||
DispatchQueue.global().async {
|
||||
self.isSideJITServerDetected() { result in
|
||||
DispatchQueue.main.async {
|
||||
switch result {
|
||||
case .success():
|
||||
let dialogMessage = UIAlertController(title: "SideJITServer Detected", message: "Would you like to enable SideJITServer", preferredStyle: .alert)
|
||||
|
||||
// Create OK button with action handler
|
||||
let ok = UIAlertAction(title: "OK", style: .default, handler: { (action) -> Void in
|
||||
UserDefaults.standard.sidejitenable = true
|
||||
})
|
||||
|
||||
let cancel = UIAlertAction(title: "Cancel", style: .cancel)
|
||||
//Add OK button to a dialog message
|
||||
dialogMessage.addAction(ok)
|
||||
dialogMessage.addAction(cancel)
|
||||
|
||||
// Present Alert to
|
||||
self.present(dialogMessage, animated: true, completion: nil)
|
||||
case .failure(_):
|
||||
print("Cannot find sideJITServer")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if #available(iOS 17, *), UserDefaults.standard.sidejitenable {
|
||||
DispatchQueue.global().async {
|
||||
self.askfornetwork()
|
||||
}
|
||||
print("SideJITServer Enabled")
|
||||
}
|
||||
|
||||
|
||||
|
||||
#if !targetEnvironment(simulator)
|
||||
start_em_proxy(bind_addr: Consts.Proxy.serverURL)
|
||||
|
||||
@@ -60,6 +97,46 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg
|
||||
#endif
|
||||
}
|
||||
|
||||
func askfornetwork() {
|
||||
let address = UserDefaults.standard.textInputSideJITServerurl ?? ""
|
||||
|
||||
var SJSURL = address
|
||||
|
||||
if (UserDefaults.standard.textInputSideJITServerurl ?? "").isEmpty {
|
||||
SJSURL = "http://sidejitserver._http._tcp.local:8080"
|
||||
}
|
||||
|
||||
// Create a network operation at launch to Refresh SideJITServer
|
||||
let url = URL(string: "\(SJSURL)/re/")!
|
||||
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
|
||||
print(data)
|
||||
}
|
||||
task.resume()
|
||||
}
|
||||
|
||||
func isSideJITServerDetected(completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
let address = UserDefaults.standard.textInputSideJITServerurl ?? ""
|
||||
|
||||
var SJSURL = address
|
||||
|
||||
if (UserDefaults.standard.textInputSideJITServerurl ?? "").isEmpty {
|
||||
SJSURL = "http://sidejitserver._http._tcp.local:8080"
|
||||
}
|
||||
|
||||
// Create a network operation at launch to Refresh SideJITServer
|
||||
let url = URL(string: SJSURL)!
|
||||
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
|
||||
if let error = error {
|
||||
print("No SideJITServer on Network")
|
||||
completion(.failure(error))
|
||||
return
|
||||
}
|
||||
completion(.success(()))
|
||||
}
|
||||
task.resume()
|
||||
return
|
||||
}
|
||||
|
||||
func fetchPairingFile() -> String? {
|
||||
let filename = "ALTPairingFile.mobiledevicepairing"
|
||||
let fm = FileManager.default
|
||||
@@ -72,16 +149,17 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg
|
||||
fm.fileExists(atPath: appResourcePath.path),
|
||||
let data = fm.contents(atPath: appResourcePath.path),
|
||||
let contents = String(data: data, encoding: .utf8),
|
||||
!contents.isEmpty {
|
||||
!contents.isEmpty,
|
||||
!UserDefaults.standard.isPairingReset {
|
||||
print("Loaded ALTPairingFile from \(appResourcePath.path)")
|
||||
return contents
|
||||
} else if let plistString = Bundle.main.object(forInfoDictionaryKey: "ALTPairingFile") as? String, !plistString.isEmpty, !plistString.contains("insert pairing file here"){
|
||||
} else if let plistString = Bundle.main.object(forInfoDictionaryKey: "ALTPairingFile") as? String, !plistString.isEmpty, !plistString.contains("insert pairing file here"), !UserDefaults.standard.isPairingReset{
|
||||
print("Loaded ALTPairingFile from Info.plist")
|
||||
return plistString
|
||||
} else {
|
||||
// Show an alert explaining the pairing file
|
||||
// Create new Alert
|
||||
let dialogMessage = UIAlertController(title: "Pairing File", message: "Select the pairing file for your device. For more information, go to https://wiki.sidestore.io/guides/install#pairing-process", preferredStyle: .alert)
|
||||
let dialogMessage = UIAlertController(title: "Pairing File", message: "Select the pairing file for your device. For more information, go to https://wiki.sidestore.io/guides/getting-started/#pairing-file", preferredStyle: .alert)
|
||||
|
||||
// Create OK button with action handler
|
||||
let ok = UIAlertAction(title: "OK", style: .default, handler: { (action) -> Void in
|
||||
@@ -93,6 +171,7 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg
|
||||
documentPickerController.shouldShowFileExtensions = true
|
||||
documentPickerController.delegate = self
|
||||
self.present(documentPickerController, animated: true, completion: nil)
|
||||
UserDefaults.standard.isPairingReset = false
|
||||
})
|
||||
|
||||
//Add OK button to a dialog message
|
||||
@@ -101,6 +180,13 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg
|
||||
// Present Alert to
|
||||
self.present(dialogMessage, animated: true, completion: nil)
|
||||
|
||||
let dialogMessage2 = UIAlertController(title: "Analytics", message: "This app contains anonymous analytics for research and project development. By continuing to use this app, you are consenting to this data collection", preferredStyle: .alert)
|
||||
|
||||
let ok2 = UIAlertAction(title: "OK", style: .default, handler: { (action) -> Void in})
|
||||
|
||||
dialogMessage2.addAction(ok2)
|
||||
self.present(dialogMessage2, animated: true, completion: nil)
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -155,7 +241,12 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg
|
||||
try! FileManager.default.removeItem(at: FileManager.default.documentsDirectory.appendingPathComponent("\(pairingFileName)"))
|
||||
displayError("minimuxer failed to start, please restart SideStore. \((error as? LocalizedError)?.failureReason ?? "UNKNOWN ERROR!!!!!! REPORT TO GITHUB ISSUES!")")
|
||||
}
|
||||
start_auto_mounter(documentsDirectory)
|
||||
if #available(iOS 17, *) {
|
||||
// TODO: iOS 17 and above have a new JIT implementation that is completely broken in SideStore :(
|
||||
}
|
||||
else {
|
||||
start_auto_mounter(documentsDirectory)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import Intents
|
||||
import Combine
|
||||
import WidgetKit
|
||||
|
||||
import minimuxer
|
||||
import AltStoreCore
|
||||
import AltSign
|
||||
import Roxas
|
||||
@@ -37,11 +38,6 @@ final class AppManagerPublisher: ObservableObject
|
||||
fileprivate(set) var refreshProgress = [String: Progress]()
|
||||
}
|
||||
|
||||
private func ==(lhs: OperatingSystemVersion, rhs: OperatingSystemVersion) -> Bool
|
||||
{
|
||||
return (lhs.majorVersion == rhs.majorVersion && lhs.minorVersion == rhs.minorVersion && lhs.patchVersion == rhs.patchVersion)
|
||||
}
|
||||
|
||||
final class AppManager
|
||||
{
|
||||
static let shared = AppManager()
|
||||
@@ -307,6 +303,45 @@ extension AppManager
|
||||
presentingViewController.present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
func clearAppCache(completion: @escaping (Result<Void, Error>) -> Void)
|
||||
{
|
||||
let clearAppCacheOperation = ClearAppCacheOperation()
|
||||
clearAppCacheOperation.resultHandler = { result in
|
||||
completion(result)
|
||||
}
|
||||
|
||||
self.run([clearAppCacheOperation], context: nil)
|
||||
}
|
||||
|
||||
func log(_ error: Error, operation: LoggedError.Operation, app: AppProtocol)
|
||||
{
|
||||
switch error {
|
||||
case ~OperationError.Code.cancelled: return // Don't log cancelled events
|
||||
default: break
|
||||
}
|
||||
// Sanitize NSError on same thread before performing background task.
|
||||
let sanitizedError = (error as NSError).sanitizedForSerialization()
|
||||
|
||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { context in
|
||||
var app = app
|
||||
if let managedApp = app as? NSManagedObject, let tempApp = context.object(with: managedApp.objectID) as? AppProtocol
|
||||
{
|
||||
app = tempApp
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
_ = LoggedError(error: sanitizedError, app: app, operation: operation, context: context)
|
||||
try context.save()
|
||||
}
|
||||
catch let saveError
|
||||
{
|
||||
print("[ALTLog] Failed to log error \(sanitizedError.domain) code \(sanitizedError.code) for \(app.bundleIdentifier):", saveError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension AppManager
|
||||
@@ -359,7 +394,7 @@ extension AppManager
|
||||
case .success(let source): fetchedSources.insert(source)
|
||||
case .failure(let error):
|
||||
let source = managedObjectContext.object(with: source.objectID) as! Source
|
||||
source.error = (error as NSError).sanitizedForCoreData()
|
||||
source.error = (error as NSError).sanitizedForSerialization()
|
||||
errors[source] = error
|
||||
}
|
||||
|
||||
@@ -447,7 +482,7 @@ extension AppManager
|
||||
group.completionHandler = { (results) in
|
||||
do
|
||||
{
|
||||
guard let result = results.values.first else { throw context.error ?? OperationError.unknown }
|
||||
guard let result = results.values.first else { throw context.error ?? OperationError.unknown() }
|
||||
completionHandler(result)
|
||||
}
|
||||
catch
|
||||
@@ -466,7 +501,7 @@ extension AppManager
|
||||
func update(_ app: InstalledApp, presentingViewController: UIViewController?, context: AuthenticatedOperationContext = AuthenticatedOperationContext(), completionHandler: @escaping (Result<InstalledApp, Error>) -> Void) -> Progress
|
||||
{
|
||||
guard let storeApp = app.storeApp else {
|
||||
completionHandler(.failure(OperationError.appNotFound))
|
||||
completionHandler(.failure(OperationError.appNotFound(name: app.name)))
|
||||
return Progress.discreteProgress(totalUnitCount: 1)
|
||||
}
|
||||
|
||||
@@ -474,7 +509,7 @@ extension AppManager
|
||||
group.completionHandler = { (results) in
|
||||
do
|
||||
{
|
||||
guard let result = results.values.first else { throw OperationError.unknown }
|
||||
guard let result = results.values.first else { throw OperationError.unknown() }
|
||||
completionHandler(result)
|
||||
}
|
||||
catch
|
||||
@@ -510,8 +545,8 @@ extension AppManager
|
||||
group.completionHandler = { (results) in
|
||||
do
|
||||
{
|
||||
guard let result = results.values.first else { throw OperationError.unknown }
|
||||
|
||||
guard let result = results.values.first else { throw OperationError.unknown() }
|
||||
|
||||
let installedApp = try result.get()
|
||||
assert(installedApp.managedObjectContext != nil)
|
||||
|
||||
@@ -549,7 +584,7 @@ extension AppManager
|
||||
group.completionHandler = { (results) in
|
||||
do
|
||||
{
|
||||
guard let result = results.values.first else { throw OperationError.unknown }
|
||||
guard let result = results.values.first else { throw OperationError.unknown() }
|
||||
|
||||
let installedApp = try result.get()
|
||||
assert(installedApp.managedObjectContext != nil)
|
||||
@@ -575,8 +610,8 @@ extension AppManager
|
||||
group.completionHandler = { (results) in
|
||||
do
|
||||
{
|
||||
guard let result = results.values.first else { throw OperationError.unknown }
|
||||
|
||||
guard let result = results.values.first else { throw OperationError.unknown() }
|
||||
|
||||
let installedApp = try result.get()
|
||||
assert(installedApp.managedObjectContext != nil)
|
||||
|
||||
@@ -600,7 +635,7 @@ extension AppManager
|
||||
group.completionHandler = { (results) in
|
||||
do
|
||||
{
|
||||
guard let result = results.values.first else { throw OperationError.unknown }
|
||||
guard let result = results.values.first else { throw OperationError.unknown() }
|
||||
|
||||
let installedApp = try result.get()
|
||||
assert(installedApp.managedObjectContext != nil)
|
||||
@@ -670,13 +705,20 @@ extension AppManager
|
||||
var installedApp: InstalledApp?
|
||||
}
|
||||
|
||||
let appName = installedApp.name
|
||||
let context = Context()
|
||||
context.installedApp = installedApp
|
||||
|
||||
|
||||
let enableJITOperation = EnableJITOperation(context: context)
|
||||
enableJITOperation.resultHandler = { (result) in
|
||||
completionHandler(result)
|
||||
switch result {
|
||||
case .success: completionHandler(.success(()))
|
||||
case .failure(let nsError as NSError):
|
||||
let localizedTitle = String(format: NSLocalizedString("Failed to enable JIT for %@", comment: ""), appName)
|
||||
let error = nsError.withLocalizedTitle(localizedTitle)
|
||||
self.log(error, operation: .enableJIT, app: installedApp)
|
||||
}
|
||||
}
|
||||
|
||||
self.run([enableJITOperation], context: context, requiresSerialQueue: true)
|
||||
@@ -754,6 +796,12 @@ extension AppManager
|
||||
let progress = self.refreshProgress[app.bundleIdentifier]
|
||||
return progress
|
||||
}
|
||||
|
||||
func isActivelyManagingApp(withBundleID bundleID: String) -> Bool
|
||||
{
|
||||
let isActivelyManaging = self.installationProgress.keys.contains(bundleID) || self.refreshProgress.keys.contains(bundleID)
|
||||
return isActivelyManaging
|
||||
}
|
||||
}
|
||||
|
||||
extension AppManager
|
||||
@@ -806,12 +854,18 @@ private extension AppManager
|
||||
|
||||
return bundleIdentifier
|
||||
}
|
||||
}
|
||||
|
||||
func isActivelyManagingApp(withBundleID bundleID: String) -> Bool
|
||||
{
|
||||
let isActivelyManaging = self.installationProgress.keys.contains(bundleID) || self.refreshProgress.keys.contains(bundleID)
|
||||
return isActivelyManaging
|
||||
|
||||
var loggedErrorOperation: LoggedError.Operation {
|
||||
switch self {
|
||||
case .install: return .install
|
||||
case .update: return .update
|
||||
case .refresh: return .refresh
|
||||
case .activate: return .activate
|
||||
case .deactivate: return .deactivate
|
||||
case .backup: return .backup
|
||||
case .restore: return .restore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
@@ -948,7 +1002,13 @@ private extension AppManager
|
||||
}
|
||||
else
|
||||
{
|
||||
DispatchQueue.main.schedule {
|
||||
UIApplication.shared.isIdleTimerDisabled = UserDefaults.standard.isIdleTimeoutDisableEnabled
|
||||
}
|
||||
performAppOperations()
|
||||
DispatchQueue.main.schedule {
|
||||
UIApplication.shared.isIdleTimerDisabled = false
|
||||
}
|
||||
}
|
||||
|
||||
return group
|
||||
@@ -1027,6 +1087,34 @@ private extension AppManager
|
||||
verifyOperation.addDependency(downloadOperation)
|
||||
|
||||
|
||||
/* Refresh Anisette Data */
|
||||
let refreshAnisetteDataOperation = FetchAnisetteDataOperation(context: group.context)
|
||||
refreshAnisetteDataOperation.resultHandler = { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): context.error = error
|
||||
case .success(let anisetteData): group.context.session?.anisetteData = anisetteData
|
||||
}
|
||||
}
|
||||
refreshAnisetteDataOperation.addDependency(verifyOperation)
|
||||
|
||||
|
||||
/* Fetch Provisioning Profiles */
|
||||
let fetchProvisioningProfilesOperation = FetchProvisioningProfilesOperation(context: context)
|
||||
fetchProvisioningProfilesOperation.additionalEntitlements = additionalEntitlements
|
||||
fetchProvisioningProfilesOperation.resultHandler = { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): context.error = error
|
||||
case .success(let provisioningProfiles):
|
||||
context.provisioningProfiles = provisioningProfiles
|
||||
print("PROVISIONING PROFILES \(context.provisioningProfiles)")
|
||||
}
|
||||
}
|
||||
fetchProvisioningProfilesOperation.addDependency(refreshAnisetteDataOperation)
|
||||
progress.addChild(fetchProvisioningProfilesOperation.progress, withPendingUnitCount: 5)
|
||||
|
||||
|
||||
/* Deactivate Apps (if necessary) */
|
||||
let deactivateAppsOperation = RSTAsyncBlockOperation { [weak self] (operation) in
|
||||
do
|
||||
@@ -1042,6 +1130,12 @@ private extension AppManager
|
||||
{
|
||||
throw error
|
||||
}
|
||||
|
||||
guard let profiles = context.provisioningProfiles else { throw OperationError.invalidParameters }
|
||||
if !profiles.contains(where: { $1.isFreeProvisioningProfile == true }) {
|
||||
operation.finish()
|
||||
return
|
||||
}
|
||||
|
||||
guard let app = context.app, let presentingViewController = context.authenticatedContext.presentingViewController else { throw OperationError.invalidParameters }
|
||||
|
||||
@@ -1061,7 +1155,7 @@ private extension AppManager
|
||||
operation.finish()
|
||||
}
|
||||
}
|
||||
deactivateAppsOperation.addDependency(verifyOperation)
|
||||
deactivateAppsOperation.addDependency(fetchProvisioningProfilesOperation)
|
||||
|
||||
|
||||
/* Patch App */
|
||||
@@ -1136,32 +1230,6 @@ private extension AppManager
|
||||
patchAppOperation.addDependency(deactivateAppsOperation)
|
||||
|
||||
|
||||
/* Refresh Anisette Data */
|
||||
let refreshAnisetteDataOperation = FetchAnisetteDataOperation(context: group.context)
|
||||
refreshAnisetteDataOperation.resultHandler = { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): context.error = error
|
||||
case .success(let anisetteData): group.context.session?.anisetteData = anisetteData
|
||||
}
|
||||
}
|
||||
refreshAnisetteDataOperation.addDependency(patchAppOperation)
|
||||
|
||||
|
||||
/* Fetch Provisioning Profiles */
|
||||
let fetchProvisioningProfilesOperation = FetchProvisioningProfilesOperation(context: context)
|
||||
fetchProvisioningProfilesOperation.additionalEntitlements = additionalEntitlements
|
||||
fetchProvisioningProfilesOperation.resultHandler = { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): context.error = error
|
||||
case .success(let provisioningProfiles): context.provisioningProfiles = provisioningProfiles
|
||||
}
|
||||
}
|
||||
fetchProvisioningProfilesOperation.addDependency(refreshAnisetteDataOperation)
|
||||
progress.addChild(fetchProvisioningProfilesOperation.progress, withPendingUnitCount: 5)
|
||||
|
||||
|
||||
/* Resign */
|
||||
let resignAppOperation = ResignAppOperation(context: context)
|
||||
resignAppOperation.resultHandler = { (result) in
|
||||
@@ -1171,7 +1239,7 @@ private extension AppManager
|
||||
case .success(let resignedApp): context.resignedApp = resignedApp
|
||||
}
|
||||
}
|
||||
resignAppOperation.addDependency(fetchProvisioningProfilesOperation)
|
||||
resignAppOperation.addDependency(patchAppOperation)
|
||||
progress.addChild(resignAppOperation.progress, withPendingUnitCount: 20)
|
||||
|
||||
|
||||
@@ -1214,7 +1282,7 @@ private extension AppManager
|
||||
progress.addChild(installOperation.progress, withPendingUnitCount: 30)
|
||||
installOperation.addDependency(sendAppOperation)
|
||||
|
||||
let operations = [downloadOperation, verifyOperation, deactivateAppsOperation, patchAppOperation, refreshAnisetteDataOperation, fetchProvisioningProfilesOperation, resignAppOperation, sendAppOperation, installOperation]
|
||||
let operations = [downloadOperation, verifyOperation, refreshAnisetteDataOperation, fetchProvisioningProfilesOperation, deactivateAppsOperation, patchAppOperation, resignAppOperation, sendAppOperation, installOperation]
|
||||
group.add(operations)
|
||||
self.run(operations, context: group.context)
|
||||
|
||||
@@ -1247,14 +1315,21 @@ private extension AppManager
|
||||
case .success(let installedApp):
|
||||
completionHandler(.success(installedApp))
|
||||
|
||||
case .failure(ALTServerError.unknownRequest), .failure(OperationError.appNotFound):
|
||||
case .failure(MinimuxerError.ProfileInstall):
|
||||
completionHandler(.failure(OperationError.noWiFi))
|
||||
|
||||
case .failure(ALTServerError.unknownRequest), .failure(OperationError.appNotFound(name: app.name)):
|
||||
// Fall back to installation if AltServer doesn't support newer provisioning profile requests,
|
||||
// OR if the cached app could not be found and we may need to redownload it.
|
||||
app.managedObjectContext?.performAndWait { // Must performAndWait to ensure we add operations before we return.
|
||||
let installProgress = self._install(app, operation: operation, group: group) { (result) in
|
||||
completionHandler(result)
|
||||
if minimuxer.ready() {
|
||||
let installProgress = self._install(app, operation: operation, group: group) { (result) in
|
||||
completionHandler(result)
|
||||
}
|
||||
progress.addChild(installProgress, withPendingUnitCount: 40)
|
||||
} else {
|
||||
completionHandler(.failure(OperationError.noWiFi))
|
||||
}
|
||||
progress.addChild(installProgress, withPendingUnitCount: 40)
|
||||
}
|
||||
|
||||
case .failure(let error):
|
||||
@@ -1521,7 +1596,7 @@ private extension AppManager
|
||||
}
|
||||
|
||||
guard let application = ALTApplication(fileURL: app.fileURL) else {
|
||||
completionHandler(.failure(OperationError.appNotFound))
|
||||
completionHandler(.failure(OperationError.appNotFound(name: app.name)))
|
||||
return progress
|
||||
}
|
||||
|
||||
@@ -1533,8 +1608,8 @@ private extension AppManager
|
||||
let temporaryDirectoryURL = context.temporaryDirectory.appendingPathComponent("AltBackup-" + UUID().uuidString)
|
||||
try FileManager.default.createDirectory(at: temporaryDirectoryURL, withIntermediateDirectories: true, attributes: nil)
|
||||
|
||||
guard let altbackupFileURL = Bundle.main.url(forResource: "AltBackup", withExtension: "ipa") else { throw OperationError.appNotFound }
|
||||
|
||||
guard let altbackupFileURL = Bundle.main.url(forResource: "AltBackup", withExtension: "ipa") else { throw OperationError.appNotFound(name: app.name) }
|
||||
|
||||
let unzippedAppBundleURL = try FileManager.default.unzipAppBundle(at: altbackupFileURL, toDirectory: temporaryDirectoryURL)
|
||||
guard let unzippedAppBundle = Bundle(url: unzippedAppBundleURL) else { throw OperationError.invalidApp }
|
||||
|
||||
@@ -1672,11 +1747,35 @@ private extension AppManager
|
||||
do { try installedApp.managedObjectContext?.save() }
|
||||
catch { print("Error saving installed app.", error) }
|
||||
}
|
||||
catch
|
||||
catch let nsError as NSError
|
||||
{
|
||||
var appName: String!
|
||||
if let app = operation.app as? (NSManagedObject & AppProtocol) {
|
||||
if let context = app.managedObjectContext {
|
||||
context.performAndWait {
|
||||
appName = app.name
|
||||
}
|
||||
} else {
|
||||
appName = NSLocalizedString("Unknown App", comment: "")
|
||||
}
|
||||
} else {
|
||||
appName = operation.app.name
|
||||
}
|
||||
|
||||
let localizedTitle: String
|
||||
switch operation {
|
||||
case .install: localizedTitle = String(format: NSLocalizedString("Failed to Install %@", comment: ""), appName)
|
||||
case .refresh: localizedTitle = String(format: NSLocalizedString("Failed to Refresh %@", comment: ""), appName)
|
||||
case .update: localizedTitle = String(format: NSLocalizedString("Failed to Update %@", comment: ""), appName)
|
||||
case .activate: localizedTitle = String(format: NSLocalizedString("Failed to Activate %@", comment: ""), appName)
|
||||
case .deactivate: localizedTitle = String(format: NSLocalizedString("Failed to Deactivate %@", comment: ""), appName)
|
||||
case .backup: localizedTitle = String(format: NSLocalizedString("Failed to Backup %@", comment: ""), appName)
|
||||
case .restore: localizedTitle = String(format: NSLocalizedString("Failed to Restore %@ Backup", comment: ""), appName)
|
||||
}
|
||||
let error = nsError.withLocalizedTitle(localizedTitle)
|
||||
group.set(.failure(error), forAppWithBundleIdentifier: operation.bundleIdentifier)
|
||||
|
||||
self.log(error, for: operation)
|
||||
self.log(error, operation: operation.loggedErrorOperation, app: operation.app)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1693,51 +1792,15 @@ private extension AppManager
|
||||
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: timeIntervalUntilNotification, repeats: false)
|
||||
|
||||
let content = UNMutableNotificationContent()
|
||||
content.title = NSLocalizedString("AltStore Expiring Soon", comment: "")
|
||||
content.body = NSLocalizedString("AltStore will expire in 24 hours. Open the app and refresh it to prevent it from expiring.", comment: "")
|
||||
content.title = NSLocalizedString("SideStore Expiring Soon", comment: "")
|
||||
content.body = NSLocalizedString("SideStore will expire in 24 hours. Open the app and refresh it to prevent it from expiring.", comment: "")
|
||||
content.sound = .default
|
||||
|
||||
let request = UNNotificationRequest(identifier: AppManager.expirationWarningNotificationID, content: content, trigger: trigger)
|
||||
UNUserNotificationCenter.current().add(request)
|
||||
}
|
||||
|
||||
func log(_ error: Error, for operation: AppOperation)
|
||||
{
|
||||
// Sanitize NSError on same thread before performing background task.
|
||||
let sanitizedError = (error as NSError).sanitizedForCoreData()
|
||||
|
||||
let loggedErrorOperation: LoggedError.Operation = {
|
||||
switch operation
|
||||
{
|
||||
case .install: return .install
|
||||
case .update: return .update
|
||||
case .refresh: return .refresh
|
||||
case .activate: return .activate
|
||||
case .deactivate: return .deactivate
|
||||
case .backup: return .backup
|
||||
case .restore: return .restore
|
||||
}
|
||||
}()
|
||||
|
||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { context in
|
||||
var app = operation.app
|
||||
if let managedApp = app as? NSManagedObject, let tempApp = context.object(with: managedApp.objectID) as? AppProtocol
|
||||
{
|
||||
app = tempApp
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
_ = LoggedError(error: sanitizedError, app: app, operation: loggedErrorOperation, context: context)
|
||||
try context.save()
|
||||
}
|
||||
catch let saveError
|
||||
{
|
||||
print("[ALTLog] Failed to log error \(sanitizedError.domain) code \(sanitizedError.code) for \(app.bundleIdentifier):", saveError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func run(_ operations: [Foundation.Operation], context: OperationContext?, requiresSerialQueue: Bool = false)
|
||||
{
|
||||
// Find "Install AltStore" operation if it already exists in `context`
|
||||
|
||||
@@ -22,13 +22,27 @@ extension AppManager
|
||||
|
||||
var managedObjectContext: NSManagedObjectContext?
|
||||
|
||||
var errorDescription: String? {
|
||||
if let error = self.primaryError
|
||||
{
|
||||
return error.localizedDescription
|
||||
var localizedTitle: String? {
|
||||
var localizedTitle: String?
|
||||
self.managedObjectContext?.performAndWait {
|
||||
if self.sources?.count == 1 {
|
||||
localizedTitle = NSLocalizedString("Failed to refresh Store", comment: "")
|
||||
} else if self.errors.count == 1 {
|
||||
guard let source = self.errors.keys.first else { return }
|
||||
localizedTitle = String(format: NSLocalizedString("Failed to refresh Source '%@'", comment: ""), source.name)
|
||||
} else {
|
||||
localizedTitle = String(format: NSLocalizedString("Failed to refresh %@ Sources", comment: ""), NSNumber(value: self.errors.count))
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return localizedTitle
|
||||
}
|
||||
|
||||
var errorDescription: String? {
|
||||
if let error = self.primaryError {
|
||||
return error.localizedDescription
|
||||
} else if let error = self.errors.values.first, self.errors.count == 1 {
|
||||
return error.localizedDescription
|
||||
} else {
|
||||
var localizedDescription: String?
|
||||
|
||||
self.managedObjectContext?.performAndWait {
|
||||
@@ -67,8 +81,14 @@ extension AppManager
|
||||
}
|
||||
|
||||
var errorUserInfo: [String : Any] {
|
||||
guard let error = self.errors.values.first, self.errors.count == 1 else { return [:] }
|
||||
return [NSUnderlyingErrorKey: error]
|
||||
let errors = Array(self.errors.values)
|
||||
var userInfo = [String: Any]()
|
||||
userInfo[ALTLocalizedTitleErrorKey] = self.localizedTitle
|
||||
userInfo[NSUnderlyingErrorKey] = self.primaryError
|
||||
if #available(iOS 14.5, *), !errors.isEmpty {
|
||||
userInfo[NSMultipleUnderlyingErrorsKey] = errors
|
||||
}
|
||||
return userInfo
|
||||
}
|
||||
|
||||
init(_ error: Error)
|
||||
|
||||
@@ -10,10 +10,12 @@ import UIKit
|
||||
import MobileCoreServices
|
||||
import Intents
|
||||
import Combine
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
import AltStoreCore
|
||||
import AltSign
|
||||
import Roxas
|
||||
import minimuxer
|
||||
|
||||
import Nuke
|
||||
|
||||
@@ -153,6 +155,13 @@ final class MyAppsViewController: UICollectionViewController
|
||||
@IBAction func unwindToMyAppsViewController(_ segue: UIStoryboardSegue)
|
||||
{
|
||||
}
|
||||
var minimuxerStatus: Bool {
|
||||
guard minimuxer.ready() else {
|
||||
ToastView(error: (OperationError.noWiFi as NSError).withLocalizedTitle("No WiFi or VPN!")).show(in: self)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
private extension MyAppsViewController
|
||||
@@ -186,7 +195,7 @@ private extension MyAppsViewController
|
||||
func makeUpdatesDataSource() -> RSTFetchedResultsCollectionViewPrefetchingDataSource<InstalledApp, UIImage>
|
||||
{
|
||||
let fetchRequest = InstalledApp.updatesFetchRequest()
|
||||
fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \InstalledApp.storeApp?.latestVersion?.date, ascending: true),
|
||||
fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \InstalledApp.storeApp?.latestSupportedVersion?.date, ascending: false),
|
||||
NSSortDescriptor(keyPath: \InstalledApp.name, ascending: true)]
|
||||
fetchRequest.returnsObjectsAsFaults = false
|
||||
|
||||
@@ -195,21 +204,21 @@ private extension MyAppsViewController
|
||||
dataSource.cellIdentifierHandler = { _ in "UpdateCell" }
|
||||
dataSource.cellConfigurationHandler = { [weak self] (cell, installedApp, indexPath) in
|
||||
guard let self = self else { return }
|
||||
guard let app = installedApp.storeApp, let latestVersion = app.latestVersion else { return }
|
||||
guard let app = installedApp.storeApp, let latestSupportedVersion = app.latestSupportedVersion else { return }
|
||||
|
||||
let cell = cell as! UpdateCollectionViewCell
|
||||
cell.layoutMargins.left = self.view.layoutMargins.left
|
||||
cell.layoutMargins.right = self.view.layoutMargins.right
|
||||
|
||||
cell.tintColor = app.tintColor ?? .altPrimary
|
||||
cell.versionDescriptionTextView.text = app.versionDescription
|
||||
cell.versionDescriptionTextView.text = latestSupportedVersion.localizedDescription
|
||||
|
||||
cell.bannerView.iconImageView.image = nil
|
||||
cell.bannerView.iconImageView.isIndicatingActivity = true
|
||||
|
||||
cell.bannerView.configure(for: app)
|
||||
|
||||
let versionDate = Date().relativeDateString(since: latestVersion.date, dateFormatter: self.dateFormatter)
|
||||
let versionDate = Date().relativeDateString(since: latestSupportedVersion.date, dateFormatter: self.dateFormatter)
|
||||
cell.bannerView.subtitleLabel.text = versionDate
|
||||
|
||||
let appName: String
|
||||
@@ -223,7 +232,7 @@ private extension MyAppsViewController
|
||||
appName = app.name
|
||||
}
|
||||
|
||||
cell.bannerView.accessibilityLabel = String(format: NSLocalizedString("%@ %@ update. Released on %@.", comment: ""), appName, latestVersion.version, versionDate)
|
||||
cell.bannerView.accessibilityLabel = String(format: NSLocalizedString("%@ %@ update. Released on %@.", comment: ""), appName, latestSupportedVersion.version, versionDate)
|
||||
|
||||
cell.bannerView.button.isIndicatingActivity = false
|
||||
cell.bannerView.button.addTarget(self, action: #selector(MyAppsViewController.updateApp(_:)), for: .primaryActionTriggered)
|
||||
@@ -327,21 +336,25 @@ private extension MyAppsViewController
|
||||
let currentDate = Date()
|
||||
|
||||
let numberOfDays = installedApp.expirationDate.numberOfCalendarDays(since: currentDate)
|
||||
let numberOfDaysText: String
|
||||
|
||||
if numberOfDays == 1
|
||||
{
|
||||
numberOfDaysText = NSLocalizedString("1 day", comment: "")
|
||||
}
|
||||
else
|
||||
{
|
||||
numberOfDaysText = String(format: NSLocalizedString("%@ days", comment: ""), NSNumber(value: numberOfDays))
|
||||
}
|
||||
let formatter = DateComponentsFormatter()
|
||||
formatter.unitsStyle = .full
|
||||
formatter.includesApproximationPhrase = false
|
||||
formatter.includesTimeRemainingPhrase = false
|
||||
|
||||
formatter.allowedUnits = [.day, .hour, .minute]
|
||||
|
||||
formatter.maximumUnitCount = 1
|
||||
|
||||
|
||||
|
||||
cell.bannerView.button.setTitle(formatter.string(from: currentDate, to: installedApp.expirationDate)?.uppercased(), for: .normal)
|
||||
|
||||
cell.bannerView.button.setTitle(numberOfDaysText.uppercased(), for: .normal)
|
||||
cell.bannerView.button.accessibilityLabel = String(format: NSLocalizedString("Refresh %@", comment: ""), installedApp.name)
|
||||
|
||||
cell.bannerView.accessibilityLabel? += ". " + String(format: NSLocalizedString("Expires in %@", comment: ""), numberOfDaysText)
|
||||
|
||||
formatter.includesTimeRemainingPhrase = true
|
||||
|
||||
cell.bannerView.accessibilityLabel? += ". " + (formatter.string(from: currentDate, to: installedApp.expirationDate) ?? NSLocalizedString("Unknown", comment: "")) + " "
|
||||
|
||||
// Make sure refresh button is correct size.
|
||||
cell.layoutIfNeeded()
|
||||
@@ -522,11 +535,9 @@ private extension MyAppsViewController
|
||||
|
||||
guard !failures.isEmpty else { return }
|
||||
|
||||
let toastView: ToastView
|
||||
|
||||
if let failure = failures.first, results.count == 1
|
||||
{
|
||||
toastView = ToastView(error: failure.value)
|
||||
ToastView(error: failure.value).show(in: self)
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -544,11 +555,10 @@ private extension MyAppsViewController
|
||||
let error = failures.first?.value as NSError?
|
||||
let detailText = error?.localizedFailure ?? error?.localizedFailureReason ?? error?.localizedDescription
|
||||
|
||||
toastView = ToastView(text: localizedText, detailText: detailText)
|
||||
let toastView = ToastView(text: localizedText, detailText: detailText, opensLog: true)
|
||||
toastView.preferredDuration = 4.0
|
||||
toastView.show(in: self)
|
||||
}
|
||||
|
||||
toastView.show(in: self)
|
||||
}
|
||||
|
||||
self.refreshGroup = nil
|
||||
@@ -639,6 +649,8 @@ private extension MyAppsViewController
|
||||
|
||||
@IBAction func refreshAllApps(_ sender: UIBarButtonItem)
|
||||
{
|
||||
guard minimuxerStatus else { return }
|
||||
|
||||
self.isRefreshingAllApps = true
|
||||
self.collectionView.collectionViewLayout.invalidateLayout()
|
||||
|
||||
@@ -682,8 +694,7 @@ private extension MyAppsViewController
|
||||
self.collectionView.reloadItems(at: [indexPath])
|
||||
|
||||
case .failure(let error):
|
||||
let toastView = ToastView(error: error)
|
||||
toastView.show(in: self)
|
||||
ToastView(error: error, opensLog: true).show(in: self)
|
||||
|
||||
self.collectionView.reloadItems(at: [indexPath])
|
||||
|
||||
@@ -701,18 +712,11 @@ private extension MyAppsViewController
|
||||
|
||||
@IBAction func sideloadApp(_ sender: UIBarButtonItem)
|
||||
{
|
||||
let supportedTypes: [String]
|
||||
guard minimuxerStatus else { return }
|
||||
|
||||
let supportedTypes = UTType.types(tag: "ipa", tagClass: .filenameExtension, conformingTo: nil)
|
||||
|
||||
if let types = UTTypeCreateAllIdentifiersForTag(kUTTagClassFilenameExtension, "ipa" as CFString, nil)?.takeRetainedValue()
|
||||
{
|
||||
supportedTypes = (types as NSArray).map { $0 as! String }
|
||||
}
|
||||
else
|
||||
{
|
||||
supportedTypes = ["com.apple.itunes.ipa"] // Declared by the system.
|
||||
}
|
||||
|
||||
let documentPickerViewController = UIDocumentPickerViewController(documentTypes: supportedTypes, in: .import)
|
||||
let documentPickerViewController = UIDocumentPickerViewController(forOpeningContentTypes: supportedTypes, asCopy: true)
|
||||
documentPickerViewController.delegate = self
|
||||
self.present(documentPickerViewController, animated: true, completion: nil)
|
||||
}
|
||||
@@ -891,9 +895,8 @@ private extension MyAppsViewController
|
||||
completion(.failure((OperationError.cancelled)))
|
||||
|
||||
case .failure(let error):
|
||||
let toastView = ToastView(error: error)
|
||||
toastView.show(in: self)
|
||||
|
||||
ToastView(error: error, opensLog: true).show(in: self)
|
||||
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
@@ -1007,13 +1010,14 @@ private extension MyAppsViewController
|
||||
UIApplication.shared.open(installedApp.openAppURL) { success in
|
||||
guard !success else { return }
|
||||
|
||||
let toastView = ToastView(error: OperationError.openAppFailed(name: installedApp.name))
|
||||
toastView.show(in: self)
|
||||
ToastView(error: OperationError.openAppFailed(name: installedApp.name), opensLog: true).show(in: self)
|
||||
}
|
||||
}
|
||||
|
||||
func refresh(_ installedApp: InstalledApp)
|
||||
{
|
||||
guard minimuxerStatus else { return }
|
||||
|
||||
let previousProgress = AppManager.shared.refreshProgress(for: installedApp)
|
||||
guard previousProgress == nil else {
|
||||
previousProgress?.cancel()
|
||||
@@ -1035,6 +1039,8 @@ private extension MyAppsViewController
|
||||
|
||||
func activate(_ installedApp: InstalledApp)
|
||||
{
|
||||
guard minimuxerStatus else { return }
|
||||
|
||||
func finish(_ result: Result<InstalledApp, Error>)
|
||||
{
|
||||
do
|
||||
@@ -1055,8 +1061,7 @@ private extension MyAppsViewController
|
||||
DispatchQueue.main.async {
|
||||
installedApp.isActive = false
|
||||
|
||||
let toastView = ToastView(error: error)
|
||||
toastView.show(in: self)
|
||||
ToastView(error: error, opensLog: true).show(in: self)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1110,7 +1115,8 @@ private extension MyAppsViewController
|
||||
|
||||
func deactivate(_ installedApp: InstalledApp, completionHandler: ((Result<InstalledApp, Error>) -> Void)? = nil)
|
||||
{
|
||||
guard installedApp.isActive else { return }
|
||||
guard installedApp.isActive, minimuxerStatus else { return }
|
||||
|
||||
installedApp.isActive = false
|
||||
|
||||
AppManager.shared.deactivate(installedApp, presentingViewController: self) { (result) in
|
||||
@@ -1123,13 +1129,12 @@ private extension MyAppsViewController
|
||||
}
|
||||
catch
|
||||
{
|
||||
print("Failed to activate app:", error)
|
||||
print("Failed to deactivate app:", error)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
installedApp.isActive = true
|
||||
|
||||
let toastView = ToastView(error: error)
|
||||
toastView.show(in: self)
|
||||
ToastView(error: error, opensLog: true).show(in: self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1160,8 +1165,7 @@ private extension MyAppsViewController
|
||||
case .success: break
|
||||
case .failure(let error):
|
||||
DispatchQueue.main.async {
|
||||
let toastView = ToastView(error: error)
|
||||
toastView.show(in: self)
|
||||
ToastView(error: error, opensLog: true).show(in: self)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1172,6 +1176,8 @@ private extension MyAppsViewController
|
||||
|
||||
func backup(_ installedApp: InstalledApp)
|
||||
{
|
||||
guard minimuxerStatus else { return }
|
||||
|
||||
let title = NSLocalizedString("Start Backup?", comment: "")
|
||||
let message = NSLocalizedString("This will replace any previous backups. Please leave SideStore open until the backup is complete.", comment: "")
|
||||
|
||||
@@ -1193,9 +1199,8 @@ private extension MyAppsViewController
|
||||
print("Failed to back up app:", error)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
let toastView = ToastView(error: error)
|
||||
toastView.show(in: self)
|
||||
|
||||
ToastView(error: error, opensLog: true).show(in: self)
|
||||
|
||||
self.collectionView.reloadSections([Section.activeApps.rawValue, Section.inactiveApps.rawValue])
|
||||
}
|
||||
}
|
||||
@@ -1211,6 +1216,8 @@ private extension MyAppsViewController
|
||||
|
||||
func restore(_ installedApp: InstalledApp)
|
||||
{
|
||||
guard minimuxerStatus else { return }
|
||||
|
||||
let message = String(format: NSLocalizedString("This will replace all data you currently have in %@.", comment: ""), installedApp.name)
|
||||
let alertController = UIAlertController(title: NSLocalizedString("Are you sure you want to restore this backup?", comment: ""), message: message, preferredStyle: .actionSheet)
|
||||
alertController.addAction(.cancel)
|
||||
@@ -1228,8 +1235,7 @@ private extension MyAppsViewController
|
||||
print("Failed to restore app:", error)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
let toastView = ToastView(error: error)
|
||||
toastView.show(in: self)
|
||||
ToastView(error: error, opensLog: true).show(in: self)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1246,8 +1252,11 @@ private extension MyAppsViewController
|
||||
{
|
||||
guard let backupURL = FileManager.default.backupDirectoryURL(for: installedApp) else { return }
|
||||
|
||||
let documentPicker = UIDocumentPickerViewController(url: backupURL, in: .exportToService)
|
||||
documentPicker.delegate = self
|
||||
let documentPicker = UIDocumentPickerViewController(forExporting: [backupURL], asCopy: true)
|
||||
|
||||
// Don't set delegate to avoid conflicting with import callbacks.
|
||||
// documentPicker.delegate = self
|
||||
|
||||
self.present(documentPicker, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
@@ -1301,8 +1310,7 @@ private extension MyAppsViewController
|
||||
print("Failed to change app icon.", error)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
let toastView = ToastView(error: error)
|
||||
toastView.show(in: self)
|
||||
ToastView(error: error, opensLog: true).show(in: self)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1311,14 +1319,22 @@ private extension MyAppsViewController
|
||||
@available(iOS 14, *)
|
||||
func enableJIT(for installedApp: InstalledApp)
|
||||
{
|
||||
guard minimuxerStatus else { return }
|
||||
|
||||
if #available(iOS 17, *) {
|
||||
ToastView(error: (OperationError.tooNewError as NSError).withLocalizedTitle("No iOS 17 On Device JIT!"), opensLog: true).show(in: self)
|
||||
AppManager.shared.log(OperationError.tooNewError, operation: .enableJIT, app: installedApp)
|
||||
return
|
||||
}
|
||||
|
||||
AppManager.shared.enableJIT(for: installedApp) { result in
|
||||
DispatchQueue.main.async {
|
||||
switch result
|
||||
{
|
||||
case .success: break
|
||||
case .failure(let error):
|
||||
let toastView = ToastView(error: error)
|
||||
toastView.show(in: self)
|
||||
ToastView(error: error, opensLog: true).show(in: self)
|
||||
AppManager.shared.log(error, operation: .enableJIT, app: installedApp)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1465,7 +1481,7 @@ extension MyAppsViewController
|
||||
let registeredAppIDs = team.appIDs.count
|
||||
|
||||
let maximumAppIDCount = 10
|
||||
let remainingAppIDs = max(maximumAppIDCount - registeredAppIDs, 0)
|
||||
let remainingAppIDs = maximumAppIDCount - registeredAppIDs
|
||||
|
||||
if remainingAppIDs == 1
|
||||
{
|
||||
@@ -1476,7 +1492,7 @@ extension MyAppsViewController
|
||||
footerView.textLabel.text = String(format: NSLocalizedString("%@ App IDs Remaining", comment: ""), NSNumber(value: remainingAppIDs))
|
||||
}
|
||||
|
||||
footerView.textLabel.isHidden = false
|
||||
footerView.textLabel.isHidden = remainingAppIDs < 0
|
||||
|
||||
case .individual, .organization, .unknown: footerView.textLabel.isHidden = true
|
||||
@unknown default: break
|
||||
@@ -2050,15 +2066,8 @@ extension MyAppsViewController: UIDocumentPickerDelegate
|
||||
{
|
||||
guard let fileURL = urls.first else { return }
|
||||
|
||||
switch controller.documentPickerMode
|
||||
{
|
||||
case .import, .open:
|
||||
self.sideloadApp(at: fileURL) { (result) in
|
||||
print("Sideloaded app at \(fileURL) with result:", result)
|
||||
}
|
||||
|
||||
case .exportToService, .moveToService: break
|
||||
@unknown default: break
|
||||
self.sideloadApp(at: fileURL) { (result) in
|
||||
print("Sideloaded app at \(fileURL) with result:", result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,9 +313,8 @@ private extension NewsViewController
|
||||
{
|
||||
case .failure(OperationError.cancelled): break // Ignore
|
||||
case .failure(let error):
|
||||
let toastView = ToastView(error: error)
|
||||
toastView.show(in: self)
|
||||
|
||||
ToastView(error: error, opensLog: true).show(in: self)
|
||||
|
||||
case .success: print("Installed app:", storeApp.bundleIdentifier)
|
||||
}
|
||||
|
||||
@@ -391,9 +390,9 @@ extension NewsViewController
|
||||
let progress = AppManager.shared.installationProgress(for: storeApp)
|
||||
footerView.bannerView.button.progress = progress
|
||||
|
||||
if let versionDate = storeApp.latestVersion?.date, versionDate > Date()
|
||||
if let versionDate = storeApp.latestSupportedVersion?.date, versionDate > Date()
|
||||
{
|
||||
footerView.bannerView.button.countdownDate = storeApp.versionDate
|
||||
footerView.bannerView.button.countdownDate = versionDate
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -426,6 +425,10 @@ extension NewsViewController: UICollectionViewDelegateFlowLayout
|
||||
return previousSize
|
||||
}
|
||||
|
||||
// Take layout margins into account.
|
||||
self.prototypeCell.layoutMargins.left = self.view.layoutMargins.left
|
||||
self.prototypeCell.layoutMargins.right = self.view.layoutMargins.right
|
||||
|
||||
let widthConstraint = self.prototypeCell.contentView.widthAnchor.constraint(equalToConstant: collectionView.bounds.width)
|
||||
NSLayoutConstraint.activate([widthConstraint])
|
||||
defer { NSLayoutConstraint.deactivate([widthConstraint]) }
|
||||
|
||||
@@ -12,8 +12,10 @@ import Network
|
||||
|
||||
import AltStoreCore
|
||||
import AltSign
|
||||
import minimuxer
|
||||
|
||||
enum AuthenticationError: LocalizedError
|
||||
typealias AuthenticationError = AuthenticationErrorCode.Error
|
||||
enum AuthenticationErrorCode: Int, ALTErrorEnum, CaseIterable
|
||||
{
|
||||
case noTeam
|
||||
case noCertificate
|
||||
@@ -22,11 +24,11 @@ enum AuthenticationError: LocalizedError
|
||||
case missingPrivateKey
|
||||
case missingCertificate
|
||||
|
||||
var errorDescription: String? {
|
||||
var errorFailureReason: String {
|
||||
switch self {
|
||||
case .noTeam: return NSLocalizedString("Developer team could not be found.", comment: "")
|
||||
case .noTeam: return NSLocalizedString("Your Apple ID has no developer teams?", comment: "")
|
||||
case .noCertificate: return NSLocalizedString("The developer certificate could not be found.", comment: "")
|
||||
case .teamSelectorError: return NSLocalizedString("Error presenting team selector view.", comment: "")
|
||||
case .noCertificate: return NSLocalizedString("Developer certificate could not be found.", comment: "")
|
||||
case .missingPrivateKey: return NSLocalizedString("The certificate's private key could not be found.", comment: "")
|
||||
case .missingCertificate: return NSLocalizedString("The certificate could not be found.", comment: "")
|
||||
}
|
||||
@@ -212,8 +214,8 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A
|
||||
guard
|
||||
let account = Account.first(satisfying: NSPredicate(format: "%K == %@", #keyPath(Account.identifier), altTeam.account.identifier), in: context),
|
||||
let team = Team.first(satisfying: NSPredicate(format: "%K == %@", #keyPath(Team.identifier), altTeam.identifier), in: context)
|
||||
else { throw AuthenticationError.noTeam }
|
||||
|
||||
else { throw AuthenticationError(.noTeam) }
|
||||
|
||||
// Account
|
||||
account.isActiveAccount = true
|
||||
|
||||
@@ -431,7 +433,7 @@ private extension AuthenticationOperation
|
||||
}
|
||||
else
|
||||
{
|
||||
completionHandler(.failure(error ?? OperationError.unknown))
|
||||
completionHandler(.failure(error ?? OperationError.unknown()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -448,7 +450,7 @@ private extension AuthenticationOperation
|
||||
if let team = teams.first {
|
||||
return completionHandler(.success(team))
|
||||
} else {
|
||||
return completionHandler(.failure(AuthenticationError.noTeam))
|
||||
return completionHandler(.failure(AuthenticationError(.noTeam)))
|
||||
}
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
@@ -459,7 +461,7 @@ private extension AuthenticationOperation
|
||||
|
||||
if !self.present(selectTeamViewController)
|
||||
{
|
||||
return completionHandler(.failure(AuthenticationError.noTeam))
|
||||
return completionHandler(.failure(AuthenticationError(.noTeam)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -488,20 +490,20 @@ private extension AuthenticationOperation
|
||||
{
|
||||
func requestCertificate()
|
||||
{
|
||||
let machineName = "AltStore - " + UIDevice.current.name
|
||||
let machineName = "SideStore - " + UIDevice.current.name
|
||||
ALTAppleAPI.shared.addCertificate(machineName: machineName, to: team, session: session) { (certificate, error) in
|
||||
do
|
||||
{
|
||||
let certificate = try Result(certificate, error).get()
|
||||
guard let privateKey = certificate.privateKey else { throw AuthenticationError.missingPrivateKey }
|
||||
|
||||
guard let privateKey = certificate.privateKey else { throw AuthenticationError(.missingPrivateKey) }
|
||||
|
||||
ALTAppleAPI.shared.fetchCertificates(for: team, session: session) { (certificates, error) in
|
||||
do
|
||||
{
|
||||
let certificates = try Result(certificates, error).get()
|
||||
|
||||
guard let certificate = certificates.first(where: { $0.serialNumber == certificate.serialNumber }) else {
|
||||
throw AuthenticationError.missingCertificate
|
||||
throw AuthenticationError(.missingCertificate)
|
||||
}
|
||||
|
||||
certificate.privateKey = privateKey
|
||||
@@ -522,7 +524,7 @@ private extension AuthenticationOperation
|
||||
|
||||
func replaceCertificate(from certificates: [ALTCertificate])
|
||||
{
|
||||
guard let certificate = certificates.first(where: { $0.machineName?.starts(with: "AltStore") == true }) ?? certificates.first else { return completionHandler(.failure(AuthenticationError.noCertificate)) }
|
||||
guard let certificate = certificates.first(where: { $0.machineName?.starts(with: "SideStore") == true }) ?? certificates.first else { return completionHandler(.failure(OperationError.notAuthenticated)) }
|
||||
|
||||
ALTAppleAPI.shared.revoke(certificate, for: team, session: session) { (success, error) in
|
||||
if let error = error, !success
|
||||
@@ -593,7 +595,7 @@ private extension AuthenticationOperation
|
||||
|
||||
func registerCurrentDevice(for team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTDevice, Error>) -> Void)
|
||||
{
|
||||
guard let udid = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.deviceID) as? String else {
|
||||
guard let udid = fetch_udid()?.toString() else {
|
||||
return completionHandler(.failure(OperationError.unknownUDID))
|
||||
}
|
||||
|
||||
|
||||
@@ -13,11 +13,12 @@ import AltStoreCore
|
||||
import EmotionalDamage
|
||||
import minimuxer
|
||||
|
||||
enum RefreshError: LocalizedError
|
||||
typealias RefreshError = RefreshErrorCode.Error
|
||||
enum RefreshErrorCode: Int, ALTErrorEnum, CaseIterable
|
||||
{
|
||||
case noInstalledApps
|
||||
|
||||
var errorDescription: String? {
|
||||
var errorFailureReason: String {
|
||||
switch self
|
||||
{
|
||||
case .noInstalledApps: return NSLocalizedString("No active apps require refreshing.", comment: "")
|
||||
@@ -94,7 +95,7 @@ final class BackgroundRefreshAppsOperation: ResultOperation<[String: Result<Inst
|
||||
super.main()
|
||||
|
||||
guard !self.installedApps.isEmpty else {
|
||||
self.finish(.failure(RefreshError.noInstalledApps))
|
||||
self.finish(.failure(RefreshError(.noInstalledApps)))
|
||||
return
|
||||
}
|
||||
start_em_proxy(bind_addr: Consts.Proxy.serverURL)
|
||||
@@ -105,8 +106,12 @@ final class BackgroundRefreshAppsOperation: ResultOperation<[String: Result<Inst
|
||||
} catch {
|
||||
self.finish(.failure(error))
|
||||
}
|
||||
start_auto_mounter(documentsDirectory)
|
||||
|
||||
if #available(iOS 17, *) {
|
||||
// TODO: iOS 17 and above have a new JIT implementation that is completely broken in SideStore :(
|
||||
} else {
|
||||
start_auto_mounter(documentsDirectory)
|
||||
}
|
||||
|
||||
self.managedObjectContext.perform {
|
||||
print("Apps to refresh:", self.installedApps.map(\.bundleIdentifier))
|
||||
|
||||
@@ -198,7 +203,7 @@ private extension BackgroundRefreshAppsOperation
|
||||
|
||||
let content = UNMutableNotificationContent()
|
||||
|
||||
var shouldPresentAlert = false
|
||||
var shouldPresentAlert = true
|
||||
|
||||
do
|
||||
{
|
||||
@@ -214,20 +219,18 @@ private extension BackgroundRefreshAppsOperation
|
||||
content.title = NSLocalizedString("Refreshed Apps", comment: "")
|
||||
content.body = NSLocalizedString("All apps have been refreshed.", comment: "")
|
||||
}
|
||||
catch RefreshError.noInstalledApps
|
||||
catch ~OperationError.Code.noWiFi, ~RefreshErrorCode.noInstalledApps
|
||||
{
|
||||
shouldPresentAlert = false
|
||||
}
|
||||
catch
|
||||
{
|
||||
print("Failed to refresh apps in background.", error)
|
||||
|
||||
|
||||
content.title = NSLocalizedString("Failed to Refresh Apps", comment: "")
|
||||
content.body = error.localizedDescription
|
||||
|
||||
shouldPresentAlert = false
|
||||
}
|
||||
|
||||
|
||||
if shouldPresentAlert
|
||||
{
|
||||
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: delay + 1, repeats: false)
|
||||
|
||||
@@ -29,6 +29,9 @@ class BackupAppOperation: ResultOperation<Void>
|
||||
private var appName: String?
|
||||
private var timeoutTimer: Timer?
|
||||
|
||||
private weak var applicationWillReturnObserver: NSObjectProtocol?
|
||||
private weak var backupResponseObserver: NSObjectProtocol?
|
||||
|
||||
init(action: Action, context: InstallAppOperationContext)
|
||||
{
|
||||
self.action = action
|
||||
@@ -43,10 +46,7 @@ class BackupAppOperation: ResultOperation<Void>
|
||||
|
||||
do
|
||||
{
|
||||
if let error = self.context.error
|
||||
{
|
||||
throw error
|
||||
}
|
||||
if let error = self.context.error { throw error }
|
||||
|
||||
guard let installedApp = self.context.installedApp, let context = installedApp.managedObjectContext else { throw OperationError.invalidParameters }
|
||||
context.perform {
|
||||
@@ -55,13 +55,15 @@ class BackupAppOperation: ResultOperation<Void>
|
||||
let appName = installedApp.name
|
||||
self.appName = appName
|
||||
|
||||
guard let altstoreApp = InstalledApp.fetchAltStore(in: context) else { throw OperationError.appNotFound }
|
||||
guard let altstoreApp = InstalledApp.fetchAltStore(in: context) else {
|
||||
throw OperationError.appNotFound(name: appName)
|
||||
}
|
||||
let altstoreOpenURL = altstoreApp.openAppURL
|
||||
|
||||
|
||||
var returnURLComponents = URLComponents(url: altstoreOpenURL, resolvingAgainstBaseURL: false)
|
||||
returnURLComponents?.host = "appBackupResponse"
|
||||
guard let returnURL = returnURLComponents?.url else { throw OperationError.openAppFailed(name: appName) }
|
||||
|
||||
|
||||
var openURLComponents = URLComponents()
|
||||
openURLComponents.scheme = installedApp.openAppURL.scheme
|
||||
openURLComponents.host = self.action.rawValue
|
||||
@@ -153,8 +155,11 @@ private extension BackupAppOperation
|
||||
{
|
||||
func registerObservers()
|
||||
{
|
||||
var applicationWillReturnObserver: NSObjectProtocol!
|
||||
applicationWillReturnObserver = NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: .main) { [weak self] (notification) in
|
||||
self.applicationWillReturnObserver = NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: .main) { [weak self] (notification) in
|
||||
defer {
|
||||
self?.applicationWillReturnObserver.map { NotificationCenter.default.removeObserver($0) }
|
||||
}
|
||||
|
||||
guard let self = self, !self.isFinished else { return }
|
||||
|
||||
self.timeoutTimer = Timer.scheduledTimer(withTimeInterval: 5, repeats: false) { [weak self] (timer) in
|
||||
@@ -166,18 +171,17 @@ private extension BackupAppOperation
|
||||
self.finish(.failure(OperationError.timedOut))
|
||||
}
|
||||
}
|
||||
|
||||
NotificationCenter.default.removeObserver(applicationWillReturnObserver!)
|
||||
}
|
||||
|
||||
var backupResponseObserver: NSObjectProtocol!
|
||||
backupResponseObserver = NotificationCenter.default.addObserver(forName: AppDelegate.appBackupDidFinish, object: nil, queue: nil) { [weak self] (notification) in
|
||||
self.backupResponseObserver = NotificationCenter.default.addObserver(forName: AppDelegate.appBackupDidFinish, object: nil, queue: nil) { [weak self] (notification) in
|
||||
defer {
|
||||
self?.backupResponseObserver.map { NotificationCenter.default.removeObserver($0) }
|
||||
}
|
||||
|
||||
self?.timeoutTimer?.invalidate()
|
||||
|
||||
let result = notification.userInfo?[AppDelegate.appBackupResultKey] as? Result<Void, Error> ?? .failure(OperationError.unknownResult)
|
||||
self?.finish(result)
|
||||
|
||||
NotificationCenter.default.removeObserver(backupResponseObserver!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
208
AltStore/Operations/ClearAppCacheOperation.swift
Normal file
@@ -0,0 +1,208 @@
|
||||
//
|
||||
// ClearAppCacheOperation.swift
|
||||
// AltStore
|
||||
//
|
||||
// Created by Riley Testut on 9/27/22.
|
||||
// Copyright © 2022 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import AltStoreCore
|
||||
/*
|
||||
struct BatchError: ALTLocalizedError
|
||||
{
|
||||
|
||||
enum Code: Int, ALTErrorCode
|
||||
{
|
||||
typealias Error = BatchError
|
||||
|
||||
case batchError
|
||||
}
|
||||
|
||||
var code: Code = .batchError
|
||||
var underlyingErrors: [Error]
|
||||
|
||||
var errorTitle: String?
|
||||
var errorFailure: String?
|
||||
|
||||
init(errors: [Error])
|
||||
{
|
||||
self.underlyingErrors = errors
|
||||
}
|
||||
|
||||
var errorFailureReason: String {
|
||||
guard !self.underlyingErrors.isEmpty else { return NSLocalizedString("An unknown error occured.", comment: "") }
|
||||
|
||||
let errorMessages = self.underlyingErrors.map { $0.localizedDescription }
|
||||
|
||||
let message = errorMessages.joined(separator: "\n\n")
|
||||
return message
|
||||
}
|
||||
}
|
||||
*/
|
||||
@objc(ClearAppCacheOperation)
|
||||
class ClearAppCacheOperation: ResultOperation<Void>
|
||||
{
|
||||
private let coordinator = NSFileCoordinator()
|
||||
private let coordinatorQueue = OperationQueue()
|
||||
|
||||
override init()
|
||||
{
|
||||
self.coordinatorQueue.name = "AltStore - ClearAppCacheOperation Queue"
|
||||
}
|
||||
|
||||
override func main()
|
||||
{
|
||||
super.main()
|
||||
|
||||
var allErrors = [Error]()
|
||||
|
||||
self.clearTemporaryDirectory { result in
|
||||
switch result
|
||||
{
|
||||
//case .failure(let batchError as BatchError): allErrors.append(contentsOf: batchError.underlyingErrors)
|
||||
case .failure(let error): allErrors.append(error)
|
||||
case .success: break
|
||||
}
|
||||
|
||||
self.removeUninstalledAppBackupDirectories { result in
|
||||
switch result
|
||||
{
|
||||
//case .failure(let batchError as BatchError): allErrors.append(contentsOf: batchError.underlyingErrors)
|
||||
case .failure(let error): allErrors.append(error)
|
||||
case .success: break
|
||||
}
|
||||
|
||||
if allErrors.isEmpty
|
||||
{
|
||||
self.finish(.success(()))
|
||||
}
|
||||
else
|
||||
{
|
||||
self.finish(.failure(OperationError.cacheClearError(errors: allErrors.map({ error in
|
||||
return error.localizedDescription
|
||||
}))))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension ClearAppCacheOperation
|
||||
{
|
||||
func clearTemporaryDirectory(completion: @escaping (Result<Void, Error>) -> Void)
|
||||
{
|
||||
let intent = NSFileAccessIntent.writingIntent(with: FileManager.default.temporaryDirectory, options: [.forDeleting])
|
||||
self.coordinator.coordinate(with: [intent], queue: self.coordinatorQueue) { (error) in
|
||||
do
|
||||
{
|
||||
if let error
|
||||
{
|
||||
throw error
|
||||
}
|
||||
|
||||
let fileURLs = try FileManager.default.contentsOfDirectory(at: intent.url,
|
||||
includingPropertiesForKeys: [],
|
||||
options: [.skipsSubdirectoryDescendants, .skipsHiddenFiles])
|
||||
var errors = [Error]()
|
||||
|
||||
for fileURL in fileURLs
|
||||
{
|
||||
do
|
||||
{
|
||||
print("[ALTLog] Removing item from temporary directory:", fileURL.lastPathComponent)
|
||||
try FileManager.default.removeItem(at: fileURL)
|
||||
}
|
||||
catch
|
||||
{
|
||||
print("[ALTLog] Failed to remove \(fileURL.lastPathComponent) from temporary directory.", error)
|
||||
errors.append(error)
|
||||
}
|
||||
}
|
||||
|
||||
if !errors.isEmpty
|
||||
{
|
||||
completion(.failure(OperationError.cacheClearError(errors: errors.map({ error in
|
||||
return error.localizedDescription
|
||||
}))))
|
||||
}
|
||||
else
|
||||
{
|
||||
completion(.success(()))
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func removeUninstalledAppBackupDirectories(completion: @escaping (Result<Void, Error>) -> Void)
|
||||
{
|
||||
guard let backupsDirectory = FileManager.default.appBackupsDirectory else { return completion(.failure(OperationError.missingAppGroup)) }
|
||||
|
||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { context in
|
||||
let installedAppBundleIDs = Set(InstalledApp.all(in: context).map { $0.bundleIdentifier })
|
||||
|
||||
let intent = NSFileAccessIntent.writingIntent(with: backupsDirectory, options: [.forDeleting])
|
||||
self.coordinator.coordinate(with: [intent], queue: self.coordinatorQueue) { (error) in
|
||||
do
|
||||
{
|
||||
if let error
|
||||
{
|
||||
throw error
|
||||
}
|
||||
|
||||
var isDirectory: ObjCBool = false
|
||||
guard FileManager.default.fileExists(atPath: intent.url.path, isDirectory: &isDirectory), isDirectory.boolValue else {
|
||||
completion(.success(()))
|
||||
return
|
||||
}
|
||||
|
||||
let fileURLs = try FileManager.default.contentsOfDirectory(at: intent.url,
|
||||
includingPropertiesForKeys: [.isDirectoryKey, .nameKey],
|
||||
options: [.skipsSubdirectoryDescendants, .skipsHiddenFiles])
|
||||
var errors = [Error]()
|
||||
|
||||
|
||||
for backupDirectory in fileURLs
|
||||
{
|
||||
do
|
||||
{
|
||||
let resourceValues = try backupDirectory.resourceValues(forKeys: [.isDirectoryKey, .nameKey])
|
||||
guard let isDirectory = resourceValues.isDirectory, let bundleID = resourceValues.name else { continue }
|
||||
|
||||
if isDirectory && !installedAppBundleIDs.contains(bundleID) && !AppManager.shared.isActivelyManagingApp(withBundleID: bundleID)
|
||||
{
|
||||
print("[ALTLog] Removing backup directory for uninstalled app:", bundleID)
|
||||
try FileManager.default.removeItem(at: backupDirectory)
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
print("[ALTLog] Failed to remove app backup directory:", error)
|
||||
errors.append(error)
|
||||
}
|
||||
}
|
||||
|
||||
if !errors.isEmpty
|
||||
{
|
||||
completion(.failure(OperationError.cacheClearError(errors: errors.map({ error in
|
||||
return error.localizedDescription
|
||||
}))))
|
||||
}
|
||||
else
|
||||
{
|
||||
completion(.success(()))
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
print("[ALTLog] Failed to remove app backup directory:", error)
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,11 +31,7 @@ final class DeactivateAppOperation: ResultOperation<InstalledApp>
|
||||
{
|
||||
super.main()
|
||||
|
||||
if let error = self.context.error
|
||||
{
|
||||
self.finish(.failure(error))
|
||||
return
|
||||
}
|
||||
if let error = self.context.error { return self.finish(.failure(error)) }
|
||||
|
||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
||||
let installedApp = context.object(with: self.app.objectID) as! InstalledApp
|
||||
@@ -45,14 +41,14 @@ final class DeactivateAppOperation: ResultOperation<InstalledApp>
|
||||
for profile in allIdentifiers {
|
||||
do {
|
||||
try remove_provisioning_profile(profile)
|
||||
self.progress.completedUnitCount += 1
|
||||
installedApp.isActive = false
|
||||
self.finish(.success(installedApp))
|
||||
break
|
||||
} catch {
|
||||
return self.finish(.failure(error))
|
||||
self.finish(.failure(error))
|
||||
}
|
||||
}
|
||||
|
||||
self.progress.completedUnitCount += 1
|
||||
installedApp.isActive = false
|
||||
self.finish(.success(installedApp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,66 +12,108 @@ import Roxas
|
||||
import AltStoreCore
|
||||
import AltSign
|
||||
|
||||
private extension DownloadAppOperation
|
||||
{
|
||||
struct DependencyError: ALTLocalizedError
|
||||
{
|
||||
let dependency: Dependency
|
||||
let error: Error
|
||||
|
||||
var failure: String? {
|
||||
return String(format: NSLocalizedString("Could not download “%@”.", comment: ""), self.dependency.preferredFilename)
|
||||
}
|
||||
|
||||
var underlyingError: Error? {
|
||||
return self.error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc(DownloadAppOperation)
|
||||
final class DownloadAppOperation: ResultOperation<ALTApplication>
|
||||
{
|
||||
let app: AppProtocol
|
||||
let context: AppOperationContext
|
||||
|
||||
|
||||
private let appName: String
|
||||
private let bundleIdentifier: String
|
||||
private var sourceURL: URL?
|
||||
private let destinationURL: URL
|
||||
|
||||
|
||||
private let session = URLSession(configuration: .default)
|
||||
private let temporaryDirectory = FileManager.default.uniqueTemporaryURL()
|
||||
|
||||
|
||||
init(app: AppProtocol, destinationURL: URL, context: AppOperationContext)
|
||||
{
|
||||
self.app = app
|
||||
self.context = context
|
||||
|
||||
|
||||
self.appName = app.name
|
||||
self.bundleIdentifier = app.bundleIdentifier
|
||||
self.sourceURL = app.url
|
||||
self.destinationURL = destinationURL
|
||||
|
||||
|
||||
super.init()
|
||||
|
||||
|
||||
// App = 3, Dependencies = 1
|
||||
self.progress.totalUnitCount = 4
|
||||
}
|
||||
|
||||
|
||||
override func main()
|
||||
{
|
||||
super.main()
|
||||
|
||||
|
||||
if let error = self.context.error
|
||||
{
|
||||
self.finish(.failure(error))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
print("Downloading App:", self.bundleIdentifier)
|
||||
|
||||
guard let sourceURL = self.sourceURL else { return self.finish(.failure(OperationError.appNotFound)) }
|
||||
|
||||
self.downloadApp(from: sourceURL) { result in
|
||||
|
||||
self.localizedFailure = String(format: NSLocalizedString("%@ could not be downloaded.", comment: ""), self.appName)
|
||||
|
||||
guard let storeApp = self.app as? StoreApp else { return self.download(self.app) }
|
||||
storeApp.managedObjectContext?.perform {
|
||||
do {
|
||||
let latestVersion = try self.verify(storeApp)
|
||||
self.download(latestVersion)
|
||||
} catch let error as VerificationError where error.code == .iOSVersionNotSupported {
|
||||
guard let presentingViewController = self.context.presentingViewController,
|
||||
let latestSupportedVersion = storeApp.latestSupportedVersion,
|
||||
case let version = latestSupportedVersion.version,
|
||||
version != storeApp.installedApp?.version else {
|
||||
return self.finish(.failure(error))
|
||||
}
|
||||
let title = NSLocalizedString("Unsupported iOS Version", comment: "")
|
||||
let message = error.localizedDescription + "\n\n" + NSLocalizedString("Would you like to download the last version compatible with this device instead?", comment: "")
|
||||
|
||||
DispatchQueue.main.async {
|
||||
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
|
||||
alertController.addAction(UIAlertAction(title: UIAlertAction.cancel.title, style: UIAlertAction.cancel.style) { _ in
|
||||
self.finish(.failure(OperationError.cancelled))
|
||||
})
|
||||
alertController.addAction(UIAlertAction(title: String(format: NSLocalizedString("Download %@ %@", comment: ""), self.appName, version), style: .default) { _ in
|
||||
self.download(latestSupportedVersion)
|
||||
})
|
||||
presentingViewController.present(alertController, animated: true)
|
||||
}
|
||||
} catch {
|
||||
self.finish(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func finish(_ result: Result<ALTApplication, any Error>) {
|
||||
do {
|
||||
try FileManager.default.removeItem(at: self.temporaryDirectory)
|
||||
} catch {
|
||||
print("Failed to remove DownloadAppOperation temporary directory: \(self.temporaryDirectory).", error)
|
||||
}
|
||||
super.finish(result)
|
||||
}
|
||||
}
|
||||
|
||||
private extension DownloadAppOperation {
|
||||
func verify(_ storeApp: StoreApp) throws -> AppVersion {
|
||||
guard let version = storeApp.latestAvailableVersion else {
|
||||
let failureReason = String(format: NSLocalizedString("The latest version of %@ could not be determined.", comment: ""), self.appName)
|
||||
throw OperationError.unknown(failureReason: failureReason)
|
||||
}
|
||||
if let minOSVersion = version.minOSVersion, !ProcessInfo.processInfo.isOperatingSystemAtLeast(minOSVersion) {
|
||||
throw VerificationError.iOSVersionNotSupported(app: storeApp, requiredOSVersion: minOSVersion)
|
||||
} else if let maxOSVersion = version.maxOSVersion, ProcessInfo.processInfo.operatingSystemVersion > maxOSVersion {
|
||||
throw VerificationError.iOSVersionNotSupported(app: storeApp, requiredOSVersion: maxOSVersion)
|
||||
}
|
||||
|
||||
return version
|
||||
}
|
||||
|
||||
func download(@Managed _ app: AppProtocol) {
|
||||
guard let sourceURL = $app.url else { return self.finish(.failure(OperationError.appNotFound(name: self.appName))) }
|
||||
|
||||
self.downloadIPA(from: sourceURL) { result in
|
||||
do
|
||||
{
|
||||
let application = try result.get()
|
||||
@@ -112,24 +154,7 @@ final class DownloadAppOperation: ResultOperation<ALTApplication>
|
||||
}
|
||||
}
|
||||
|
||||
override func finish(_ result: Result<ALTApplication, Error>)
|
||||
{
|
||||
do
|
||||
{
|
||||
try FileManager.default.removeItem(at: self.temporaryDirectory)
|
||||
}
|
||||
catch
|
||||
{
|
||||
print("Failed to remove DownloadAppOperation temporary directory: \(self.temporaryDirectory).", error)
|
||||
}
|
||||
|
||||
super.finish(result)
|
||||
}
|
||||
}
|
||||
|
||||
private extension DownloadAppOperation
|
||||
{
|
||||
func downloadApp(from sourceURL: URL, completionHandler: @escaping (Result<ALTApplication, Error>) -> Void)
|
||||
func downloadIPA(from sourceURL: URL, completionHandler: @escaping (Result<ALTApplication, Error>) -> Void)
|
||||
{
|
||||
func finishOperation(_ result: Result<URL, Error>)
|
||||
{
|
||||
@@ -138,8 +163,8 @@ private extension DownloadAppOperation
|
||||
let fileURL = try result.get()
|
||||
|
||||
var isDirectory: ObjCBool = false
|
||||
guard FileManager.default.fileExists(atPath: fileURL.path, isDirectory: &isDirectory) else { throw OperationError.appNotFound }
|
||||
|
||||
guard FileManager.default.fileExists(atPath: fileURL.path, isDirectory: &isDirectory) else { throw OperationError.appNotFound(name: self.appName) }
|
||||
|
||||
try FileManager.default.createDirectory(at: self.temporaryDirectory, withIntermediateDirectories: true, attributes: nil)
|
||||
|
||||
let appBundleURL: URL
|
||||
@@ -178,6 +203,9 @@ private extension DownloadAppOperation
|
||||
let downloadTask = self.session.downloadTask(with: sourceURL) { (fileURL, response, error) in
|
||||
do
|
||||
{
|
||||
if let response = response as? HTTPURLResponse {
|
||||
guard response.statusCode != 404 else { throw CocoaError(.fileNoSuchFile, userInfo: [NSURLErrorKey: sourceURL]) }
|
||||
}
|
||||
let (fileURL, _) = try Result((fileURL, response), error).get()
|
||||
finishOperation(.success(fileURL))
|
||||
|
||||
@@ -252,7 +280,7 @@ private extension DownloadAppOperation
|
||||
let altstorePlist = try PropertyListDecoder().decode(AltStorePlist.self, from: data)
|
||||
|
||||
var dependencyURLs = Set<URL>()
|
||||
var dependencyError: DependencyError?
|
||||
var dependencyError: Error?
|
||||
|
||||
let dispatchGroup = DispatchGroup()
|
||||
let progress = Progress(totalUnitCount: Int64(altstorePlist.dependencies.count), parent: self.progress, pendingUnitCount: 1)
|
||||
@@ -285,7 +313,7 @@ private extension DownloadAppOperation
|
||||
}
|
||||
catch let error as DecodingError
|
||||
{
|
||||
let nsError = (error as NSError).withLocalizedFailure(String(format: NSLocalizedString("Could not download dependencies for %@.", comment: ""), application.name))
|
||||
let nsError = (error as NSError).withLocalizedFailure(String(format: NSLocalizedString("Could not determine dependencies for %@.", comment: ""), application.name))
|
||||
completionHandler(.failure(nsError))
|
||||
}
|
||||
catch
|
||||
@@ -294,7 +322,7 @@ private extension DownloadAppOperation
|
||||
}
|
||||
}
|
||||
|
||||
func download(_ dependency: Dependency, for application: ALTApplication, progress: Progress, completionHandler: @escaping (Result<URL, DependencyError>) -> Void)
|
||||
func download(_ dependency: Dependency, for application: ALTApplication, progress: Progress, completionHandler: @escaping (Result<URL, Error>) -> Void)
|
||||
{
|
||||
let downloadTask = self.session.downloadTask(with: dependency.downloadURL) { (fileURL, response, error) in
|
||||
do
|
||||
@@ -315,9 +343,10 @@ private extension DownloadAppOperation
|
||||
|
||||
completionHandler(.success(destinationURL))
|
||||
}
|
||||
catch
|
||||
catch let error as NSError
|
||||
{
|
||||
completionHandler(.failure(DependencyError(dependency: dependency, error: error)))
|
||||
let localizedFailure = String(format: NSLocalizedString("The dependency '%@' could not be downloaded.", comment: ""), dependency.preferredFilename)
|
||||
completionHandler(.failure(error.withLocalizedFailure(localizedFailure)))
|
||||
}
|
||||
}
|
||||
progress.addChild(downloadTask.progress, withPendingUnitCount: 1)
|
||||
|
||||
@@ -9,9 +9,17 @@
|
||||
import UIKit
|
||||
import Combine
|
||||
import minimuxer
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
import AltStoreCore
|
||||
|
||||
enum SideJITServerErrorType: Error {
|
||||
case invalidURL
|
||||
case errorConnecting
|
||||
case deviceNotFound
|
||||
case other(String)
|
||||
}
|
||||
|
||||
@available(iOS 14, *)
|
||||
protocol EnableJITContext
|
||||
{
|
||||
@@ -43,15 +51,105 @@ final class EnableJITOperation<Context: EnableJITContext>: ResultOperation<Void>
|
||||
}
|
||||
|
||||
guard let installedApp = self.context.installedApp else { return self.finish(.failure(OperationError.invalidParameters)) }
|
||||
|
||||
installedApp.managedObjectContext?.perform {
|
||||
do {
|
||||
try debug_app(installedApp.resignedBundleIdentifier)
|
||||
} catch {
|
||||
return self.finish(.failure(error))
|
||||
}
|
||||
if #available(iOS 17, *) {
|
||||
let sideJITenabled = UserDefaults.standard.sidejitenable
|
||||
let SideJITIP = UserDefaults.standard.textInputSideJITServerurl ?? ""
|
||||
|
||||
self.finish(.success(()))
|
||||
if sideJITenabled {
|
||||
installedApp.managedObjectContext?.perform {
|
||||
EnableJITSideJITServer(serverurl: SideJITIP, installedapp: installedApp) { result in
|
||||
switch result {
|
||||
case .failure(let error):
|
||||
switch error {
|
||||
case .invalidURL, .errorConnecting:
|
||||
self.finish(.failure(OperationError.unableToConnectSideJIT))
|
||||
case .deviceNotFound:
|
||||
self.finish(.failure(OperationError.unableToRespondSideJITDevice))
|
||||
case .other(let message):
|
||||
if let startRange = message.range(of: "<p>"),
|
||||
let endRange = message.range(of: "</p>", range: startRange.upperBound..<message.endIndex) {
|
||||
let pContent = message[startRange.upperBound..<endRange.lowerBound]
|
||||
self.finish(.failure(OperationError.SideJITIssue(error: String(pContent))))
|
||||
print(message + " + " + String(pContent))
|
||||
} else {
|
||||
print(message)
|
||||
self.finish(.failure(OperationError.SideJITIssue(error: message)))
|
||||
}
|
||||
}
|
||||
case .success():
|
||||
self.finish(.success(()))
|
||||
print("Thank you for using this, it was made by Stossy11 and tested by trolley or sniper1239408")
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
installedApp.managedObjectContext?.perform {
|
||||
var retries = 3
|
||||
while (retries > 0){
|
||||
do {
|
||||
try debug_app(installedApp.resignedBundleIdentifier)
|
||||
self.finish(.success(()))
|
||||
retries = 0
|
||||
} catch {
|
||||
retries -= 1
|
||||
if (retries <= 0){
|
||||
self.finish(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 17, *)
|
||||
func EnableJITSideJITServer(serverurl: String, installedapp: InstalledApp, completion: @escaping (Result<Void, SideJITServerErrorType>) -> Void) {
|
||||
guard let udid = fetch_udid()?.toString() else {
|
||||
completion(.failure(.other("Unable to get UDID")))
|
||||
return
|
||||
}
|
||||
|
||||
var SJSURL = serverurl
|
||||
|
||||
if (UserDefaults.standard.textInputSideJITServerurl ?? "").isEmpty {
|
||||
SJSURL = "http://sidejitserver._http._tcp.local:8080"
|
||||
}
|
||||
|
||||
if !SJSURL.hasPrefix("http") {
|
||||
completion(.failure(.invalidURL))
|
||||
return
|
||||
}
|
||||
|
||||
let fullurl = SJSURL + "/\(udid)/" + installedapp.resignedBundleIdentifier
|
||||
|
||||
let url = URL(string: fullurl)!
|
||||
|
||||
let task = URLSession.shared.dataTask(with: url) {(data, response, error) in
|
||||
if let error = error {
|
||||
completion(.failure(.errorConnecting))
|
||||
return
|
||||
}
|
||||
|
||||
guard let data = data, let datastring = String(data: data, encoding: .utf8) else { return }
|
||||
|
||||
if datastring == "Enabled JIT for '\(installedapp.name)'!" {
|
||||
let content = UNMutableNotificationContent()
|
||||
content.title = "JIT Successfully Enabled"
|
||||
content.subtitle = "JIT Enabled For \(installedapp.name)"
|
||||
content.sound = UNNotificationSound.default
|
||||
|
||||
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 0.1, repeats: false)
|
||||
let request = UNNotificationRequest(identifier: "EnabledJIT", content: content, trigger: nil)
|
||||
|
||||
UNUserNotificationCenter.current().add(request)
|
||||
completion(.success(()))
|
||||
} else {
|
||||
let errorType: SideJITServerErrorType = datastring == "Could not find device!" ? .deviceNotFound : .other(datastring)
|
||||
completion(.failure(errorType))
|
||||
}
|
||||
}
|
||||
|
||||
task.resume()
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ final class FetchAnisetteDataOperation: ResultOperation<ALTAnisetteData>, WebSoc
|
||||
return
|
||||
}
|
||||
|
||||
self.url = AnisetteManager.currentURL
|
||||
self.url = URL(string: UserDefaults.standard.menuAnisetteURL)
|
||||
print("Anisette URL: \(self.url!.absoluteString)")
|
||||
|
||||
if let identifier = Keychain.shared.identifier,
|
||||
@@ -218,7 +218,7 @@ final class FetchAnisetteDataOperation: ResultOperation<ALTAnisetteData>, WebSoc
|
||||
self.socket.connect()
|
||||
}
|
||||
|
||||
func didReceive(event: WebSocketEvent, client: WebSocket) {
|
||||
func didReceive(event: WebSocketEvent, client: WebSocketClient) {
|
||||
switch event {
|
||||
case .text(let string):
|
||||
do {
|
||||
@@ -408,6 +408,7 @@ final class FetchAnisetteDataOperation: ResultOperation<ALTAnisetteData>, WebSoc
|
||||
func fetchAnisetteV3(_ identifier: String, _ adiPb: String) {
|
||||
fetchClientInfo {
|
||||
print("Fetching anisette V3")
|
||||
let url = UserDefaults.standard.menuAnisetteURL
|
||||
var request = URLRequest(url: self.url!.appendingPathComponent("v3").appendingPathComponent("get_headers"))
|
||||
request.httpMethod = "POST"
|
||||
request.httpBody = try! JSONSerialization.data(withJSONObject: [
|
||||
@@ -429,7 +430,7 @@ final class FetchAnisetteDataOperation: ResultOperation<ALTAnisetteData>, WebSoc
|
||||
}
|
||||
}
|
||||
|
||||
extension WebSocket {
|
||||
extension WebSocketClient {
|
||||
func json(_ dictionary: [String: String]) {
|
||||
let data = try! JSONSerialization.data(withJSONObject: dictionary, options: [])
|
||||
self.write(string: String(data: data, encoding: .utf8)!)
|
||||
|
||||
@@ -45,8 +45,8 @@ final class FetchProvisioningProfilesOperation: ResultOperation<[String: ALTProv
|
||||
let session = self.context.session
|
||||
else { return self.finish(.failure(OperationError.invalidParameters)) }
|
||||
|
||||
guard let app = self.context.app else { return self.finish(.failure(OperationError.appNotFound)) }
|
||||
|
||||
guard let app = self.context.app else { return self.finish(.failure(OperationError.appNotFound(name: nil))) }
|
||||
|
||||
self.progress.totalUnitCount = Int64(1 + app.appExtensions.count)
|
||||
|
||||
self.prepareProvisioningProfile(for: app, parentApp: nil, team: team, session: session) { (result) in
|
||||
@@ -260,11 +260,7 @@ extension FetchProvisioningProfilesOperation
|
||||
{
|
||||
if let expirationDate = sortedExpirationDates.first
|
||||
{
|
||||
throw OperationError.maximumAppIDLimitReached(application: application, requiredAppIDs: requiredAppIDs, availableAppIDs: availableAppIDs, nextExpirationDate: expirationDate)
|
||||
}
|
||||
else
|
||||
{
|
||||
throw ALTAppleAPIError(.maximumAppIDLimitReached)
|
||||
throw OperationError.maximumAppIDLimitReached(appName: application.name, requiredAppIDs: requiredAppIDs, availableAppIDs: availableAppIDs, expirationDate: expirationDate)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -290,7 +286,7 @@ extension FetchProvisioningProfilesOperation
|
||||
{
|
||||
if let expirationDate = sortedExpirationDates.first
|
||||
{
|
||||
throw OperationError.maximumAppIDLimitReached(application: application, requiredAppIDs: requiredAppIDs, availableAppIDs: availableAppIDs, nextExpirationDate: expirationDate)
|
||||
throw OperationError.maximumAppIDLimitReached(appName: application.name, requiredAppIDs: requiredAppIDs, availableAppIDs: availableAppIDs, expirationDate: expirationDate)
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -41,12 +41,14 @@ final class InstallAppOperation: ResultOperation<InstalledApp>
|
||||
|
||||
guard
|
||||
let certificate = self.context.certificate,
|
||||
let resignedApp = self.context.resignedApp
|
||||
let resignedApp = self.context.resignedApp,
|
||||
let provisioningProfiles = self.context.provisioningProfiles
|
||||
else { return self.finish(.failure(OperationError.invalidParameters)) }
|
||||
|
||||
let backgroundContext = DatabaseManager.shared.persistentContainer.newBackgroundContext()
|
||||
backgroundContext.perform {
|
||||
|
||||
|
||||
/* App */
|
||||
let installedApp: InstalledApp
|
||||
|
||||
@@ -116,8 +118,7 @@ final class InstallAppOperation: ResultOperation<InstalledApp>
|
||||
// Temporary directory and resigned .ipa no longer needed, so delete them now to ensure AltStore doesn't quit before we get the chance to.
|
||||
self.cleanUp()
|
||||
|
||||
var activeProfiles: Set<String>?
|
||||
if let sideloadedAppsLimit = UserDefaults.standard.activeAppsLimit
|
||||
if let sideloadedAppsLimit = UserDefaults.standard.activeAppsLimit, provisioningProfiles.contains(where: { $1.isFreeProvisioningProfile == true })
|
||||
{
|
||||
// When installing these new profiles, AltServer will remove all non-active profiles to ensure we remain under limit.
|
||||
|
||||
@@ -142,15 +143,14 @@ final class InstallAppOperation: ResultOperation<InstalledApp>
|
||||
installedApp.isActive = false
|
||||
}
|
||||
}
|
||||
|
||||
activeProfiles = Set(activeApps.flatMap { (installedApp) -> [String] in
|
||||
let appExtensionProfiles = installedApp.appExtensions.map { $0.resignedBundleIdentifier }
|
||||
return [installedApp.resignedBundleIdentifier] + appExtensionProfiles
|
||||
})
|
||||
}
|
||||
else
|
||||
{
|
||||
installedApp.isActive = true
|
||||
}
|
||||
|
||||
var installing = true
|
||||
if installedApp.storeApp?.bundleIdentifier == Bundle.Info.appbundleIdentifier {
|
||||
if installedApp.storeApp?.bundleIdentifier.range(of: Bundle.Info.appbundleIdentifier) != nil {
|
||||
// Reinstalling ourself will hang until we leave the app, so we need to exit it without force closing
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
|
||||
if UIApplication.shared.applicationState != .active {
|
||||
@@ -162,30 +162,26 @@ final class InstallAppOperation: ResultOperation<InstalledApp>
|
||||
return
|
||||
}
|
||||
print("We are still installing after 3 seconds")
|
||||
|
||||
UNUserNotificationCenter.current().getNotificationSettings { settings in
|
||||
switch (settings.authorizationStatus) {
|
||||
case .authorized, .ephemeral, .provisional:
|
||||
print("Notifications are enabled")
|
||||
|
||||
|
||||
let content = UNMutableNotificationContent()
|
||||
content.title = "Refreshing..."
|
||||
content.body = "To finish refreshing, SideStore must be moved to the background, which it does by opening Safari. Please reopen SideStore after it is done refreshing!"
|
||||
content.body = "SideStore will automatically move to the homescreen to finish refreshing!"
|
||||
let notification = UNNotificationRequest(identifier: Bundle.Info.appbundleIdentifier + ".FinishRefreshNotification", content: content, trigger: UNTimeIntervalNotificationTrigger(timeInterval: 2, repeats: false))
|
||||
UNUserNotificationCenter.current().add(notification)
|
||||
|
||||
DispatchQueue.main.async { UIApplication.shared.open(URL(string: "x-web-search://")!) }
|
||||
|
||||
break
|
||||
default:
|
||||
print("Notifications are not enabled")
|
||||
|
||||
let alert = UIAlertController(title: "Finish Refresh", message: "To finish refreshing, SideStore must be moved to the background. To do this, you can either go to the Home Screen or open Safari by pressing Continue. Please reopen SideStore after doing this.", preferredStyle: .alert)
|
||||
|
||||
let alert = UIAlertController(title: "Finish Refresh", message: "Please reopen SideStore after the process is finished.To finish refreshing, SideStore must be moved to the background. To do this, you can either go to the Home Screen manually or by hitting Continue. Please reopen SideStore after doing this.", preferredStyle: .alert)
|
||||
alert.addAction(UIAlertAction(title: NSLocalizedString("Continue", comment: ""), style: .default, handler: { _ in
|
||||
print("Opening Safari")
|
||||
DispatchQueue.main.async { UIApplication.shared.open(URL(string: "x-web-search://")!) }
|
||||
print("Going home")
|
||||
UIApplication.shared.perform(#selector(NSXPCConnection.suspend))
|
||||
}))
|
||||
|
||||
|
||||
DispatchQueue.main.async {
|
||||
let keyWindow = UIApplication.shared.windows.filter { $0.isKeyWindow }.first
|
||||
if var topController = keyWindow?.rootViewController {
|
||||
@@ -194,27 +190,24 @@ final class InstallAppOperation: ResultOperation<InstalledApp>
|
||||
}
|
||||
topController.present(alert, animated: true)
|
||||
} else {
|
||||
print("No key window? Let's just open Safari")
|
||||
UIApplication.shared.open(URL(string: "x-web-search://")!)
|
||||
print("No key window? Let's just go home")
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
UIApplication.shared.perform(#selector(NSXPCConnection.suspend))
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
try install_ipa(installedApp.bundleIdentifier)
|
||||
installing = false
|
||||
} catch {
|
||||
installedApp.refreshedDate = Date()
|
||||
self.finish(.success(installedApp))
|
||||
} catch let error {
|
||||
installing = false
|
||||
return self.finish(.failure(error))
|
||||
self.finish(.failure(error))
|
||||
}
|
||||
|
||||
installedApp.refreshedDate = Date()
|
||||
self.finish(.success(installedApp))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,10 @@ import Roxas
|
||||
class ResultOperation<ResultType>: Operation
|
||||
{
|
||||
var resultHandler: ((Result<ResultType, Error>) -> Void)?
|
||||
|
||||
|
||||
// Should only be set by subclasses
|
||||
var localizedFailure: String?
|
||||
|
||||
@available(*, unavailable)
|
||||
override func finish()
|
||||
{
|
||||
@@ -22,16 +25,20 @@ class ResultOperation<ResultType>: Operation
|
||||
func finish(_ result: Result<ResultType, Error>)
|
||||
{
|
||||
guard !self.isFinished else { return }
|
||||
|
||||
|
||||
var result = result
|
||||
|
||||
if self.isCancelled
|
||||
{
|
||||
self.resultHandler?(.failure(OperationError.cancelled))
|
||||
result = .failure(OperationError.cancelled)
|
||||
}
|
||||
else
|
||||
{
|
||||
self.resultHandler?(result)
|
||||
else if case .failure(let nsError as NSError) = result, let localizedFailure, nsError.localizedFailure == nil {
|
||||
// Error doesn't have its own localizedFailure, so we give it the Operation's (if it exists)
|
||||
let error = nsError.withLocalizedFailure(localizedFailure)
|
||||
result = .failure(error)
|
||||
}
|
||||
|
||||
self.resultHandler?(result)
|
||||
|
||||
super.finish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,64 +8,186 @@
|
||||
|
||||
import Foundation
|
||||
import AltSign
|
||||
import AltStoreCore
|
||||
import minimuxer
|
||||
|
||||
enum OperationError: LocalizedError
|
||||
extension OperationError
|
||||
{
|
||||
static let domain = OperationError.unknown._domain
|
||||
enum Code: Int, ALTErrorCode, CaseIterable {
|
||||
typealias Error = OperationError
|
||||
|
||||
// General
|
||||
case unknown = 1000
|
||||
case unknownResult
|
||||
case cancelled
|
||||
case timedOut
|
||||
case unableToConnectSideJIT
|
||||
case unableToRespondSideJITDevice
|
||||
case wrongSideJITIP
|
||||
case SideJITIssue // (error: String)
|
||||
case refreshsidejit
|
||||
case notAuthenticated
|
||||
case appNotFound
|
||||
case unknownUDID
|
||||
case invalidApp
|
||||
case invalidParameters
|
||||
case maximumAppIDLimitReached//((application: ALTApplication, requiredAppIDs: Int, availableAppIDs: Int, nextExpirationDate: Date)
|
||||
case noSources
|
||||
case openAppFailed//(name: String)
|
||||
case missingAppGroup
|
||||
|
||||
// Connection
|
||||
case noWiFi = 1200
|
||||
case tooNewError
|
||||
case anisetteV1Error//(message: String)
|
||||
case provisioningError//(result: String, message: String?)
|
||||
case anisetteV3Error//(message: String)
|
||||
|
||||
case cacheClearError//(errors: [String])
|
||||
}
|
||||
|
||||
static let unknownResult: OperationError = .init(code: .unknownResult)
|
||||
static let cancelled: OperationError = .init(code: .cancelled)
|
||||
static let timedOut: OperationError = .init(code: .timedOut)
|
||||
static let unableToConnectSideJIT: OperationError = .init(code: .unableToConnectSideJIT)
|
||||
static let unableToRespondSideJITDevice: OperationError = .init(code: .unableToRespondSideJITDevice)
|
||||
static let wrongSideJITIP: OperationError = .init(code: .wrongSideJITIP)
|
||||
static let notAuthenticated: OperationError = .init(code: .notAuthenticated)
|
||||
static let unknownUDID: OperationError = .init(code: .unknownUDID)
|
||||
static let invalidApp: OperationError = .init(code: .invalidApp)
|
||||
static let invalidParameters: OperationError = .init(code: .invalidParameters)
|
||||
static let noSources: OperationError = .init(code: .noSources)
|
||||
static let missingAppGroup: OperationError = .init(code: .missingAppGroup)
|
||||
|
||||
static let noWiFi: OperationError = .init(code: .noWiFi)
|
||||
static let tooNewError: OperationError = .init(code: .tooNewError)
|
||||
static let provisioningError: OperationError = .init(code: .provisioningError)
|
||||
static let anisetteV1Error: OperationError = .init(code: .anisetteV1Error)
|
||||
static let anisetteV3Error: OperationError = .init(code: .anisetteV3Error)
|
||||
|
||||
static let cacheClearError: OperationError = .init(code: .cacheClearError)
|
||||
|
||||
static func unknown(failureReason: String? = nil, file: String = #fileID, line: UInt = #line) -> OperationError {
|
||||
OperationError(code: .unknown, failureReason: failureReason, sourceFile: file, sourceLine: line)
|
||||
}
|
||||
|
||||
static func appNotFound(name: String?) -> OperationError {
|
||||
OperationError(code: .appNotFound, appName: name)
|
||||
}
|
||||
|
||||
static func openAppFailed(name: String?) -> OperationError {
|
||||
OperationError(code: .openAppFailed, appName: name)
|
||||
}
|
||||
|
||||
case unknown
|
||||
case unknownResult
|
||||
case cancelled
|
||||
case timedOut
|
||||
static func SideJITIssue(error: String?) -> OperationError {
|
||||
var o = OperationError(code: .SideJITIssue)
|
||||
o.errorFailure = error
|
||||
return o
|
||||
}
|
||||
|
||||
case notAuthenticated
|
||||
case appNotFound
|
||||
|
||||
case unknownUDID
|
||||
|
||||
case invalidApp
|
||||
case invalidParameters
|
||||
|
||||
case maximumAppIDLimitReached(application: ALTApplication, requiredAppIDs: Int, availableAppIDs: Int, nextExpirationDate: Date)
|
||||
|
||||
case noSources
|
||||
|
||||
case openAppFailed(name: String)
|
||||
case missingAppGroup
|
||||
|
||||
case anisetteV1Error(message: String)
|
||||
case provisioningError(result: String, message: String?)
|
||||
case anisetteV3Error(message: String)
|
||||
|
||||
var failureReason: String? {
|
||||
switch self {
|
||||
case .unknown: return NSLocalizedString("An unknown error occured.", comment: "")
|
||||
static func maximumAppIDLimitReached(appName: String, requiredAppIDs: Int, availableAppIDs: Int, expirationDate: Date) -> OperationError {
|
||||
OperationError(code: .maximumAppIDLimitReached, appName: appName, requiredAppIDs: requiredAppIDs, availableAppIDs: availableAppIDs, expirationDate: expirationDate)
|
||||
}
|
||||
|
||||
static func provisioningError(result: String, message: String?) -> OperationError {
|
||||
var o = OperationError(code: .provisioningError, failureReason: result)
|
||||
o.errorTitle = message
|
||||
return o
|
||||
}
|
||||
|
||||
static func cacheClearError(errors: [String]) -> OperationError {
|
||||
OperationError(code: .cacheClearError, failureReason: errors.joined(separator: "\n"))
|
||||
}
|
||||
|
||||
static func anisetteV1Error(message: String) -> OperationError {
|
||||
OperationError(code: .anisetteV1Error, failureReason: message)
|
||||
}
|
||||
|
||||
static func anisetteV3Error(message: String) -> OperationError {
|
||||
OperationError(code: .anisetteV3Error, failureReason: message)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
struct OperationError: ALTLocalizedError {
|
||||
|
||||
let code: Code
|
||||
|
||||
var errorTitle: String?
|
||||
var errorFailure: String?
|
||||
|
||||
var appName: String?
|
||||
|
||||
var requiredAppIDs: Int?
|
||||
var availableAppIDs: Int?
|
||||
var expirationDate: Date?
|
||||
|
||||
var sourceFile: String?
|
||||
var sourceLine: UInt?
|
||||
|
||||
private var _failureReason: String?
|
||||
|
||||
private init(code: Code, failureReason: String? = nil,
|
||||
appName: String? = nil, requiredAppIDs: Int? = nil, availableAppIDs: Int? = nil,
|
||||
expirationDate: Date? = nil, sourceFile: String? = nil, sourceLine: UInt? = nil){
|
||||
self.code = code
|
||||
self._failureReason = failureReason
|
||||
|
||||
self.appName = appName
|
||||
self.requiredAppIDs = requiredAppIDs
|
||||
self.availableAppIDs = availableAppIDs
|
||||
self.expirationDate = expirationDate
|
||||
self.sourceFile = sourceFile
|
||||
self.sourceLine = sourceLine
|
||||
}
|
||||
|
||||
var errorFailureReason: String {
|
||||
switch self.code {
|
||||
case .unknown:
|
||||
var failureReason = self._failureReason ?? NSLocalizedString("An unknown error occurred.", comment: "")
|
||||
guard let sourceFile, let sourceLine else { return failureReason }
|
||||
failureReason += " (\(sourceFile) line \(sourceLine)"
|
||||
return failureReason
|
||||
case .unknownResult: return NSLocalizedString("The operation returned an unknown result.", comment: "")
|
||||
case .cancelled: return NSLocalizedString("The operation was cancelled.", comment: "")
|
||||
case .timedOut: return NSLocalizedString("The operation timed out.", comment: "")
|
||||
case .notAuthenticated: return NSLocalizedString("You are not signed in.", comment: "")
|
||||
case .appNotFound: return NSLocalizedString("App not found.", comment: "")
|
||||
case .unknownUDID: return NSLocalizedString("Unknown device UDID.", comment: "")
|
||||
case .invalidApp: return NSLocalizedString("The app is invalid.", comment: "")
|
||||
case .unknownUDID: return NSLocalizedString("SideStore could not determine this device's UDID.", comment: "")
|
||||
case .invalidApp: return NSLocalizedString("The app is in an invalid format.", comment: "")
|
||||
case .invalidParameters: return NSLocalizedString("Invalid parameters.", comment: "")
|
||||
case .maximumAppIDLimitReached: return NSLocalizedString("Cannot register more than 10 App IDs within a 7 day period.", comment: "")
|
||||
case .noSources: return NSLocalizedString("There are no SideStore sources.", comment: "")
|
||||
case .openAppFailed(let name): return String(format: NSLocalizedString("SideStore was denied permission to launch %@.", comment: ""), name)
|
||||
case .missingAppGroup: return NSLocalizedString("SideStore's shared app group could not be found.", comment: "")
|
||||
case .maximumAppIDLimitReached: return NSLocalizedString("Cannot register more than 10 App IDs.", comment: "")
|
||||
case .anisetteV1Error(let message): return String(format: NSLocalizedString("An error occurred when getting anisette data from a V1 server: %@. Try using another anisette server.", comment: ""), message)
|
||||
case .provisioningError(let result, let message): return String(format: NSLocalizedString("An error occurred when provisioning: %@%@. Please try again. If the issue persists, report it on GitHub Issues!", comment: ""), result, message != nil ? (" (" + message! + ")") : "")
|
||||
case .anisetteV3Error(let message): return String(format: NSLocalizedString("An error occurred when getting anisette data from a V3 server: %@. Please try again. If the issue persists, report it on GitHub Issues!", comment: ""), message)
|
||||
case .missingAppGroup: return NSLocalizedString("SideStore's shared app group could not be accessed.", comment: "")
|
||||
case .appNotFound:
|
||||
let appName = self.appName ?? NSLocalizedString("The app", comment: "")
|
||||
return String(format: NSLocalizedString("%@ could not be found.", comment: ""), appName)
|
||||
case .openAppFailed:
|
||||
let appName = self.appName ?? NSLocalizedString("The app", comment: "")
|
||||
return String(format: NSLocalizedString("SideStore was denied permission to launch %@.", comment: ""), appName)
|
||||
case .noWiFi: return NSLocalizedString("You do not appear to be connected to WiFi and/or the WireGuard VPN!\nSideStore will never be able to install or refresh applications without WiFi and the WireGuard VPN.", comment: "")
|
||||
case .tooNewError: return NSLocalizedString("iOS 17 has changed how JIT is enabled therefore SideStore cannot enable it at this time, sorry for any inconvenience.\nWe will let everyone know once we have a solution!", comment: "")
|
||||
case .unableToConnectSideJIT: return NSLocalizedString("Unable to connect to SideJITServer Please check that you are on the Same Wi-Fi and your Firewall has been set correctly", comment: "")
|
||||
case .unableToRespondSideJITDevice: return NSLocalizedString("SideJITServer is unable to connect to your iDevice Please make sure you have paired your Device by doing 'SideJITServer -y' or try Refreshing SideJITServer from Settings", comment: "")
|
||||
case .wrongSideJITIP: return NSLocalizedString("Incorrect SideJITServer IP Please make sure that you are on the Samw Wifi as SideJITServer", comment: "")
|
||||
case .refreshsidejit: return NSLocalizedString("Unable to find App Please try Refreshing SideJITServer from Settings", comment: "")
|
||||
case .anisetteV1Error: return NSLocalizedString("An error occurred when getting anisette data from a V1 server: %@. Try using another anisette server.", comment: "")
|
||||
case .provisioningError: return NSLocalizedString("An error occurred when provisioning: %@ %@. Please try again. If the issue persists, report it on GitHub Issues!", comment: "")
|
||||
case .anisetteV3Error: return NSLocalizedString("An error occurred when getting anisette data from a V3 server: %@. Please try again. If the issue persists, report it on GitHub Issues!", comment: "")
|
||||
case .cacheClearError: return NSLocalizedString("An error occurred while clearing cache: %@", comment: "")
|
||||
case .SideJITIssue: return NSLocalizedString("An error occurred while using SideJIT: %@", comment: "")
|
||||
}
|
||||
}
|
||||
|
||||
var recoverySuggestion: String? {
|
||||
switch self
|
||||
switch self.code
|
||||
{
|
||||
case .maximumAppIDLimitReached(let application, let requiredAppIDs, let availableAppIDs, let date):
|
||||
case .noWiFi: return NSLocalizedString("Make sure the VPN is toggled on and you are connected to any WiFi network!", comment: "")
|
||||
case .maximumAppIDLimitReached:
|
||||
let baseMessage = NSLocalizedString("Delete sideloaded apps to free up App ID slots.", comment: "")
|
||||
let message: String
|
||||
|
||||
guard let appName, let requiredAppIDs, let availableAppIDs, let expirationDate else { return baseMessage }
|
||||
var message: String
|
||||
|
||||
if requiredAppIDs > 1
|
||||
{
|
||||
let availableText: String
|
||||
@@ -77,23 +199,23 @@ enum OperationError: LocalizedError
|
||||
default: availableText = String(format: NSLocalizedString("only %@ are available", comment: ""), NSNumber(value: availableAppIDs))
|
||||
}
|
||||
|
||||
let prefixMessage = String(format: NSLocalizedString("%@ requires %@ App IDs, but %@.", comment: ""), application.name, NSNumber(value: requiredAppIDs), availableText)
|
||||
message = prefixMessage + " " + baseMessage
|
||||
let prefixMessage = String(format: NSLocalizedString("%@ requires %@ App IDs, but %@.", comment: ""), appName, NSNumber(value: requiredAppIDs), availableText)
|
||||
message = prefixMessage + " " + baseMessage + "\n\n"
|
||||
}
|
||||
else
|
||||
{
|
||||
let dateComponents = Calendar.current.dateComponents([.day, .hour, .minute], from: Date(), to: date)
|
||||
|
||||
let dateComponentsFormatter = DateComponentsFormatter()
|
||||
dateComponentsFormatter.maximumUnitCount = 1
|
||||
dateComponentsFormatter.unitsStyle = .full
|
||||
|
||||
let remainingTime = dateComponentsFormatter.string(from: dateComponents)!
|
||||
|
||||
let remainingTimeMessage = String(format: NSLocalizedString("You can register another App ID in %@.", comment: ""), remainingTime)
|
||||
message = baseMessage + " " + remainingTimeMessage
|
||||
message = baseMessage + " "
|
||||
}
|
||||
|
||||
|
||||
let dateComponents = Calendar.current.dateComponents([.day, .hour, .minute], from: Date(), to: expirationDate)
|
||||
let dateFormatter = DateComponentsFormatter()
|
||||
dateFormatter.maximumUnitCount = 1
|
||||
dateFormatter.unitsStyle = .full
|
||||
|
||||
let remainingTime = dateFormatter.string(from: dateComponents)!
|
||||
|
||||
message += String(format: NSLocalizedString("You can register another App ID in %@.", comment: ""), remainingTime)
|
||||
|
||||
return message
|
||||
|
||||
default: return nil
|
||||
@@ -138,8 +260,8 @@ extension MinimuxerError: LocalizedError {
|
||||
return self.createService(name: "AFC")
|
||||
case .RwAfc:
|
||||
return NSLocalizedString("AFC was unable to manage files on the device", comment: "")
|
||||
case .InstallApp:
|
||||
return NSLocalizedString("Unable to install the app from the staging directory", comment: "")
|
||||
case .InstallApp(let message):
|
||||
return NSLocalizedString("Unable to install the app: \(message.toString())", comment: "")
|
||||
case .UninstallApp:
|
||||
return NSLocalizedString("Unable to uninstall the app", comment: "")
|
||||
|
||||
|
||||
@@ -25,22 +25,38 @@ protocol PatchAppContext
|
||||
var error: Error? { get }
|
||||
}
|
||||
|
||||
enum PatchAppError: LocalizedError
|
||||
extension PatchAppError
|
||||
{
|
||||
case unsupportedOperatingSystemVersion(OperatingSystemVersion)
|
||||
|
||||
var errorDescription: String? {
|
||||
switch self
|
||||
{
|
||||
case .unsupportedOperatingSystemVersion(let osVersion):
|
||||
var osVersionString = "\(osVersion.majorVersion).\(osVersion.minorVersion)"
|
||||
if osVersion.patchVersion != 0
|
||||
{
|
||||
osVersionString += ".\(osVersion.patchVersion)"
|
||||
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: "")
|
||||
}
|
||||
|
||||
let errorDescription = String(format: NSLocalizedString("The OTA download URL for iOS %@ could not be determined.", comment: ""), osVersionString)
|
||||
return errorDescription
|
||||
return String(format: NSLocalizedString("The OTA download URL for %@ could not be determined.", comment: ""), osVersionString)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -439,7 +439,7 @@ private extension PatchViewController
|
||||
|
||||
do
|
||||
{
|
||||
guard let (bundleIdentifier, result) = results.first else { throw refreshGroup?.context.error ?? OperationError.unknown }
|
||||
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)
|
||||
|
||||
@@ -35,31 +35,27 @@ final class RefreshAppOperation: ResultOperation<InstalledApp>
|
||||
|
||||
do
|
||||
{
|
||||
if let error = self.context.error
|
||||
{
|
||||
throw error
|
||||
}
|
||||
if let error = self.context.error { return self.finish(.failure(error)) }
|
||||
|
||||
guard let profiles = self.context.provisioningProfiles else { throw OperationError.invalidParameters }
|
||||
|
||||
guard let app = self.context.app else { throw OperationError.appNotFound }
|
||||
|
||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
||||
print("Sending refresh app request...")
|
||||
guard let profiles = self.context.provisioningProfiles else { return self.finish(.failure(OperationError.invalidParameters)) }
|
||||
guard let app = self.context.app else { return self.finish(.failure(OperationError(.appNotFound(name: nil)))) }
|
||||
|
||||
for p in profiles {
|
||||
do {
|
||||
let bytes = p.value.data.toRustByteSlice()
|
||||
try install_provisioning_profile(bytes.forRust())
|
||||
} catch {
|
||||
return self.finish(.failure(error))
|
||||
}
|
||||
for p in profiles {
|
||||
do {
|
||||
let bytes = p.value.data.toRustByteSlice()
|
||||
try install_provisioning_profile(bytes.forRust())
|
||||
} catch {
|
||||
self.finish(.failure(MinimuxerError.ProfileInstall))
|
||||
}
|
||||
|
||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
||||
|
||||
self.progress.completedUnitCount += 1
|
||||
|
||||
let predicate = NSPredicate(format: "%K == %@", #keyPath(InstalledApp.bundleIdentifier), app.bundleIdentifier)
|
||||
self.managedObjectContext.perform {
|
||||
guard let installedApp = InstalledApp.first(satisfying: predicate, in: self.managedObjectContext) else {
|
||||
self.finish(.failure(OperationError(.appNotFound(name: app.name))))
|
||||
return
|
||||
}
|
||||
installedApp.update(provisioningProfile: p.value)
|
||||
@@ -72,9 +68,5 @@ final class RefreshAppOperation: ResultOperation<InstalledApp>
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
self.finish(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import Roxas
|
||||
|
||||
import AltStoreCore
|
||||
import AltSign
|
||||
import minimuxer
|
||||
|
||||
@objc(ResignAppOperation)
|
||||
final class ResignAppOperation: ResultOperation<ALTApplication>
|
||||
@@ -115,7 +116,9 @@ private extension ResignAppOperation
|
||||
|
||||
infoDictionary[kCFBundleIdentifierKey as String] = profile.bundleIdentifier
|
||||
infoDictionary[Bundle.Info.altBundleID] = identifier
|
||||
infoDictionary[Bundle.Info.devicePairingString] = Bundle.main.object(forInfoDictionaryKey: "ALTPairingFile") as? String
|
||||
infoDictionary[Bundle.Info.devicePairingString] = "<insert pairing file here>"
|
||||
infoDictionary.removeValue(forKey: "DTXcode")
|
||||
infoDictionary.removeValue(forKey: "DTXcodeBuild")
|
||||
|
||||
for (key, value) in additionalInfoDictionaryValues
|
||||
{
|
||||
@@ -181,9 +184,9 @@ private extension ResignAppOperation
|
||||
|
||||
if app.isAltStoreApp
|
||||
{
|
||||
guard let udid = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.deviceID) as? String else { throw OperationError.unknownUDID }
|
||||
guard let udid = fetch_udid()?.toString() as? String else { throw OperationError.unknownUDID }
|
||||
guard let pairingFileString = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.devicePairingString) as? String else { throw OperationError.unknownUDID }
|
||||
additionalValues[Bundle.Info.devicePairingString] = pairingFileString
|
||||
additionalValues[Bundle.Info.devicePairingString] = "<insert pairing file here>"
|
||||
additionalValues[Bundle.Info.deviceID] = udid
|
||||
additionalValues[Bundle.Info.serverID] = UserDefaults.standard.preferredServerID
|
||||
|
||||
@@ -202,7 +205,7 @@ private extension ResignAppOperation
|
||||
// The embedded certificate + certificate identifier are already in app bundle, no need to update them.
|
||||
}
|
||||
}
|
||||
else if infoDictionary.keys.contains(Bundle.Info.deviceID), let udid = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.deviceID) as? String
|
||||
else if infoDictionary.keys.contains(Bundle.Info.deviceID), let udid = fetch_udid()?.toString() as? String
|
||||
{
|
||||
// There is an ALTDeviceID entry, so assume the app is using AltKit and replace it with the device's UDID.
|
||||
additionalValues[Bundle.Info.deviceID] = udid
|
||||
@@ -227,6 +230,7 @@ private extension ResignAppOperation
|
||||
|
||||
// Prepare app
|
||||
try prepare(appBundle, additionalInfoDictionaryValues: additionalValues)
|
||||
try self.removeMissingAppExtensionReferences(from: appBundle)
|
||||
|
||||
if let directory = appBundle.builtInPlugInsURL, let enumerator = FileManager.default.enumerator(at: directory, includingPropertiesForKeys: nil, options: [.skipsSubdirectoryDescendants])
|
||||
{
|
||||
@@ -267,4 +271,28 @@ private extension ResignAppOperation
|
||||
|
||||
return progress
|
||||
}
|
||||
|
||||
func removeMissingAppExtensionReferences(from bundle: Bundle) throws
|
||||
{
|
||||
// If app extensions have been removed from an app (either by AltStore or the developer),
|
||||
// we must remove all references to them from SC_Info/Manifest.plist (if it exists).
|
||||
|
||||
let scInfoURL = bundle.bundleURL.appendingPathComponent("SC_Info")
|
||||
let manifestPlistURL = scInfoURL.appendingPathComponent("Manifest.plist")
|
||||
|
||||
guard let manifestPlist = NSMutableDictionary(contentsOf: manifestPlistURL), let sinfReplicationPaths = manifestPlist["SinfReplicationPaths"] as? [String] else { return }
|
||||
|
||||
// Remove references to missing files.
|
||||
let filteredReplicationPaths = sinfReplicationPaths.filter { path in
|
||||
guard let fileURL = URL(string: path, relativeTo: bundle.bundleURL) else { return false }
|
||||
|
||||
let fileExists = FileManager.default.fileExists(atPath: fileURL.path)
|
||||
return fileExists
|
||||
}
|
||||
|
||||
manifestPlist["SinfReplicationPaths"] = filteredReplicationPaths
|
||||
|
||||
// Save updated Manifest.plist to disk.
|
||||
try manifestPlist.write(to: manifestPlistURL)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,8 +33,7 @@ final class SendAppOperation: ResultOperation<()>
|
||||
|
||||
if let error = self.context.error
|
||||
{
|
||||
self.finish(.failure(error))
|
||||
return
|
||||
return self.finish(.failure(error))
|
||||
}
|
||||
|
||||
guard let resignedApp = self.context.resignedApp else { return self.finish(.failure(OperationError.invalidParameters)) }
|
||||
@@ -49,15 +48,16 @@ final class SendAppOperation: ResultOperation<()>
|
||||
do {
|
||||
let bytes = Data(data).toRustByteSlice()
|
||||
try yeet_app_afc(app.bundleIdentifier, bytes.forRust())
|
||||
self.progress.completedUnitCount += 1
|
||||
self.finish(.success(()))
|
||||
} catch {
|
||||
return self.finish(.failure(error))
|
||||
self.finish(.failure(MinimuxerError.RwAfc))
|
||||
self.progress.completedUnitCount += 1
|
||||
self.finish(.success(()))
|
||||
}
|
||||
|
||||
self.progress.completedUnitCount += 1
|
||||
self.finish(.success(()))
|
||||
} else {
|
||||
print("IPA doesn't exist????")
|
||||
self.finish(.failure(ALTServerError(.underlyingError)))
|
||||
self.finish(.failure(OperationError(.appNotFound(name: resignedApp.name))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,48 +8,87 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
import AltStoreCore
|
||||
import AltSign
|
||||
import Roxas
|
||||
|
||||
enum VerificationError: ALTLocalizedError
|
||||
extension VerificationError
|
||||
{
|
||||
case privateEntitlements(ALTApplication, entitlements: [String: Any])
|
||||
case mismatchedBundleIdentifiers(ALTApplication, sourceBundleID: String)
|
||||
case iOSVersionNotSupported(ALTApplication)
|
||||
enum Code: Int, ALTErrorCode, CaseIterable {
|
||||
typealias Error = VerificationError
|
||||
|
||||
case privateEntitlements
|
||||
case mismatchedBundleIdentifiers
|
||||
case iOSVersionNotSupported
|
||||
}
|
||||
|
||||
static func privateEntitlements(_ entitlements: [String: Any], app: ALTApplication) -> VerificationError {
|
||||
VerificationError(code: .privateEntitlements, app: app, entitlements: entitlements)
|
||||
}
|
||||
|
||||
static func mismatchedBundleIdentifiers(sourceBundleID: String, app: ALTApplication) -> VerificationError {
|
||||
VerificationError(code: .mismatchedBundleIdentifiers, app: app, sourceBundleID: sourceBundleID)
|
||||
}
|
||||
|
||||
static func iOSVersionNotSupported(app: AppProtocol, osVersion: OperatingSystemVersion = ProcessInfo.processInfo.operatingSystemVersion, requiredOSVersion: OperatingSystemVersion?) -> VerificationError {
|
||||
VerificationError(code: .iOSVersionNotSupported, app: app)
|
||||
}
|
||||
}
|
||||
|
||||
struct VerificationError: ALTLocalizedError {
|
||||
let code: Code
|
||||
|
||||
var errorTitle: String?
|
||||
var errorFailure: String?
|
||||
|
||||
@Managed var app: AppProtocol?
|
||||
var entitlements: [String: Any]?
|
||||
var sourceBundleID: String?
|
||||
var deviceOSVersion: OperatingSystemVersion?
|
||||
var requiredOSVersion: OperatingSystemVersion?
|
||||
|
||||
var app: ALTApplication {
|
||||
switch self
|
||||
{
|
||||
case .privateEntitlements(let app, _): return app
|
||||
case .mismatchedBundleIdentifiers(let app, _): return app
|
||||
case .iOSVersionNotSupported(let app): return app
|
||||
var errorDescription: String? {
|
||||
switch self.code {
|
||||
case .iOSVersionNotSupported:
|
||||
guard let deviceOSVersion else { return nil }
|
||||
|
||||
var failureReason = self.errorFailureReason
|
||||
if self.app == nil {
|
||||
let firstLetter = failureReason.prefix(1).lowercased()
|
||||
failureReason = firstLetter + failureReason.dropFirst()
|
||||
}
|
||||
|
||||
return String(formatted: "This device is running iOS %@, but %@", deviceOSVersion.stringValue, failureReason)
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
|
||||
var failure: String? {
|
||||
return String(format: NSLocalizedString("“%@” could not be installed.", comment: ""), app.name)
|
||||
}
|
||||
|
||||
var failureReason: String? {
|
||||
switch self
|
||||
|
||||
var errorFailureReason: String {
|
||||
switch self.code
|
||||
{
|
||||
case .privateEntitlements(let app, _):
|
||||
return String(format: NSLocalizedString("“%@” requires private permissions.", comment: ""), app.name)
|
||||
|
||||
case .mismatchedBundleIdentifiers(let app, let sourceBundleID):
|
||||
return String(format: NSLocalizedString("The bundle ID “%@” does not match the one specified by the source (“%@”).", comment: ""), app.bundleIdentifier, sourceBundleID)
|
||||
|
||||
case .iOSVersionNotSupported(let app):
|
||||
let name = app.name
|
||||
|
||||
var version = "iOS \(app.minimumiOSVersion.majorVersion).\(app.minimumiOSVersion.minorVersion)"
|
||||
if app.minimumiOSVersion.patchVersion > 0
|
||||
{
|
||||
version += ".\(app.minimumiOSVersion.patchVersion)"
|
||||
case .privateEntitlements:
|
||||
let appName = self.$app.name ?? NSLocalizedString("The app", comment: "")
|
||||
return String(formatted: "“%@” requires private permissions.", appName)
|
||||
|
||||
case .mismatchedBundleIdentifiers:
|
||||
if let appBundleID = self.$app.bundleIdentifier, let bundleID = self.sourceBundleID {
|
||||
return String(formatted: "The bundle ID '%@' does not match the one specified by the source ('%@').", appBundleID, bundleID)
|
||||
} else {
|
||||
return NSLocalizedString("The bundle ID does not match the one specified by the source.", comment: "")
|
||||
}
|
||||
|
||||
case .iOSVersionNotSupported:
|
||||
let appName = self.$app.name ?? NSLocalizedString("The app", comment: "")
|
||||
let deviceOSVersion = self.deviceOSVersion ?? ProcessInfo.processInfo.operatingSystemVersion
|
||||
|
||||
guard let requiredOSVersion else {
|
||||
return String(formatted: "%@ does not support iOS %@.", appName, deviceOSVersion.stringValue)
|
||||
}
|
||||
if deviceOSVersion > requiredOSVersion {
|
||||
return String(formatted: "%@ requires iOS %@ or earlier", appName, requiredOSVersion.stringValue)
|
||||
} else {
|
||||
return String(formatted: "%@ requires iOS %@ or later", appName, requiredOSVersion.stringValue)
|
||||
}
|
||||
|
||||
let localizedDescription = String(format: NSLocalizedString("%@ requires %@.", comment: ""), name, version)
|
||||
return localizedDescription
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,12 +119,14 @@ final class VerifyAppOperation: ResultOperation<Void>
|
||||
|
||||
guard let app = self.context.app else { throw OperationError.invalidParameters }
|
||||
|
||||
guard app.bundleIdentifier == self.context.bundleIdentifier else {
|
||||
throw VerificationError.mismatchedBundleIdentifiers(app, sourceBundleID: self.context.bundleIdentifier)
|
||||
if !["ny.litritt.ignited", "com.litritt.ignited"].contains(where: { $0 == app.bundleIdentifier }) {
|
||||
guard app.bundleIdentifier == self.context.bundleIdentifier else {
|
||||
throw VerificationError.mismatchedBundleIdentifiers(sourceBundleID: self.context.bundleIdentifier, app: app)
|
||||
}
|
||||
}
|
||||
|
||||
guard ProcessInfo.processInfo.isOperatingSystemAtLeast(app.minimumiOSVersion) else {
|
||||
throw VerificationError.iOSVersionNotSupported(app)
|
||||
throw VerificationError.iOSVersionNotSupported(app: app, requiredOSVersion: app.minimumiOSVersion)
|
||||
}
|
||||
|
||||
if #available(iOS 13.5, *)
|
||||
@@ -116,7 +157,7 @@ final class VerifyAppOperation: ResultOperation<Void>
|
||||
let entitlements = try PropertyListSerialization.propertyList(from: entitlementsPlist.data(using: .utf8)!, options: [], format: nil) as! [String: Any]
|
||||
|
||||
app.hasPrivateEntitlements = true
|
||||
let error = VerificationError.privateEntitlements(app, entitlements: entitlements)
|
||||
let error = VerificationError.privateEntitlements(entitlements, app: app)
|
||||
self.process(error) { (result) in
|
||||
self.finish(result.mapError { $0 as Error })
|
||||
}
|
||||
@@ -145,9 +186,10 @@ private extension VerifyAppOperation
|
||||
guard let presentingViewController = self.context.presentingViewController else { return completion(.failure(error)) }
|
||||
|
||||
DispatchQueue.main.async {
|
||||
switch error
|
||||
switch error.code
|
||||
{
|
||||
case .privateEntitlements(_, let entitlements):
|
||||
case .privateEntitlements:
|
||||
guard let entitlements = error.entitlements else { return completion(.failure(error)) }
|
||||
let permissions = entitlements.keys.sorted().joined(separator: "\n")
|
||||
let message = String(format: NSLocalizedString("""
|
||||
You must allow access to these private permissions before continuing:
|
||||
@@ -166,8 +208,7 @@ private extension VerifyAppOperation
|
||||
}))
|
||||
presentingViewController.present(alertController, animated: true, completion: nil)
|
||||
|
||||
case .mismatchedBundleIdentifiers: return completion(.failure(error))
|
||||
case .iOSVersionNotSupported: return completion(.failure(error))
|
||||
case .mismatchedBundleIdentifiers, .iOSVersionNotSupported: return completion(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1,158 @@
|
||||
{"images":[{"size":"60x60","expected-size":"180","filename":"180.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"40x40","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"60x60","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"57x57","expected-size":"57","filename":"57.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"87","filename":"87.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"57x57","expected-size":"114","filename":"114.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"60","filename":"60.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"1024x1024","filename":"1024.png","expected-size":"1024","idiom":"ios-marketing","folder":"Assets.xcassets/AppIcon.appiconset/","scale":"1x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"72x72","expected-size":"72","filename":"72.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"76x76","expected-size":"152","filename":"152.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"50x50","expected-size":"100","filename":"100.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"76x76","expected-size":"76","filename":"76.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"50x50","expected-size":"50","filename":"50.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"72x72","expected-size":"144","filename":"144.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"40x40","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"83.5x83.5","expected-size":"167","filename":"167.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"20x20","expected-size":"20","filename":"20.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"}]}
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "40.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"filename" : "60.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"filename" : "29.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "1x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"filename" : "58.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"filename" : "87.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"filename" : "80.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"filename" : "120.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"filename" : "57.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "1x",
|
||||
"size" : "57x57"
|
||||
},
|
||||
{
|
||||
"filename" : "114.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "57x57"
|
||||
},
|
||||
{
|
||||
"filename" : "120.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"filename" : "180.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"filename" : "20.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"filename" : "40.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"filename" : "29.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"filename" : "58.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"filename" : "40.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"filename" : "80.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"filename" : "50.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "50x50"
|
||||
},
|
||||
{
|
||||
"filename" : "100.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "50x50"
|
||||
},
|
||||
{
|
||||
"filename" : "72.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "72x72"
|
||||
},
|
||||
{
|
||||
"filename" : "144.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "72x72"
|
||||
},
|
||||
{
|
||||
"filename" : "76.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "76x76"
|
||||
},
|
||||
{
|
||||
"filename" : "152.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "76x76"
|
||||
},
|
||||
{
|
||||
"filename" : "167.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "83.5x83.5"
|
||||
},
|
||||
{
|
||||
"filename" : "1024.png",
|
||||
"idiom" : "ios-marketing",
|
||||
"scale" : "1x",
|
||||
"size" : "1024x1024"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "riley.jpg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 73 KiB |
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "shane.jpeg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 43 KiB |
BIN
AltStore/Resources/Assets.xcassets/SideStore.imageset/1024.png
vendored
Normal file
|
After Width: | Height: | Size: 846 KiB |
21
AltStore/Resources/Assets.xcassets/SideStore.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "1024.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -1,79 +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>StringsTable</key>
|
||||
<string>Root</string>
|
||||
<key>ApplicationGroupContainerIdentifier</key>
|
||||
<string>group.$(APP_GROUP_IDENTIFIER)</string>
|
||||
<key>PreferenceSpecifiers</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>Type</key>
|
||||
<string>PSMultiValueSpecifier</string>
|
||||
<key>Title</key>
|
||||
<string>Anisette Server</string>
|
||||
<key>Key</key>
|
||||
<string>customAnisetteURL</string>
|
||||
<key>DefaultValue</key>
|
||||
<string>https://ani.sidestore.io</string>
|
||||
<key>Titles</key>
|
||||
<array>
|
||||
<string>SideStore</string>
|
||||
<string>Macley (US)</string>
|
||||
<string>Macley (DE)</string>
|
||||
<string>DrPudding</string>
|
||||
<string>Sideloadly</string>
|
||||
<string>Nick</string>
|
||||
<string>Jawshoeadan</string>
|
||||
<string>crystall1nedev</string>
|
||||
</array>
|
||||
<key>Values</key>
|
||||
<array>
|
||||
<string>https://ani.sidestore.io</string>
|
||||
<string>http://us1.sternserv.tech</string>
|
||||
<string>http://de1.sternserv.tech</string>
|
||||
<string>https://sign.rheaa.xyz</string>
|
||||
<string>https://sideloadly.io/anisette/irGb3Quww8zrhgqnzmrx</string>
|
||||
<string>http://45.33.29.114</string>
|
||||
<string>https://anisette.jawshoeadan.me</string>
|
||||
<string>https://anisette.crystall1ne.software/</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>Type</key>
|
||||
<string>PSGroupSpecifier</string>
|
||||
<key>Title</key>
|
||||
<string>Danger Zone</string>
|
||||
<key>FooterText</key>
|
||||
<string>If you disable the toggle then app will use the server you input into the "Anisette URL" box rather than one selected from the above selector.</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>Type</key>
|
||||
<string>PSToggleSwitchSpecifier</string>
|
||||
<key>Title</key>
|
||||
<string>Use preferred servers</string>
|
||||
<key>Key</key>
|
||||
<string>textServer</string>
|
||||
<key>DefaultValue</key>
|
||||
<true/>
|
||||
<key>FooterText</key>
|
||||
<string>chicken</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>Type</key>
|
||||
<string>PSTextFieldSpecifier</string>
|
||||
<key>Title</key>
|
||||
<string>Anisette URL</string>
|
||||
<key>Key</key>
|
||||
<string>textInputAnisetteURL</string>
|
||||
<key>AutocapitalizationType</key>
|
||||
<string>None</string>
|
||||
<key>AutocorrectionType</key>
|
||||
<string>No</string>
|
||||
<key>KeyboardType</key>
|
||||
<string>URL</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,27 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21225" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21207"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
|
||||
<capability name="Named colors" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<collectionReusableView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" reuseIdentifier="AboutHeader" id="xq2-Pl-zaG" customClass="AboutPatreonHeaderView" customModule="AltStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="445"/>
|
||||
<collectionReusableView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" reuseIdentifier="AboutHeader" id="xq2-Pl-zaG" customClass="AboutPatreonHeaderView" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="390" height="682"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" axis="vertical" spacing="25" translatesAutoresizingMaskIntoConstraints="NO" id="XiA-Jf-XMp">
|
||||
<rect key="frame" x="16" y="2" width="343" height="393"/>
|
||||
<rect key="frame" x="16" y="2" width="358" height="630"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="10" translatesAutoresizingMaskIntoConstraints="NO" id="5Ol-zN-wYv">
|
||||
<rect key="frame" x="0.0" y="0.0" width="343" height="317"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="358" height="426"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="10" translatesAutoresizingMaskIntoConstraints="NO" id="f7H-EV-7Sx">
|
||||
<rect key="frame" x="0.0" y="0.0" width="343" height="55"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="358" height="55"/>
|
||||
<subviews>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="SideStore" translatesAutoresizingMaskIntoConstraints="NO" id="pn6-Ic-MJm">
|
||||
<rect key="frame" x="0.0" y="0.0" width="55" height="55"/>
|
||||
@@ -31,7 +31,7 @@
|
||||
</constraints>
|
||||
</imageView>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" distribution="equalSpacing" spacing="10" translatesAutoresizingMaskIntoConstraints="NO" id="si2-MA-3RH">
|
||||
<rect key="frame" x="65" y="0.0" width="278" height="55"/>
|
||||
<rect key="frame" x="65" y="0.0" width="293" height="55"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="2" translatesAutoresizingMaskIntoConstraints="NO" id="hkS-oz-wiT">
|
||||
<rect key="frame" x="0.0" y="0.0" width="83" height="55"/>
|
||||
@@ -51,7 +51,7 @@
|
||||
</subviews>
|
||||
</stackView>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="2" translatesAutoresizingMaskIntoConstraints="NO" id="TFB-qo-cbh">
|
||||
<rect key="frame" x="195" y="0.0" width="83" height="55"/>
|
||||
<rect key="frame" x="210" y="0.0" width="83" height="55"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="Zpb-k3-y7l">
|
||||
<rect key="frame" x="0.0" y="0.0" width="83" height="50"/>
|
||||
@@ -75,11 +75,13 @@
|
||||
</constraints>
|
||||
</stackView>
|
||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" editable="NO" textAlignment="natural" selectable="NO" translatesAutoresizingMaskIntoConstraints="NO" id="FeG-e5-LJl">
|
||||
<rect key="frame" x="0.0" y="65" width="343" height="252"/>
|
||||
<rect key="frame" x="0.0" y="65" width="358" height="361"/>
|
||||
<color key="backgroundColor" white="1" alpha="0.13" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<string key="text">Hello, thank you for using SideStore!
|
||||
<string key="text">Thank you for using SideStore!
|
||||
|
||||
If you would subscribe to the patreon that would support us and make sure we can continue developing SideStore for you.
|
||||
Subscribing to the patreon supports us and makes sure we can continue developing SideStore for you.
|
||||
|
||||
Following us on social media allows us to give quick updates and spread the word about sideloading!
|
||||
|
||||
-SideTeam</string>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
@@ -89,10 +91,10 @@ If you would subscribe to the patreon that would support us and make sure we can
|
||||
</subviews>
|
||||
</stackView>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="13" translatesAutoresizingMaskIntoConstraints="NO" id="QS9-vO-bj8">
|
||||
<rect key="frame" x="0.0" y="342" width="343" height="51"/>
|
||||
<rect key="frame" x="0.0" y="451" width="358" height="179"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="yEi-L6-kQ8">
|
||||
<rect key="frame" x="0.0" y="0.0" width="343" height="51"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="358" height="51"/>
|
||||
<color key="backgroundColor" name="SettingsHighlighted"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="51" id="l4o-vb-cMy"/>
|
||||
@@ -102,6 +104,28 @@ If you would subscribe to the patreon that would support us and make sure we can
|
||||
<color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</state>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="hov-Ce-LaM" userLabel="Twitter Button">
|
||||
<rect key="frame" x="0.0" y="64" width="358" height="51"/>
|
||||
<color key="backgroundColor" name="SettingsHighlighted"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="51" id="m0M-GX-KKG"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="19"/>
|
||||
<state key="normal" title="Follow us on Twitter!">
|
||||
<color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</state>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="VdY-7Q-amF" userLabel="Twitter Button">
|
||||
<rect key="frame" x="0.0" y="128" width="358" height="51"/>
|
||||
<color key="backgroundColor" name="SettingsHighlighted"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="51" id="kDo-b8-6tZ"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="19"/>
|
||||
<state key="normal" title="Follow us on Instagram!">
|
||||
<color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</state>
|
||||
</button>
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
@@ -114,19 +138,21 @@ If you would subscribe to the patreon that would support us and make sure we can
|
||||
<constraint firstItem="XiA-Jf-XMp" firstAttribute="top" secondItem="xq2-Pl-zaG" secondAttribute="top" constant="2" id="j8p-JX-Dcz"/>
|
||||
</constraints>
|
||||
<connections>
|
||||
<outlet property="instagramButton" destination="VdY-7Q-amF" id="5kj-9x-k4F"/>
|
||||
<outlet property="rileyImageView" destination="pn6-Ic-MJm" id="60i-Q0-ojz"/>
|
||||
<outlet property="rileyLabel" destination="DTd-Yu-HXr" id="O0y-JB-gWp"/>
|
||||
<outlet property="shaneLabel" destination="Zpb-k3-y7l" id="aQN-6B-s5T"/>
|
||||
<outlet property="supportButton" destination="yEi-L6-kQ8" id="Dzo-vd-SnD"/>
|
||||
<outlet property="textView" destination="FeG-e5-LJl" id="K0M-lF-I6u"/>
|
||||
<outlet property="twitterButton" destination="hov-Ce-LaM" id="gib-Lt-qtY"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="138" y="138"/>
|
||||
<point key="canvasLocation" x="147.82608695652175" y="58.258928571428569"/>
|
||||
</collectionReusableView>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="SideStore" width="180" height="180"/>
|
||||
<image name="SideStore" width="1024" height="1024"/>
|
||||
<namedColor name="SettingsHighlighted">
|
||||
<color red="0.23529411764705882" green="0.0" blue="0.40392156862745099" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color red="0.38823529411764707" green="0.011764705882352941" blue="0.58823529411764708" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</namedColor>
|
||||
</resources>
|
||||
</document>
|
||||
|
||||
@@ -10,6 +10,11 @@ import Foundation
|
||||
|
||||
public struct AnisetteManager {
|
||||
|
||||
var menuURL: String {
|
||||
var url: String
|
||||
url = UserDefaults.standard.menuAnisetteURL
|
||||
return url
|
||||
}
|
||||
/// User defined URL from Settings/UserDefaults
|
||||
static var userURL: String? {
|
||||
var urlString: String?
|
||||
|
||||
179
AltStore/Settings/AnisetteServerList.swift
Normal file
@@ -0,0 +1,179 @@
|
||||
//
|
||||
// AnisetteServerList.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by ny on 6/18/24.
|
||||
// Copyright © 2024 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SwiftUI
|
||||
import AltStoreCore
|
||||
|
||||
typealias SUIButton = SwiftUI.Button
|
||||
|
||||
// MARK: - AnisetteServerData
|
||||
struct AnisetteServerData: Codable {
|
||||
let servers: [Server]
|
||||
}
|
||||
|
||||
// MARK: - Server
|
||||
struct Server: Codable {
|
||||
var name: String
|
||||
var address: String
|
||||
}
|
||||
|
||||
struct AniServer: Codable {
|
||||
var name: String
|
||||
var url: URL
|
||||
}
|
||||
|
||||
class AnisetteViewModel: ObservableObject {
|
||||
@Published var selected: String = ""
|
||||
|
||||
@Published var source: String = "https://servers.sidestore.io/servers.json"
|
||||
@Published var servers: [Server] = []
|
||||
|
||||
func getListOfServers() {
|
||||
URLSession.shared.dataTask(with: URL(string: source)!) { data, response, error in
|
||||
if let error = error {
|
||||
return
|
||||
}
|
||||
if let data = data {
|
||||
do {
|
||||
let servers = try Foundation.JSONDecoder().decode(AnisetteServerData.self, from: data)
|
||||
DispatchQueue.main.async {
|
||||
self.servers = servers.servers.map { Server(name: $0.name, address: $0.address) }
|
||||
}
|
||||
} catch {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
.resume()
|
||||
for server in servers {
|
||||
print(server)
|
||||
print(server.name.count)
|
||||
print(server.name)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct AnisetteServers: View {
|
||||
@Environment(\.presentationMode) var presentationMode
|
||||
@StateObject var viewModel: AnisetteViewModel = AnisetteViewModel()
|
||||
@State var selected: String? = nil
|
||||
var errorCallback: () -> ()
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
ZStack {
|
||||
Color(UIColor(named: "SettingsBackground")!).ignoresSafeArea(.all)
|
||||
.onAppear {
|
||||
viewModel.getListOfServers()
|
||||
}
|
||||
VStack {
|
||||
if #available(iOS 16.0, *) {
|
||||
SwiftUI.List($viewModel.servers, id: \.address, selection: $selected) { server in
|
||||
HStack {
|
||||
VStack(alignment: .leading) {
|
||||
Text("\(server.name.wrappedValue)")
|
||||
.font(.headline)
|
||||
.underline(true, color: .white)
|
||||
Text("\(server.address.wrappedValue)")
|
||||
.fontWeight(.thin)
|
||||
}
|
||||
if selected != nil {
|
||||
if server.address.wrappedValue == selected {
|
||||
Spacer()
|
||||
Image(systemName: "checkmark")
|
||||
.onAppear {
|
||||
UserDefaults.standard.menuAnisetteURL = server.address.wrappedValue
|
||||
print(UserDefaults.synchronize(.standard)())
|
||||
print(UserDefaults.standard.menuAnisetteURL)
|
||||
print(server.address.wrappedValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.backgroundStyle((selected == nil) ? Color(UIColor(named: "SettingsHighlighted")!) : Color(UIColor(named: "SettingsBackground")!))
|
||||
.listRowSeparatorTint(.white)
|
||||
.listRowBackground((selected == nil) ? Color(UIColor(named: "SettingsHighlighted")!).ignoresSafeArea(.all) : Color(UIColor(named: "SettingsBackground")!).ignoresSafeArea(.all))
|
||||
}
|
||||
.listStyle(.plain)
|
||||
.scrollContentBackground(.hidden)
|
||||
.listRowBackground(Color(UIColor(named: "SettingsBackground")!).ignoresSafeArea(.all))
|
||||
|
||||
} else {
|
||||
List(selection: $selected) {
|
||||
ForEach($viewModel.servers, id: \.name) { server in
|
||||
VStack {
|
||||
HStack {
|
||||
Text("\(server.name.wrappedValue)")
|
||||
.foregroundColor(.white)
|
||||
.frame(alignment: .center)
|
||||
Text("\(server.address.wrappedValue)")
|
||||
.foregroundColor(.white)
|
||||
.frame(alignment: .center)
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.listStyle(.plain)
|
||||
// Fallback on earlier versions
|
||||
}
|
||||
if #available(iOS 15.0, *) {
|
||||
TextField("Anisette Server List", text: $viewModel.source)
|
||||
.padding(.leading, 5)
|
||||
.padding(.vertical, 10)
|
||||
.frame(alignment: .center)
|
||||
.textFieldStyle(.plain)
|
||||
.border(.white, width: 1)
|
||||
.onSubmit {
|
||||
UserDefaults.standard.menuAnisetteList = viewModel.source
|
||||
viewModel.getListOfServers()
|
||||
}
|
||||
SUIButton(action: {
|
||||
viewModel.getListOfServers()
|
||||
}, label: {
|
||||
Text("Refresh Servers")
|
||||
})
|
||||
.padding(.bottom, 20)
|
||||
SUIButton(role: .destructive, action: {
|
||||
#if !DEBUG
|
||||
if Keychain.shared.adiPb != nil {
|
||||
Keychain.shared.adiPb = nil
|
||||
}
|
||||
#endif
|
||||
print("Cleared adi.pb from keychain")
|
||||
errorCallback()
|
||||
self.presentationMode.wrappedValue.dismiss()
|
||||
}, label: {
|
||||
Text("Reset adi.pb")
|
||||
// if (selected != nil) {
|
||||
// Text("\(selected!.uuidString)")
|
||||
// }
|
||||
})
|
||||
.padding(.bottom, 20)
|
||||
} else {
|
||||
// Fallback on earlier versions
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.navigationTitle("Anisette Servers")
|
||||
.onAppear {
|
||||
if UserDefaults.standard.menuAnisetteList != "" {
|
||||
viewModel.source = UserDefaults.standard.menuAnisetteList
|
||||
} else {
|
||||
viewModel.source = "https://servers.sidestore.io/servers.json"
|
||||
}
|
||||
print(UserDefaults.standard.menuAnisetteURL)
|
||||
print(UserDefaults.standard.menuAnisetteList)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
53
AltStore/Settings/Error Log/ErrorDetailsViewController.swift
Normal file
@@ -0,0 +1,53 @@
|
||||
//
|
||||
// ErrorDetailsViewController.swift
|
||||
// AltStore
|
||||
//
|
||||
// Created by Riley Testut on 10/5/22.
|
||||
// Copyright © 2022 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
import AltStoreCore
|
||||
|
||||
class ErrorDetailsViewController: UIViewController
|
||||
{
|
||||
var loggedError: LoggedError?
|
||||
|
||||
@IBOutlet private var textView: UITextView!
|
||||
|
||||
override func viewDidLoad()
|
||||
{
|
||||
super.viewDidLoad()
|
||||
|
||||
if let error = self.loggedError?.error
|
||||
{
|
||||
self.title = error.localizedErrorCode
|
||||
|
||||
let font = self.textView.font ?? UIFont.preferredFont(forTextStyle: .body)
|
||||
let detailedDescription = error.formattedDetailedDescription(with: font)
|
||||
self.textView.attributedText = detailedDescription
|
||||
}
|
||||
else
|
||||
{
|
||||
self.title = NSLocalizedString("Error Details", comment: "")
|
||||
}
|
||||
|
||||
self.navigationController?.navigationBar.tintColor = .altPrimary
|
||||
|
||||
if #available(iOS 15, *), let sheetController = self.navigationController?.sheetPresentationController
|
||||
{
|
||||
sheetController.detents = [.medium(), .large()]
|
||||
sheetController.selectedDetentIdentifier = .medium
|
||||
sheetController.prefersGrabberVisible = true
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidLayoutSubviews()
|
||||
{
|
||||
super.viewDidLayoutSubviews()
|
||||
|
||||
self.textView.textContainerInset.left = self.view.layoutMargins.left
|
||||
self.textView.textContainerInset.right = self.view.layoutMargins.right
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,16 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
@objc(ErrorLogMenuButton)
|
||||
private final class ErrorLogMenuButton: UIButton {
|
||||
@available(iOS 14.0, *)
|
||||
override func menuAttachmentPoint(for configuration: UIContextMenuConfiguration) -> CGPoint {
|
||||
var point = super.menuAttachmentPoint(for: configuration)
|
||||
point.y = self.bounds.midY
|
||||
return point
|
||||
}
|
||||
}
|
||||
|
||||
@objc(ErrorLogTableViewCell)
|
||||
final class ErrorLogTableViewCell: UITableViewCell
|
||||
{
|
||||
|
||||
@@ -21,6 +21,13 @@ final class ErrorLogViewController: UITableViewController
|
||||
private lazy var dataSource = self.makeDataSource()
|
||||
private var expandedErrorIDs = Set<NSManagedObjectID>()
|
||||
|
||||
private var isScrolling = false {
|
||||
didSet {
|
||||
guard self.isScrolling != oldValue else { return }
|
||||
self.updateButtonInteractivity()
|
||||
}
|
||||
}
|
||||
|
||||
private lazy var timeFormatter: DateFormatter = {
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateStyle = .none
|
||||
@@ -39,6 +46,15 @@ final class ErrorLogViewController: UITableViewController
|
||||
self.tableView.dataSource = self.dataSource
|
||||
self.tableView.prefetchDataSource = self.dataSource
|
||||
}
|
||||
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||
guard let loggedError = sender as? LoggedError, segue.identifier == "showErrorDetails" else { return }
|
||||
|
||||
let navigationController = segue.destination as! UINavigationController
|
||||
|
||||
let errorDetailsViewController = navigationController.viewControllers.first as! ErrorDetailsViewController
|
||||
errorDetailsViewController.loggedError = loggedError
|
||||
}
|
||||
}
|
||||
|
||||
private extension ErrorLogViewController
|
||||
@@ -60,14 +76,8 @@ private extension ErrorLogViewController
|
||||
let cell = cell as! ErrorLogTableViewCell
|
||||
cell.dateLabel.text = self.timeFormatter.string(from: loggedError.date)
|
||||
cell.errorFailureLabel.text = loggedError.localizedFailure ?? NSLocalizedString("Operation Failed", comment: "")
|
||||
|
||||
switch loggedError.domain
|
||||
{
|
||||
case AltServerErrorDomain: cell.errorCodeLabel?.text = String(format: NSLocalizedString("AltServer Error %@", comment: ""), NSNumber(value: loggedError.code))
|
||||
case OperationError.domain: cell.errorCodeLabel?.text = String(format: NSLocalizedString("AltStore Error %@", comment: ""), NSNumber(value: loggedError.code))
|
||||
default: cell.errorCodeLabel?.text = loggedError.error.localizedErrorCode
|
||||
}
|
||||
|
||||
cell.errorCodeLabel.text = loggedError.error.localizedErrorCode
|
||||
|
||||
let nsError = loggedError.error as NSError
|
||||
let errorDescription = [nsError.localizedDescription, nsError.localizedRecoverySuggestion].compactMap { $0 }.joined(separator: "\n\n")
|
||||
cell.errorDescriptionTextView.text = errorDescription
|
||||
@@ -93,12 +103,19 @@ private extension ErrorLogViewController
|
||||
},
|
||||
UIAction(title: NSLocalizedString("Search FAQ", comment: ""), image: UIImage(systemName: "magnifyingglass")) { [weak self] _ in
|
||||
self?.searchFAQ(for: loggedError)
|
||||
},
|
||||
UIAction(title: NSLocalizedString("View More Details", comment: ""), image: UIImage(systemName: "ellipsis.circle")) { [weak self] _ in
|
||||
|
||||
}
|
||||
])
|
||||
|
||||
cell.menuButton.menu = menu
|
||||
cell.menuButton.showsMenuAsPrimaryAction = self.isScrolling ? false : true
|
||||
cell.selectionStyle = .none
|
||||
} else {
|
||||
cell.menuButton.isUserInteractionEnabled = false
|
||||
}
|
||||
|
||||
|
||||
// Include errorDescriptionTextView's text in cell summary.
|
||||
cell.accessibilityLabel = [cell.errorFailureLabel.text, cell.dateLabel.text, cell.errorCodeLabel.text, cell.errorDescriptionTextView.text].compactMap { $0 }.joined(separator: ". ")
|
||||
|
||||
@@ -232,22 +249,27 @@ private extension ErrorLogViewController
|
||||
|
||||
func searchFAQ(for loggedError: LoggedError)
|
||||
{
|
||||
let baseURL = URL(string: "https://faq.altstore.io/getting-started/troubleshooting-guide")!
|
||||
let baseURL = URL(string: "https://faq.altstore.io/getting-started/error-codes")!
|
||||
var components = URLComponents(url: baseURL, resolvingAgainstBaseURL: false)!
|
||||
|
||||
let query = [loggedError.domain, "\(loggedError.code)"].joined(separator: "+")
|
||||
let query = [loggedError.domain, "\(loggedError.error.displayCode)"].joined(separator: "+")
|
||||
components.queryItems = [URLQueryItem(name: "q", value: query)]
|
||||
|
||||
let safariViewController = SFSafariViewController(url: components.url ?? baseURL)
|
||||
safariViewController.preferredControlTintColor = .altPrimary
|
||||
self.present(safariViewController, animated: true)
|
||||
}
|
||||
|
||||
func viewMoreDetails(for loggedError: LoggedError) {
|
||||
self.performSegue(withIdentifier: "showErrorDetails", sender: loggedError)
|
||||
}
|
||||
}
|
||||
|
||||
extension ErrorLogViewController
|
||||
{
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
|
||||
{
|
||||
guard #unavailable(iOS 14) else { return }
|
||||
let loggedError = self.dataSource.item(at: indexPath)
|
||||
|
||||
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
||||
@@ -321,3 +343,32 @@ extension ErrorLogViewController: QLPreviewControllerDataSource {
|
||||
return fileURL as QLPreviewItem
|
||||
}
|
||||
}
|
||||
|
||||
extension ErrorLogViewController
|
||||
{
|
||||
override func scrollViewWillBeginDragging(_ scrollView: UIScrollView)
|
||||
{
|
||||
self.isScrolling = true
|
||||
}
|
||||
|
||||
override func scrollViewDidEndDecelerating(_ scrollView: UIScrollView)
|
||||
{
|
||||
self.isScrolling = false
|
||||
}
|
||||
|
||||
override func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool)
|
||||
{
|
||||
guard !decelerate else { return }
|
||||
self.isScrolling = false
|
||||
}
|
||||
|
||||
private func updateButtonInteractivity()
|
||||
{
|
||||
guard #available(iOS 14, *) else { return }
|
||||
|
||||
for case let cell as ErrorLogTableViewCell in self.tableView.visibleCells
|
||||
{
|
||||
cell.menuButton.showsMenuAsPrimaryAction = self.isScrolling ? false : true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +56,8 @@ final class PatronsFooterView: UICollectionReusableView
|
||||
final class AboutPatreonHeaderView: UICollectionReusableView
|
||||
{
|
||||
@IBOutlet var supportButton: UIButton!
|
||||
@IBOutlet var twitterButton: UIButton!
|
||||
@IBOutlet var instagramButton: UIButton!
|
||||
@IBOutlet var accountButton: UIButton!
|
||||
@IBOutlet var textView: UITextView!
|
||||
|
||||
@@ -79,12 +81,12 @@ final class AboutPatreonHeaderView: UICollectionReusableView
|
||||
imageView.layer.cornerRadius = imageView.bounds.midY
|
||||
}
|
||||
|
||||
for button in [self.supportButton, self.accountButton].compactMap({ $0 })
|
||||
for button in [self.supportButton, self.accountButton, self.twitterButton, self.instagramButton].compactMap({ $0 })
|
||||
{
|
||||
button.clipsToBounds = true
|
||||
button.layer.cornerRadius = 16
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func layoutMarginsDidChange()
|
||||
{
|
||||
|
||||
@@ -111,7 +111,9 @@ private extension PatreonViewController
|
||||
headerView.layoutMargins = self.view.layoutMargins
|
||||
|
||||
headerView.supportButton.addTarget(self, action: #selector(PatreonViewController.openPatreonURL(_:)), for: .primaryActionTriggered)
|
||||
|
||||
headerView.twitterButton.addTarget(self, action: #selector(PatreonViewController.openTwitterURL(_:)), for: .primaryActionTriggered)
|
||||
headerView.instagramButton.addTarget(self, action: #selector(PatreonViewController.openInstagramURL(_:)), for: .primaryActionTriggered)
|
||||
|
||||
let defaultSupportButtonTitle = NSLocalizedString("Become a patron", comment: "")
|
||||
let isPatronSupportButtonTitle = NSLocalizedString("View Patreon", comment: "")
|
||||
|
||||
@@ -180,6 +182,24 @@ private extension PatreonViewController
|
||||
self.present(safariViewController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
@objc func openTwitterURL(_ sender: UIButton)
|
||||
{
|
||||
let twitterURL = URL(string: "https://twitter.com/SideStore_io")!
|
||||
|
||||
let safariViewController = SFSafariViewController(url: twitterURL)
|
||||
safariViewController.preferredControlTintColor = self.view.tintColor
|
||||
self.present(safariViewController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
@objc func openInstagramURL(_ sender: UIButton)
|
||||
{
|
||||
let twitterURL = URL(string: "https://instagram.com/sidestore.io")!
|
||||
|
||||
let safariViewController = SFSafariViewController(url: twitterURL)
|
||||
safariViewController.preferredControlTintColor = self.view.tintColor
|
||||
self.present(safariViewController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
@IBAction func authenticate(_ sender: UIBarButtonItem)
|
||||
{
|
||||
PatreonAPI.shared.authenticate { (result) in
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="5Rz-4h-jJ8">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="5Rz-4h-jJ8">
|
||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22685"/>
|
||||
<capability name="Named colors" minToolsVersion="9.0"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
@@ -20,8 +20,8 @@
|
||||
<color key="backgroundColor" name="SettingsBackground"/>
|
||||
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<color key="separatorColor" white="1" alpha="0.25" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<label key="tableFooterView" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="SideStore 1.0" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="bUR-rp-Nw2">
|
||||
<rect key="frame" x="0.0" y="1194" width="375" height="25"/>
|
||||
<label key="tableFooterView" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="SideStore 1.0" textAlignment="center" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" id="bUR-rp-Nw2">
|
||||
<rect key="frame" x="0.0" y="1245" width="375" height="25"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="0.69999999999999996" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
@@ -167,8 +167,8 @@
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Join the beta" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="3Il-5a-5Zp">
|
||||
<rect key="frame" x="30" y="15.5" width="106" height="20.5"/>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Support the team" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="3Il-5a-5Zp">
|
||||
<rect key="frame" x="30" y="15.5" width="142.5" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
@@ -236,15 +236,51 @@
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="NO"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="amC-sE-8O0" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="GYp-O0-pse" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="444" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="GYp-O0-pse" id="vDG-ZV-xRS">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Disable Idle Timeout" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="PCh-Up-aJJ">
|
||||
<rect key="frame" x="30" y="15.5" width="166" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="iQA-wm-5ag">
|
||||
<rect key="frame" x="296" y="10" width="51" height="31"/>
|
||||
<connections>
|
||||
<action selector="toggleNoIdleTimeoutEnabled:" destination="aMk-Xp-UL8" eventType="valueChanged" id="WSl-Jc-g5J"/>
|
||||
</connections>
|
||||
</switch>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="iQA-wm-5ag" secondAttribute="trailing" id="MJ1-HF-Dln"/>
|
||||
<constraint firstItem="PCh-Up-aJJ" firstAttribute="leading" secondItem="vDG-ZV-xRS" secondAttribute="leadingMargin" id="Ocu-jn-RAQ"/>
|
||||
<constraint firstItem="iQA-wm-5ag" firstAttribute="centerY" secondItem="vDG-ZV-xRS" secondAttribute="centerY" id="c6W-bN-VAb"/>
|
||||
<constraint firstItem="PCh-Up-aJJ" firstAttribute="centerY" secondItem="vDG-ZV-xRS" secondAttribute="centerY" id="mL6-LB-cjn"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<color key="backgroundColor" white="1" alpha="0.14999999999999999" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<edgeInsets key="layoutMargins" top="8" left="30" bottom="8" right="30"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="style">
|
||||
<integer key="value" value="2"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="YES"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="amC-sE-8O0" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="495" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="amC-sE-8O0" id="GEO-2e-E4k">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Add to Siri…" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="c6K-fI-CVr">
|
||||
<rect key="frame" x="30" y="15.5" width="100.5" height="20.5"/>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Allow Siri To Refresh Apps…" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="c6K-fI-CVr">
|
||||
<rect key="frame" x="30" y="15.5" width="228.5" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
@@ -269,7 +305,7 @@
|
||||
<tableViewSection headerTitle="" id="eHy-qI-w5w">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="30h-59-88f" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="535" width="375" height="51"/>
|
||||
<rect key="frame" x="0.0" y="586" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="30h-59-88f" id="7qD-DW-Jls">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
@@ -309,28 +345,28 @@
|
||||
<tableViewSection headerTitle="" id="J90-vn-u2O">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="i4T-2q-jF3" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="626" width="375" height="51"/>
|
||||
<rect key="frame" x="0.0" y="677" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="i4T-2q-jF3" id="VTQ-H4-aCM">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Developers" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="hRA-OK-Vjw">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Developers" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="hRA-OK-Vjw">
|
||||
<rect key="frame" x="30" y="15.5" width="86" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="0.80000000000000004" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" spacing="14" translatesAutoresizingMaskIntoConstraints="NO" id="lx9-35-OSk">
|
||||
<stackView opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="14" translatesAutoresizingMaskIntoConstraints="NO" id="lx9-35-OSk">
|
||||
<rect key="frame" x="187.5" y="15.5" width="157.5" height="20.5"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="SideStore Team" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="JAA-iZ-VGb">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="SideStore Team" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="JAA-iZ-VGb">
|
||||
<rect key="frame" x="0.0" y="0.0" width="125.5" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="Mmj-3V-fTb">
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="Mmj-3V-fTb">
|
||||
<rect key="frame" x="139.5" y="0.0" width="18" height="20.5"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
@@ -353,28 +389,28 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="oHX-oR-nwJ" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="677" width="375" height="51"/>
|
||||
<rect key="frame" x="0.0" y="721" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="oHX-oR-nwJ" id="hN4-i5-igu">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="UI Designer" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="oqY-wY-1Vf">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="UI Designer" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="oqY-wY-1Vf">
|
||||
<rect key="frame" x="30" y="15.5" width="89" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="0.80000000000000004" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" spacing="14" translatesAutoresizingMaskIntoConstraints="NO" id="gUq-6Q-t5X">
|
||||
<stackView opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="14" translatesAutoresizingMaskIntoConstraints="NO" id="gUq-6Q-t5X">
|
||||
<rect key="frame" x="198" y="15.5" width="147" height="20.5"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Fabian (thdev)" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ylE-VL-7Fq">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Fabian (thdev)" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ylE-VL-7Fq">
|
||||
<rect key="frame" x="0.0" y="0.0" width="115" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="e3L-vR-Jae">
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="e3L-vR-Jae">
|
||||
<rect key="frame" x="129" y="0.0" width="18" height="20.5"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
@@ -397,7 +433,7 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="0MT-ht-Sit" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="728" width="375" height="51"/>
|
||||
<rect key="frame" x="0.0" y="772" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="0MT-ht-Sit" id="OZp-WM-5H7">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
@@ -441,7 +477,7 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="O5R-Al-lGj" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="779" width="375" height="51"/>
|
||||
<rect key="frame" x="0.0" y="823" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="O5R-Al-lGj" id="CrG-Mr-xQq">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
@@ -481,7 +517,7 @@
|
||||
<tableViewSection headerTitle="" id="OMa-EK-hRI">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="FMZ-as-Ljo" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="870" width="375" height="51"/>
|
||||
<rect key="frame" x="0.0" y="914" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="FMZ-as-Ljo" id="JzL-Of-A3T">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
@@ -514,7 +550,7 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="Qca-pU-sJh" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="921" width="375" height="51"/>
|
||||
<rect key="frame" x="0.0" y="965" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Qca-pU-sJh" id="QtU-8J-VQN">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
@@ -550,7 +586,7 @@
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="rE2-P4-OaE" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="972" width="375" height="51"/>
|
||||
<rect key="frame" x="0.0" y="1016" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="rE2-P4-OaE" id="qIT-rz-ZUb">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
@@ -582,11 +618,77 @@
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="YES"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
<connections>
|
||||
<segue destination="g8a-Rf-zWa" kind="show" identifier="showErrorLog" id="SSW-vL-86I"/>
|
||||
<segue destination="g8a-Rf-zWa" kind="show" identifier="showErrorLog" id="vFC-Id-Ww6"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="VrV-qI-zXF" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1067" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="VrV-qI-zXF" id="w1r-uY-4pD">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Refresh SideJITServer" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="46q-DB-5nc">
|
||||
<rect key="frame" x="30" y="15.5" width="183" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="wvD-eZ-nQI">
|
||||
<rect key="frame" x="327" y="16.5" width="18" height="18"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="wvD-eZ-nQI" firstAttribute="centerY" secondItem="w1r-uY-4pD" secondAttribute="centerY" id="O6Y-Y1-yxv"/>
|
||||
<constraint firstItem="46q-DB-5nc" firstAttribute="centerY" secondItem="w1r-uY-4pD" secondAttribute="centerY" id="ROS-YF-6jb"/>
|
||||
<constraint firstItem="46q-DB-5nc" firstAttribute="leading" secondItem="w1r-uY-4pD" secondAttribute="leadingMargin" id="acd-O8-WTI"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="wvD-eZ-nQI" secondAttribute="trailing" id="taB-EP-QMM"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<color key="backgroundColor" white="1" alpha="0.14999999999999999" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<edgeInsets key="layoutMargins" top="8" left="30" bottom="8" right="30"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="style">
|
||||
<integer key="value" value="2"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="YES"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="eZ3-BT-q4D" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1074" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="eZ3-BT-q4D" id="17m-VV-hzf">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Clear Cache" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="IbH-V1-ce3">
|
||||
<rect key="frame" x="30" y="15.5" width="98.5" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="FZe-BJ-fOm">
|
||||
<rect key="frame" x="327" y="16.5" width="18" height="18"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="FZe-BJ-fOm" firstAttribute="centerY" secondItem="17m-VV-hzf" secondAttribute="centerY" id="bGv-Np-5aO"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="FZe-BJ-fOm" secondAttribute="trailing" id="ccb-JP-Eqi"/>
|
||||
<constraint firstItem="IbH-V1-ce3" firstAttribute="centerY" secondItem="17m-VV-hzf" secondAttribute="centerY" id="iQJ-gN-sRF"/>
|
||||
<constraint firstItem="IbH-V1-ce3" firstAttribute="leading" secondItem="17m-VV-hzf" secondAttribute="leadingMargin" id="m1g-Y6-aT5"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<color key="backgroundColor" white="1" alpha="0.14999999999999999" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<edgeInsets key="layoutMargins" top="8" left="30" bottom="8" right="30"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="style">
|
||||
<integer key="value" value="2"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="YES"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="VNn-u4-cN8" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1023" width="375" height="51"/>
|
||||
<rect key="frame" x="0.0" y="1125" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="VNn-u4-cN8" id="4bh-qe-l2N">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
@@ -619,14 +721,14 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="e7s-hL-kv9" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1074" width="375" height="51"/>
|
||||
<rect key="frame" x="0.0" y="1176" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="e7s-hL-kv9" id="yjL-Mu-HTk">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Reset adi.pb" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="eds-Dj-36y">
|
||||
<rect key="frame" x="30" y="15.5" width="102" height="20.5"/>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Anisette Servers" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="eds-Dj-36y">
|
||||
<rect key="frame" x="30" y="15.5" width="135.5" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
@@ -644,39 +746,6 @@
|
||||
</tableViewCellContentView>
|
||||
<color key="backgroundColor" white="1" alpha="0.14999999999999999" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<edgeInsets key="layoutMargins" top="8" left="30" bottom="8" right="30"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="style">
|
||||
<integer key="value" value="2"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="YES"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="fj2-EJ-Z98" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1125" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="fj2-EJ-Z98" id="BcT-Fs-KNg">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Advanced Settings" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="OcM-OM-uDE">
|
||||
<rect key="frame" x="30" y="15.5" width="154" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="Pcu-Sy-yfZ">
|
||||
<rect key="frame" x="327" y="16.5" width="18" height="18"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="Pcu-Sy-yfZ" secondAttribute="trailing" id="CFy-IO-4eb"/>
|
||||
<constraint firstItem="OcM-OM-uDE" firstAttribute="centerY" secondItem="BcT-Fs-KNg" secondAttribute="centerY" id="OGl-h4-FPx"/>
|
||||
<constraint firstItem="Pcu-Sy-yfZ" firstAttribute="centerY" secondItem="BcT-Fs-KNg" secondAttribute="centerY" id="R7L-4O-lTn"/>
|
||||
<constraint firstItem="OcM-OM-uDE" firstAttribute="leading" secondItem="BcT-Fs-KNg" secondAttribute="leadingMargin" id="yoh-C6-UC5"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<color key="backgroundColor" white="1" alpha="0.14999999999999999" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<edgeInsets key="layoutMargins" top="8" left="30" bottom="8" right="30"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="style">
|
||||
<integer key="value" value="3"/>
|
||||
@@ -689,7 +758,6 @@
|
||||
</sections>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="aMk-Xp-UL8" id="c6c-fR-8C4"/>
|
||||
<outlet property="delegate" destination="aMk-Xp-UL8" id="moP-1B-lRq"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
<navigationItem key="navigationItem" title="Settings" id="Ddg-UQ-KJ8"/>
|
||||
@@ -698,6 +766,7 @@
|
||||
<outlet property="accountNameLabel" destination="CnN-M1-AYK" id="Ldc-Py-Bix"/>
|
||||
<outlet property="accountTypeLabel" destination="434-MW-Den" id="mNB-QE-4Jg"/>
|
||||
<outlet property="backgroundRefreshSwitch" destination="DPu-zD-Als" id="eiG-Hv-Vko"/>
|
||||
<outlet property="noIdleTimeoutSwitch" destination="iQA-wm-5ag" id="jHC-js-q0Y"/>
|
||||
<outlet property="versionLabel" destination="bUR-rp-Nw2" id="85I-5R-hqz"/>
|
||||
</connections>
|
||||
</tableViewController>
|
||||
@@ -713,7 +782,7 @@
|
||||
<toolbarItems/>
|
||||
<simulatedTabBarMetrics key="simulatedBottomBarMetrics"/>
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" barStyle="black" largeTitles="YES" id="Jtn-cs-Tvp" customClass="NavigationBar" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="96"/>
|
||||
<rect key="frame" x="0.0" y="20" width="375" height="96"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<color key="barTintColor" name="SettingsBackground"/>
|
||||
@@ -814,7 +883,7 @@
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" layoutMarginsFollowReadableWidth="YES" contentInsetAdjustmentBehavior="never" indicatorStyle="white" editable="NO" translatesAutoresizingMaskIntoConstraints="NO" id="oQQ-pR-oKc">
|
||||
<rect key="frame" x="0.0" y="44" width="375" height="574"/>
|
||||
<rect key="frame" x="0.0" y="64" width="375" height="554"/>
|
||||
<edgeInsets key="layoutMargins" top="8" left="30" bottom="8" right="30"/>
|
||||
<string key="text">Jay Freeman (ldid)
|
||||
Copyright (C) 2007-2012 Jay Freeman (saurik)
|
||||
@@ -917,7 +986,7 @@ Settings by i cons from the Noun Project</string>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1697" y="313"/>
|
||||
</scene>
|
||||
<!--Patreon-->
|
||||
<!--Support us-->
|
||||
<scene sceneID="Lnh-9P-HnL">
|
||||
<objects>
|
||||
<collectionViewController id="dp8-8j-vt9" customClass="PatreonViewController" customModule="SideStore" customModuleProvider="target" sceneMemberID="viewController">
|
||||
@@ -964,7 +1033,7 @@ Settings by i cons from the Noun Project</string>
|
||||
<outlet property="delegate" destination="dp8-8j-vt9" id="790-Kr-6l7"/>
|
||||
</connections>
|
||||
</collectionView>
|
||||
<navigationItem key="navigationItem" title="Patreon" largeTitleDisplayMode="always" id="uUV-1f-xEq"/>
|
||||
<navigationItem key="navigationItem" title="Support us" largeTitleDisplayMode="always" id="uUV-1f-xEq"/>
|
||||
</collectionViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="qq3-Hj-S9f" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
@@ -1048,7 +1117,7 @@ Settings by i cons from the Noun Project</string>
|
||||
</textView>
|
||||
</subviews>
|
||||
</stackView>
|
||||
<button opaque="NO" contentMode="scaleToFill" showsMenuAsPrimaryAction="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="ba2-EY-tf5">
|
||||
<button opaque="NO" contentMode="scaleToFill" showsMenuAsPrimaryAction="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="ba2-EY-tf5" customClass="ErrorLogMenuButton">
|
||||
<rect key="frame" x="0.0" y="0.0" width="343" height="107.5"/>
|
||||
<accessibility key="accessibilityConfiguration">
|
||||
<bool key="isElement" value="NO"/>
|
||||
@@ -1097,11 +1166,73 @@ Settings by i cons from the Noun Project</string>
|
||||
</barButtonItem>
|
||||
</rightBarButtonItems>
|
||||
</navigationItem>
|
||||
<connections>
|
||||
<segue destination="7gm-d1-zWK" kind="presentation" identifier="showErrorDetails" id="9vz-y6-evp"/>
|
||||
</connections>
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="rU1-TZ-TD8" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1697" y="1774"/>
|
||||
</scene>
|
||||
<!--Error Details View Controller-->
|
||||
<scene sceneID="XNO-Yg-I7t">
|
||||
<objects>
|
||||
<viewController id="xB2-Se-VVg" customClass="ErrorDetailsViewController" customModule="SideStore" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="eBQ-se-VIy">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="647"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" editable="NO" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="ctd-NB-4ov">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="647"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
<color key="textColor" systemColor="labelColor"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||
</textView>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="Nm8-69-Ngi"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
<constraints>
|
||||
<constraint firstItem="ctd-NB-4ov" firstAttribute="leading" secondItem="eBQ-se-VIy" secondAttribute="leading" id="Cv1-Te-gBH"/>
|
||||
<constraint firstItem="ctd-NB-4ov" firstAttribute="top" secondItem="eBQ-se-VIy" secondAttribute="top" id="HRY-Rg-iMI"/>
|
||||
<constraint firstAttribute="trailing" secondItem="ctd-NB-4ov" secondAttribute="trailing" id="Lc1-K7-iuq"/>
|
||||
<constraint firstAttribute="bottom" secondItem="ctd-NB-4ov" secondAttribute="bottom" id="zCz-Cy-Y5z"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<navigationItem key="navigationItem" id="XpE-V9-EaY">
|
||||
<barButtonItem key="rightBarButtonItem" style="done" systemItem="done" id="rnr-dX-4Ev">
|
||||
<connections>
|
||||
<segue destination="ZSp-1n-UJ9" kind="unwind" unwindAction="unwindFromErrorDetails:" id="TFu-zD-QyF"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
</navigationItem>
|
||||
<connections>
|
||||
<outlet property="textView" destination="ctd-NB-4ov" id="x2C-9R-Xz1"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="8AM-Vx-XTN" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
<exit id="ZSp-1n-UJ9" userLabel="Exit" sceneMemberID="exit"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="3389.5999999999999" y="1772.5637181409297"/>
|
||||
</scene>
|
||||
<!--Navigation Controller-->
|
||||
<scene sceneID="4LJ-Od-dCK">
|
||||
<objects>
|
||||
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="7gm-d1-zWK" sceneMemberID="viewController">
|
||||
<toolbarItems/>
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" id="dI0-sh-yGf">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="56"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</navigationBar>
|
||||
<nil name="viewControllers"/>
|
||||
<connections>
|
||||
<segue destination="xB2-Se-VVg" kind="relationship" relationship="rootViewController" id="RpP-UM-JfJ"/>
|
||||
</connections>
|
||||
</navigationController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="OXW-bf-HIj" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="2554" y="1773"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="Next" width="18" height="18"/>
|
||||
@@ -1114,7 +1245,10 @@ Settings by i cons from the Noun Project</string>
|
||||
<color white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</systemColor>
|
||||
<systemColor name="labelColor">
|
||||
<color red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</systemColor>
|
||||
<systemColor name="systemBackgroundColor">
|
||||
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</systemColor>
|
||||
</resources>
|
||||
</document>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SwiftUI
|
||||
import SafariServices
|
||||
import MessageUI
|
||||
import Intents
|
||||
@@ -30,13 +31,14 @@ extension SettingsViewController
|
||||
fileprivate enum AppRefreshRow: Int, CaseIterable
|
||||
{
|
||||
case backgroundRefresh
|
||||
case noIdleTimeout
|
||||
|
||||
@available(iOS 14, *)
|
||||
case addToSiri
|
||||
|
||||
static var allCases: [AppRefreshRow] {
|
||||
guard #available(iOS 14, *) else { return [.backgroundRefresh] }
|
||||
return [.backgroundRefresh, .addToSiri]
|
||||
guard #available(iOS 14, *) else { return [.backgroundRefresh, .noIdleTimeout] }
|
||||
return [.backgroundRefresh, .noIdleTimeout, .addToSiri]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,9 +55,12 @@ extension SettingsViewController
|
||||
case sendFeedback
|
||||
case refreshAttempts
|
||||
case errorLog
|
||||
case refreshSideJITServer
|
||||
case clearCache
|
||||
case resetPairingFile
|
||||
case resetAdiPb
|
||||
case anisetteServers
|
||||
case advancedSettings
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,6 +78,9 @@ final class SettingsViewController: UITableViewController
|
||||
@IBOutlet private var accountTypeLabel: UILabel!
|
||||
|
||||
@IBOutlet private var backgroundRefreshSwitch: UISwitch!
|
||||
@IBOutlet private var noIdleTimeoutSwitch: UISwitch!
|
||||
|
||||
@IBOutlet private var refreshSideJITServer: UILabel!
|
||||
|
||||
@IBOutlet private var versionLabel: UILabel!
|
||||
|
||||
@@ -85,6 +93,7 @@ final class SettingsViewController: UITableViewController
|
||||
super.init(coder: aDecoder)
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(SettingsViewController.openPatreonSettings(_:)), name: AppDelegate.openPatreonSettingsDeepLinkNotification, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(SettingsViewController.openErrorLog(_:)), name: ToastView.openErrorLogNotification, object: nil)
|
||||
}
|
||||
|
||||
override func viewDidLoad()
|
||||
@@ -102,16 +111,36 @@ final class SettingsViewController: UITableViewController
|
||||
debugModeGestureRecognizer.numberOfTouchesRequired = 3
|
||||
self.tableView.addGestureRecognizer(debugModeGestureRecognizer)
|
||||
|
||||
var versionString: String = ""
|
||||
if let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String
|
||||
{
|
||||
self.versionLabel.text = NSLocalizedString(String(format: "SideStore %@", version), comment: "SideStore Version")
|
||||
versionString += "SideStore \(version)"
|
||||
if let xcode = Bundle.main.object(forInfoDictionaryKey: "DTXcode") as? String {
|
||||
versionString += " - Xcode \(xcode) - "
|
||||
if let build = Bundle.main.object(forInfoDictionaryKey: "DTXcodeBuild") as? String {
|
||||
versionString += "\(build)"
|
||||
}
|
||||
}
|
||||
if let pairing = Bundle.main.object(forInfoDictionaryKey: "ALTPairingFile") as? String {
|
||||
let pair_test = pairing == "<insert pairing file here>"
|
||||
if !pair_test {
|
||||
versionString += " - \(!pair_test)"
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
self.versionLabel.text = NSLocalizedString("SideStore", comment: "")
|
||||
versionString += "SideStore\t"
|
||||
}
|
||||
versionString += "\n\(Bundle.Info.appbundleIdentifier)"
|
||||
|
||||
self.versionLabel.text = NSLocalizedString(versionString, comment: "SideStore Version")
|
||||
|
||||
self.tableView.contentInset.bottom = 20
|
||||
self.versionLabel.numberOfLines = 0
|
||||
self.versionLabel.lineBreakMode = .byWordWrapping
|
||||
self.versionLabel.setNeedsUpdateConstraints()
|
||||
|
||||
self.tableView.contentInset.bottom = 40
|
||||
|
||||
self.update()
|
||||
|
||||
@@ -128,6 +157,18 @@ final class SettingsViewController: UITableViewController
|
||||
|
||||
self.update()
|
||||
}
|
||||
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||
if segue.identifier == "anisetteServers" {
|
||||
let controller = UIHostingController(rootView: AnisetteServers(selected: UserDefaults.standard.menuAnisetteURL, errorCallback: {
|
||||
ToastView(text: "Cleared adi.pb!", detailText: "You will need to log back into Apple ID in SideStore.").show(in: self)
|
||||
}))
|
||||
self.show(controller, sender: nil)
|
||||
} else {
|
||||
super.prepare(for: segue, sender: sender)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private extension SettingsViewController
|
||||
@@ -148,6 +189,7 @@ private extension SettingsViewController
|
||||
}
|
||||
|
||||
self.backgroundRefreshSwitch.isOn = UserDefaults.standard.isBackgroundRefreshEnabled
|
||||
self.noIdleTimeoutSwitch.isOn = UserDefaults.standard.isIdleTimeoutDisableEnabled
|
||||
|
||||
if self.isViewLoaded
|
||||
{
|
||||
@@ -178,11 +220,11 @@ private extension SettingsViewController
|
||||
case .patreon:
|
||||
if isHeader
|
||||
{
|
||||
settingsHeaderFooterView.primaryLabel.text = NSLocalizedString("PATREON", comment: "")
|
||||
settingsHeaderFooterView.primaryLabel.text = NSLocalizedString("SUPPORT US", comment: "")
|
||||
}
|
||||
else
|
||||
{
|
||||
settingsHeaderFooterView.secondaryLabel.text = NSLocalizedString("Support the SideStore Team by becoming a patron!", comment: "")
|
||||
settingsHeaderFooterView.secondaryLabel.text = NSLocalizedString("Support the SideStore Team by following our socials or becoming a patron!", comment: "")
|
||||
}
|
||||
|
||||
case .account:
|
||||
@@ -199,7 +241,7 @@ private extension SettingsViewController
|
||||
}
|
||||
else
|
||||
{
|
||||
settingsHeaderFooterView.secondaryLabel.text = NSLocalizedString("Enable Background Refresh to automatically refresh apps in the background when connected to Wi-Fi.", comment: "")
|
||||
settingsHeaderFooterView.secondaryLabel.text = NSLocalizedString("Enable Background Refresh to automatically refresh apps in the background when connected to Wi-Fi. \n\nDisable the Idle Timeout toggle to allow SideStore to not let your device go to sleep during a refresh or install of any apps.", comment: "")
|
||||
}
|
||||
|
||||
case .instructions:
|
||||
@@ -280,6 +322,11 @@ private extension SettingsViewController
|
||||
UserDefaults.standard.isBackgroundRefreshEnabled = sender.isOn
|
||||
}
|
||||
|
||||
@IBAction func toggleNoIdleTimeoutEnabled(_ sender: UISwitch)
|
||||
{
|
||||
UserDefaults.standard.isIdleTimeoutDisableEnabled = sender.isOn
|
||||
}
|
||||
|
||||
@available(iOS 14, *)
|
||||
@IBAction func addRefreshAppsShortcut()
|
||||
{
|
||||
@@ -291,6 +338,39 @@ private extension SettingsViewController
|
||||
self.present(viewController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
func clearCache()
|
||||
{
|
||||
let alertController = UIAlertController(title: NSLocalizedString("Are you sure you want to clear SideStore's cache?", comment: ""),
|
||||
message: NSLocalizedString("This will remove all temporary files as well as backups for uninstalled apps.", comment: ""),
|
||||
preferredStyle: .actionSheet)
|
||||
alertController.addAction(UIAlertAction(title: UIAlertAction.cancel.title, style: UIAlertAction.cancel.style) { [weak self] _ in
|
||||
self?.tableView.indexPathForSelectedRow.map { self?.tableView.deselectRow(at: $0, animated: true) }
|
||||
})
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Clear Cache", comment: ""), style: .destructive) { [weak self] _ in
|
||||
AppManager.shared.clearAppCache { result in
|
||||
DispatchQueue.main.async {
|
||||
self?.tableView.indexPathForSelectedRow.map { self?.tableView.deselectRow(at: $0, animated: true) }
|
||||
|
||||
switch result
|
||||
{
|
||||
case .success: break
|
||||
case .failure(let error):
|
||||
let alertController = UIAlertController(title: NSLocalizedString("Unable to Clear Cache", comment: ""), message: error.localizedDescription, preferredStyle: .alert)
|
||||
alertController.addAction(.ok)
|
||||
self?.present(alertController, animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if let popoverController = alertController.popoverPresentationController {
|
||||
popoverController.sourceView = self.view
|
||||
popoverController.sourceRect = CGRect(x: self.view.bounds.midX, y: self.view.bounds.midY, width: 0, height: 0)
|
||||
}
|
||||
|
||||
self.present(alertController, animated: true)
|
||||
}
|
||||
|
||||
@IBAction func handleDebugModeGesture(_ gestureRecognizer: UISwipeGestureRecognizer)
|
||||
{
|
||||
self.debugGestureCounter += 1
|
||||
@@ -345,6 +425,15 @@ private extension SettingsViewController
|
||||
self.performSegue(withIdentifier: "showPatreon", sender: nil)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func openErrorLog(_: Notification) {
|
||||
guard self.presentedViewController == nil else { return }
|
||||
|
||||
self.navigationController?.popViewController(animated: false)
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
self.performSegue(withIdentifier: "showErrorLog", sender: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension SettingsViewController
|
||||
@@ -386,6 +475,17 @@ extension SettingsViewController
|
||||
cell.style = .single
|
||||
}
|
||||
|
||||
if AppRefreshRow.AllCases().count == 1
|
||||
{
|
||||
if let cell = cell as? InsetGroupTableViewCell,
|
||||
indexPath.section == Section.appRefresh.rawValue,
|
||||
indexPath.row == AppRefreshRow.backgroundRefresh.rawValue
|
||||
{
|
||||
cell.style = .single
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return cell
|
||||
}
|
||||
|
||||
@@ -465,11 +565,13 @@ extension SettingsViewController
|
||||
switch row
|
||||
{
|
||||
case .backgroundRefresh: break
|
||||
case .noIdleTimeout: break
|
||||
case .addToSiri:
|
||||
guard #available(iOS 14, *) else { return }
|
||||
self.addRefreshAppsShortcut()
|
||||
}
|
||||
|
||||
|
||||
case .credits:
|
||||
let row = CreditsRow.allCases[indexPath.row]
|
||||
switch row
|
||||
@@ -507,6 +609,65 @@ extension SettingsViewController
|
||||
let toastView = ToastView(text: NSLocalizedString("Cannot Send Mail", comment: ""), detailText: nil)
|
||||
toastView.show(in: self)
|
||||
}
|
||||
|
||||
case .refreshSideJITServer:
|
||||
if #available(iOS 17, *) {
|
||||
let alertController = UIAlertController(
|
||||
title: NSLocalizedString("Are you sure to Refresh SideJITServer?", comment: ""),
|
||||
message: NSLocalizedString("if you do not have SideJITServer setup this will do nothing", comment: ""),
|
||||
preferredStyle: UIAlertController.Style.actionSheet)
|
||||
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Refresh", comment: ""), style: .destructive){ _ in
|
||||
if UserDefaults.standard.sidejitenable {
|
||||
var SJSURL = ""
|
||||
if (UserDefaults.standard.textInputSideJITServerurl ?? "").isEmpty {
|
||||
SJSURL = "http://sidejitserver._http._tcp.local:8080"
|
||||
} else {
|
||||
SJSURL = UserDefaults.standard.textInputSideJITServerurl ?? ""
|
||||
} // replace with your URL
|
||||
|
||||
|
||||
let url = URL(string: SJSURL + "/re/")!
|
||||
|
||||
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
|
||||
if let error = error {
|
||||
print("Error: \(error)")
|
||||
} else {
|
||||
// Do nothing with data or response
|
||||
}
|
||||
}
|
||||
|
||||
task.resume()
|
||||
}
|
||||
})
|
||||
|
||||
alertController.addAction(.cancel)
|
||||
//Fix crash on iPad
|
||||
alertController.popoverPresentationController?.sourceView = self.tableView
|
||||
alertController.popoverPresentationController?.sourceRect = self.tableView.rectForRow(at: indexPath)
|
||||
self.present(alertController, animated: true)
|
||||
self.tableView.deselectRow(at: indexPath, animated: true)
|
||||
} else {
|
||||
let alertController = UIAlertController(
|
||||
title: NSLocalizedString("You are not on iOS 17+ This will not work", comment: ""),
|
||||
message: NSLocalizedString("This is meant for 'SideJITServer' and it only works on iOS 17+ ", comment: ""),
|
||||
preferredStyle: UIAlertController.Style.actionSheet)
|
||||
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .destructive){ _ in
|
||||
print("Not on iOS 17")
|
||||
})
|
||||
|
||||
alertController.addAction(.cancel)
|
||||
//Fix crash on iPad
|
||||
alertController.popoverPresentationController?.sourceView = self.tableView
|
||||
alertController.popoverPresentationController?.sourceRect = self.tableView.rectForRow(at: indexPath)
|
||||
self.present(alertController, animated: true)
|
||||
self.tableView.deselectRow(at: indexPath, animated: true)
|
||||
}
|
||||
|
||||
|
||||
case .clearCache: self.clearCache()
|
||||
|
||||
case .resetPairingFile:
|
||||
let filename = "ALTPairingFile.mobiledevicepairing"
|
||||
let fm = FileManager.default
|
||||
@@ -518,11 +679,12 @@ extension SettingsViewController
|
||||
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Delete and Reset", comment: ""), style: .destructive){ _ in
|
||||
if fm.fileExists(atPath: documentsPath.path), let contents = try? String(contentsOf: documentsPath), !contents.isEmpty {
|
||||
UserDefaults.standard.isPairingReset = true
|
||||
try? fm.removeItem(atPath: documentsPath.path)
|
||||
NSLog("Pairing File Reseted")
|
||||
}
|
||||
self.tableView.deselectRow(at: indexPath, animated: true)
|
||||
let dialogMessage = UIAlertController(title: NSLocalizedString("Pairing File Reseted", comment: ""), message: NSLocalizedString("Please restart SideStore", comment: ""), preferredStyle: .alert)
|
||||
let dialogMessage = UIAlertController(title: NSLocalizedString("Pairing File Reset", comment: ""), message: NSLocalizedString("Please restart SideStore", comment: ""), preferredStyle: .alert)
|
||||
self.present(dialogMessage, animated: true, completion: nil)
|
||||
})
|
||||
alertController.addAction(.cancel)
|
||||
@@ -531,25 +693,11 @@ extension SettingsViewController
|
||||
alertController.popoverPresentationController?.sourceRect = self.tableView.rectForRow(at: indexPath)
|
||||
self.present(alertController, animated: true)
|
||||
self.tableView.deselectRow(at: indexPath, animated: true)
|
||||
case .resetAdiPb:
|
||||
let alertController = UIAlertController(
|
||||
title: NSLocalizedString("Are you sure you want to reset the adi.pb file?", comment: ""),
|
||||
message: NSLocalizedString("The adi.pb file is used to generate anisette data, which is required to log into an Apple ID. If you are having issues with account related things, you can try this. However, you will be required to do 2FA again. This will do nothing if you are using an older anisette server.", comment: ""),
|
||||
preferredStyle: UIAlertController.Style.actionSheet)
|
||||
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Reset adi.pb", comment: ""), style: .destructive){ _ in
|
||||
if Keychain.shared.adiPb != nil {
|
||||
Keychain.shared.adiPb = nil
|
||||
print("Cleared adi.pb from keychain")
|
||||
}
|
||||
self.tableView.deselectRow(at: indexPath, animated: true)
|
||||
})
|
||||
alertController.addAction(.cancel)
|
||||
//Fix crash on iPad
|
||||
alertController.popoverPresentationController?.sourceView = self.tableView
|
||||
alertController.popoverPresentationController?.sourceRect = self.tableView.rectForRow(at: indexPath)
|
||||
self.present(alertController, animated: true)
|
||||
self.tableView.deselectRow(at: indexPath, animated: true)
|
||||
case .anisetteServers:
|
||||
self.prepare(for: UIStoryboardSegue(identifier: "anisetteServers", source: self, destination: UIHostingController(rootView: AnisetteServers(selected: "", errorCallback: {
|
||||
ToastView(text: "Reset adi.pb", detailText: "Buh").show(in: self)
|
||||
}))), sender: nil)
|
||||
// self.performSegue(withIdentifier: "anisetteServers", sender: nil)
|
||||
case .advancedSettings:
|
||||
// Create the URL that deep links to your app's custom settings.
|
||||
if let url = URL(string: UIApplication.openSettingsURLString) {
|
||||
@@ -559,6 +707,7 @@ extension SettingsViewController
|
||||
ELOG("UIApplication.openSettingsURLString invalid")
|
||||
}
|
||||
case .refreshAttempts, .errorLog: break
|
||||
|
||||
}
|
||||
|
||||
default: break
|
||||
|
||||
@@ -12,17 +12,22 @@ import CoreData
|
||||
import AltStoreCore
|
||||
import Roxas
|
||||
|
||||
struct SourceError: LocalizedError
|
||||
struct SourceError: ALTLocalizedError
|
||||
{
|
||||
enum Code
|
||||
enum Code: Int, ALTErrorCode
|
||||
{
|
||||
typealias Error = SourceError
|
||||
|
||||
case unsupported
|
||||
}
|
||||
|
||||
var code: Code
|
||||
var errorTitle: String?
|
||||
var errorFailure: String?
|
||||
|
||||
@Managed var source: Source
|
||||
|
||||
var errorDescription: String? {
|
||||
var errorFailureReason: String {
|
||||
switch self.code
|
||||
{
|
||||
case .unsupported: return String(format: NSLocalizedString("The source “%@” is not supported by this version of SideStore.", comment: ""), self.$source.name)
|
||||
@@ -197,7 +202,7 @@ private extension SourcesViewController
|
||||
{
|
||||
let alertController = UIAlertController(title: NSLocalizedString("Add Source", comment: ""), message: nil, preferredStyle: .alert)
|
||||
alertController.addTextField { (textField) in
|
||||
textField.placeholder = "https://apps.altstore.io"
|
||||
textField.placeholder = "https://apps.sidestore.io"
|
||||
textField.textContentType = .URL
|
||||
}
|
||||
alertController.addAction(.cancel)
|
||||
@@ -545,19 +550,19 @@ extension SourcesViewController: UICollectionViewDelegateFlowLayout
|
||||
footerView.textView.delegate = self
|
||||
|
||||
let attributedText = NSMutableAttributedString(
|
||||
string: NSLocalizedString("SideStore has reviewed these sources to make sure they meet our safety standards.\n\nSupport for untrusted sources is currently in beta, but you can help test them out by", comment: ""),
|
||||
string: NSLocalizedString("SideStore has reviewed these sources to make sure they meet our safety standards.", comment: ""),
|
||||
attributes: [.font: font, .foregroundColor: UIColor.gray]
|
||||
)
|
||||
attributedText.mutableString.append(" ")
|
||||
//attributedText.mutableString.append(" ")
|
||||
|
||||
let boldedFont = UIFont(descriptor: font.fontDescriptor.withSymbolicTraits(.traitBold)!, size: font.pointSize)
|
||||
let openPatreonURL = URL(string: "https://SideStore.io/patreon")!
|
||||
//let boldedFont = UIFont(descriptor: font.fontDescriptor.withSymbolicTraits(.traitBold)!, size: font.pointSize)
|
||||
//let openPatreonURL = URL(string: "https://SideStore.io/")!
|
||||
|
||||
let joinPatreonText = NSAttributedString(
|
||||
string: NSLocalizedString("joining our Patreon.", comment: ""),
|
||||
attributes: [.font: boldedFont, .link: openPatreonURL, .underlineColor: UIColor.clear]
|
||||
)
|
||||
attributedText.append(joinPatreonText)
|
||||
// let joinPatreonText = NSAttributedString(
|
||||
// string: NSLocalizedString("", comment: ""),
|
||||
// attributes: [.font: boldedFont, .link: openPatreonURL, .underlineColor: UIColor.clear]
|
||||
//)
|
||||
//attributedText.append(joinPatreonText)
|
||||
|
||||
footerView.textView.attributedText = attributedText
|
||||
footerView.textView.textAlignment = .natural
|
||||
|
||||
@@ -33,6 +33,7 @@ final class TabBarController: UITabBarController
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(TabBarController.openPatreonSettings(_:)), name: AppDelegate.openPatreonSettingsDeepLinkNotification, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(TabBarController.importApp(_:)), name: AppDelegate.importAppDeepLinkNotification, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(TabBarController.presentSources(_:)), name: AppDelegate.addSourceDeepLinkNotification, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(TabBarController.openErrorLog(_:)), name: ToastView.openErrorLogNotification, object: nil)
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool)
|
||||
@@ -141,4 +142,7 @@ private extension TabBarController
|
||||
{
|
||||
self.selectedIndex = Tab.myApps.rawValue
|
||||
}
|
||||
@objc func openErrorLog(_: Notification){
|
||||
self.selectedIndex = Tab.settings.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,23 +10,27 @@ import Foundation
|
||||
import CoreData
|
||||
|
||||
@propertyWrapper @dynamicMemberLookup
|
||||
struct Managed<ManagedObject: NSManagedObject>
|
||||
struct Managed<ManagedObject>
|
||||
{
|
||||
var wrappedValue: ManagedObject {
|
||||
didSet {
|
||||
self.managedObjectContext = self.wrappedValue.managedObjectContext
|
||||
self.managedObjectContext = self.managedObject?.managedObjectContext
|
||||
}
|
||||
}
|
||||
private var managedObjectContext: NSManagedObjectContext?
|
||||
|
||||
|
||||
var projectedValue: Managed<ManagedObject> {
|
||||
return self
|
||||
}
|
||||
|
||||
private var managedObjectContext: NSManagedObjectContext?
|
||||
private var managedObject: NSManagedObject? {
|
||||
return self.wrappedValue as? NSManagedObject
|
||||
}
|
||||
|
||||
init(wrappedValue: ManagedObject)
|
||||
{
|
||||
self.wrappedValue = wrappedValue
|
||||
self.managedObjectContext = wrappedValue.managedObjectContext
|
||||
self.managedObjectContext = self.managedObject?.managedObjectContext
|
||||
}
|
||||
|
||||
subscript<T>(dynamicMember keyPath: KeyPath<ManagedObject, T>) -> T
|
||||
@@ -46,4 +50,18 @@ struct Managed<ManagedObject: NSManagedObject>
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Optionals
|
||||
subscript<Wrapped, T>(dynamicMember keyPath: KeyPath<Wrapped, T>) -> T? where ManagedObject == Optional<Wrapped> {
|
||||
var result: T?
|
||||
|
||||
if let context = self.managedObjectContext {
|
||||
context.performAndWait {
|
||||
result = self.wrappedValue?[keyPath: keyPath] as? T
|
||||
}
|
||||
} else {
|
||||
result = self.wrappedValue?[keyPath: keyPath] as? T
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,5 +23,6 @@ FOUNDATION_EXPORT const unsigned char AltStoreCoreVersionString[];
|
||||
// Shared
|
||||
#import <AltStoreCore/ALTConstants.h>
|
||||
#import <AltStoreCore/ALTConnection.h>
|
||||
#import <AltStoreCore/ALTWrappedError.h>
|
||||
#import <AltStoreCore/NSError+ALTServerError.h>
|
||||
#import <AltStoreCore/CFNotificationName+AltStore.h>
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// OperatingSystemVersion+Comparable.swift
|
||||
// AltStoreCore
|
||||
//
|
||||
// Created by nythepegasus on 5/9/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension OperatingSystemVersion: Comparable {
|
||||
public static func ==(lhs: OperatingSystemVersion, rhs: OperatingSystemVersion) -> Bool {
|
||||
return lhs.majorVersion == rhs.majorVersion && lhs.minorVersion == rhs.minorVersion && lhs.patchVersion == rhs.patchVersion
|
||||
}
|
||||
|
||||
public static func <(lhs: OperatingSystemVersion, rhs: OperatingSystemVersion) -> Bool {
|
||||
return lhs.stringValue.compare(rhs.stringValue, options: .numeric) == .orderedAscending
|
||||
}
|
||||
}
|
||||
14
AltStoreCore/Extensions/String+SideStore.swift
Normal file
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// String+SideStore.swift
|
||||
// AltStoreCore
|
||||
//
|
||||
// Created by nythepegasus on 5/9/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public extension String {
|
||||
init(formatted: String, comment: String? = nil, _ args: String...) {
|
||||
self.init(format: NSLocalizedString(formatted, comment: comment ?? ""), args)
|
||||
}
|
||||
}
|
||||
@@ -22,11 +22,17 @@ public extension UserDefaults
|
||||
@NSManaged var firstLaunch: Date?
|
||||
@NSManaged var requiresAppGroupMigration: Bool
|
||||
@NSManaged var textServer: Bool
|
||||
@NSManaged var sidejitenable: Bool
|
||||
@NSManaged var textInputSideJITServerurl: String?
|
||||
@NSManaged var textInputAnisetteURL: String?
|
||||
@NSManaged var customAnisetteURL: String?
|
||||
@NSManaged var menuAnisetteURL: String
|
||||
@NSManaged var menuAnisetteList: String
|
||||
@NSManaged var preferredServerID: String?
|
||||
|
||||
@NSManaged var isBackgroundRefreshEnabled: Bool
|
||||
@NSManaged var isIdleTimeoutDisableEnabled: Bool
|
||||
@NSManaged var isPairingReset: Bool
|
||||
@NSManaged var isDebugModeEnabled: Bool
|
||||
@NSManaged var presentedLaunchReminderNotification: Bool
|
||||
|
||||
@@ -72,11 +78,14 @@ public extension UserDefaults
|
||||
|
||||
let defaults = [
|
||||
#keyPath(UserDefaults.isBackgroundRefreshEnabled): true,
|
||||
#keyPath(UserDefaults.isIdleTimeoutDisableEnabled): true,
|
||||
#keyPath(UserDefaults.isPairingReset): true,
|
||||
#keyPath(UserDefaults.isLegacyDeactivationSupported): isLegacyDeactivationSupported,
|
||||
#keyPath(UserDefaults.activeAppLimitIncludesExtensions): activeAppLimitIncludesExtensions,
|
||||
#keyPath(UserDefaults.localServerSupportsRefreshing): localServerSupportsRefreshing,
|
||||
#keyPath(UserDefaults.requiresAppGroupMigration): true
|
||||
]
|
||||
#keyPath(UserDefaults.requiresAppGroupMigration): true,
|
||||
#keyPath(UserDefaults.menuAnisetteURL): "https://ani.sidestore.io"
|
||||
] as [String : Any]
|
||||
|
||||
UserDefaults.standard.register(defaults: defaults)
|
||||
UserDefaults.shared.register(defaults: defaults)
|
||||
|
||||
@@ -40,7 +40,7 @@ public class AppVersion: NSManagedObject, Decodable, Fetchable
|
||||
|
||||
/* Relationships */
|
||||
@NSManaged public private(set) var app: StoreApp?
|
||||
@NSManaged public private(set) var latestVersionApp: StoreApp?
|
||||
@NSManaged @objc(latestVersionApp) public internal(set) var latestSupportedVersionApp: StoreApp?
|
||||
|
||||
private override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?)
|
||||
{
|
||||
@@ -54,6 +54,8 @@ public class AppVersion: NSManagedObject, Decodable, Fetchable
|
||||
case localizedDescription
|
||||
case downloadURL
|
||||
case size
|
||||
case minOSVersion
|
||||
case maxOSVersion
|
||||
}
|
||||
|
||||
public required init(from decoder: Decoder) throws
|
||||
@@ -72,6 +74,9 @@ public class AppVersion: NSManagedObject, Decodable, Fetchable
|
||||
|
||||
self.downloadURL = try container.decode(URL.self, forKey: .downloadURL)
|
||||
self.size = try container.decode(Int64.self, forKey: .size)
|
||||
|
||||
self._minOSVersion = try container.decodeIfPresent(String.self, forKey: .minOSVersion)
|
||||
self._maxOSVersion = try container.decodeIfPresent(String.self, forKey: .maxOSVersion)
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -113,4 +118,13 @@ public extension AppVersion
|
||||
|
||||
return appVersion
|
||||
}
|
||||
|
||||
var isSupported: Bool {
|
||||
if let minOSVersion = self.minOSVersion, !ProcessInfo.processInfo.isOperatingSystemAtLeast(minOSVersion) {
|
||||
return false
|
||||
} else if let maxOSVersion = self.maxOSVersion, ProcessInfo.processInfo.operatingSystemVersion > maxOSVersion {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,11 +13,11 @@ import Roxas
|
||||
|
||||
extension CFNotificationName
|
||||
{
|
||||
fileprivate static let willAccessDatabase = CFNotificationName("com.rileytestut.AltStore.WillAccessDatabase" as CFString)
|
||||
fileprivate static let willMigrateDatabase = CFNotificationName("com.rileytestut.AltStore.WillMigrateDatabase" as CFString)
|
||||
}
|
||||
|
||||
private let ReceivedWillAccessDatabaseNotification: @convention(c) (CFNotificationCenter?, UnsafeMutableRawPointer?, CFNotificationName?, UnsafeRawPointer?, CFDictionary?) -> Void = { (center, observer, name, object, userInfo) in
|
||||
DatabaseManager.shared.receivedWillAccessDatabaseNotification()
|
||||
private let ReceivedWillMigrateDatabaseNotification: @convention(c) (CFNotificationCenter?, UnsafeMutableRawPointer?, CFNotificationName?, UnsafeRawPointer?, CFDictionary?) -> Void = { (center, observer, name, object, userInfo) in
|
||||
DatabaseManager.shared.receivedWillMigrateDatabaseNotification()
|
||||
}
|
||||
|
||||
fileprivate class PersistentContainer: RSTPersistentContainer
|
||||
@@ -52,15 +52,15 @@ public class DatabaseManager
|
||||
private let coordinator = NSFileCoordinator()
|
||||
private let coordinatorQueue = OperationQueue()
|
||||
|
||||
private var ignoreWillAccessDatabaseNotification = false
|
||||
|
||||
private var ignoreWillMigrateDatabaseNotification = false
|
||||
|
||||
private init()
|
||||
{
|
||||
self.persistentContainer = PersistentContainer(name: "AltStore", bundle: Bundle(for: DatabaseManager.self))
|
||||
self.persistentContainer.preferredMergePolicy = MergePolicy()
|
||||
|
||||
let observer = Unmanaged.passUnretained(self).toOpaque()
|
||||
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), observer, ReceivedWillAccessDatabaseNotification, CFNotificationName.willAccessDatabase.rawValue, nil, .deliverImmediately)
|
||||
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), observer, ReceivedWillMigrateDatabaseNotification, CFNotificationName.willMigrateDatabase.rawValue, nil, .deliverImmediately)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,10 +87,13 @@ public extension DatabaseManager
|
||||
|
||||
guard !self.isStarted else { return finish(nil) }
|
||||
|
||||
// Quit any other running AltStore processes to prevent concurrent database access during and after migration.
|
||||
self.ignoreWillAccessDatabaseNotification = true
|
||||
CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(), .willAccessDatabase, nil, nil, true)
|
||||
|
||||
if self.persistentContainer.isMigrationRequired {
|
||||
|
||||
// Quit any other running AltStore processes to prevent concurrent database access during and after migration.
|
||||
self.ignoreWillMigrateDatabaseNotification = true
|
||||
CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(), .willMigrateDatabase, nil, nil, true)
|
||||
}
|
||||
|
||||
self.migrateDatabaseToAppGroupIfNeeded { (result) in
|
||||
switch result
|
||||
{
|
||||
@@ -229,7 +232,7 @@ private extension DatabaseManager
|
||||
else
|
||||
{
|
||||
storeApp = StoreApp.makeAltStoreApp(in: context)
|
||||
storeApp.latestVersion?.version = localApp.version
|
||||
storeApp.latestSupportedVersion?.version = localApp.version
|
||||
storeApp.source = altStoreSource
|
||||
}
|
||||
|
||||
@@ -417,13 +420,13 @@ private extension DatabaseManager
|
||||
}
|
||||
}
|
||||
|
||||
func receivedWillAccessDatabaseNotification()
|
||||
func receivedWillMigrateDatabaseNotification()
|
||||
{
|
||||
defer { self.ignoreWillAccessDatabaseNotification = false }
|
||||
|
||||
defer { self.ignoreWillMigrateDatabaseNotification = false }
|
||||
|
||||
// Ignore notifications sent by the current process.
|
||||
guard !self.ignoreWillAccessDatabaseNotification else { return }
|
||||
|
||||
guard !self.ignoreWillMigrateDatabaseNotification else { return }
|
||||
|
||||
exit(104)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,14 +62,14 @@ public class InstalledApp: NSManagedObject, InstalledAppProtocol
|
||||
|
||||
@objc public var hasUpdate: Bool {
|
||||
if self.storeApp == nil { return false }
|
||||
if self.storeApp!.latestVersion == nil { return false }
|
||||
|
||||
if self.storeApp!.latestSupportedVersion == nil { return false }
|
||||
|
||||
let currentVersion = SemanticVersion(self.version)
|
||||
let latestVersion = SemanticVersion(self.storeApp!.latestVersion!.version)
|
||||
|
||||
let latestVersion = SemanticVersion(self.storeApp!.latestSupportedVersion!.version)
|
||||
|
||||
if currentVersion == nil || latestVersion == nil {
|
||||
// One of the versions is not valid SemVer, fall back to comparing the version strings by character
|
||||
return self.version < self.storeApp!.latestVersion!.version
|
||||
return self.version < self.storeApp!.latestSupportedVersion!.version
|
||||
}
|
||||
|
||||
return currentVersion! < latestVersion!
|
||||
@@ -163,8 +163,8 @@ public extension InstalledApp
|
||||
class func updatesFetchRequest() -> NSFetchRequest<InstalledApp>
|
||||
{
|
||||
let fetchRequest = InstalledApp.fetchRequest() as NSFetchRequest<InstalledApp>
|
||||
fetchRequest.predicate = NSPredicate(format: "%K == YES AND %K == YES",
|
||||
#keyPath(InstalledApp.isActive), #keyPath(InstalledApp.hasUpdate))
|
||||
fetchRequest.predicate = NSPredicate(format: "%K == YES AND %K != nil AND %K != %K",
|
||||
#keyPath(InstalledApp.isActive), #keyPath(InstalledApp.storeApp), #keyPath(InstalledApp.version), #keyPath(InstalledApp.storeApp.latestSupportedVersion.version))
|
||||
return fetchRequest
|
||||
}
|
||||
|
||||
@@ -192,7 +192,7 @@ public extension InstalledApp
|
||||
|
||||
class func fetchAppsForRefreshingAll(in context: NSManagedObjectContext) -> [InstalledApp]
|
||||
{
|
||||
var predicate = NSPredicate(format: "%K == YES AND %K != %@", #keyPath(InstalledApp.isActive), #keyPath(InstalledApp.bundleIdentifier), StoreApp.altstoreAppID)
|
||||
let predicate = NSPredicate(format: "%K == YES AND %K != %@", #keyPath(InstalledApp.isActive), #keyPath(InstalledApp.bundleIdentifier), StoreApp.altstoreAppID)
|
||||
print("Fetch Apps for Refreshing All 'AltStore' predicate: \(String(describing: predicate))")
|
||||
|
||||
// if let patreonAccount = DatabaseManager.shared.patreonAccount(in: context), patreonAccount.isPatron, PatreonAPI.shared.isAuthenticated
|
||||
@@ -223,7 +223,7 @@ public extension InstalledApp
|
||||
// Date 6 hours before now.
|
||||
let date = Date().addingTimeInterval(-1 * 6 * 60 * 60)
|
||||
|
||||
var predicate = NSPredicate(format: "(%K == YES) AND (%K < %@) AND (%K != %@)",
|
||||
let predicate = NSPredicate(format: "(%K == YES) AND (%K < %@) AND (%K != %@)",
|
||||
#keyPath(InstalledApp.isActive),
|
||||
#keyPath(InstalledApp.refreshedDate), date as NSDate,
|
||||
#keyPath(InstalledApp.bundleIdentifier), StoreApp.altstoreAppID)
|
||||
@@ -275,14 +275,12 @@ public extension InstalledApp
|
||||
|
||||
do { try FileManager.default.createDirectory(at: appsDirectoryURL, withIntermediateDirectories: true, attributes: nil) }
|
||||
catch { print("Creating App Directory Error: \(error)") }
|
||||
print("`appsDirectoryURL` is set to: \(appsDirectoryURL.absoluteString)")
|
||||
return appsDirectoryURL
|
||||
}
|
||||
|
||||
class var legacyAppsDirectoryURL: URL {
|
||||
let baseDirectory = FileManager.default.applicationSupportDirectory
|
||||
let appsDirectoryURL = baseDirectory.appendingPathComponent("Apps")
|
||||
print("legacy `appsDirectoryURL` is set to: \(appsDirectoryURL.absoluteString)")
|
||||
return appsDirectoryURL
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@ extension LoggedError
|
||||
case deactivate
|
||||
case backup
|
||||
case restore
|
||||
case connection
|
||||
case enableJIT
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +68,12 @@ public class LoggedError: NSManagedObject, Fetchable
|
||||
self.date = date
|
||||
self._operation = operation?.rawValue
|
||||
|
||||
let nsError = error as NSError
|
||||
let nsError: NSError
|
||||
if let error = error as? ALTServerError, error.code == .underlyingError, let underlyingError = error.underlyingError {
|
||||
nsError = underlyingError as NSError
|
||||
} else {
|
||||
nsError = error as NSError
|
||||
}
|
||||
self.domain = nsError.domain
|
||||
self.code = Int32(nsError.code)
|
||||
self.userInfo = nsError.userInfo
|
||||
@@ -91,7 +98,7 @@ public extension LoggedError
|
||||
return app
|
||||
}
|
||||
|
||||
var error: Error {
|
||||
var error: NSError {
|
||||
let nsError = NSError(domain: self.domain, code: Int(self.code), userInfo: self.userInfo)
|
||||
return nsError
|
||||
}
|
||||
@@ -113,6 +120,8 @@ public extension LoggedError
|
||||
case .deactivate: return String(format: NSLocalizedString("Deactivate %@ Failed", comment: ""), self.appName)
|
||||
case .backup: return String(format: NSLocalizedString("Backup %@ Failed", comment: ""), self.appName)
|
||||
case .restore: return String(format: NSLocalizedString("Restore %@ Failed", comment: ""), self.appName)
|
||||
case .connection: return String(format: NSLocalizedString("Connection during %@ Failed", comment: ""), self.appName)
|
||||
case .enableJIT: return String(format: NSLocalizedString("Enabling JIT for %@ Failed", comment: ""), self.appName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ open class MergePolicy: RSTRelationshipPreservingMergePolicy
|
||||
let conflictingAppVersions = conflict.conflictingObjects.lazy.compactMap { $0 as? AppVersion }
|
||||
|
||||
// Primary AppVersion == AppVersion whose latestVersionApp.latestVersion points back to itself.
|
||||
if let primaryAppVersion = conflictingAppVersions.first(where: { $0.latestVersionApp?.latestVersion == $0 }),
|
||||
if let primaryAppVersion = conflictingAppVersions.first(where: { $0.latestSupportedVersionApp?.latestSupportedVersion == $0 }),
|
||||
let secondaryAppVersion = conflictingAppVersions.first(where: { $0 != primaryAppVersion })
|
||||
{
|
||||
secondaryAppVersion.managedObjectContext?.delete(secondaryAppVersion)
|
||||
|
||||
@@ -48,7 +48,7 @@ fileprivate extension NSManagedObject
|
||||
|
||||
func setStoreAppLatestVersion(_ appVersion: NSManagedObject)
|
||||
{
|
||||
self.setValue(appVersion, forKey: #keyPath(StoreApp.latestVersion))
|
||||
self.setValue(appVersion, forKey: #keyPath(StoreApp.latestSupportedVersion))
|
||||
|
||||
let versions = NSOrderedSet(array: [appVersion])
|
||||
self.setValue(versions, forKey: #keyPath(StoreApp._versions))
|
||||
|
||||
@@ -146,7 +146,7 @@ public class StoreApp: NSManagedObject, Decodable, Fetchable
|
||||
@NSManaged @objc(source) public var _source: Source?
|
||||
@NSManaged @objc(permissions) public var _permissions: NSOrderedSet
|
||||
|
||||
@NSManaged public private(set) var latestVersion: AppVersion?
|
||||
@NSManaged @objc(latestVersion) public private(set) var latestSupportedVersion: AppVersion?
|
||||
@NSManaged @objc(versions) public private(set) var _versions: NSOrderedSet
|
||||
|
||||
@NSManaged public private(set) var loggedErrors: NSSet /* Set<LoggedError> */ // Use NSSet to avoid eagerly fetching values.
|
||||
@@ -169,31 +169,6 @@ public class StoreApp: NSManagedObject, Decodable, Fetchable
|
||||
return self._versions.array as! [AppVersion]
|
||||
}
|
||||
|
||||
@nonobjc public var size: Int64? {
|
||||
guard let version = self.latestVersion else { return nil }
|
||||
return version.size
|
||||
}
|
||||
|
||||
@nonobjc public var version: String? {
|
||||
guard let version = self.latestVersion else { return nil }
|
||||
return version.version
|
||||
}
|
||||
|
||||
@nonobjc public var versionDescription: String? {
|
||||
guard let version = self.latestVersion else { return nil }
|
||||
return version.localizedDescription
|
||||
}
|
||||
|
||||
@nonobjc public var versionDate: Date? {
|
||||
guard let version = self.latestVersion else { return nil }
|
||||
return version.date
|
||||
}
|
||||
|
||||
@nonobjc public var downloadURL: URL? {
|
||||
guard let version = self.latestVersion else { return nil }
|
||||
return version.downloadURL
|
||||
}
|
||||
|
||||
private override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?)
|
||||
{
|
||||
super.init(entity: entity, insertInto: context)
|
||||
@@ -314,16 +289,30 @@ public class StoreApp: NSManagedObject, Decodable, Fetchable
|
||||
}
|
||||
}
|
||||
|
||||
private extension StoreApp
|
||||
internal extension StoreApp
|
||||
{
|
||||
func setVersions(_ versions: [AppVersion])
|
||||
{
|
||||
guard let latestVersion = versions.first else { preconditionFailure("StoreApp must have at least one AppVersion.") }
|
||||
|
||||
self.latestVersion = latestVersion
|
||||
self._versions = NSOrderedSet(array: versions)
|
||||
|
||||
let latestSupportedVersion = versions.first(where: { $0.isSupported })
|
||||
self.latestSupportedVersion = latestSupportedVersion
|
||||
|
||||
for case let version as AppVersion in self._versions
|
||||
{
|
||||
if version == latestSupportedVersion
|
||||
{
|
||||
version.latestSupportedVersionApp = self
|
||||
}
|
||||
else
|
||||
{
|
||||
// Ensure we replace any previous relationship when merging.
|
||||
version.latestSupportedVersionApp = nil
|
||||
}
|
||||
}
|
||||
|
||||
// Preserve backwards compatibility by assigning legacy property values.
|
||||
guard let latestVersion = versions.first else { preconditionFailure("StoreApp must have at least one AppVersion.") }
|
||||
self._version = latestVersion.version
|
||||
self._versionDate = latestVersion.date
|
||||
self._versionDescription = latestVersion.localizedDescription
|
||||
@@ -334,6 +323,10 @@ private extension StoreApp
|
||||
|
||||
public extension StoreApp
|
||||
{
|
||||
var latestAvailableVersion: AppVersion? {
|
||||
return self._versions.firstObject as? AppVersion
|
||||
}
|
||||
|
||||
@nonobjc class func fetchRequest() -> NSFetchRequest<StoreApp>
|
||||
{
|
||||
return NSFetchRequest<StoreApp>(entityName: "StoreApp")
|
||||
|
||||
@@ -40,7 +40,7 @@ extension ALTApplication: AppProtocol
|
||||
extension StoreApp: AppProtocol
|
||||
{
|
||||
public var url: URL? {
|
||||
return self.downloadURL
|
||||
return self.latestAvailableVersion?.downloadURL
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,3 +50,17 @@ extension InstalledApp: AppProtocol
|
||||
return self.fileURL
|
||||
}
|
||||
}
|
||||
|
||||
extension AppVersion: AppProtocol {
|
||||
public var name: String {
|
||||
return self.app?.name ?? self.bundleIdentifier
|
||||
}
|
||||
|
||||
public var bundleIdentifier: String {
|
||||
return self.appBundleID
|
||||
}
|
||||
|
||||
public var url: URL? {
|
||||
return self.downloadURL
|
||||
}
|
||||
}
|
||||
|
||||
BIN
AltWidget/Assets.xcassets/AltStore.imageset/1024.png
vendored
Normal file
|
After Width: | Height: | Size: 846 KiB |
@@ -5,12 +5,12 @@
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "Group 23_120.png",
|
||||
"filename" : "1024.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "Group 23_180.png",
|
||||
"filename" : "1024.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 21 KiB |
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "group16Copy2.pdf",
|
||||
"filename" : "sidestore-logo.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
|
||||
17
AltWidget/Assets.xcassets/Badge.imageset/sidestore-logo.svg
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="100" height="100">
|
||||
<title>sidestore-logo-svg</title>
|
||||
<style>
|
||||
.s0 { fill: none }
|
||||
.s1 { fill: #bebbb4 }
|
||||
</style>
|
||||
<g id="Layer">
|
||||
<g id="Layer">
|
||||
<path id="Main" class="s0" />
|
||||
<g id="Main1">
|
||||
<g id="Layer">
|
||||
<path id="Layer" fill-rule="evenodd" class="s1" d="m86 50c0 2.3-1.9 4.2-4.2 4.2h-50.9c-2.3 0-4.2-1.9-4.2-4.2 0-2.3 1.9-4.2 4.2-4.2h50.9c2.3 0 4.2 1.9 4.2 4.2zm-31.8-23.3v2.1c0 3.4-1.3 6.6-3.7 9-2.4 2.4-5.6 3.7-9 3.7h-10.6c-2.2 0-4.4 0.9-5.9 2.5-1.6 1.6-2.5 3.8-2.5 6 0 2.2 0.9 4.4 2.5 6 1.5 1.6 3.7 2.5 5.9 2.5h10.6c3.4 0 6.6 1.3 9 3.7 2.4 2.4 3.7 5.6 3.7 9v10.8c0 1.1-0.4 2.1-1.1 2.8-0.8 0.8-1.8 1.2-2.9 1.2h-0.4c-1.1 0-2.1-0.4-2.9-1.2-0.7-0.7-1.1-1.7-1.1-2.8v-10.8c0-1.1-0.5-2.2-1.3-3-0.8-0.8-1.8-1.3-3-1.3h-10.6c-4.5 0-8.8-1.7-11.9-4.9-3.2-3.2-5-7.5-5-12 0-4.5 1.8-8.8 5-12 3.1-3.2 7.4-4.9 11.9-4.9h10.6c1.2 0 2.2-0.5 3-1.3 0.8-0.8 1.3-1.9 1.3-3v-2.1h-4.3l8.5-12.7 8.5 12.7zm12.7 55.3c0 2.2-1.8 4-4 4h-0.5c-2.2 0-4-1.8-4-4v-19.5c0-2.2 1.8-4 4-4h0.5c2.2 0 4 1.8 4 4z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "altminicon.pdf",
|
||||
"filename" : "cir.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
|
||||
3
AltWidget/Assets.xcassets/SmallIcon.imageset/cir.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="100" height="100" version="1.1" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="50" cy="50" r="50" fill="#fff" stroke-width=".265"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 175 B |
16
AltWidget/Resources/ReleaseEntitlements.plist
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>application-identifier</key>
|
||||
<string>XYZ0123456.com.SideStore.SideStore.AltWidget</string>
|
||||
<key>com.apple.developer.team-identifier</key>
|
||||
<string>XYZ0123456</string>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.com.SideStore.SideStore</string>
|
||||
</array>
|
||||
<key>get-task-allow</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,8 +1,8 @@
|
||||
// Configuration settings file format documentation can be found at:
|
||||
// https://help.apple.com/xcode/#/dev745c5c974
|
||||
|
||||
MARKETING_VERSION = 0.3.2
|
||||
CURRENT_PROJECT_VERSION = 3050
|
||||
MARKETING_VERSION = 0.5.6
|
||||
CURRENT_PROJECT_VERSION = 5060
|
||||
|
||||
// Vars to be overwritten by `CodeSigning.xcconfig` if exists
|
||||
DEVELOPMENT_TEAM = S32Z3HMYVQ
|
||||
|
||||
@@ -7,7 +7,7 @@ There are many ways to contribute to SideStore, so if you aren't a developer, th
|
||||
- [Writing documentation](https://github.com/SideStore/SideStore-Docs)
|
||||
- [Submitting detailed bug reports and suggesting new features](https://github.com/SideStore/SideStore/issues/new/choose)
|
||||
- Helping out with support
|
||||
- [Discord](https://discord.gg/RgpFBX3Q3k)
|
||||
- [Discord](https://discord.gg/sidestore-949183273383395328)
|
||||
- [GitHub Discussions](https://github.com/SideStore/SideStore/discussions)
|
||||
|
||||
However, this guide will focus on the development side of things. For now, we will only have setup information here, but you can [join our Discord](https://discord.gg/RgpFBX3Q3k) if you need help
|
||||
|
||||
2
Dependencies/Roxas
vendored
2
Dependencies/libfragmentzip
vendored
2
Dependencies/libimobiledevice
vendored
2
Dependencies/libimobiledevice-glue
vendored
2
Dependencies/libplist
vendored
2
Dependencies/libusbmuxd
vendored
1
Makefile
@@ -171,6 +171,7 @@ build:
|
||||
fakesign:
|
||||
rm -rf archive.xcarchive/Products/Applications/SideStore.app/Frameworks/AltStoreCore.framework/Frameworks/
|
||||
ldid -SAltStore/Resources/ReleaseEntitlements.plist archive.xcarchive/Products/Applications/SideStore.app/SideStore
|
||||
ldid -SAltWidget/Resources/ReleaseEntitlements.plist archive.xcarchive/Products/Applications/SideStore.app/PlugIns/AltWidgetExtension.appex/AltWidgetExtension
|
||||
|
||||
ipa:
|
||||
mkdir Payload
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
# SideStore
|
||||
|
||||
> SideStore is an *untethered, community driven* alternative app store for non-jailbroken iOS devices
|
||||
> SideStore is an *untethered, community driven* alternative app store for non-jailbroken iOS devices
|
||||
|
||||
[](https://www.gnu.org/licenses/agpl-3.0)
|
||||
[](https://makeapullrequest.com)
|
||||
[](https://github.com/SideStore/SideStore/actions/workflows/nightly.yml)
|
||||
[](https://github.com/SideStore/SideStore/actions/workflows/beta.yml)
|
||||
[](https://discord.gg/sidestore-949183273383395328)
|
||||
|
||||

|
||||
|
||||
SideStore is an iOS application that allows you to sideload apps onto your iOS device with just your Apple ID. SideStore resigns apps with your personal development certificate, and then uses a [specially designed VPN](https://github.com/jkcoxson/Secret-Tunnel) in order to trick iOS into installing them. SideStore will periodically "refresh" your apps in the background, to keep their normal 7-day development period from expiring.
|
||||
SideStore is an iOS application that allows you to sideload apps onto your iOS device with just your Apple ID. SideStore resigns apps with your personal development certificate, and then uses a [specially designed VPN](https://github.com/jkcoxson/em_proxy) in order to trick iOS into installing them. SideStore will periodically "refresh" your apps in the background, to keep their normal 7-day development period from expiring.
|
||||
|
||||
SideStore's goal is to provide an untethered sideloading experience. It's a community driven fork of [AltStore](https://github.com/rileytestut/AltStore), and has already implemented some of the community's most-requested features.
|
||||
|
||||
|
||||
@@ -8,9 +8,16 @@
|
||||
|
||||
#import "NSError+ALTServerError.h"
|
||||
|
||||
NSErrorDomain const AltServerErrorDomain = @"com.rileytestut.AltServer";
|
||||
NSErrorDomain const AltServerInstallationErrorDomain = @"com.rileytestut.AltServer.Installation";
|
||||
NSErrorDomain const AltServerConnectionErrorDomain = @"com.rileytestut.AltServer.Connection";
|
||||
#if TARGET_OS_OSX
|
||||
#import "AltServer-Swift.h"
|
||||
#else
|
||||
#import "AltStoreCore/AltStoreCore-Swift.h"
|
||||
#endif
|
||||
|
||||
NSErrorDomain const AltServerErrorDomain = @"AltServer.ServerError";
|
||||
NSErrorDomain const AltServerInstallationErrorDomain = @"AltServer.InstallationError";
|
||||
NSErrorDomain const AltServerConnectionErrorDomain = @"AltServer.ConnectionError";
|
||||
|
||||
|
||||
NSErrorUserInfoKey const ALTUnderlyingErrorDomainErrorKey = @"underlyingErrorDomain";
|
||||
NSErrorUserInfoKey const ALTUnderlyingErrorCodeErrorKey = @"underlyingErrorCode";
|
||||
@@ -24,8 +31,16 @@ NSErrorUserInfoKey const ALTOperatingSystemVersionErrorKey = @"ALTOperatingSyste
|
||||
|
||||
+ (void)load
|
||||
{
|
||||
[NSError setUserInfoValueProviderForDomain:AltServerErrorDomain provider:^id _Nullable(NSError * _Nonnull error, NSErrorUserInfoKey _Nonnull userInfoKey) {
|
||||
if ([userInfoKey isEqualToString:NSLocalizedFailureReasonErrorKey])
|
||||
[NSError alt_setUserInfoValueProviderForDomain:AltServerErrorDomain provider:^id _Nullable(NSError * _Nonnull error, NSErrorUserInfoKey _Nonnull userInfoKey) {
|
||||
if ([userInfoKey isEqualToString:NSLocalizedDescriptionKey])
|
||||
{
|
||||
return [error altserver_localizedDescription];
|
||||
}
|
||||
else if ([userInfoKey isEqualToString:NSLocalizedFailureErrorKey])
|
||||
{
|
||||
return [error altserver_localizedFailure];
|
||||
}
|
||||
else if ([userInfoKey isEqualToString:NSLocalizedFailureReasonErrorKey])
|
||||
{
|
||||
return [error altserver_localizedFailureReason];
|
||||
}
|
||||
@@ -41,10 +56,10 @@ NSErrorUserInfoKey const ALTOperatingSystemVersionErrorKey = @"ALTOperatingSyste
|
||||
return nil;
|
||||
}];
|
||||
|
||||
[NSError setUserInfoValueProviderForDomain:AltServerConnectionErrorDomain provider:^id _Nullable(NSError * _Nonnull error, NSErrorUserInfoKey _Nonnull userInfoKey) {
|
||||
if ([userInfoKey isEqualToString:NSLocalizedDescriptionKey])
|
||||
[NSError alt_setUserInfoValueProviderForDomain:AltServerConnectionErrorDomain provider:^id _Nullable(NSError * _Nonnull error, NSErrorUserInfoKey _Nonnull userInfoKey) {
|
||||
if ([userInfoKey isEqualToString:NSLocalizedFailureReasonErrorKey])
|
||||
{
|
||||
return [error altserver_connection_localizedDescription];
|
||||
return [error altserver_connection_localizedFailureReason];
|
||||
}
|
||||
else if ([userInfoKey isEqualToString:NSLocalizedRecoverySuggestionErrorKey])
|
||||
{
|
||||
@@ -55,6 +70,53 @@ NSErrorUserInfoKey const ALTOperatingSystemVersionErrorKey = @"ALTOperatingSyste
|
||||
}];
|
||||
}
|
||||
|
||||
- (nullable NSString *)altserver_localizedDescription
|
||||
{
|
||||
switch ((ALTServerError)self.code)
|
||||
{
|
||||
case ALTServerErrorUnderlyingError:
|
||||
{
|
||||
// We're wrapping another error, so return the wrapped error's localized description.
|
||||
NSError *underlyingError = self.userInfo[NSUnderlyingErrorKey];
|
||||
return underlyingError.localizedDescription;
|
||||
}
|
||||
|
||||
default:
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (nullable NSString *)altserver_localizedFailure
|
||||
{
|
||||
switch ((ALTServerError)self.code)
|
||||
{
|
||||
case ALTServerErrorUnderlyingError:
|
||||
{
|
||||
NSError *underlyingError = self.userInfo[NSUnderlyingErrorKey];
|
||||
return underlyingError.alt_localizedFailure;
|
||||
}
|
||||
|
||||
case ALTServerErrorConnectionFailed:
|
||||
{
|
||||
NSError *underlyingError = self.userInfo[NSUnderlyingErrorKey];
|
||||
if (underlyingError.localizedFailureReason != nil)
|
||||
{
|
||||
// Only return localized failure if there is an underlying error with failure reason.
|
||||
#if TARGET_OS_OSX
|
||||
return NSLocalizedString(@"There was an error connecting to the device.", @"");
|
||||
#else
|
||||
return NSLocalizedString(@"AltServer could not establish a connection to SideStore.", @"");
|
||||
#endif
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
default:
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (nullable NSString *)altserver_localizedFailureReason
|
||||
{
|
||||
switch ((ALTServerError)self.code)
|
||||
@@ -80,12 +142,21 @@ NSErrorUserInfoKey const ALTOperatingSystemVersionErrorKey = @"ALTOperatingSyste
|
||||
return NSLocalizedString(@"An unknown error occured.", @"");
|
||||
|
||||
case ALTServerErrorConnectionFailed:
|
||||
{
|
||||
NSError *underlyingError = self.userInfo[NSUnderlyingErrorKey];
|
||||
if (underlyingError.localizedFailureReason != nil)
|
||||
{
|
||||
return underlyingError.localizedFailureReason;
|
||||
}
|
||||
|
||||
// Return fallback failure reason if there isn't an underlying error with failure reason.
|
||||
#if TARGET_OS_OSX
|
||||
return NSLocalizedString(@"There was an error connecting to the device.", @"");
|
||||
#else
|
||||
return NSLocalizedString(@"Could not connect to SideStore.", @"");
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
case ALTServerErrorLostConnection:
|
||||
return NSLocalizedString(@"Lost connection to SideStore.", @"");
|
||||
|
||||
@@ -93,8 +164,8 @@ NSErrorUserInfoKey const ALTOperatingSystemVersionErrorKey = @"ALTOperatingSyste
|
||||
return NSLocalizedString(@"SideStore could not find this device.", @"");
|
||||
|
||||
case ALTServerErrorDeviceWriteFailed:
|
||||
return NSLocalizedString(@"Failed to write app data to device.", @"");
|
||||
|
||||
return NSLocalizedString(@"SideStore could not write data to this device.", @"");
|
||||
|
||||
case ALTServerErrorInvalidRequest:
|
||||
return NSLocalizedString(@"SideStore received an invalid request.", @"");
|
||||
|
||||
@@ -102,14 +173,20 @@ NSErrorUserInfoKey const ALTOperatingSystemVersionErrorKey = @"ALTOperatingSyste
|
||||
return NSLocalizedString(@"SideStore sent an invalid response.", @"");
|
||||
|
||||
case ALTServerErrorInvalidApp:
|
||||
return NSLocalizedString(@"The app is invalid.", @"");
|
||||
|
||||
return NSLocalizedString(@"The app is in an invalid format.", @"");
|
||||
|
||||
case ALTServerErrorInstallationFailed:
|
||||
{
|
||||
NSError *underlyingError = self.userInfo[NSUnderlyingErrorKey];
|
||||
if (underlyingError != nil) {
|
||||
return underlyingError.localizedFailureReason ?: underlyingError.localizedDescription;
|
||||
}
|
||||
return NSLocalizedString(@"An error occured while installing the app.", @"");
|
||||
|
||||
}
|
||||
|
||||
case ALTServerErrorMaximumFreeAppLimitReached:
|
||||
return NSLocalizedString(@"Cannot activate more than 3 apps with a non-developer Apple ID.", @"");
|
||||
|
||||
return NSLocalizedString(@"You cannot activate more than 3 apps with a non-developer Apple ID.", @"");
|
||||
|
||||
case ALTServerErrorUnsupportediOSVersion:
|
||||
return NSLocalizedString(@"Your device must be running iOS 12.2 or later to install SideStore.", @"");
|
||||
|
||||
@@ -117,8 +194,8 @@ NSErrorUserInfoKey const ALTOperatingSystemVersionErrorKey = @"ALTOperatingSyste
|
||||
return NSLocalizedString(@"SideStore does not support this request.", @"");
|
||||
|
||||
case ALTServerErrorUnknownResponse:
|
||||
return NSLocalizedString(@"Received an unknown response from SideStore.", @"");
|
||||
|
||||
return NSLocalizedString(@"SideStore received an unknown response from SideStore.", @"");
|
||||
|
||||
case ALTServerErrorInvalidAnisetteData:
|
||||
return NSLocalizedString(@"The provided anisette data is invalid.", @"");
|
||||
|
||||
@@ -153,7 +230,19 @@ NSErrorUserInfoKey const ALTOperatingSystemVersionErrorKey = @"ALTOperatingSyste
|
||||
{
|
||||
switch ((ALTServerError)self.code)
|
||||
{
|
||||
case ALTServerErrorUnderlyingError:
|
||||
{
|
||||
NSError *underlyingError = self.userInfo[NSUnderlyingErrorKey];
|
||||
return underlyingError.localizedRecoverySuggestion;
|
||||
}
|
||||
case ALTServerErrorConnectionFailed:
|
||||
{
|
||||
NSError *underlyingError = self.userInfo[NSUnderlyingErrorKey];
|
||||
if (underlyingError.localizedRecoverySuggestion != nil){
|
||||
return underlyingError.localizedRecoverySuggestion;
|
||||
}
|
||||
// If there is no underlying error found, fall through to AltServerErrorDeviceNotFound
|
||||
}
|
||||
case ALTServerErrorDeviceNotFound:
|
||||
return NSLocalizedString(@"Make sure you have trusted this device with your computer and Wi-Fi sync is enabled.", @"");
|
||||
|
||||
@@ -182,6 +271,13 @@ NSErrorUserInfoKey const ALTOperatingSystemVersionErrorKey = @"ALTOperatingSyste
|
||||
{
|
||||
switch ((ALTServerError)self.code)
|
||||
{
|
||||
case ALTServerErrorUnderlyingError:
|
||||
{
|
||||
NSError *underlyingError = self.userInfo[NSUnderlyingErrorKey];
|
||||
return underlyingError.alt_localizedDebugDescription;
|
||||
|
||||
}
|
||||
|
||||
case ALTServerErrorIncompatibleDeveloperDisk:
|
||||
{
|
||||
NSString *path = self.userInfo[NSFilePathErrorKey];
|
||||
@@ -191,7 +287,7 @@ NSErrorUserInfoKey const ALTOperatingSystemVersionErrorKey = @"ALTOperatingSyste
|
||||
}
|
||||
|
||||
NSString *osVersion = [self altserver_osVersion] ?: NSLocalizedString(@"this device's OS version", @"");
|
||||
NSString *debugDescription = [NSString stringWithFormat:NSLocalizedString(@"The Developer disk located at\n\n%@\n\nis incompatible with %@.", @""), path, osVersion];
|
||||
NSString *debugDescription = [NSString stringWithFormat:NSLocalizedString(@"The Developer disk located at %@ is incompatible with %@.", @""), path, osVersion];
|
||||
return debugDescription;
|
||||
}
|
||||
|
||||
@@ -232,7 +328,7 @@ NSErrorUserInfoKey const ALTOperatingSystemVersionErrorKey = @"ALTOperatingSyste
|
||||
|
||||
#pragma mark - AltServerConnectionErrorDomain -
|
||||
|
||||
- (nullable NSString *)altserver_connection_localizedDescription
|
||||
- (nullable NSString *)altserver_connection_localizedFailureReason
|
||||
{
|
||||
switch ((ALTServerConnectionError)self.code)
|
||||
{
|
||||
|
||||