Compare commits
710 Commits
1.4.3
...
junepark67
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d49098a8be | ||
|
|
b4e18c50d3 | ||
|
|
d18482a04a | ||
|
|
f3d9dd777d | ||
|
|
e117c4b9a3 | ||
|
|
95666178e5 | ||
|
|
56403466b9 | ||
|
|
c7344ef548 | ||
|
|
71c4abfce8 | ||
|
|
0613af2240 | ||
|
|
dd832ad6df | ||
|
|
8cf7bc9998 | ||
|
|
c1cf11c04c | ||
|
|
0058c40f46 | ||
|
|
1397389f95 | ||
|
|
6a2d3e1d22 | ||
|
|
46ac704013 | ||
|
|
0fdab2a5c5 | ||
|
|
8f4586bfef | ||
|
|
52d0c9861f | ||
|
|
feace61eb4 | ||
|
|
1f5cc8f283 | ||
|
|
60b8520237 | ||
|
|
6a67c5e9a2 | ||
|
|
bcc241518c | ||
|
|
6dfa8f1556 | ||
|
|
19dde692b2 | ||
|
|
14dc93b5d2 | ||
|
|
547620235e | ||
|
|
b43fd0a54b | ||
|
|
8b782c9416 | ||
|
|
aab4e62e24 | ||
|
|
1713fccfc4 | ||
|
|
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 | ||
|
|
ed2270ff46 | ||
|
|
45b6c3b338 | ||
|
|
84e2284f56 | ||
|
|
1c0d0be622 | ||
|
|
a9ce0f487d | ||
|
|
07533e0365 | ||
|
|
ee5ddd4264 | ||
|
|
f519d22d81 | ||
|
|
51ed87086a | ||
|
|
1ca3aa3cdb | ||
|
|
0178c63f6a | ||
|
|
8a97c409fa | ||
|
|
3dd0735305 | ||
|
|
536f775baa | ||
|
|
00f7a684a3 | ||
|
|
d79b166a6a | ||
|
|
b3d827f56a | ||
|
|
40bcef1dcb | ||
|
|
6146f1bdaa | ||
|
|
f5d82d9ef0 | ||
|
|
b2a29ae606 | ||
|
|
98ccba53a2 | ||
|
|
9bfda36647 | ||
|
|
5710cdf19c | ||
|
|
20cf54bfcd | ||
|
|
2ce639e750 | ||
|
|
b1ed413c4f | ||
|
|
b8c3060037 | ||
|
|
c3ea4940d7 | ||
|
|
40e1225b87 | ||
|
|
0c171122b2 | ||
|
|
6d0f4bb3da | ||
|
|
5e2cc6e20c | ||
|
|
99cb43bbea | ||
|
|
ca7d8277f7 | ||
|
|
337d26333e | ||
|
|
ebb64d255b | ||
|
|
7dcb199f68 | ||
|
|
4334e887de | ||
|
|
4e84dc4cc8 | ||
|
|
1a1ed072bf | ||
|
|
ae457f07c4 | ||
|
|
00095942c3 | ||
|
|
d1caa5fc21 | ||
|
|
813e2f97ac | ||
|
|
bcb5a90f5e | ||
|
|
020a1a3149 | ||
|
|
c4d649ec58 | ||
|
|
c02cf2c284 | ||
|
|
c30afd042e | ||
|
|
17640fe6cf | ||
|
|
2e4f6ee420 | ||
|
|
a3768d9221 | ||
|
|
80c3390363 | ||
|
|
a5e3869d8f | ||
|
|
aa7d7c2d02 | ||
|
|
015f205569 | ||
|
|
e59fb15926 | ||
|
|
173c585f2d | ||
|
|
6f8c27793e | ||
|
|
332b81c803 | ||
|
|
4b343b500d | ||
|
|
e87c537642 | ||
|
|
2e6300cce2 | ||
|
|
09514d15a6 | ||
|
|
0de23dcba0 | ||
|
|
bacb153151 | ||
|
|
a01aa299d8 | ||
|
|
44edbddbd8 | ||
|
|
79d677cf3c | ||
|
|
be39b6512f | ||
|
|
fcfeea35da | ||
|
|
7d0eb8c61e | ||
|
|
4d8438a6b6 | ||
|
|
f611244e35 | ||
|
|
546a978d3b | ||
|
|
70b23fb073 | ||
|
|
a56ca597d6 | ||
|
|
679e0228a8 | ||
|
|
e153394323 | ||
|
|
5bd1fcfcfd | ||
|
|
2a392ddc44 | ||
|
|
b5cb8bc0d9 | ||
|
|
fa170bcf98 | ||
|
|
7939d46949 | ||
|
|
ab9df8201a | ||
|
|
4a670ec091 | ||
|
|
10e57e59c4 | ||
|
|
b9ec43ef34 | ||
|
|
42197cd375 | ||
|
|
704852973b | ||
|
|
056b4200df | ||
|
|
250a7d8627 | ||
|
|
1ba51e161e | ||
|
|
32e58af896 | ||
|
|
312fa6fe76 | ||
|
|
afbe0837ba | ||
|
|
36ad2a720f | ||
|
|
901e3b14bb | ||
|
|
588d209f7b | ||
|
|
554c54e6be | ||
|
|
b0fac34ffc | ||
|
|
5ede9f7c6b | ||
|
|
c7254fd23e | ||
|
|
55fcea04af | ||
|
|
c212c0a6b2 | ||
|
|
a31fd6709a | ||
|
|
e367fd2b73 | ||
|
|
1ca67d0241 | ||
|
|
8ffa952ff9 | ||
|
|
da246fa30b | ||
|
|
13f306742e | ||
|
|
f3815dc45e | ||
|
|
d086254012 | ||
|
|
bc4d5ba097 | ||
|
|
c556783fe3 | ||
|
|
5fba4c12aa | ||
|
|
7e0dde3ece | ||
|
|
fc03e83531 | ||
|
|
4c441077c7 | ||
|
|
4a5ca81e9a | ||
|
|
75eebe8f8c | ||
|
|
271a8cdac5 | ||
|
|
25103c1188 | ||
|
|
d81058e606 | ||
|
|
693df54b3b | ||
|
|
ae6ed99dc4 | ||
|
|
14bd58e741 | ||
|
|
6d35a7a4ba | ||
|
|
46b0d1ceac | ||
|
|
67a66d2fcd | ||
|
|
43e90b57ea | ||
|
|
c80740e590 | ||
|
|
54ccb9611e | ||
|
|
8fcb897800 | ||
|
|
699eda5d1b | ||
|
|
d7d0a83550 | ||
|
|
e3c331c911 | ||
|
|
eda4dd6aec | ||
|
|
8ad7be474d | ||
|
|
a64435f155 | ||
|
|
fa160124d2 | ||
|
|
5765cb8330 | ||
|
|
f472b227bb | ||
|
|
d2b419c42e | ||
|
|
09d4de660f | ||
|
|
728dcd8523 | ||
|
|
93cf9bf6a9 | ||
|
|
50841f5e24 | ||
|
|
fc6d92d1fc | ||
|
|
7162a029bb | ||
|
|
d797ddd668 | ||
|
|
989e8c3aa6 | ||
|
|
08b79af242 | ||
|
|
0d2f346a30 | ||
|
|
39f1d5f5fd | ||
|
|
05008bb7f8 | ||
|
|
be90d6fc45 | ||
|
|
a1bcdf9924 | ||
|
|
b0e001393c | ||
|
|
2d08941f6a | ||
|
|
d0fef1f312 | ||
|
|
68342cb0d4 | ||
|
|
2b419212a7 | ||
|
|
b2cbc7e34d | ||
|
|
61247e575b | ||
|
|
31e18266d1 | ||
|
|
df8a8de889 | ||
|
|
8a037d6b29 | ||
|
|
47b555b98c | ||
|
|
0c2dae475e | ||
|
|
dc676d04d8 | ||
|
|
15b54bff50 | ||
|
|
47bd4b4c0b | ||
|
|
3c8b36ddfe | ||
|
|
608df3fddd | ||
|
|
c092c285ee | ||
|
|
93b745e379 | ||
|
|
c18db77ade | ||
|
|
2c0b167e6b | ||
|
|
313254d0c8 | ||
|
|
6f519c97d3 | ||
|
|
17a3e16b1d | ||
|
|
8199358088 | ||
|
|
412928eeaa | ||
|
|
51e1b935bd | ||
|
|
742b51e5e2 | ||
|
|
fdb5e2eebb | ||
|
|
0192f64cd2 | ||
|
|
193298ac87 | ||
|
|
a81cb81799 | ||
|
|
ad8a7fdc9b | ||
|
|
5440afcebe | ||
|
|
715d7e664c | ||
|
|
aa182cfa68 | ||
|
|
f92dd7a872 | ||
|
|
b02b9197d0 | ||
|
|
86d02be70c | ||
|
|
cb990978ee | ||
|
|
a103202c92 | ||
|
|
9d7b133037 | ||
|
|
f727f2a1a9 | ||
|
|
03034768d9 | ||
|
|
aed3e20e08 | ||
|
|
74bac6d986 | ||
|
|
7ebecc353a | ||
|
|
f0302b0d1e | ||
|
|
0b004ad089 | ||
|
|
c9001f068b | ||
|
|
96e0554aae | ||
|
|
31b4aadaba | ||
|
|
f46fa5392a | ||
|
|
3b6a17f193 | ||
|
|
aea77d3b8c | ||
|
|
7cfbe077db | ||
|
|
7bb620f941 | ||
|
|
5b0341a733 | ||
|
|
d99225da1f | ||
|
|
f279180a37 | ||
|
|
9a22018477 | ||
|
|
197119e56d | ||
|
|
dd055ddc5d | ||
|
|
94c3277245 | ||
|
|
fdaf402472 | ||
|
|
bce7764f75 | ||
|
|
70a258aae2 | ||
|
|
77cf00e8e4 | ||
|
|
de05579d1f | ||
|
|
c7d4b722d0 | ||
|
|
02b837c54b | ||
|
|
50e0e88cc2 | ||
|
|
c34245ff21 | ||
|
|
f1a8334f59 | ||
|
|
c8fc4ea500 | ||
|
|
39805bc103 | ||
|
|
2c615682df | ||
|
|
1257e4efac | ||
|
|
5e4a21087e | ||
|
|
2aaef99a54 | ||
|
|
161d3a795d | ||
|
|
9b671cb1a9 | ||
|
|
07d9a9f2c3 | ||
|
|
efabe7f536 | ||
|
|
82aead976e | ||
|
|
17be52c7b6 | ||
|
|
d0a196ec40 | ||
|
|
d484de185d | ||
|
|
96e4e7a4e8 | ||
|
|
4adb34b959 | ||
|
|
819bc12a68 | ||
|
|
eb23e5365f | ||
|
|
84e2faf8a8 | ||
|
|
84f58efc17 | ||
|
|
42254ee4a1 | ||
|
|
7c564aed7a | ||
|
|
8cdcb29274 | ||
|
|
403a369df9 | ||
|
|
5527912cd1 | ||
|
|
c8531dfe37 | ||
|
|
ed8bb2e5a1 | ||
|
|
dd66355488 | ||
|
|
fc3f83231c | ||
|
|
e70c712020 | ||
|
|
1b34aeaec4 | ||
|
|
2566bfa2ed | ||
|
|
ba06f2bbc6 | ||
|
|
1e4fe1680f | ||
|
|
2effb199a1 | ||
|
|
23c139320a | ||
|
|
f65eba606e | ||
|
|
7a2825da9a | ||
|
|
2975eddfe9 | ||
|
|
7f28eae954 | ||
|
|
789be5e942 | ||
|
|
85b114cdfd | ||
|
|
53d063e994 | ||
|
|
0ab081ccbc | ||
|
|
00ce9d64dc | ||
|
|
be20c024aa | ||
|
|
992fb9839a | ||
|
|
96ae2ee7ac | ||
|
|
379cecb08f | ||
|
|
9089b271b3 | ||
|
|
c9d522fad5 | ||
|
|
be80aa1512 | ||
|
|
c1d64a8027 | ||
|
|
1bc2aa9d38 | ||
|
|
e167ee104b | ||
|
|
43b85da314 | ||
|
|
b6c21c9766 | ||
|
|
874da8c8d6 | ||
|
|
989580d196 | ||
|
|
03ef54c37b | ||
|
|
ab56dda275 | ||
|
|
3a91f958e3 | ||
|
|
79913a0c9c | ||
|
|
d0fe64ecfa | ||
|
|
4da69685a1 | ||
|
|
bf560dd10d | ||
|
|
7ce76ee28d | ||
|
|
540e9bad29 | ||
|
|
22c2e2c4e5 | ||
|
|
f67d9dcdfa | ||
|
|
edfcadcbdc | ||
|
|
156bcc7d54 | ||
|
|
63ff912d76 | ||
|
|
6cbfaac4f4 | ||
|
|
6ad6e0d8c0 | ||
|
|
7c38bb03b9 | ||
|
|
5574172d99 | ||
|
|
bc8081ebae | ||
|
|
6ed6132c54 | ||
|
|
76c02c98d8 | ||
|
|
012a7885ff | ||
|
|
2b3d41d982 | ||
|
|
a56a48145b | ||
|
|
8dc097e23c | ||
|
|
0323520389 | ||
|
|
e1e395023d | ||
|
|
850214b103 | ||
|
|
02e9805482 | ||
|
|
0feae8402e | ||
|
|
042da53b54 | ||
|
|
aa1b2bace7 | ||
|
|
6b1b4d6015 | ||
|
|
ebeac417e5 | ||
|
|
be005616ea | ||
|
|
ec3a9b0615 | ||
|
|
0b3e651c4b | ||
|
|
426bdd3aa1 | ||
|
|
75e29d61f8 | ||
|
|
dfab283154 | ||
|
|
986c0d7edc | ||
|
|
918c44bc89 | ||
|
|
b8f680d74a | ||
|
|
76fcf6d545 | ||
|
|
c51d25c58b | ||
|
|
1efdba096c | ||
|
|
c4505b7c42 | ||
|
|
d5235bd40b | ||
|
|
cc3feb4843 | ||
|
|
353d105c04 | ||
|
|
070cb6c873 | ||
|
|
a066dda0f9 | ||
|
|
ac929c2603 | ||
|
|
9102402a18 | ||
|
|
54a0fc21d8 | ||
|
|
e2b8b7369e | ||
|
|
bcfbe515a4 | ||
|
|
46834ab5ce | ||
|
|
646000920f | ||
|
|
5ea83ccea1 | ||
|
|
03c6473685 | ||
|
|
d37890fac4 | ||
|
|
d5057ea8ea | ||
|
|
2cbebbe9b7 | ||
|
|
71b1885f74 | ||
|
|
2a8e3887ad | ||
|
|
2f92ce6bda | ||
|
|
9c58755317 | ||
|
|
9c1fe4d63b | ||
|
|
994d3c74fd | ||
|
|
a413c24b45 | ||
|
|
dc276a6393 | ||
|
|
cf6448845f | ||
|
|
b45c859861 | ||
|
|
fd81092392 | ||
|
|
26ef3073ae | ||
|
|
72a684a22f | ||
|
|
14529030be | ||
|
|
9570b797fd | ||
|
|
cdb5fb34dd | ||
|
|
ddff6a24f3 | ||
|
|
ae3c0acfc0 | ||
|
|
eef23ae49d | ||
|
|
2262f04fb3 | ||
|
|
b7a99ed508 | ||
|
|
38f68de3ea | ||
|
|
6b6f016189 | ||
|
|
82faa89912 | ||
|
|
dfd49de8d1 | ||
|
|
aa8dd80e54 | ||
|
|
751d9419ff | ||
|
|
643d7bf6fa | ||
|
|
afb20f79a9 | ||
|
|
6c2a83964b | ||
|
|
07daff261a | ||
|
|
8ddeb7f9fb | ||
|
|
1f7c089c70 | ||
|
|
f1f6852ab4 | ||
|
|
e5d66defbc | ||
|
|
29913c5b09 | ||
|
|
21d807d0c3 | ||
|
|
a6e5c32166 | ||
|
|
0b3e94b974 | ||
|
|
6db5aec672 | ||
|
|
73ff5fe9dc | ||
|
|
77694aac8e | ||
|
|
a04a27c1e3 | ||
|
|
7a547c70e3 | ||
|
|
b0abf0e7a5 | ||
|
|
48c49c6ec7 | ||
|
|
16564500e2 | ||
|
|
6f6b17b211 | ||
|
|
f1618ad9df | ||
|
|
947a14d6a2 | ||
|
|
f030ecd66f | ||
|
|
ec86fb77b0 | ||
|
|
cfa246adc5 | ||
|
|
ebb236e47c | ||
|
|
37b00d670b | ||
|
|
23516d0466 | ||
|
|
2b4f1ce1c2 | ||
|
|
c786858f17 | ||
|
|
a149cb231b | ||
|
|
3c9ef728e1 | ||
|
|
4257f58f96 | ||
|
|
ddfab31781 | ||
|
|
5e3e8f2809 | ||
|
|
fefa8b174d | ||
|
|
fb3d732a62 | ||
|
|
0658e323ae | ||
|
|
35046b33ff | ||
|
|
8f4c70c9cc | ||
|
|
c9bc14ab7f | ||
|
|
0f023905c8 | ||
|
|
61dc02514a | ||
|
|
590998fbaa | ||
|
|
7a1f631113 | ||
|
|
706229640f | ||
|
|
0397db51f7 | ||
|
|
e53928cf1e | ||
|
|
17b8fd6e6f | ||
|
|
03338b589c | ||
|
|
ac8560afd3 | ||
|
|
ef0cae6953 | ||
|
|
ed396b400d | ||
|
|
e6ef288a69 | ||
|
|
619d16ddd3 | ||
|
|
943fe79d3c | ||
|
|
310d4619b4 | ||
|
|
35e9d8752b | ||
|
|
aa057918ee | ||
|
|
5428ebf129 | ||
|
|
1384037430 | ||
|
|
c2bda2241c | ||
|
|
337d432fdd | ||
|
|
45d104fd2c | ||
|
|
00b2e25b01 | ||
|
|
1b16193e21 | ||
|
|
203aec2854 | ||
|
|
5997ac5424 | ||
|
|
b4f97aadf1 | ||
|
|
b7caaeb788 | ||
|
|
c3ca4fa8f3 | ||
|
|
d5563aafba | ||
|
|
c89c244225 | ||
|
|
08e540e12f | ||
|
|
2849eebb28 | ||
|
|
683307b9af | ||
|
|
5231ea1c1e | ||
|
|
35ae81c76c | ||
|
|
a4d7d94301 | ||
|
|
44b0092b44 | ||
|
|
c6b8f69ef2 | ||
|
|
eac35ef8f4 | ||
|
|
e9eee50b3e | ||
|
|
f7c797e0b0 | ||
|
|
f9d66e0a78 | ||
|
|
a9d1d6edf5 | ||
|
|
babb2c0856 | ||
|
|
0570f2cd5b | ||
|
|
9c72b7ae8f | ||
|
|
e4b0b153e5 | ||
|
|
3edd8d5ebe | ||
|
|
bf68a284bb | ||
|
|
978544ed3f | ||
|
|
98135bc5fd | ||
|
|
626924bc34 | ||
|
|
a0fd2b6d16 | ||
|
|
44c431e9e0 | ||
|
|
6852f892f0 | ||
|
|
ec1eaf00eb | ||
|
|
ae0aa7dc65 | ||
|
|
29f78c7429 | ||
|
|
cd8834e368 | ||
|
|
f3fc967710 | ||
|
|
7d93c64b5b | ||
|
|
d4b957db23 | ||
|
|
d9678855a0 | ||
|
|
65c01e3f6e | ||
|
|
6821cee443 | ||
|
|
15a12da321 | ||
|
|
009d064576 | ||
|
|
3a4e2d9f9b | ||
|
|
e7afa235f7 | ||
|
|
edc5bd5d21 | ||
|
|
c06b09e00c | ||
|
|
3eeba27191 | ||
|
|
c6d1a040a1 | ||
|
|
558a3fc865 | ||
|
|
e0b50ac80c | ||
|
|
07ef7ae18f | ||
|
|
d07bd33e06 | ||
|
|
1616ca1c34 | ||
|
|
52fe74fbea | ||
|
|
8857ccbf86 | ||
|
|
279a290b60 | ||
|
|
128a3fe2f2 | ||
|
|
c97acfc76c | ||
|
|
bc2dae1b21 | ||
|
|
983b8ebe38 | ||
|
|
b6ba4640de | ||
|
|
5214aaafe7 | ||
|
|
39713f95ea | ||
|
|
b88f56e185 | ||
|
|
248444c04d | ||
|
|
dbd27e6113 | ||
|
|
3f09a79645 | ||
|
|
98b3746b25 | ||
|
|
07f8c38820 | ||
|
|
8393c07601 | ||
|
|
22d7595357 | ||
|
|
2157d95c56 | ||
|
|
729b2a1f0d | ||
|
|
cbcd5fbd2c | ||
|
|
a3318b1253 | ||
|
|
e59c7e1124 | ||
|
|
8dc108030d | ||
|
|
6e4feecff0 | ||
|
|
77c085ef1a | ||
|
|
acc202031c |
39
.editorconfig
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# EditorConfig is awesome: https://EditorConfig.org
|
||||||
|
|
||||||
|
# top-most EditorConfig file
|
||||||
|
root = true
|
||||||
|
|
||||||
|
# Unix-style newlines with a newline ending every file
|
||||||
|
[*]
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
# Matches multiple files with brace expansion notation
|
||||||
|
# Set default charset
|
||||||
|
[*.{js,py}]
|
||||||
|
charset = utf-8# 4 space indentation
|
||||||
|
|
||||||
|
# Swift files
|
||||||
|
[*.swift]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
charset = utf-8# 4 space indentation
|
||||||
|
|
||||||
|
# 4 space indentation
|
||||||
|
[*.py]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
|
||||||
|
# Tab indentation (no size specified)
|
||||||
|
[Makefile]
|
||||||
|
indent_style = tab
|
||||||
|
|
||||||
|
# Indentation override for all JS under lib directory
|
||||||
|
[lib/**.js]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
# Matches the exact files either package.json or .travis.yml
|
||||||
|
[{package.json,.travis.yml}]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
1
.github/CODEOWNERS
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
* @JoeMatt @lonkelle @nythepegasus @Spidy123222 @SternXD
|
||||||
39
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
name: Bug Report
|
||||||
|
description: Report a bug
|
||||||
|
title: "[BUG] "
|
||||||
|
labels: ["bug"]
|
||||||
|
assignees: []
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thanks for taking the time to fill out this bug report! Before you continue filling out the report, please **[search in GitHub Issues](https://github.com/SideStore/SideStore/issues?q=is%3Aissue+is%3Aopen) for the bug you are experiencing** in case it has already been reported.
|
||||||
|
|
||||||
|
**Please use [Discord](https://discord.gg/sidestore-949183273383395328) or [GitHub Discussions](https://github.com/SideStore/SideStore/discussions) for support.**
|
||||||
|
- type: textarea
|
||||||
|
id: description
|
||||||
|
attributes:
|
||||||
|
label: Describe the bug
|
||||||
|
description: What is the bug and how did you discover it?
|
||||||
|
placeholder: Please be clear and concise with your description.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: how-to-reproduce
|
||||||
|
attributes:
|
||||||
|
label: Instructions to reproduce
|
||||||
|
description: Please include clear and consistent instructions for reproducing the bug to make it easier for us to fix it.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: app-version
|
||||||
|
attributes:
|
||||||
|
label: What version of SideStore are you using?
|
||||||
|
description: To retrieve this, go to `Settings` in the SideStore app and scroll down to the bottom.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: other-info
|
||||||
|
attributes:
|
||||||
|
label: Other info
|
||||||
|
description: If you have any other comments, other info that might be useful, or if you found a workaround, please put it here.
|
||||||
10
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# force issue template usage
|
||||||
|
blank_issues_enabled: false
|
||||||
|
|
||||||
|
contact_links:
|
||||||
|
- name: Discord
|
||||||
|
url: https://discord.gg/sidestore-949183273383395328
|
||||||
|
about: If you need support, please go here first instead of making an issue!
|
||||||
|
- name: GitHub Discussions
|
||||||
|
url: https://github.com/SideStore/SideStore/discussions
|
||||||
|
about: As an alternative to Discord, you can also make a new GitHub discussion.
|
||||||
32
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
name: Feature Request
|
||||||
|
description: Suggest a feature
|
||||||
|
title: "[FEATURE REQUEST] "
|
||||||
|
labels: ["enhancement"]
|
||||||
|
assignees: []
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thanks for taking the time to fill out this feature request! Before you continue filling out the form, please **[search in GitHub Issues](https://github.com/SideStore/SideStore/issues?q=is%3Aissue+is%3Aopen) for the feature you are suggestion** in case it has already been suggested.
|
||||||
|
|
||||||
|
**Please use [Discord](https://discord.gg/sidestore-949183273383395328) or [GitHub Discussions](https://github.com/SideStore/SideStore/discussions) for support.**
|
||||||
|
- type: textarea
|
||||||
|
id: description
|
||||||
|
attributes:
|
||||||
|
label: Describe the feature
|
||||||
|
description: What is the feature? How would it work?
|
||||||
|
placeholder: Please be clear and concise with your description.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: use-cases
|
||||||
|
attributes:
|
||||||
|
label: Use cases
|
||||||
|
description: Please include multiple use cases where this feature would be useful.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: alternatives
|
||||||
|
attributes:
|
||||||
|
label: Alternatives
|
||||||
|
description: If you have alternative ideas of how this feature could work, you can put them here.
|
||||||
12
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
### Changes
|
||||||
|
|
||||||
|
<!-- Fill this list with what your PR changes. Example: -->
|
||||||
|
- Fix bug
|
||||||
|
- Change UI for QOL
|
||||||
|
|
||||||
|
<!-- If your PR is ready to be merged, you can remove this section. -->
|
||||||
|
### Todo before merge
|
||||||
|
|
||||||
|
<!-- Example: -->
|
||||||
|
- [x] Finish UI changes
|
||||||
|
- [ ] Test
|
||||||
77
.github/workflows/attach_build_products.yml
vendored
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
name: Add artifact links to pull request and related issues
|
||||||
|
on:
|
||||||
|
workflow_run:
|
||||||
|
workflows: [Pull Request SideStore build]
|
||||||
|
types: [completed]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
artifacts-url-comments:
|
||||||
|
name: add artifact links to pull request and related issues job
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
||||||
|
steps:
|
||||||
|
- name: add artifact links to pull request and related issues step
|
||||||
|
uses: tonyhallett/artifacts-url-comments@v1.1.0
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
prefix: Builds for this Pull Request are available at
|
||||||
|
suffix: Have a nice day.
|
||||||
|
format: name
|
||||||
|
addTo: pull
|
||||||
|
# addTo: pullandissues
|
||||||
|
nightly-link-comment:
|
||||||
|
if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/github-script@v6
|
||||||
|
with:
|
||||||
|
# This snippet is public-domain, taken from
|
||||||
|
# https://github.com/oprypin/nightly.link/blob/master/.github/workflows/pr-comment.yml
|
||||||
|
script: |
|
||||||
|
async function upsertComment(owner, repo, issue_number, purpose, body) {
|
||||||
|
const {data: comments} = await github.rest.issues.listComments(
|
||||||
|
{owner, repo, issue_number});
|
||||||
|
|
||||||
|
const marker = `<!-- bot: ${purpose} -->`;
|
||||||
|
body = marker + "\n" + body;
|
||||||
|
|
||||||
|
const existing = comments.filter((c) => c.body.includes(marker));
|
||||||
|
if (existing.length > 0) {
|
||||||
|
const last = existing[existing.length - 1];
|
||||||
|
core.info(`Updating comment ${last.id}`);
|
||||||
|
await github.rest.issues.updateComment({
|
||||||
|
owner, repo,
|
||||||
|
body,
|
||||||
|
comment_id: last.id,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
core.info(`Creating a comment in issue / PR #${issue_number}`);
|
||||||
|
await github.rest.issues.createComment({issue_number, body, owner, repo});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const {owner, repo} = context.repo;
|
||||||
|
const run_id = ${{github.event.workflow_run.id}};
|
||||||
|
|
||||||
|
const pull_requests = ${{ toJSON(github.event.workflow_run.pull_requests) }};
|
||||||
|
if (!pull_requests.length) {
|
||||||
|
return core.error("This workflow doesn't match any pull requests!");
|
||||||
|
}
|
||||||
|
|
||||||
|
const artifacts = await github.paginate(
|
||||||
|
github.rest.actions.listWorkflowRunArtifacts, {owner, repo, run_id});
|
||||||
|
if (!artifacts.length) {
|
||||||
|
return core.error(`No artifacts found`);
|
||||||
|
}
|
||||||
|
let body = `Download the artifacts for this pull request (nightly.link):\n`;
|
||||||
|
for (const art of artifacts) {
|
||||||
|
body += `\n* [${art.name}.zip](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
core.info("Review thread message body:", body);
|
||||||
|
|
||||||
|
for (const pr of pull_requests) {
|
||||||
|
await upsertComment(owner, repo, pr.number,
|
||||||
|
"nightly-link", body);
|
||||||
|
}
|
||||||
103
.github/workflows/beta.yml
vendored
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
name: Beta SideStore build
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- '[0-9]+.[0-9]+.[0-9]+-beta.[0-9]+' # example: 1.0.0-beta.1
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build and upload SideStore Beta
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- os: 'macos-12'
|
||||||
|
version: '14.2'
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: brew install ldid
|
||||||
|
|
||||||
|
- name: Change version to tag
|
||||||
|
run: sed -e '/MARKETING_VERSION = .*/s/= .*/= ${{ github.ref_name }}/' -i '' Build.xcconfig
|
||||||
|
|
||||||
|
- name: Get version
|
||||||
|
id: version
|
||||||
|
run: echo "version=$(grep MARKETING_VERSION Build.xcconfig | sed -e "s/MARKETING_VERSION = //g")" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Echo version
|
||||||
|
run: echo "${{ steps.version.outputs.version }}"
|
||||||
|
|
||||||
|
- name: Setup Xcode
|
||||||
|
uses: maxim-lobanov/setup-xcode@v1.4.1
|
||||||
|
with:
|
||||||
|
xcode-version: ${{ matrix.version }}
|
||||||
|
|
||||||
|
|
||||||
|
- name: Cache Build
|
||||||
|
uses: irgaly/xcode-cache@v1
|
||||||
|
with:
|
||||||
|
key: xcode-cache-deriveddata-${{ github.sha }}
|
||||||
|
restore-keys: xcode-cache-deriveddata
|
||||||
|
|
||||||
|
- name: Build SideStore
|
||||||
|
run: make build | xcpretty && exit ${PIPESTATUS[0]}
|
||||||
|
|
||||||
|
- name: Fakesign app
|
||||||
|
run: make fakesign
|
||||||
|
|
||||||
|
- name: Convert to IPA
|
||||||
|
run: make ipa
|
||||||
|
|
||||||
|
- name: Get current date
|
||||||
|
id: date
|
||||||
|
run: echo "date=$(date -u +'%c')" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Get current date in AltStore date form
|
||||||
|
id: date_altstore
|
||||||
|
run: echo "date=$(date -u +'%Y-%m-%d')" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Upload to new beta release
|
||||||
|
uses: softprops/action-gh-release@v1
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
name: ${{ steps.version.outputs.version }}
|
||||||
|
tag_name: ${{ github.ref_name }}
|
||||||
|
draft: true
|
||||||
|
prerelease: true
|
||||||
|
files: SideStore.ipa
|
||||||
|
body: |
|
||||||
|
<!-- NOTE: to reset SideSource cache, go to `https://apps.sidestore.io/reset-cache/nightly/<sidesource key>`. This is not included in the GitHub Action since it makes draft releases so they can be edited and have a changelog. -->
|
||||||
|
Beta builds are hand-picked builds from development commits that will allow you to try out new features earlier than normal. However, **they might contain bugs and other issues. Use at your own risk!**
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
- TODO
|
||||||
|
|
||||||
|
## Build Info
|
||||||
|
|
||||||
|
Built at (UTC): `${{ steps.date.outputs.date }}`
|
||||||
|
Built at (UTC date): `${{ steps.date_altstore.outputs.date }}`
|
||||||
|
Commit SHA: `${{ github.sha }}`
|
||||||
|
Version: `${{ steps.version.outputs.version }}`
|
||||||
|
|
||||||
|
- name: Add version to IPA file name
|
||||||
|
run: mv SideStore.ipa SideStore-${{ steps.version.outputs.version }}.ipa
|
||||||
|
|
||||||
|
- name: Upload SideStore.ipa Artifact
|
||||||
|
uses: actions/upload-artifact@v3.1.0
|
||||||
|
with:
|
||||||
|
name: SideStore-${{ steps.version.outputs.version }}.ipa
|
||||||
|
path: SideStore-${{ steps.version.outputs.version }}.ipa
|
||||||
|
|
||||||
|
- name: Upload *.dSYM Artifact
|
||||||
|
uses: actions/upload-artifact@v3.1.0
|
||||||
|
with:
|
||||||
|
name: SideStore-${{ steps.version.outputs.version }}-dSYM
|
||||||
|
path: ./*.dSYM/
|
||||||
28
.github/workflows/increase-nightly-build-num.sh
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Ensure we are in root directory
|
||||||
|
cd "$(dirname "$0")/../.."
|
||||||
|
|
||||||
|
DATE=`date -u +'%Y.%m.%d'`
|
||||||
|
BUILD_NUM=1
|
||||||
|
|
||||||
|
write() {
|
||||||
|
sed -e "/MARKETING_VERSION = .*/s/$/-nightly.$DATE.$BUILD_NUM+$(git rev-parse --short HEAD)/" -i '' Build.xcconfig
|
||||||
|
echo "$DATE,$BUILD_NUM" > .nightly-build-num
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ ! -f ".nightly-build-num" ]; then
|
||||||
|
write
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
LAST_DATE=`cat .nightly-build-num | perl -n -e '/([^,]*),([^ ]*)$/ && print $1'`
|
||||||
|
LAST_BUILD_NUM=`cat .nightly-build-num | perl -n -e '/([^,]*),([^ ]*)$/ && print $2'`
|
||||||
|
|
||||||
|
if [[ "$DATE" != "$LAST_DATE" ]]; then
|
||||||
|
write
|
||||||
|
else
|
||||||
|
BUILD_NUM=`expr $LAST_BUILD_NUM + 1`
|
||||||
|
write
|
||||||
|
fi
|
||||||
|
|
||||||
109
.github/workflows/nightly.yml
vendored
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
name: Nightly SideStore build
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- develop
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build and upload SideStore Nightly
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- os: 'macos-12'
|
||||||
|
version: '14.2'
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: brew install ldid
|
||||||
|
|
||||||
|
- name: Cache .nightly-build-num
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: .nightly-build-num
|
||||||
|
key: nightly-build-num
|
||||||
|
|
||||||
|
- name: Increase nightly build number and set as version
|
||||||
|
run: bash .github/workflows/increase-nightly-build-num.sh
|
||||||
|
|
||||||
|
- name: Get version
|
||||||
|
id: version
|
||||||
|
run: echo "version=$(grep MARKETING_VERSION Build.xcconfig | sed -e "s/MARKETING_VERSION = //g")" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Echo version
|
||||||
|
run: echo "${{ steps.version.outputs.version }}"
|
||||||
|
|
||||||
|
- name: Setup Xcode
|
||||||
|
uses: maxim-lobanov/setup-xcode@v1.4.1
|
||||||
|
with:
|
||||||
|
xcode-version: ${{ matrix.version }}
|
||||||
|
|
||||||
|
- name: Cache Build
|
||||||
|
uses: irgaly/xcode-cache@v1
|
||||||
|
with:
|
||||||
|
key: xcode-cache-deriveddata-${{ github.sha }}
|
||||||
|
restore-keys: xcode-cache-deriveddata-
|
||||||
|
|
||||||
|
- name: Build SideStore
|
||||||
|
run: make build | xcpretty && exit ${PIPESTATUS[0]}
|
||||||
|
|
||||||
|
- name: Fakesign app
|
||||||
|
run: make fakesign
|
||||||
|
|
||||||
|
- name: Convert to IPA
|
||||||
|
run: make ipa
|
||||||
|
|
||||||
|
- name: Get current date
|
||||||
|
id: date
|
||||||
|
run: echo "date=$(date -u +'%c')" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Get current date in AltStore date form
|
||||||
|
id: date_altstore
|
||||||
|
run: echo "date=$(date -u +'%Y-%m-%d')" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Upload to nightly release
|
||||||
|
uses: IsaacShelton/update-existing-release@v1.3.1
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
release: "Nightly"
|
||||||
|
tag: "nightly"
|
||||||
|
prerelease: true
|
||||||
|
files: SideStore.ipa
|
||||||
|
body: |
|
||||||
|
This is an ⚠️ **EXPERIMENTAL** ⚠️ nightly build for commit [${{ github.sha }}](https://github.com/${{ github.repository }}/commit/${{ github.sha }}).
|
||||||
|
|
||||||
|
Nightly builds are **extremely experimental builds only meant to be used by developers and alpha testers. They often contain bugs and experimental features. Use at your own risk!**
|
||||||
|
|
||||||
|
If you want to try out new features early but want a lower chance of bugs, you can look at [SideStore Beta](https://github.com/${{ github.repository }}/releases?q=beta).
|
||||||
|
|
||||||
|
## Build Info
|
||||||
|
|
||||||
|
Built at (UTC): `${{ steps.date.outputs.date }}`
|
||||||
|
Built at (UTC date): `${{ steps.date_altstore.outputs.date }}`
|
||||||
|
Commit SHA: `${{ github.sha }}`
|
||||||
|
Version: `${{ steps.version.outputs.version }}`
|
||||||
|
|
||||||
|
- name: Add version to IPA file name
|
||||||
|
run: mv SideStore.ipa SideStore-${{ steps.version.outputs.version }}.ipa
|
||||||
|
|
||||||
|
- name: Upload SideStore.ipa Artifact
|
||||||
|
uses: actions/upload-artifact@v3.1.0
|
||||||
|
with:
|
||||||
|
name: SideStore-${{ steps.version.outputs.version }}.ipa
|
||||||
|
path: SideStore-${{ steps.version.outputs.version }}.ipa
|
||||||
|
|
||||||
|
- name: Upload *.dSYM Artifact
|
||||||
|
uses: actions/upload-artifact@v3.1.0
|
||||||
|
with:
|
||||||
|
name: SideStore-${{ steps.version.outputs.version }}-dSYM
|
||||||
|
path: ./*.dSYM/
|
||||||
70
.github/workflows/pr.yml
vendored
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
name: Pull Request SideStore build
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build and upload SideStore
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- os: 'macos-12'
|
||||||
|
version: '14.2'
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: brew install ldid
|
||||||
|
|
||||||
|
- name: Add PR suffix to version
|
||||||
|
run: sed -e "/MARKETING_VERSION = .*/s/\$/-pr.${{ github.event.pull_request.number }}+$(git rev-parse --short ${COMMIT:-HEAD})/" -i '' Build.xcconfig
|
||||||
|
env:
|
||||||
|
COMMIT: ${{ github.event.pull_request.head.sha }}
|
||||||
|
|
||||||
|
- name: Get version
|
||||||
|
id: version
|
||||||
|
run: echo "version=$(grep MARKETING_VERSION Build.xcconfig | sed -e "s/MARKETING_VERSION = //g")" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Echo version
|
||||||
|
run: echo "${{ steps.version.outputs.version }}"
|
||||||
|
|
||||||
|
- name: Setup Xcode
|
||||||
|
uses: maxim-lobanov/setup-xcode@v1.4.1
|
||||||
|
with:
|
||||||
|
xcode-version: ${{ matrix.version }}
|
||||||
|
|
||||||
|
- name: Cache Build
|
||||||
|
uses: irgaly/xcode-cache@v1
|
||||||
|
with:
|
||||||
|
key: xcode-cache-deriveddata-${{ github.sha }}
|
||||||
|
restore-keys: xcode-cache-deriveddata-
|
||||||
|
|
||||||
|
- name: Build SideStore
|
||||||
|
run: make build | xcpretty && exit ${PIPESTATUS[0]}
|
||||||
|
|
||||||
|
- name: Fakesign app
|
||||||
|
run: make fakesign
|
||||||
|
|
||||||
|
- name: Convert to IPA
|
||||||
|
run: make ipa
|
||||||
|
|
||||||
|
- name: Add version to IPA file name
|
||||||
|
run: mv SideStore.ipa SideStore-${{ steps.version.outputs.version }}.ipa
|
||||||
|
|
||||||
|
- name: Upload SideStore.ipa Artifact
|
||||||
|
uses: actions/upload-artifact@v3.1.0
|
||||||
|
with:
|
||||||
|
name: SideStore-${{ steps.version.outputs.version }}.ipa
|
||||||
|
path: SideStore-${{ steps.version.outputs.version }}.ipa
|
||||||
|
|
||||||
|
- name: Upload *.dSYM Artifact
|
||||||
|
uses: actions/upload-artifact@v3.1.0
|
||||||
|
with:
|
||||||
|
name: SideStore-${{ steps.version.outputs.version }}-dSYM
|
||||||
|
path: ./*.dSYM/
|
||||||
99
.github/workflows/stable.yml
vendored
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
name: Stable SideStore build
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- '[0-9]+.[0-9]+.[0-9]+' # example: 1.0.0
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build and upload SideStore
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- os: 'macos-12'
|
||||||
|
version: '14.2'
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: brew install ldid
|
||||||
|
|
||||||
|
- name: Change version to tag
|
||||||
|
run: sed -e '/MARKETING_VERSION = .*/s/= .*/= ${{ github.ref_name }}/' -i '' Build.xcconfig
|
||||||
|
|
||||||
|
- name: Get version
|
||||||
|
id: version
|
||||||
|
run: echo "version=$(grep MARKETING_VERSION Build.xcconfig | sed -e "s/MARKETING_VERSION = //g")" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Echo version
|
||||||
|
run: echo "${{ steps.version.outputs.version }}"
|
||||||
|
|
||||||
|
- name: Setup Xcode
|
||||||
|
uses: maxim-lobanov/setup-xcode@v1.4.1
|
||||||
|
with:
|
||||||
|
xcode-version: ${{ matrix.version }}
|
||||||
|
|
||||||
|
- name: Cache Build
|
||||||
|
uses: irgaly/xcode-cache@v1
|
||||||
|
with:
|
||||||
|
key: xcode-cache-deriveddata-${{ github.sha }}
|
||||||
|
restore-keys: xcode-cache-deriveddata-
|
||||||
|
|
||||||
|
- name: Build SideStore
|
||||||
|
run: make build | xcpretty && exit ${PIPESTATUS[0]}
|
||||||
|
|
||||||
|
- name: Fakesign app
|
||||||
|
run: make fakesign
|
||||||
|
|
||||||
|
- name: Convert to IPA
|
||||||
|
run: make ipa
|
||||||
|
|
||||||
|
- name: Get current date
|
||||||
|
id: date
|
||||||
|
run: echo "date=$(date -u +'%c')" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Get current date in AltStore date form
|
||||||
|
id: date_altstore
|
||||||
|
run: echo "date=$(date -u +'%Y-%m-%d')" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Upload to new stable release
|
||||||
|
uses: softprops/action-gh-release@v1
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
name: ${{ steps.version.outputs.version }}
|
||||||
|
tag_name: ${{ github.ref_name }}
|
||||||
|
draft: true
|
||||||
|
files: SideStore.ipa
|
||||||
|
body: |
|
||||||
|
<!-- NOTE: to reset SideSource cache, go to `https://apps.sidestore.io/reset-cache/nightly/<sidesource key>`. This is not included in the GitHub Action since it makes draft releases so they can be edited and have a changelog. -->
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
- TODO
|
||||||
|
|
||||||
|
## Build Info
|
||||||
|
|
||||||
|
Built at (UTC): `${{ steps.date.outputs.date }}`
|
||||||
|
Built at (UTC date): `${{ steps.date_altstore.outputs.date }}`
|
||||||
|
Commit SHA: `${{ github.sha }}`
|
||||||
|
Version: `${{ steps.version.outputs.version }}`
|
||||||
|
|
||||||
|
- name: Add version to IPA file name
|
||||||
|
run: mv SideStore.ipa SideStore-${{ steps.version.outputs.version }}.ipa
|
||||||
|
|
||||||
|
- name: Upload SideStore.ipa Artifact
|
||||||
|
uses: actions/upload-artifact@v3.1.0
|
||||||
|
with:
|
||||||
|
name: SideStore-${{ steps.version.outputs.version }}.ipa
|
||||||
|
path: SideStore-${{ steps.version.outputs.version }}.ipa
|
||||||
|
|
||||||
|
- name: Upload *.dSYM Artifact
|
||||||
|
uses: actions/upload-artifact@v3.1.0
|
||||||
|
with:
|
||||||
|
name: SideStore-${{ steps.version.outputs.version }}-dSYM
|
||||||
|
path: ./*.dSYM/
|
||||||
22
.gitignore
vendored
@@ -8,7 +8,7 @@
|
|||||||
## Build generated
|
## Build generated
|
||||||
build/
|
build/
|
||||||
DerivedData
|
DerivedData
|
||||||
|
archive.xcarchive
|
||||||
## Various settings
|
## Various settings
|
||||||
*.pbxuser
|
*.pbxuser
|
||||||
!default.pbxuser
|
!default.pbxuser
|
||||||
@@ -19,7 +19,7 @@ DerivedData
|
|||||||
*.perspectivev3
|
*.perspectivev3
|
||||||
!default.perspectivev3
|
!default.perspectivev3
|
||||||
xcuserdata
|
xcuserdata
|
||||||
|
AltStore.xcodeproj/project.xcworkspace/xcshareddata/swiftpm
|
||||||
## Other
|
## Other
|
||||||
*.xccheckout
|
*.xccheckout
|
||||||
*.moved-aside
|
*.moved-aside
|
||||||
@@ -27,4 +27,20 @@ xcuserdata
|
|||||||
*.xcscmblueprint
|
*.xcscmblueprint
|
||||||
|
|
||||||
## Obj-C/Swift specific
|
## Obj-C/Swift specific
|
||||||
*.hmap
|
*.hmap
|
||||||
|
/newrelic_agent.log
|
||||||
|
/CodeSigning.xcconfig
|
||||||
|
/.vscode
|
||||||
|
|
||||||
|
## AppCode specific
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
Payload/
|
||||||
|
SideStore.ipa
|
||||||
|
*.dSYM
|
||||||
|
|
||||||
|
Dependencies/.*-prebuilt-fetch-*
|
||||||
|
Dependencies/minimuxer/*
|
||||||
|
Dependencies/em_proxy/*
|
||||||
|
!Dependencies/**/.gitkeep
|
||||||
|
.nightly-build-num
|
||||||
|
|||||||
13
.gitmodules
vendored
@@ -1,18 +1,21 @@
|
|||||||
[submodule "Dependencies/Roxas"]
|
[submodule "Dependencies/Roxas"]
|
||||||
path = Dependencies/Roxas
|
path = Dependencies/Roxas
|
||||||
url = https://github.com/rileytestut/Roxas.git
|
url = https://github.com/rileytestut/Roxas.git
|
||||||
[submodule "Dependencies/AltSign"]
|
|
||||||
path = Dependencies/AltSign
|
|
||||||
url = https://github.com/rileytestut/AltSign.git
|
|
||||||
[submodule "Dependencies/libimobiledevice"]
|
[submodule "Dependencies/libimobiledevice"]
|
||||||
path = Dependencies/libimobiledevice
|
path = Dependencies/libimobiledevice
|
||||||
url = https://github.com/rileytestut/libimobiledevice.git
|
url = https://github.com/libimobiledevice/libimobiledevice
|
||||||
[submodule "Dependencies/libusbmuxd"]
|
[submodule "Dependencies/libusbmuxd"]
|
||||||
path = Dependencies/libusbmuxd
|
path = Dependencies/libusbmuxd
|
||||||
url = https://github.com/libimobiledevice/libusbmuxd.git
|
url = https://github.com/libimobiledevice/libusbmuxd.git
|
||||||
[submodule "Dependencies/libplist"]
|
[submodule "Dependencies/libplist"]
|
||||||
path = Dependencies/libplist
|
path = Dependencies/libplist
|
||||||
url = https://github.com/libimobiledevice/libplist.git
|
url = https://github.com/SideStore/libplist.git
|
||||||
[submodule "Dependencies/MarkdownAttributedString"]
|
[submodule "Dependencies/MarkdownAttributedString"]
|
||||||
path = Dependencies/MarkdownAttributedString
|
path = Dependencies/MarkdownAttributedString
|
||||||
url = https://github.com/chockenberry/MarkdownAttributedString.git
|
url = https://github.com/chockenberry/MarkdownAttributedString.git
|
||||||
|
[submodule "Dependencies/libimobiledevice-glue"]
|
||||||
|
path = Dependencies/libimobiledevice-glue
|
||||||
|
url = https://github.com/libimobiledevice/libimobiledevice-glue
|
||||||
|
[submodule "Dependencies/libfragmentzip"]
|
||||||
|
path = Dependencies/libfragmentzip
|
||||||
|
url = https://github.com/SideStore/libfragmentzip.git
|
||||||
|
|||||||
3
AltBackup.xcconfig
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#include "Build.xcconfig"
|
||||||
|
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = $(PRODUCT_BUNDLE_IDENTIFIER).AltBackup
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
<dict>
|
<dict>
|
||||||
<key>com.apple.security.application-groups</key>
|
<key>com.apple.security.application-groups</key>
|
||||||
<array>
|
<array>
|
||||||
<string>group.com.rileytestut.AltStore</string>
|
<string>group.$(APP_GROUP_IDENTIFIER)</string>
|
||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -5,9 +5,9 @@
|
|||||||
"color-space" : "srgb",
|
"color-space" : "srgb",
|
||||||
"components" : {
|
"components" : {
|
||||||
"alpha" : "1.000",
|
"alpha" : "1.000",
|
||||||
"blue" : "0.518",
|
"blue" : "175",
|
||||||
"green" : "0.502",
|
"green" : "4",
|
||||||
"red" : "0.004"
|
"red" : "115"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"idiom" : "universal"
|
"idiom" : "universal"
|
||||||
@@ -23,9 +23,9 @@
|
|||||||
"color-space" : "srgb",
|
"color-space" : "srgb",
|
||||||
"components" : {
|
"components" : {
|
||||||
"alpha" : "1.000",
|
"alpha" : "1.000",
|
||||||
"blue" : "0.404",
|
"blue" : "150",
|
||||||
"green" : "0.322",
|
"green" : "3",
|
||||||
"red" : "0.008"
|
"red" : "99"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"idiom" : "universal"
|
"idiom" : "universal"
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ struct BackupError: ALTLocalizedError
|
|||||||
let sourceFile: String
|
let sourceFile: String
|
||||||
let sourceFileLine: Int
|
let sourceFileLine: Int
|
||||||
|
|
||||||
var errorFailure: String?
|
var failure: String?
|
||||||
|
|
||||||
var failureReason: String? {
|
var failureReason: String? {
|
||||||
switch self.code
|
switch self.code
|
||||||
@@ -60,7 +60,7 @@ struct BackupError: ALTLocalizedError
|
|||||||
var errorUserInfo: [String : Any] {
|
var errorUserInfo: [String : Any] {
|
||||||
let userInfo: [String: Any?] = [NSLocalizedDescriptionKey: self.errorDescription,
|
let userInfo: [String: Any?] = [NSLocalizedDescriptionKey: self.errorDescription,
|
||||||
NSLocalizedFailureReasonErrorKey: self.failureReason,
|
NSLocalizedFailureReasonErrorKey: self.failureReason,
|
||||||
NSLocalizedFailureErrorKey: self.errorFailure,
|
NSLocalizedFailureErrorKey: self.failure,
|
||||||
ErrorUserInfoKey.sourceFile: self.sourceFile,
|
ErrorUserInfoKey.sourceFile: self.sourceFile,
|
||||||
ErrorUserInfoKey.sourceFileLine: self.sourceFileLine]
|
ErrorUserInfoKey.sourceFileLine: self.sourceFileLine]
|
||||||
return userInfo.compactMapValues { $0 }
|
return userInfo.compactMapValues { $0 }
|
||||||
@@ -69,7 +69,7 @@ struct BackupError: ALTLocalizedError
|
|||||||
init(_ code: Code, description: String? = nil, file: String = #file, line: Int = #line)
|
init(_ code: Code, description: String? = nil, file: String = #file, line: Int = #line)
|
||||||
{
|
{
|
||||||
self.code = code
|
self.code = code
|
||||||
self.errorFailure = description
|
self.failure = description
|
||||||
self.sourceFile = file
|
self.sourceFile = file
|
||||||
self.sourceFileLine = line
|
self.sourceFileLine = line
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,11 @@
|
|||||||
<dict>
|
<dict>
|
||||||
<key>ALTAppGroups</key>
|
<key>ALTAppGroups</key>
|
||||||
<array>
|
<array>
|
||||||
<string>group.com.rileytestut.AltStore</string>
|
<string>group.$(APP_GROUP_IDENTIFIER)</string>
|
||||||
|
<string>group.com.SideStore.SideStore</string>
|
||||||
</array>
|
</array>
|
||||||
<key>ALTBundleIdentifier</key>
|
<key>ALTBundleIdentifier</key>
|
||||||
<string>com.rileytestut.AltBackup</string>
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ private extension ViewController
|
|||||||
self.textLabel.text = String(format: NSLocalizedString("%@ is inactive.", comment: ""),
|
self.textLabel.text = String(format: NSLocalizedString("%@ is inactive.", comment: ""),
|
||||||
Bundle.main.appName ?? NSLocalizedString("App", comment: ""))
|
Bundle.main.appName ?? NSLocalizedString("App", comment: ""))
|
||||||
|
|
||||||
self.detailTextLabel.text = String(format: NSLocalizedString("Refresh %@ in AltStore to continue using it.", comment: ""),
|
self.detailTextLabel.text = String(format: NSLocalizedString("Refresh %@ in SideStore to continue using it.", comment: ""),
|
||||||
Bundle.main.appName ?? NSLocalizedString("this app", comment: ""))
|
Bundle.main.appName ?? NSLocalizedString("this app", comment: ""))
|
||||||
|
|
||||||
self.detailTextLabel.isHidden = false
|
self.detailTextLabel.isHidden = false
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>application-identifier</key>
|
<key>application-identifier</key>
|
||||||
<string>6XVY5G3U44.com.rileytestut.AltDaemon</string>
|
<string>$(DEVELOPMENT_TEAM).$(ORG_IDENTIFIER).AltDaemon</string>
|
||||||
<key>get-task-allow</key>
|
<key>get-task-allow</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>platform-application</key>
|
<key>platform-application</key>
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ extension XPCConnectionHandler: NSXPCListenerDelegate
|
|||||||
guard
|
guard
|
||||||
let codeSigningInfo = signingInfo as? [String: Any],
|
let codeSigningInfo = signingInfo as? [String: Any],
|
||||||
let bundleIdentifier = codeSigningInfo["identifier"] as? String,
|
let bundleIdentifier = codeSigningInfo["identifier"] as? String,
|
||||||
bundleIdentifier.contains("com.rileytestut.AltStore")
|
bundleIdentifier.contains(Bundle.Info.appbundleIdentifier)
|
||||||
else { return false }
|
else { return false }
|
||||||
|
|
||||||
let connection = XPCConnection(newConnection)
|
let connection = XPCConnection(newConnection)
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
//
|
|
||||||
// ALTPluginService.h
|
|
||||||
// AltPlugin
|
|
||||||
//
|
|
||||||
// Created by Riley Testut on 11/14/19.
|
|
||||||
// Copyright © 2019 Riley Testut. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
|
||||||
|
|
||||||
@class ALTAnisetteData;
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
|
||||||
|
|
||||||
@interface ALTPluginService : NSObject
|
|
||||||
|
|
||||||
@property (class, nonatomic, readonly) ALTPluginService *sharedService;
|
|
||||||
|
|
||||||
- (ALTAnisetteData *)requestAnisetteData;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
//
|
|
||||||
// ALTPluginService.m
|
|
||||||
// AltPlugin
|
|
||||||
//
|
|
||||||
// Created by Riley Testut on 11/14/19.
|
|
||||||
// Copyright © 2019 Riley Testut. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import "ALTPluginService.h"
|
|
||||||
|
|
||||||
#import <dlfcn.h>
|
|
||||||
|
|
||||||
#import "ALTAnisetteData.h"
|
|
||||||
|
|
||||||
@import AppKit;
|
|
||||||
|
|
||||||
@interface AKAppleIDSession : NSObject
|
|
||||||
- (id)appleIDHeadersForRequest:(id)arg1;
|
|
||||||
@end
|
|
||||||
|
|
||||||
@interface AKDevice
|
|
||||||
+ (AKDevice *)currentDevice;
|
|
||||||
- (NSString *)uniqueDeviceIdentifier;
|
|
||||||
- (NSString *)serialNumber;
|
|
||||||
- (NSString *)serverFriendlyDescription;
|
|
||||||
@end
|
|
||||||
|
|
||||||
@interface ALTPluginService ()
|
|
||||||
|
|
||||||
@property (nonatomic, readonly) NSISO8601DateFormatter *dateFormatter;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation ALTPluginService
|
|
||||||
|
|
||||||
+ (instancetype)sharedService
|
|
||||||
{
|
|
||||||
static ALTPluginService *_service = nil;
|
|
||||||
static dispatch_once_t onceToken;
|
|
||||||
dispatch_once(&onceToken, ^{
|
|
||||||
_service = [[self alloc] init];
|
|
||||||
});
|
|
||||||
|
|
||||||
return _service;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (instancetype)init
|
|
||||||
{
|
|
||||||
self = [super init];
|
|
||||||
if (self)
|
|
||||||
{
|
|
||||||
_dateFormatter = [[NSISO8601DateFormatter alloc] init];
|
|
||||||
}
|
|
||||||
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
+ (void)initialize
|
|
||||||
{
|
|
||||||
[[ALTPluginService sharedService] start];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)start
|
|
||||||
{
|
|
||||||
dlopen("/System/Library/PrivateFrameworks/AuthKit.framework/AuthKit", RTLD_NOW);
|
|
||||||
|
|
||||||
[[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(receiveNotification:) name:@"com.rileytestut.AltServer.FetchAnisetteData" object:nil];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (ALTAnisetteData *)requestAnisetteData
|
|
||||||
{
|
|
||||||
NSMutableURLRequest* req = [[NSMutableURLRequest alloc] initWithURL:[[NSURL alloc] initWithString:@"https://developerservices2.apple.com/services/QH65B2/listTeams.action?clientId=XABBG36SBA"]];
|
|
||||||
[req setHTTPMethod:@"POST"];
|
|
||||||
|
|
||||||
AKAppleIDSession *session = [[NSClassFromString(@"AKAppleIDSession") alloc] initWithIdentifier:@"com.apple.gs.xcode.auth"];
|
|
||||||
NSDictionary *headers = [session appleIDHeadersForRequest:req];
|
|
||||||
|
|
||||||
AKDevice *device = [NSClassFromString(@"AKDevice") currentDevice];
|
|
||||||
NSDate *date = [self.dateFormatter dateFromString:headers[@"X-Apple-I-Client-Time"]];
|
|
||||||
|
|
||||||
ALTAnisetteData *anisetteData = [[NSClassFromString(@"ALTAnisetteData") alloc] initWithMachineID:headers[@"X-Apple-I-MD-M"]
|
|
||||||
oneTimePassword:headers[@"X-Apple-I-MD"]
|
|
||||||
localUserID:headers[@"X-Apple-I-MD-LU"]
|
|
||||||
routingInfo:[headers[@"X-Apple-I-MD-RINFO"] longLongValue]
|
|
||||||
deviceUniqueIdentifier:device.uniqueDeviceIdentifier
|
|
||||||
deviceSerialNumber:device.serialNumber
|
|
||||||
deviceDescription:device.serverFriendlyDescription
|
|
||||||
date:date
|
|
||||||
locale:[NSLocale currentLocale]
|
|
||||||
timeZone:[NSTimeZone localTimeZone]];
|
|
||||||
|
|
||||||
return anisetteData;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)receiveNotification:(NSNotification *)notification
|
|
||||||
{
|
|
||||||
NSString *requestUUID = notification.userInfo[@"requestUUID"];
|
|
||||||
|
|
||||||
ALTAnisetteData *anisetteData = [self requestAnisetteData];
|
|
||||||
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:anisetteData requiringSecureCoding:YES error:nil];
|
|
||||||
|
|
||||||
[[NSDistributedNotificationCenter defaultCenter] postNotificationName:@"com.rileytestut.AltServer.AnisetteDataResponse" object:nil userInfo:@{@"requestUUID": requestUUID, @"anisetteData": data} deliverImmediately:YES];
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
@@ -1,78 +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>CFBundleDevelopmentRegion</key>
|
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
|
||||||
<key>CFBundleExecutable</key>
|
|
||||||
<string>$(EXECUTABLE_NAME)</string>
|
|
||||||
<key>CFBundleIdentifier</key>
|
|
||||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
|
||||||
<string>6.0</string>
|
|
||||||
<key>CFBundleName</key>
|
|
||||||
<string>$(PRODUCT_NAME)</string>
|
|
||||||
<key>CFBundlePackageType</key>
|
|
||||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
|
||||||
<key>CFBundleShortVersionString</key>
|
|
||||||
<string>$(MARKETING_VERSION)</string>
|
|
||||||
<key>CFBundleVersion</key>
|
|
||||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
|
||||||
<key>NSHumanReadableCopyright</key>
|
|
||||||
<string>Copyright © 2019 Riley Testut. All rights reserved.</string>
|
|
||||||
<key>NSPrincipalClass</key>
|
|
||||||
<string>ALTPluginService</string>
|
|
||||||
<key>Supported10.14PluginCompatibilityUUIDs</key>
|
|
||||||
<array>
|
|
||||||
<string># UUIDs for versions from 10.12 to 99.99.99</string>
|
|
||||||
<string># For mail version 10.0 (3226) on OS X Version 10.12 (build 16A319)</string>
|
|
||||||
<string>36CCB8BB-2207-455E-89BC-B9D6E47ABB5B</string>
|
|
||||||
<string># For mail version 10.1 (3251) on OS X Version 10.12.1 (build 16B2553a)</string>
|
|
||||||
<string>9054AFD9-2607-489E-8E63-8B09A749BC61</string>
|
|
||||||
<string># For mail version 10.2 (3259) on OS X Version 10.12.2 (build 16D12b)</string>
|
|
||||||
<string>1CD3B36A-0E3B-4A26-8F7E-5BDF96AAC97E</string>
|
|
||||||
<string># For mail version 10.3 (3273) on OS X Version 10.12.4 (build 16G1036)</string>
|
|
||||||
<string>21560BD9-A3CC-482E-9B99-95B7BF61EDC1</string>
|
|
||||||
<string># For mail version 11.0 (3441.0.1) on OS X Version 10.13 (build 17A315i)</string>
|
|
||||||
<string>C86CD990-4660-4E36-8CDA-7454DEB2E199</string>
|
|
||||||
<string># For mail version 12.0 (3445.100.39) on OS X Version 10.14.1 (build 18B45d)</string>
|
|
||||||
<string>A4343FAF-AE18-40D0-8A16-DFAE481AF9C1</string>
|
|
||||||
<string># For mail version 13.0 (3594.4.2) on OS X Version 10.15 (build 19A558d)</string>
|
|
||||||
<string>6EEA38FB-1A0B-469B-BB35-4C2E0EEA9053</string>
|
|
||||||
</array>
|
|
||||||
<key>Supported10.15PluginCompatibilityUUIDs</key>
|
|
||||||
<array>
|
|
||||||
<string># UUIDs for versions from 10.12 to 99.99.99</string>
|
|
||||||
<string># For mail version 10.0 (3226) on OS X Version 10.12 (build 16A319)</string>
|
|
||||||
<string>36CCB8BB-2207-455E-89BC-B9D6E47ABB5B</string>
|
|
||||||
<string># For mail version 10.1 (3251) on OS X Version 10.12.1 (build 16B2553a)</string>
|
|
||||||
<string>9054AFD9-2607-489E-8E63-8B09A749BC61</string>
|
|
||||||
<string># For mail version 10.2 (3259) on OS X Version 10.12.2 (build 16D12b)</string>
|
|
||||||
<string>1CD3B36A-0E3B-4A26-8F7E-5BDF96AAC97E</string>
|
|
||||||
<string># For mail version 10.3 (3273) on OS X Version 10.12.4 (build 16G1036)</string>
|
|
||||||
<string>21560BD9-A3CC-482E-9B99-95B7BF61EDC1</string>
|
|
||||||
<string># For mail version 11.0 (3441.0.1) on OS X Version 10.13 (build 17A315i)</string>
|
|
||||||
<string>C86CD990-4660-4E36-8CDA-7454DEB2E199</string>
|
|
||||||
<string># For mail version 12.0 (3445.100.39) on OS X Version 10.14.1 (build 18B45d)</string>
|
|
||||||
<string>A4343FAF-AE18-40D0-8A16-DFAE481AF9C1</string>
|
|
||||||
<string># For mail version 13.0 (3594.4.2) on OS X Version 10.15 (build 19A558d)</string>
|
|
||||||
<string>6EEA38FB-1A0B-469B-BB35-4C2E0EEA9053</string>
|
|
||||||
</array>
|
|
||||||
<key>Supported11.0PluginCompatibilityUUIDs</key>
|
|
||||||
<array>
|
|
||||||
<string>D985F0E4-3BBC-4B95-BBA1-12056AC4A531</string>
|
|
||||||
</array>
|
|
||||||
<key>Supported11.1PluginCompatibilityUUIDs</key>
|
|
||||||
<array>
|
|
||||||
<string>D985F0E4-3BBC-4B95-BBA1-12056AC4A531</string>
|
|
||||||
</array>
|
|
||||||
<key>Supported11.2PluginCompatibilityUUIDs</key>
|
|
||||||
<array>
|
|
||||||
<string>D985F0E4-3BBC-4B95-BBA1-12056AC4A531</string>
|
|
||||||
</array>
|
|
||||||
<key>Supported11.3PluginCompatibilityUUIDs</key>
|
|
||||||
<array>
|
|
||||||
<string>D985F0E4-3BBC-4B95-BBA1-12056AC4A531</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
//
|
|
||||||
// Use this file to import your target's public headers that you would like to expose to Swift.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import "ALTDeviceManager.h"
|
|
||||||
#import "ALTWiredConnection.h"
|
|
||||||
#import "ALTNotificationConnection.h"
|
|
||||||
|
|
||||||
// Shared
|
|
||||||
#import "ALTConstants.h"
|
|
||||||
#import "ALTConnection.h"
|
|
||||||
#import "AltXPCProtocol.h"
|
|
||||||
#import "NSError+ALTServerError.h"
|
|
||||||
#import "CFNotificationName+AltStore.h"
|
|
||||||
@@ -1,5 +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/>
|
|
||||||
</plist>
|
|
||||||
@@ -1,146 +0,0 @@
|
|||||||
//
|
|
||||||
// AnisetteDataManager.swift
|
|
||||||
// AltServer
|
|
||||||
//
|
|
||||||
// Created by Riley Testut on 11/16/19.
|
|
||||||
// Copyright © 2019 Riley Testut. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
private extension Bundle
|
|
||||||
{
|
|
||||||
struct ID
|
|
||||||
{
|
|
||||||
static let mail = "com.apple.mail"
|
|
||||||
static let altXPC = "com.rileytestut.AltXPC"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private extension ALTAnisetteData
|
|
||||||
{
|
|
||||||
func sanitize(byReplacingBundleID bundleID: String)
|
|
||||||
{
|
|
||||||
guard let range = self.deviceDescription.lowercased().range(of: "(" + bundleID.lowercased()) else { return }
|
|
||||||
|
|
||||||
var adjustedDescription = self.deviceDescription[..<range.lowerBound]
|
|
||||||
adjustedDescription += "(com.apple.dt.Xcode/3594.4.19)>"
|
|
||||||
|
|
||||||
self.deviceDescription = String(adjustedDescription)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class AnisetteDataManager: NSObject
|
|
||||||
{
|
|
||||||
static let shared = AnisetteDataManager()
|
|
||||||
|
|
||||||
private var anisetteDataCompletionHandlers: [String: (Result<ALTAnisetteData, Error>) -> Void] = [:]
|
|
||||||
private var anisetteDataTimers: [String: Timer] = [:]
|
|
||||||
|
|
||||||
private lazy var xpcConnection: NSXPCConnection = {
|
|
||||||
let connection = NSXPCConnection(serviceName: Bundle.ID.altXPC)
|
|
||||||
connection.remoteObjectInterface = NSXPCInterface(with: AltXPCProtocol.self)
|
|
||||||
connection.resume()
|
|
||||||
return connection
|
|
||||||
}()
|
|
||||||
|
|
||||||
private override init()
|
|
||||||
{
|
|
||||||
super.init()
|
|
||||||
|
|
||||||
DistributedNotificationCenter.default().addObserver(self, selector: #selector(AnisetteDataManager.handleAnisetteDataResponse(_:)), name: Notification.Name("com.rileytestut.AltServer.AnisetteDataResponse"), object: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func requestAnisetteData(_ completion: @escaping (Result<ALTAnisetteData, Error>) -> Void)
|
|
||||||
{
|
|
||||||
self.requestAnisetteDataFromXPCService { (result) in
|
|
||||||
do
|
|
||||||
{
|
|
||||||
let anisetteData = try result.get()
|
|
||||||
completion(.success(anisetteData))
|
|
||||||
}
|
|
||||||
catch CocoaError.xpcConnectionInterrupted
|
|
||||||
{
|
|
||||||
// SIP and/or AMFI are not disabled, so fall back to Mail plug-in.
|
|
||||||
self.requestAnisetteDataFromPlugin { (result) in
|
|
||||||
completion(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
completion(.failure(error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func isXPCAvailable(completion: @escaping (Bool) -> Void)
|
|
||||||
{
|
|
||||||
guard let proxy = self.xpcConnection.remoteObjectProxyWithErrorHandler({ (error) in
|
|
||||||
completion(false)
|
|
||||||
}) as? AltXPCProtocol else { return }
|
|
||||||
|
|
||||||
proxy.ping {
|
|
||||||
completion(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private extension AnisetteDataManager
|
|
||||||
{
|
|
||||||
func requestAnisetteDataFromXPCService(completion: @escaping (Result<ALTAnisetteData, Error>) -> Void)
|
|
||||||
{
|
|
||||||
guard let proxy = self.xpcConnection.remoteObjectProxyWithErrorHandler({ (error) in
|
|
||||||
print("Anisette XPC Error:", error)
|
|
||||||
completion(.failure(error))
|
|
||||||
}) as? AltXPCProtocol else { return }
|
|
||||||
|
|
||||||
proxy.requestAnisetteData { (anisetteData, error) in
|
|
||||||
anisetteData?.sanitize(byReplacingBundleID: Bundle.ID.altXPC)
|
|
||||||
completion(Result(anisetteData, error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func requestAnisetteDataFromPlugin(completion: @escaping (Result<ALTAnisetteData, Error>) -> Void)
|
|
||||||
{
|
|
||||||
let requestUUID = UUID().uuidString
|
|
||||||
self.anisetteDataCompletionHandlers[requestUUID] = completion
|
|
||||||
|
|
||||||
let timer = Timer(timeInterval: 1.0, repeats: false) { (timer) in
|
|
||||||
self.finishRequest(forUUID: requestUUID, result: .failure(ALTServerError(.pluginNotFound)))
|
|
||||||
}
|
|
||||||
self.anisetteDataTimers[requestUUID] = timer
|
|
||||||
|
|
||||||
RunLoop.main.add(timer, forMode: .default)
|
|
||||||
|
|
||||||
DistributedNotificationCenter.default().postNotificationName(Notification.Name("com.rileytestut.AltServer.FetchAnisetteData"), object: nil, userInfo: ["requestUUID": requestUUID], options: .deliverImmediately)
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func handleAnisetteDataResponse(_ notification: Notification)
|
|
||||||
{
|
|
||||||
guard let userInfo = notification.userInfo, let requestUUID = userInfo["requestUUID"] as? String else { return }
|
|
||||||
|
|
||||||
if
|
|
||||||
let archivedAnisetteData = userInfo["anisetteData"] as? Data,
|
|
||||||
let anisetteData = try? NSKeyedUnarchiver.unarchivedObject(ofClass: ALTAnisetteData.self, from: archivedAnisetteData)
|
|
||||||
{
|
|
||||||
anisetteData.sanitize(byReplacingBundleID: Bundle.ID.mail)
|
|
||||||
self.finishRequest(forUUID: requestUUID, result: .success(anisetteData))
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
self.finishRequest(forUUID: requestUUID, result: .failure(ALTServerError(.invalidAnisetteData)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func finishRequest(forUUID requestUUID: String, result: Result<ALTAnisetteData, Error>)
|
|
||||||
{
|
|
||||||
let completionHandler = self.anisetteDataCompletionHandlers[requestUUID]
|
|
||||||
self.anisetteDataCompletionHandlers[requestUUID] = nil
|
|
||||||
|
|
||||||
let timer = self.anisetteDataTimers[requestUUID]
|
|
||||||
self.anisetteDataTimers[requestUUID] = nil
|
|
||||||
|
|
||||||
timer?.invalidate()
|
|
||||||
completionHandler?(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,397 +0,0 @@
|
|||||||
//
|
|
||||||
// AppDelegate.swift
|
|
||||||
// AltServer
|
|
||||||
//
|
|
||||||
// Created by Riley Testut on 5/24/19.
|
|
||||||
// Copyright © 2019 Riley Testut. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Cocoa
|
|
||||||
import UserNotifications
|
|
||||||
|
|
||||||
import AltSign
|
|
||||||
|
|
||||||
import LaunchAtLogin
|
|
||||||
|
|
||||||
#if STAGING
|
|
||||||
private let altstoreAppURL = URL(string: "https://f000.backblazeb2.com/file/altstore-staging/altstore.ipa")!
|
|
||||||
#elseif BETA
|
|
||||||
private let altstoreAppURL = URL(string: "https://f000.backblazeb2.com/file/altstore/altstore-beta.ipa")!
|
|
||||||
#else
|
|
||||||
private let altstoreAppURL = URL(string: "https://f000.backblazeb2.com/file/altstore/altstore.ipa")!
|
|
||||||
#endif
|
|
||||||
|
|
||||||
@NSApplicationMain
|
|
||||||
class AppDelegate: NSObject, NSApplicationDelegate {
|
|
||||||
|
|
||||||
private let pluginManager = PluginManager()
|
|
||||||
|
|
||||||
private var statusItem: NSStatusItem?
|
|
||||||
|
|
||||||
private var connectedDevices = [ALTDevice]()
|
|
||||||
|
|
||||||
private weak var authenticationAlert: NSAlert?
|
|
||||||
|
|
||||||
@IBOutlet private var appMenu: NSMenu!
|
|
||||||
@IBOutlet private var connectedDevicesMenu: NSMenu!
|
|
||||||
@IBOutlet private var sideloadIPAConnectedDevicesMenu: NSMenu!
|
|
||||||
@IBOutlet private var launchAtLoginMenuItem: NSMenuItem!
|
|
||||||
@IBOutlet private var installMailPluginMenuItem: NSMenuItem!
|
|
||||||
|
|
||||||
private weak var authenticationAppleIDTextField: NSTextField?
|
|
||||||
private weak var authenticationPasswordTextField: NSSecureTextField?
|
|
||||||
|
|
||||||
func applicationDidFinishLaunching(_ aNotification: Notification)
|
|
||||||
{
|
|
||||||
UserDefaults.standard.registerDefaults()
|
|
||||||
|
|
||||||
UNUserNotificationCenter.current().delegate = self
|
|
||||||
|
|
||||||
ServerConnectionManager.shared.start()
|
|
||||||
ALTDeviceManager.shared.start()
|
|
||||||
|
|
||||||
let item = NSStatusBar.system.statusItem(withLength: -1)
|
|
||||||
item.menu = self.appMenu
|
|
||||||
item.button?.image = NSImage(named: "MenuBarIcon")
|
|
||||||
self.statusItem = item
|
|
||||||
|
|
||||||
self.appMenu.delegate = self
|
|
||||||
self.connectedDevicesMenu.delegate = self
|
|
||||||
self.sideloadIPAConnectedDevicesMenu.delegate = self
|
|
||||||
|
|
||||||
UNUserNotificationCenter.current().requestAuthorization(options: [.alert]) { (success, error) in
|
|
||||||
guard success else { return }
|
|
||||||
|
|
||||||
if !UserDefaults.standard.didPresentInitialNotification
|
|
||||||
{
|
|
||||||
let content = UNMutableNotificationContent()
|
|
||||||
content.title = NSLocalizedString("AltServer Running", comment: "")
|
|
||||||
content.body = NSLocalizedString("AltServer runs in the background as a menu bar app listening for AltStore.", comment: "")
|
|
||||||
|
|
||||||
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
|
|
||||||
UNUserNotificationCenter.current().add(request)
|
|
||||||
|
|
||||||
UserDefaults.standard.didPresentInitialNotification = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.pluginManager.isUpdateAvailable
|
|
||||||
{
|
|
||||||
self.installMailPlugin()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func applicationWillTerminate(_ aNotification: Notification)
|
|
||||||
{
|
|
||||||
// Insert code here to tear down your application
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private extension AppDelegate
|
|
||||||
{
|
|
||||||
@objc func installAltStore(_ item: NSMenuItem)
|
|
||||||
{
|
|
||||||
guard let index = item.menu?.index(of: item), index != -1 else { return }
|
|
||||||
|
|
||||||
let device = self.connectedDevices[index]
|
|
||||||
self.installApplication(at: altstoreAppURL, to: device)
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func sideloadIPA(_ item: NSMenuItem)
|
|
||||||
{
|
|
||||||
guard let index = item.menu?.index(of: item), index != -1 else { return }
|
|
||||||
|
|
||||||
let device = self.connectedDevices[index]
|
|
||||||
|
|
||||||
let openPanel = NSOpenPanel()
|
|
||||||
openPanel.canChooseDirectories = false
|
|
||||||
openPanel.allowsMultipleSelection = false
|
|
||||||
openPanel.allowedFileTypes = ["ipa"]
|
|
||||||
openPanel.begin { (response) in
|
|
||||||
guard let fileURL = openPanel.url, response == .OK else { return }
|
|
||||||
self.installApplication(at: fileURL, to: device)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func installApplication(at url: URL, to device: ALTDevice)
|
|
||||||
{
|
|
||||||
let alert = NSAlert()
|
|
||||||
alert.messageText = NSLocalizedString("Please enter your Apple ID and password.", comment: "")
|
|
||||||
alert.informativeText = NSLocalizedString("Your Apple ID and password are not saved and are only sent to Apple for authentication.", comment: "")
|
|
||||||
|
|
||||||
let textFieldSize = NSSize(width: 300, height: 22)
|
|
||||||
|
|
||||||
let appleIDTextField = NSTextField(frame: NSRect(x: 0, y: 0, width: textFieldSize.width, height: textFieldSize.height))
|
|
||||||
appleIDTextField.delegate = self
|
|
||||||
appleIDTextField.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
appleIDTextField.placeholderString = NSLocalizedString("Apple ID", comment: "")
|
|
||||||
alert.window.initialFirstResponder = appleIDTextField
|
|
||||||
self.authenticationAppleIDTextField = appleIDTextField
|
|
||||||
|
|
||||||
let passwordTextField = NSSecureTextField(frame: NSRect(x: 0, y: 0, width: textFieldSize.width, height: textFieldSize.height))
|
|
||||||
passwordTextField.delegate = self
|
|
||||||
passwordTextField.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
passwordTextField.placeholderString = NSLocalizedString("Password", comment: "")
|
|
||||||
self.authenticationPasswordTextField = passwordTextField
|
|
||||||
|
|
||||||
appleIDTextField.nextKeyView = passwordTextField
|
|
||||||
|
|
||||||
let stackView = NSStackView(frame: NSRect(x: 0, y: 0, width: textFieldSize.width, height: textFieldSize.height * 2))
|
|
||||||
stackView.orientation = .vertical
|
|
||||||
stackView.distribution = .equalSpacing
|
|
||||||
stackView.spacing = 0
|
|
||||||
stackView.addArrangedSubview(appleIDTextField)
|
|
||||||
stackView.addArrangedSubview(passwordTextField)
|
|
||||||
alert.accessoryView = stackView
|
|
||||||
|
|
||||||
alert.addButton(withTitle: NSLocalizedString("Install", comment: ""))
|
|
||||||
alert.addButton(withTitle: NSLocalizedString("Cancel", comment: ""))
|
|
||||||
|
|
||||||
self.authenticationAlert = alert
|
|
||||||
self.validate()
|
|
||||||
|
|
||||||
NSRunningApplication.current.activate(options: .activateIgnoringOtherApps)
|
|
||||||
|
|
||||||
let response = alert.runModal()
|
|
||||||
guard response == .alertFirstButtonReturn else { return }
|
|
||||||
|
|
||||||
let username = appleIDTextField.stringValue
|
|
||||||
let password = passwordTextField.stringValue
|
|
||||||
|
|
||||||
func install()
|
|
||||||
{
|
|
||||||
ALTDeviceManager.shared.installApplication(at: url, to: device, appleID: username, password: password) { (result) in
|
|
||||||
switch result
|
|
||||||
{
|
|
||||||
case .success(let application):
|
|
||||||
let content = UNMutableNotificationContent()
|
|
||||||
content.title = NSLocalizedString("Installation Succeeded", comment: "")
|
|
||||||
content.body = String(format: NSLocalizedString("%@ was successfully installed on %@.", comment: ""), application.name, device.name)
|
|
||||||
|
|
||||||
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
|
|
||||||
UNUserNotificationCenter.current().add(request)
|
|
||||||
|
|
||||||
case .failure(InstallError.cancelled), .failure(ALTAppleAPIError.requiresTwoFactorAuthentication):
|
|
||||||
// Ignore
|
|
||||||
break
|
|
||||||
|
|
||||||
case .failure(let error as NSError):
|
|
||||||
|
|
||||||
let alert = NSAlert()
|
|
||||||
alert.alertStyle = .critical
|
|
||||||
alert.messageText = NSLocalizedString("Installation Failed", comment: "")
|
|
||||||
|
|
||||||
if let underlyingError = error.userInfo[NSUnderlyingErrorKey] as? Error
|
|
||||||
{
|
|
||||||
alert.informativeText = underlyingError.localizedDescription
|
|
||||||
}
|
|
||||||
else if let recoverySuggestion = error.localizedRecoverySuggestion
|
|
||||||
{
|
|
||||||
alert.informativeText = error.localizedDescription + "\n\n" + recoverySuggestion
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
alert.informativeText = error.localizedDescription
|
|
||||||
}
|
|
||||||
|
|
||||||
NSRunningApplication.current.activate(options: .activateIgnoringOtherApps)
|
|
||||||
|
|
||||||
alert.runModal()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !self.pluginManager.isMailPluginInstalled || self.pluginManager.isUpdateAvailable
|
|
||||||
{
|
|
||||||
AnisetteDataManager.shared.isXPCAvailable { (isAvailable) in
|
|
||||||
if isAvailable
|
|
||||||
{
|
|
||||||
// XPC service is available, so we don't need to install/update Mail plug-in.
|
|
||||||
// Users can still manually do so from the AltServer menu.
|
|
||||||
install()
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.installMailPlugin { (result) in
|
|
||||||
switch result
|
|
||||||
{
|
|
||||||
case .failure: break
|
|
||||||
case .success: install()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
install()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func toggleLaunchAtLogin(_ item: NSMenuItem)
|
|
||||||
{
|
|
||||||
LaunchAtLogin.isEnabled.toggle()
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func handleInstallMailPluginMenuItem(_ item: NSMenuItem)
|
|
||||||
{
|
|
||||||
if !self.pluginManager.isMailPluginInstalled || self.pluginManager.isUpdateAvailable
|
|
||||||
{
|
|
||||||
self.installMailPlugin()
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
self.uninstallMailPlugin()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func installMailPlugin(completion: ((Result<Void, Error>) -> Void)? = nil)
|
|
||||||
{
|
|
||||||
self.pluginManager.installMailPlugin { (result) in
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
switch result
|
|
||||||
{
|
|
||||||
case .failure(PluginError.cancelled): break
|
|
||||||
case .failure(let error):
|
|
||||||
let alert = NSAlert()
|
|
||||||
alert.messageText = NSLocalizedString("Failed to Install Mail Plug-in", comment: "")
|
|
||||||
alert.informativeText = error.localizedDescription
|
|
||||||
alert.runModal()
|
|
||||||
|
|
||||||
case .success:
|
|
||||||
let alert = NSAlert()
|
|
||||||
alert.messageText = NSLocalizedString("Mail Plug-in Installed", comment: "")
|
|
||||||
alert.informativeText = NSLocalizedString("Please restart Mail and enable AltPlugin in Mail's Preferences. Mail must be running when installing or refreshing apps with AltServer.", comment: "")
|
|
||||||
alert.runModal()
|
|
||||||
}
|
|
||||||
|
|
||||||
completion?(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func uninstallMailPlugin()
|
|
||||||
{
|
|
||||||
self.pluginManager.uninstallMailPlugin { (result) in
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
switch result
|
|
||||||
{
|
|
||||||
case .failure(PluginError.cancelled): break
|
|
||||||
case .failure(let error):
|
|
||||||
let alert = NSAlert()
|
|
||||||
alert.messageText = NSLocalizedString("Failed to Uninstall Mail Plug-in", comment: "")
|
|
||||||
alert.informativeText = error.localizedDescription
|
|
||||||
alert.runModal()
|
|
||||||
|
|
||||||
case .success:
|
|
||||||
let alert = NSAlert()
|
|
||||||
alert.messageText = NSLocalizedString("Mail Plug-in Uninstalled", comment: "")
|
|
||||||
alert.informativeText = NSLocalizedString("Please restart Mail for changes to take effect. You will not be able to use AltServer until the plug-in is reinstalled.", comment: "")
|
|
||||||
alert.runModal()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension AppDelegate: NSMenuDelegate
|
|
||||||
{
|
|
||||||
func menuWillOpen(_ menu: NSMenu)
|
|
||||||
{
|
|
||||||
guard menu == self.appMenu else { return }
|
|
||||||
|
|
||||||
self.connectedDevices = ALTDeviceManager.shared.availableDevices
|
|
||||||
|
|
||||||
self.launchAtLoginMenuItem.target = self
|
|
||||||
self.launchAtLoginMenuItem.action = #selector(AppDelegate.toggleLaunchAtLogin(_:))
|
|
||||||
self.launchAtLoginMenuItem.state = LaunchAtLogin.isEnabled ? .on : .off
|
|
||||||
|
|
||||||
if self.pluginManager.isUpdateAvailable
|
|
||||||
{
|
|
||||||
self.installMailPluginMenuItem.title = NSLocalizedString("Update Mail Plug-in", comment: "")
|
|
||||||
}
|
|
||||||
else if self.pluginManager.isMailPluginInstalled
|
|
||||||
{
|
|
||||||
self.installMailPluginMenuItem.title = NSLocalizedString("Uninstall Mail Plug-in", comment: "")
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
self.installMailPluginMenuItem.title = NSLocalizedString("Install Mail Plug-in", comment: "")
|
|
||||||
}
|
|
||||||
self.installMailPluginMenuItem.target = self
|
|
||||||
self.installMailPluginMenuItem.action = #selector(AppDelegate.handleInstallMailPluginMenuItem(_:))
|
|
||||||
}
|
|
||||||
|
|
||||||
func numberOfItems(in menu: NSMenu) -> Int
|
|
||||||
{
|
|
||||||
guard menu == self.connectedDevicesMenu || menu == self.sideloadIPAConnectedDevicesMenu else { return -1 }
|
|
||||||
|
|
||||||
return self.connectedDevices.isEmpty ? 1 : self.connectedDevices.count
|
|
||||||
}
|
|
||||||
|
|
||||||
func menu(_ menu: NSMenu, update item: NSMenuItem, at index: Int, shouldCancel: Bool) -> Bool
|
|
||||||
{
|
|
||||||
guard menu == self.connectedDevicesMenu || menu == self.sideloadIPAConnectedDevicesMenu else { return false }
|
|
||||||
|
|
||||||
if self.connectedDevices.isEmpty
|
|
||||||
{
|
|
||||||
item.title = NSLocalizedString("No Connected Devices", comment: "")
|
|
||||||
item.isEnabled = false
|
|
||||||
item.target = nil
|
|
||||||
item.action = nil
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
let device = self.connectedDevices[index]
|
|
||||||
item.title = device.name
|
|
||||||
item.isEnabled = true
|
|
||||||
item.target = self
|
|
||||||
item.action = (menu == self.connectedDevicesMenu) ? #selector(AppDelegate.installAltStore(_:)) : #selector(AppDelegate.sideloadIPA(_:))
|
|
||||||
item.tag = index
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension AppDelegate: NSTextFieldDelegate
|
|
||||||
{
|
|
||||||
func controlTextDidChange(_ obj: Notification)
|
|
||||||
{
|
|
||||||
self.validate()
|
|
||||||
}
|
|
||||||
|
|
||||||
func controlTextDidEndEditing(_ obj: Notification)
|
|
||||||
{
|
|
||||||
self.validate()
|
|
||||||
}
|
|
||||||
|
|
||||||
private func validate()
|
|
||||||
{
|
|
||||||
guard
|
|
||||||
let appleID = self.authenticationAppleIDTextField?.stringValue.trimmingCharacters(in: .whitespacesAndNewlines),
|
|
||||||
let password = self.authenticationPasswordTextField?.stringValue.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
||||||
else { return }
|
|
||||||
|
|
||||||
if appleID.isEmpty || password.isEmpty
|
|
||||||
{
|
|
||||||
self.authenticationAlert?.buttons.first?.isEnabled = false
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
self.authenticationAlert?.buttons.first?.isEnabled = true
|
|
||||||
}
|
|
||||||
|
|
||||||
self.authenticationAlert?.layout()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension AppDelegate: UNUserNotificationCenterDelegate
|
|
||||||
{
|
|
||||||
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void)
|
|
||||||
{
|
|
||||||
completionHandler([.alert, .sound, .badge])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
{
|
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"size" : "16x16",
|
|
||||||
"idiom" : "mac",
|
|
||||||
"filename" : "Icon@16.png",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "16x16",
|
|
||||||
"idiom" : "mac",
|
|
||||||
"filename" : "Icon@32-1.png",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "32x32",
|
|
||||||
"idiom" : "mac",
|
|
||||||
"filename" : "Icon@32.png",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "32x32",
|
|
||||||
"idiom" : "mac",
|
|
||||||
"filename" : "Icon@64.png",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "128x128",
|
|
||||||
"idiom" : "mac",
|
|
||||||
"filename" : "Icon@128.png",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "128x128",
|
|
||||||
"idiom" : "mac",
|
|
||||||
"filename" : "Icon@256-1.png",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "256x256",
|
|
||||||
"idiom" : "mac",
|
|
||||||
"filename" : "Icon@256.png",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "256x256",
|
|
||||||
"idiom" : "mac",
|
|
||||||
"filename" : "Icon@512-1.png",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "512x512",
|
|
||||||
"idiom" : "mac",
|
|
||||||
"filename" : "Icon@512.png",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "512x512",
|
|
||||||
"idiom" : "mac",
|
|
||||||
"filename" : "Icon@1024.png",
|
|
||||||
"scale" : "2x"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"version" : 1,
|
|
||||||
"author" : "xcode"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 294 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 112 KiB |
|
Before Width: | Height: | Size: 112 KiB |
|
Before Width: | Height: | Size: 6.7 KiB |
@@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"info" : {
|
|
||||||
"version" : 1,
|
|
||||||
"author" : "xcode"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"filename" : "MenuBar@19.png",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"filename" : "MenuBar@38.png",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "3x"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"version" : 1,
|
|
||||||
"author" : "xcode"
|
|
||||||
},
|
|
||||||
"properties" : {
|
|
||||||
"template-rendering-intent" : "template"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 3.6 KiB |
@@ -1,381 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="17503.1" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
|
||||||
<dependencies>
|
|
||||||
<deployment identifier="macosx"/>
|
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="17503.1"/>
|
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
|
||||||
</dependencies>
|
|
||||||
<scenes>
|
|
||||||
<!--Application-->
|
|
||||||
<scene sceneID="JPo-4y-FX3">
|
|
||||||
<objects>
|
|
||||||
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
|
|
||||||
<stackView distribution="fill" orientation="vertical" alignment="leading" spacing="4" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" id="urc-xw-Dhc">
|
|
||||||
<rect key="frame" x="0.0" y="0.0" width="300" height="46"/>
|
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
|
||||||
<subviews>
|
|
||||||
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="zLd-d8-ghZ">
|
|
||||||
<rect key="frame" x="0.0" y="25" width="300" height="21"/>
|
|
||||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" placeholderString="Apple ID" drawsBackground="YES" id="BXa-Re-rs3">
|
|
||||||
<font key="font" metaFont="system"/>
|
|
||||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
|
||||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
|
||||||
</textFieldCell>
|
|
||||||
<connections>
|
|
||||||
<outlet property="delegate" destination="Voe-Tx-rLC" id="QtW-r2-Vuh"/>
|
|
||||||
<outlet property="nextKeyView" destination="9rp-Vx-rvB" id="bQY-qj-Sej"/>
|
|
||||||
</connections>
|
|
||||||
</textField>
|
|
||||||
<secureTextField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="9rp-Vx-rvB">
|
|
||||||
<rect key="frame" x="0.0" y="0.0" width="300" height="21"/>
|
|
||||||
<secureTextFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" placeholderString="Password" drawsBackground="YES" usesSingleLineMode="YES" id="xqJ-wt-DlP">
|
|
||||||
<font key="font" metaFont="system"/>
|
|
||||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
|
||||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
|
||||||
<allowedInputSourceLocales>
|
|
||||||
<string>NSAllRomanInputSourcesLocaleIdentifier</string>
|
|
||||||
</allowedInputSourceLocales>
|
|
||||||
</secureTextFieldCell>
|
|
||||||
<connections>
|
|
||||||
<outlet property="delegate" destination="Voe-Tx-rLC" id="qav-xj-izy"/>
|
|
||||||
</connections>
|
|
||||||
</secureTextField>
|
|
||||||
</subviews>
|
|
||||||
<constraints>
|
|
||||||
<constraint firstItem="9rp-Vx-rvB" firstAttribute="width" secondItem="urc-xw-Dhc" secondAttribute="width" id="Eht-pU-Gyh"/>
|
|
||||||
<constraint firstItem="zLd-d8-ghZ" firstAttribute="width" secondItem="urc-xw-Dhc" secondAttribute="width" id="mg7-Kq-abL"/>
|
|
||||||
<constraint firstAttribute="width" constant="300" id="zqf-x6-BET"/>
|
|
||||||
</constraints>
|
|
||||||
<visibilityPriorities>
|
|
||||||
<integer value="1000"/>
|
|
||||||
<integer value="1000"/>
|
|
||||||
</visibilityPriorities>
|
|
||||||
<customSpacing>
|
|
||||||
<real value="3.4028234663852886e+38"/>
|
|
||||||
<real value="3.4028234663852886e+38"/>
|
|
||||||
</customSpacing>
|
|
||||||
</stackView>
|
|
||||||
<customObject id="Ady-hI-5gd" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
|
||||||
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="AltServer" customModuleProvider="target">
|
|
||||||
<connections>
|
|
||||||
<outlet property="appMenu" destination="uQy-DD-JDr" id="7cY-Ov-AOW"/>
|
|
||||||
<outlet property="authenticationAppleIDTextField" destination="zLd-d8-ghZ" id="wW5-0J-zdq"/>
|
|
||||||
<outlet property="authenticationPasswordTextField" destination="9rp-Vx-rvB" id="ZoC-DI-jzQ"/>
|
|
||||||
<outlet property="connectedDevicesMenu" destination="KJ9-WY-pW1" id="Mcv-64-iFU"/>
|
|
||||||
<outlet property="installMailPluginMenuItem" destination="3CM-gV-X2G" id="lio-ha-z0S"/>
|
|
||||||
<outlet property="launchAtLoginMenuItem" destination="IyR-FQ-upe" id="Fxn-EP-hwH"/>
|
|
||||||
<outlet property="sideloadIPAConnectedDevicesMenu" destination="IuI-bV-fTY" id="QQw-St-HfG"/>
|
|
||||||
</connections>
|
|
||||||
</customObject>
|
|
||||||
<customObject id="Arf-IC-5eb" customClass="SUUpdater"/>
|
|
||||||
<application id="hnw-xV-0zn" sceneMemberID="viewController">
|
|
||||||
<menu key="mainMenu" title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
|
|
||||||
<items>
|
|
||||||
<menuItem title="AltServer" id="1Xt-HY-uBw">
|
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
|
||||||
<menu key="submenu" title="AltServer" systemMenu="apple" id="uQy-DD-JDr">
|
|
||||||
<items>
|
|
||||||
<menuItem title="About AltServer" id="5kV-Vb-QxS">
|
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
|
||||||
<connections>
|
|
||||||
<action selector="orderFrontStandardAboutPanel:" target="Ady-hI-5gd" id="Exp-CZ-Vem"/>
|
|
||||||
</connections>
|
|
||||||
</menuItem>
|
|
||||||
<menuItem isSeparatorItem="YES" id="wFC-TO-SCJ"/>
|
|
||||||
<menuItem title="Install AltStore" id="MJ8-Lt-SSV">
|
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
|
||||||
<menu key="submenu" title="Install AltStore" systemMenu="recentDocuments" id="KJ9-WY-pW1">
|
|
||||||
<items>
|
|
||||||
<menuItem title="No Connected Devices" id="N5N-3K-XuR">
|
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
|
||||||
<connections>
|
|
||||||
<action selector="clearRecentDocuments:" target="Ady-hI-5gd" id="DKG-yI-Ujv"/>
|
|
||||||
</connections>
|
|
||||||
</menuItem>
|
|
||||||
</items>
|
|
||||||
<connections>
|
|
||||||
<outlet property="delegate" destination="Voe-Tx-rLC" id="VYb-BL-Zri"/>
|
|
||||||
</connections>
|
|
||||||
</menu>
|
|
||||||
</menuItem>
|
|
||||||
<menuItem title="Sideload .ipa" id="x0e-zI-0A2" userLabel="Install .ipa">
|
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
|
||||||
<menu key="submenu" title="Sideload .ipa" systemMenu="recentDocuments" id="IuI-bV-fTY">
|
|
||||||
<items>
|
|
||||||
<menuItem title="No Connected Devices" id="in5-an-MD0">
|
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
|
||||||
<connections>
|
|
||||||
<action selector="clearRecentDocuments:" target="Ady-hI-5gd" id="aUE-On-axK"/>
|
|
||||||
</connections>
|
|
||||||
</menuItem>
|
|
||||||
</items>
|
|
||||||
<connections>
|
|
||||||
<outlet property="delegate" destination="Voe-Tx-rLC" id="N3K-su-XV6"/>
|
|
||||||
</connections>
|
|
||||||
</menu>
|
|
||||||
</menuItem>
|
|
||||||
<menuItem isSeparatorItem="YES" id="1ZZ-BB-xHy"/>
|
|
||||||
<menuItem title="Launch at Login" id="IyR-FQ-upe" userLabel="Launch At Login">
|
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
|
||||||
</menuItem>
|
|
||||||
<menuItem title="Install Mail Plug-in" id="3CM-gV-X2G" userLabel="Mail Plug-in">
|
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
|
||||||
</menuItem>
|
|
||||||
<menuItem isSeparatorItem="YES" id="mVM-Nm-Zi9"/>
|
|
||||||
<menuItem title="Check for Updates..." id="Tnq-gD-Eic" userLabel="Check for Updates">
|
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
|
||||||
<connections>
|
|
||||||
<action selector="checkForUpdates:" target="Arf-IC-5eb" id="7JG-du-nr4"/>
|
|
||||||
</connections>
|
|
||||||
</menuItem>
|
|
||||||
<menuItem isSeparatorItem="YES" id="hmG-xg-qgm"/>
|
|
||||||
<menuItem title="Quit AltServer" keyEquivalent="q" id="4sb-4s-VLi">
|
|
||||||
<connections>
|
|
||||||
<action selector="terminate:" target="Ady-hI-5gd" id="Te7-pn-YzF"/>
|
|
||||||
</connections>
|
|
||||||
</menuItem>
|
|
||||||
</items>
|
|
||||||
</menu>
|
|
||||||
</menuItem>
|
|
||||||
<menuItem title="Edit" id="5QF-Oa-p0T">
|
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
|
||||||
<menu key="submenu" title="Edit" id="W48-6f-4Dl">
|
|
||||||
<items>
|
|
||||||
<menuItem title="Undo" keyEquivalent="z" id="dRJ-4n-Yzg">
|
|
||||||
<connections>
|
|
||||||
<action selector="undo:" target="Ady-hI-5gd" id="M6e-cu-g7V"/>
|
|
||||||
</connections>
|
|
||||||
</menuItem>
|
|
||||||
<menuItem title="Redo" keyEquivalent="Z" id="6dh-zS-Vam">
|
|
||||||
<connections>
|
|
||||||
<action selector="redo:" target="Ady-hI-5gd" id="oIA-Rs-6OD"/>
|
|
||||||
</connections>
|
|
||||||
</menuItem>
|
|
||||||
<menuItem isSeparatorItem="YES" id="WRV-NI-Exz"/>
|
|
||||||
<menuItem title="Cut" keyEquivalent="x" id="uRl-iY-unG">
|
|
||||||
<connections>
|
|
||||||
<action selector="cut:" target="Ady-hI-5gd" id="YJe-68-I9s"/>
|
|
||||||
</connections>
|
|
||||||
</menuItem>
|
|
||||||
<menuItem title="Copy" keyEquivalent="c" id="x3v-GG-iWU">
|
|
||||||
<connections>
|
|
||||||
<action selector="copy:" target="Ady-hI-5gd" id="G1f-GL-Joy"/>
|
|
||||||
</connections>
|
|
||||||
</menuItem>
|
|
||||||
<menuItem title="Paste" keyEquivalent="v" id="gVA-U4-sdL">
|
|
||||||
<connections>
|
|
||||||
<action selector="paste:" target="Ady-hI-5gd" id="UvS-8e-Qdg"/>
|
|
||||||
</connections>
|
|
||||||
</menuItem>
|
|
||||||
<menuItem title="Paste and Match Style" keyEquivalent="V" id="WeT-3V-zwk">
|
|
||||||
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
|
|
||||||
<connections>
|
|
||||||
<action selector="pasteAsPlainText:" target="Ady-hI-5gd" id="cEh-KX-wJQ"/>
|
|
||||||
</connections>
|
|
||||||
</menuItem>
|
|
||||||
<menuItem title="Delete" id="pa3-QI-u2k">
|
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
|
||||||
<connections>
|
|
||||||
<action selector="delete:" target="Ady-hI-5gd" id="0Mk-Ml-PaM"/>
|
|
||||||
</connections>
|
|
||||||
</menuItem>
|
|
||||||
<menuItem title="Select All" keyEquivalent="a" id="Ruw-6m-B2m">
|
|
||||||
<connections>
|
|
||||||
<action selector="selectAll:" target="Ady-hI-5gd" id="VNm-Mi-diN"/>
|
|
||||||
</connections>
|
|
||||||
</menuItem>
|
|
||||||
<menuItem isSeparatorItem="YES" id="uyl-h8-XO2"/>
|
|
||||||
<menuItem title="Find" id="4EN-yA-p0u">
|
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
|
||||||
<menu key="submenu" title="Find" id="1b7-l0-nxx">
|
|
||||||
<items>
|
|
||||||
<menuItem title="Find…" tag="1" keyEquivalent="f" id="Xz5-n4-O0W">
|
|
||||||
<connections>
|
|
||||||
<action selector="performFindPanelAction:" target="Ady-hI-5gd" id="cD7-Qs-BN4"/>
|
|
||||||
</connections>
|
|
||||||
</menuItem>
|
|
||||||
<menuItem title="Find and Replace…" tag="12" keyEquivalent="f" id="YEy-JH-Tfz">
|
|
||||||
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
|
|
||||||
<connections>
|
|
||||||
<action selector="performFindPanelAction:" target="Ady-hI-5gd" id="WD3-Gg-5AJ"/>
|
|
||||||
</connections>
|
|
||||||
</menuItem>
|
|
||||||
<menuItem title="Find Next" tag="2" keyEquivalent="g" id="q09-fT-Sye">
|
|
||||||
<connections>
|
|
||||||
<action selector="performFindPanelAction:" target="Ady-hI-5gd" id="NDo-RZ-v9R"/>
|
|
||||||
</connections>
|
|
||||||
</menuItem>
|
|
||||||
<menuItem title="Find Previous" tag="3" keyEquivalent="G" id="OwM-mh-QMV">
|
|
||||||
<connections>
|
|
||||||
<action selector="performFindPanelAction:" target="Ady-hI-5gd" id="HOh-sY-3ay"/>
|
|
||||||
</connections>
|
|
||||||
</menuItem>
|
|
||||||
<menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="buJ-ug-pKt">
|
|
||||||
<connections>
|
|
||||||
<action selector="performFindPanelAction:" target="Ady-hI-5gd" id="U76-nv-p5D"/>
|
|
||||||
</connections>
|
|
||||||
</menuItem>
|
|
||||||
<menuItem title="Jump to Selection" keyEquivalent="j" id="S0p-oC-mLd">
|
|
||||||
<connections>
|
|
||||||
<action selector="centerSelectionInVisibleArea:" target="Ady-hI-5gd" id="IOG-6D-g5B"/>
|
|
||||||
</connections>
|
|
||||||
</menuItem>
|
|
||||||
</items>
|
|
||||||
</menu>
|
|
||||||
</menuItem>
|
|
||||||
<menuItem title="Spelling and Grammar" id="Dv1-io-Yv7">
|
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
|
||||||
<menu key="submenu" title="Spelling" id="3IN-sU-3Bg">
|
|
||||||
<items>
|
|
||||||
<menuItem title="Show Spelling and Grammar" keyEquivalent=":" id="HFo-cy-zxI">
|
|
||||||
<connections>
|
|
||||||
<action selector="showGuessPanel:" target="Ady-hI-5gd" id="vFj-Ks-hy3"/>
|
|
||||||
</connections>
|
|
||||||
</menuItem>
|
|
||||||
<menuItem title="Check Document Now" keyEquivalent=";" id="hz2-CU-CR7">
|
|
||||||
<connections>
|
|
||||||
<action selector="checkSpelling:" target="Ady-hI-5gd" id="fz7-VC-reM"/>
|
|
||||||
</connections>
|
|
||||||
</menuItem>
|
|
||||||
<menuItem isSeparatorItem="YES" id="bNw-od-mp5"/>
|
|
||||||
<menuItem title="Check Spelling While Typing" id="rbD-Rh-wIN">
|
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
|
||||||
<connections>
|
|
||||||
<action selector="toggleContinuousSpellChecking:" target="Ady-hI-5gd" id="7w6-Qz-0kB"/>
|
|
||||||
</connections>
|
|
||||||
</menuItem>
|
|
||||||
<menuItem title="Check Grammar With Spelling" id="mK6-2p-4JG">
|
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
|
||||||
<connections>
|
|
||||||
<action selector="toggleGrammarChecking:" target="Ady-hI-5gd" id="muD-Qn-j4w"/>
|
|
||||||
</connections>
|
|
||||||
</menuItem>
|
|
||||||
<menuItem title="Correct Spelling Automatically" id="78Y-hA-62v">
|
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
|
||||||
<connections>
|
|
||||||
<action selector="toggleAutomaticSpellingCorrection:" target="Ady-hI-5gd" id="2lM-Qi-WAP"/>
|
|
||||||
</connections>
|
|
||||||
</menuItem>
|
|
||||||
</items>
|
|
||||||
</menu>
|
|
||||||
</menuItem>
|
|
||||||
<menuItem title="Substitutions" id="9ic-FL-obx">
|
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
|
||||||
<menu key="submenu" title="Substitutions" id="FeM-D8-WVr">
|
|
||||||
<items>
|
|
||||||
<menuItem title="Show Substitutions" id="z6F-FW-3nz">
|
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
|
||||||
<connections>
|
|
||||||
<action selector="orderFrontSubstitutionsPanel:" target="Ady-hI-5gd" id="oku-mr-iSq"/>
|
|
||||||
</connections>
|
|
||||||
</menuItem>
|
|
||||||
<menuItem isSeparatorItem="YES" id="gPx-C9-uUO"/>
|
|
||||||
<menuItem title="Smart Copy/Paste" id="9yt-4B-nSM">
|
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
|
||||||
<connections>
|
|
||||||
<action selector="toggleSmartInsertDelete:" target="Ady-hI-5gd" id="3IJ-Se-DZD"/>
|
|
||||||
</connections>
|
|
||||||
</menuItem>
|
|
||||||
<menuItem title="Smart Quotes" id="hQb-2v-fYv">
|
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
|
||||||
<connections>
|
|
||||||
<action selector="toggleAutomaticQuoteSubstitution:" target="Ady-hI-5gd" id="ptq-xd-QOA"/>
|
|
||||||
</connections>
|
|
||||||
</menuItem>
|
|
||||||
<menuItem title="Smart Dashes" id="rgM-f4-ycn">
|
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
|
||||||
<connections>
|
|
||||||
<action selector="toggleAutomaticDashSubstitution:" target="Ady-hI-5gd" id="oCt-pO-9gS"/>
|
|
||||||
</connections>
|
|
||||||
</menuItem>
|
|
||||||
<menuItem title="Smart Links" id="cwL-P1-jid">
|
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
|
||||||
<connections>
|
|
||||||
<action selector="toggleAutomaticLinkDetection:" target="Ady-hI-5gd" id="Gip-E3-Fov"/>
|
|
||||||
</connections>
|
|
||||||
</menuItem>
|
|
||||||
<menuItem title="Data Detectors" id="tRr-pd-1PS">
|
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
|
||||||
<connections>
|
|
||||||
<action selector="toggleAutomaticDataDetection:" target="Ady-hI-5gd" id="R1I-Nq-Kbl"/>
|
|
||||||
</connections>
|
|
||||||
</menuItem>
|
|
||||||
<menuItem title="Text Replacement" id="HFQ-gK-NFA">
|
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
|
||||||
<connections>
|
|
||||||
<action selector="toggleAutomaticTextReplacement:" target="Ady-hI-5gd" id="DvP-Fe-Py6"/>
|
|
||||||
</connections>
|
|
||||||
</menuItem>
|
|
||||||
</items>
|
|
||||||
</menu>
|
|
||||||
</menuItem>
|
|
||||||
<menuItem title="Transformations" id="2oI-Rn-ZJC">
|
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
|
||||||
<menu key="submenu" title="Transformations" id="c8a-y6-VQd">
|
|
||||||
<items>
|
|
||||||
<menuItem title="Make Upper Case" id="vmV-6d-7jI">
|
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
|
||||||
<connections>
|
|
||||||
<action selector="uppercaseWord:" target="Ady-hI-5gd" id="sPh-Tk-edu"/>
|
|
||||||
</connections>
|
|
||||||
</menuItem>
|
|
||||||
<menuItem title="Make Lower Case" id="d9M-CD-aMd">
|
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
|
||||||
<connections>
|
|
||||||
<action selector="lowercaseWord:" target="Ady-hI-5gd" id="iUZ-b5-hil"/>
|
|
||||||
</connections>
|
|
||||||
</menuItem>
|
|
||||||
<menuItem title="Capitalize" id="UEZ-Bs-lqG">
|
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
|
||||||
<connections>
|
|
||||||
<action selector="capitalizeWord:" target="Ady-hI-5gd" id="26H-TL-nsh"/>
|
|
||||||
</connections>
|
|
||||||
</menuItem>
|
|
||||||
</items>
|
|
||||||
</menu>
|
|
||||||
</menuItem>
|
|
||||||
<menuItem title="Speech" id="xrE-MZ-jX0">
|
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
|
||||||
<menu key="submenu" title="Speech" id="3rS-ZA-NoH">
|
|
||||||
<items>
|
|
||||||
<menuItem title="Start Speaking" id="Ynk-f8-cLZ">
|
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
|
||||||
<connections>
|
|
||||||
<action selector="startSpeaking:" target="Ady-hI-5gd" id="654-Ng-kyl"/>
|
|
||||||
</connections>
|
|
||||||
</menuItem>
|
|
||||||
<menuItem title="Stop Speaking" id="Oyz-dy-DGm">
|
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
|
||||||
<connections>
|
|
||||||
<action selector="stopSpeaking:" target="Ady-hI-5gd" id="dX8-6p-jy9"/>
|
|
||||||
</connections>
|
|
||||||
</menuItem>
|
|
||||||
</items>
|
|
||||||
</menu>
|
|
||||||
</menuItem>
|
|
||||||
</items>
|
|
||||||
</menu>
|
|
||||||
</menuItem>
|
|
||||||
<menuItem title="Help" id="wpr-3q-Mcd">
|
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
|
||||||
<menu key="submenu" title="Help" systemMenu="help" id="F2S-fz-NVQ">
|
|
||||||
<items>
|
|
||||||
<menuItem title="AltServer Help" keyEquivalent="?" id="FKE-Sm-Kum">
|
|
||||||
<connections>
|
|
||||||
<action selector="showHelp:" target="Ady-hI-5gd" id="y7X-2Q-9no"/>
|
|
||||||
</connections>
|
|
||||||
</menuItem>
|
|
||||||
</items>
|
|
||||||
</menu>
|
|
||||||
</menuItem>
|
|
||||||
</items>
|
|
||||||
</menu>
|
|
||||||
<connections>
|
|
||||||
<outlet property="delegate" destination="Voe-Tx-rLC" id="PrD-fu-P6m"/>
|
|
||||||
</connections>
|
|
||||||
</application>
|
|
||||||
</objects>
|
|
||||||
<point key="canvasLocation" x="75" y="0.0"/>
|
|
||||||
</scene>
|
|
||||||
</scenes>
|
|
||||||
</document>
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
//
|
|
||||||
// ALTNotificationConnection+Private.h
|
|
||||||
// AltServer
|
|
||||||
//
|
|
||||||
// Created by Riley Testut on 1/10/20.
|
|
||||||
// Copyright © 2020 Riley Testut. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import "ALTNotificationConnection.h"
|
|
||||||
|
|
||||||
#include <libimobiledevice/libimobiledevice.h>
|
|
||||||
#include <libimobiledevice/notification_proxy.h>
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
|
||||||
|
|
||||||
@interface ALTNotificationConnection ()
|
|
||||||
|
|
||||||
@property (nonatomic, readonly) np_client_t client;
|
|
||||||
|
|
||||||
- (instancetype)initWithDevice:(ALTDevice *)device client:(np_client_t)client;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
//
|
|
||||||
// ALTNotificationConnection.h
|
|
||||||
// AltServer
|
|
||||||
//
|
|
||||||
// Created by Riley Testut on 1/10/20.
|
|
||||||
// Copyright © 2020 Riley Testut. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import "AltSign.h"
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
|
||||||
|
|
||||||
NS_SWIFT_NAME(NotificationConnection)
|
|
||||||
@interface ALTNotificationConnection : NSObject
|
|
||||||
|
|
||||||
@property (nonatomic, copy, readonly) ALTDevice *device;
|
|
||||||
|
|
||||||
@property (nonatomic, copy, nullable) void (^receivedNotificationHandler)(CFNotificationName notification);
|
|
||||||
|
|
||||||
- (void)startListeningForNotifications:(NSArray<NSString *> *)notifications
|
|
||||||
completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler;
|
|
||||||
|
|
||||||
- (void)sendNotification:(CFNotificationName)notification
|
|
||||||
completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler;
|
|
||||||
|
|
||||||
- (void)disconnect;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
//
|
|
||||||
// ALTNotificationConnection.m
|
|
||||||
// AltServer
|
|
||||||
//
|
|
||||||
// Created by Riley Testut on 1/10/20.
|
|
||||||
// Copyright © 2020 Riley Testut. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import "ALTNotificationConnection+Private.h"
|
|
||||||
|
|
||||||
#import "NSError+ALTServerError.h"
|
|
||||||
|
|
||||||
void ALTDeviceReceivedNotification(const char *notification, void *user_data);
|
|
||||||
|
|
||||||
@implementation ALTNotificationConnection
|
|
||||||
|
|
||||||
- (instancetype)initWithDevice:(ALTDevice *)device client:(np_client_t)client
|
|
||||||
{
|
|
||||||
self = [super init];
|
|
||||||
if (self)
|
|
||||||
{
|
|
||||||
_device = [device copy];
|
|
||||||
_client = client;
|
|
||||||
}
|
|
||||||
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)dealloc
|
|
||||||
{
|
|
||||||
[self disconnect];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)disconnect
|
|
||||||
{
|
|
||||||
np_client_free(self.client);
|
|
||||||
_client = nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)startListeningForNotifications:(NSArray<NSString *> *)notifications completionHandler:(void (^)(BOOL, NSError * _Nullable))completionHandler
|
|
||||||
{
|
|
||||||
dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
|
|
||||||
const char **notificationNames = (const char **)malloc((notifications.count + 1) * sizeof(char *));
|
|
||||||
for (int i = 0; i < notifications.count; i++)
|
|
||||||
{
|
|
||||||
NSString *name = notifications[i];
|
|
||||||
notificationNames[i] = name.UTF8String;
|
|
||||||
}
|
|
||||||
notificationNames[notifications.count] = NULL; // Must have terminating NULL entry.
|
|
||||||
|
|
||||||
np_error_t result = np_observe_notifications(self.client, notificationNames);
|
|
||||||
if (result != NP_E_SUCCESS)
|
|
||||||
{
|
|
||||||
return completionHandler(NO, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorLostConnection userInfo:nil]);
|
|
||||||
}
|
|
||||||
|
|
||||||
result = np_set_notify_callback(self.client, ALTDeviceReceivedNotification, (__bridge void *)self);
|
|
||||||
if (result != NP_E_SUCCESS)
|
|
||||||
{
|
|
||||||
return completionHandler(NO, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorLostConnection userInfo:nil]);
|
|
||||||
}
|
|
||||||
|
|
||||||
completionHandler(YES, nil);
|
|
||||||
|
|
||||||
free(notificationNames);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)sendNotification:(CFNotificationName)notification completionHandler:(void (^)(BOOL, NSError * _Nullable))completionHandler
|
|
||||||
{
|
|
||||||
dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
|
|
||||||
np_error_t result = np_post_notification(self.client, [(__bridge NSString *)notification UTF8String]);
|
|
||||||
if (result == NP_E_SUCCESS)
|
|
||||||
{
|
|
||||||
completionHandler(YES, nil);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
completionHandler(NO, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorLostConnection userInfo:nil]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
void ALTDeviceReceivedNotification(const char *notification, void *user_data)
|
|
||||||
{
|
|
||||||
ALTNotificationConnection *connection = (__bridge ALTNotificationConnection *)user_data;
|
|
||||||
|
|
||||||
if (connection.receivedNotificationHandler)
|
|
||||||
{
|
|
||||||
connection.receivedNotificationHandler((__bridge CFNotificationName)@(notification));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
//
|
|
||||||
// ALTWiredConnection+Private.h
|
|
||||||
// AltServer
|
|
||||||
//
|
|
||||||
// Created by Riley Testut on 1/10/20.
|
|
||||||
// Copyright © 2020 Riley Testut. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import "ALTWiredConnection.h"
|
|
||||||
|
|
||||||
#include <libimobiledevice/libimobiledevice.h>
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
|
||||||
|
|
||||||
@interface ALTWiredConnection ()
|
|
||||||
|
|
||||||
@property (nonatomic, readwrite, getter=isConnected) BOOL connected;
|
|
||||||
|
|
||||||
@property (nonatomic, readonly) idevice_connection_t connection;
|
|
||||||
|
|
||||||
- (instancetype)initWithDevice:(ALTDevice *)device connection:(idevice_connection_t)connection;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
//
|
|
||||||
// ALTWiredConnection.h
|
|
||||||
// AltServer
|
|
||||||
//
|
|
||||||
// Created by Riley Testut on 1/10/20.
|
|
||||||
// Copyright © 2020 Riley Testut. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import "AltSign.h"
|
|
||||||
|
|
||||||
#import "ALTConnection.h"
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
|
||||||
|
|
||||||
NS_SWIFT_NAME(WiredConnection)
|
|
||||||
@interface ALTWiredConnection : NSObject <ALTConnection>
|
|
||||||
|
|
||||||
@property (nonatomic, readonly, getter=isConnected) BOOL connected;
|
|
||||||
|
|
||||||
@property (nonatomic, copy, readonly) ALTDevice *device;
|
|
||||||
|
|
||||||
- (void)sendData:(NSData *)data completionHandler:(void (^)(BOOL, NSError * _Nullable))completionHandler;
|
|
||||||
- (void)receiveDataWithExpectedSize:(NSInteger)expectedSize completionHandler:(void (^)(NSData * _Nullable, NSError * _Nullable))completionHandler;
|
|
||||||
|
|
||||||
- (void)disconnect;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
//
|
|
||||||
// ALTWiredConnection.m
|
|
||||||
// AltServer
|
|
||||||
//
|
|
||||||
// Created by Riley Testut on 1/10/20.
|
|
||||||
// Copyright © 2020 Riley Testut. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import "ALTWiredConnection+Private.h"
|
|
||||||
|
|
||||||
#import "ALTConnection.h"
|
|
||||||
#import "NSError+ALTServerError.h"
|
|
||||||
|
|
||||||
@implementation ALTWiredConnection
|
|
||||||
|
|
||||||
- (instancetype)initWithDevice:(ALTDevice *)device connection:(idevice_connection_t)connection
|
|
||||||
{
|
|
||||||
self = [super init];
|
|
||||||
if (self)
|
|
||||||
{
|
|
||||||
_device = [device copy];
|
|
||||||
_connection = connection;
|
|
||||||
}
|
|
||||||
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)dealloc
|
|
||||||
{
|
|
||||||
[self disconnect];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)disconnect
|
|
||||||
{
|
|
||||||
if (![self isConnected])
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
idevice_disconnect(self.connection);
|
|
||||||
_connection = nil;
|
|
||||||
|
|
||||||
self.connected = NO;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)sendData:(NSData *)data completionHandler:(void (^)(BOOL, NSError * _Nullable))completionHandler
|
|
||||||
{
|
|
||||||
void (^finish)(NSError *error) = ^(NSError *error) {
|
|
||||||
if (error != nil)
|
|
||||||
{
|
|
||||||
NSLog(@"Send Error: %@", error);
|
|
||||||
completionHandler(NO, error);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
completionHandler(YES, nil);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
|
|
||||||
NSMutableData *mutableData = [data mutableCopy];
|
|
||||||
while (mutableData.length > 0)
|
|
||||||
{
|
|
||||||
uint32_t sentBytes = 0;
|
|
||||||
if (idevice_connection_send(self.connection, (const char *)mutableData.bytes, (int32_t)mutableData.length, &sentBytes) != IDEVICE_E_SUCCESS)
|
|
||||||
{
|
|
||||||
return finish([NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorLostConnection userInfo:nil]);
|
|
||||||
}
|
|
||||||
|
|
||||||
[mutableData replaceBytesInRange:NSMakeRange(0, sentBytes) withBytes:NULL length:0];
|
|
||||||
}
|
|
||||||
|
|
||||||
finish(nil);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)receiveDataWithExpectedSize:(NSInteger)expectedSize completionHandler:(void (^)(NSData * _Nullable, NSError * _Nullable))completionHandler
|
|
||||||
{
|
|
||||||
void (^finish)(NSData *data, NSError *error) = ^(NSData *data, NSError *error) {
|
|
||||||
if (error != nil)
|
|
||||||
{
|
|
||||||
NSLog(@"Receive Data Error: %@", error);
|
|
||||||
}
|
|
||||||
|
|
||||||
completionHandler(data, error);
|
|
||||||
};
|
|
||||||
|
|
||||||
dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
|
|
||||||
char bytes[4096];
|
|
||||||
NSMutableData *receivedData = [NSMutableData dataWithCapacity:expectedSize];
|
|
||||||
|
|
||||||
while (receivedData.length < expectedSize)
|
|
||||||
{
|
|
||||||
uint32_t size = MIN(4096, (uint32_t)expectedSize - (uint32_t)receivedData.length);
|
|
||||||
|
|
||||||
uint32_t receivedBytes = 0;
|
|
||||||
if (idevice_connection_receive_timeout(self.connection, bytes, size, &receivedBytes, 10000) != IDEVICE_E_SUCCESS)
|
|
||||||
{
|
|
||||||
return finish(nil, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorLostConnection userInfo:nil]);
|
|
||||||
}
|
|
||||||
|
|
||||||
NSData *data = [NSData dataWithBytesNoCopy:bytes length:receivedBytes freeWhenDone:NO];
|
|
||||||
[receivedData appendData:data];
|
|
||||||
}
|
|
||||||
|
|
||||||
finish(receivedData, nil);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - NSObject -
|
|
||||||
|
|
||||||
- (NSString *)description
|
|
||||||
{
|
|
||||||
return [NSString stringWithFormat:@"%@ (Wired)", self.device.name];
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
@@ -1,218 +0,0 @@
|
|||||||
//
|
|
||||||
// RequestHandler.swift
|
|
||||||
// AltServer
|
|
||||||
//
|
|
||||||
// Created by Riley Testut on 5/23/19.
|
|
||||||
// Copyright © 2019 Riley Testut. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
typealias ServerConnectionManager = ConnectionManager<ServerRequestHandler>
|
|
||||||
|
|
||||||
private let connectionManager = ConnectionManager(requestHandler: ServerRequestHandler(),
|
|
||||||
connectionHandlers: [WirelessConnectionHandler(), WiredConnectionHandler()])
|
|
||||||
|
|
||||||
extension ServerConnectionManager
|
|
||||||
{
|
|
||||||
static var shared: ConnectionManager {
|
|
||||||
return connectionManager
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ServerRequestHandler: RequestHandler
|
|
||||||
{
|
|
||||||
func handleAnisetteDataRequest(_ request: AnisetteDataRequest, for connection: Connection, completionHandler: @escaping (Result<AnisetteDataResponse, Error>) -> Void)
|
|
||||||
{
|
|
||||||
AnisetteDataManager.shared.requestAnisetteData { (result) in
|
|
||||||
switch result
|
|
||||||
{
|
|
||||||
case .failure(let error): completionHandler(.failure(error))
|
|
||||||
case .success(let anisetteData):
|
|
||||||
let response = AnisetteDataResponse(anisetteData: anisetteData)
|
|
||||||
completionHandler(.success(response))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func handlePrepareAppRequest(_ request: PrepareAppRequest, for connection: Connection, completionHandler: @escaping (Result<InstallationProgressResponse, Error>) -> Void)
|
|
||||||
{
|
|
||||||
var temporaryURL: URL?
|
|
||||||
|
|
||||||
func finish(_ result: Result<InstallationProgressResponse, Error>)
|
|
||||||
{
|
|
||||||
if let temporaryURL = temporaryURL
|
|
||||||
{
|
|
||||||
do { try FileManager.default.removeItem(at: temporaryURL) }
|
|
||||||
catch { print("Failed to remove .ipa.", error) }
|
|
||||||
}
|
|
||||||
|
|
||||||
completionHandler(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
self.receiveApp(for: request, from: connection) { (result) in
|
|
||||||
print("Received app with result:", result)
|
|
||||||
|
|
||||||
switch result
|
|
||||||
{
|
|
||||||
case .failure(let error): finish(.failure(error))
|
|
||||||
case .success(let fileURL):
|
|
||||||
temporaryURL = fileURL
|
|
||||||
|
|
||||||
print("Awaiting begin installation request...")
|
|
||||||
|
|
||||||
connection.receiveRequest() { (result) in
|
|
||||||
print("Received begin installation request with result:", result)
|
|
||||||
|
|
||||||
switch result
|
|
||||||
{
|
|
||||||
case .failure(let error): finish(.failure(error))
|
|
||||||
case .success(.beginInstallation(let installRequest)):
|
|
||||||
print("Installing app to device \(request.udid)...")
|
|
||||||
|
|
||||||
self.installApp(at: fileURL, toDeviceWithUDID: request.udid, activeProvisioningProfiles: installRequest.activeProfiles, connection: connection) { (result) in
|
|
||||||
print("Installed app to device with result:", result)
|
|
||||||
switch result
|
|
||||||
{
|
|
||||||
case .failure(let error): finish(.failure(error))
|
|
||||||
case .success:
|
|
||||||
let response = InstallationProgressResponse(progress: 1.0)
|
|
||||||
finish(.success(response))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case .success: finish(.failure(ALTServerError(.unknownRequest)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleInstallProvisioningProfilesRequest(_ request: InstallProvisioningProfilesRequest, for connection: Connection,
|
|
||||||
completionHandler: @escaping (Result<InstallProvisioningProfilesResponse, Error>) -> Void)
|
|
||||||
{
|
|
||||||
ALTDeviceManager.shared.installProvisioningProfiles(request.provisioningProfiles, toDeviceWithUDID: request.udid, activeProvisioningProfiles: request.activeProfiles) { (success, error) in
|
|
||||||
if let error = error, !success
|
|
||||||
{
|
|
||||||
print("Failed to install profiles \(request.provisioningProfiles.map { $0.bundleIdentifier }):", error)
|
|
||||||
completionHandler(.failure(ALTServerError(error)))
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
print("Installed profiles:", request.provisioningProfiles.map { $0.bundleIdentifier })
|
|
||||||
|
|
||||||
let response = InstallProvisioningProfilesResponse()
|
|
||||||
completionHandler(.success(response))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleRemoveProvisioningProfilesRequest(_ request: RemoveProvisioningProfilesRequest, for connection: Connection,
|
|
||||||
completionHandler: @escaping (Result<RemoveProvisioningProfilesResponse, Error>) -> Void)
|
|
||||||
{
|
|
||||||
ALTDeviceManager.shared.removeProvisioningProfiles(forBundleIdentifiers: request.bundleIdentifiers, fromDeviceWithUDID: request.udid) { (success, error) in
|
|
||||||
if let error = error, !success
|
|
||||||
{
|
|
||||||
print("Failed to remove profiles \(request.bundleIdentifiers):", error)
|
|
||||||
completionHandler(.failure(ALTServerError(error)))
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
print("Removed profiles:", request.bundleIdentifiers)
|
|
||||||
|
|
||||||
let response = RemoveProvisioningProfilesResponse()
|
|
||||||
completionHandler(.success(response))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleRemoveAppRequest(_ request: RemoveAppRequest, for connection: Connection, completionHandler: @escaping (Result<RemoveAppResponse, Error>) -> Void)
|
|
||||||
{
|
|
||||||
ALTDeviceManager.shared.removeApp(forBundleIdentifier: request.bundleIdentifier, fromDeviceWithUDID: request.udid) { (success, error) in
|
|
||||||
if let error = error, !success
|
|
||||||
{
|
|
||||||
print("Failed to remove app \(request.bundleIdentifier):", error)
|
|
||||||
completionHandler(.failure(ALTServerError(error)))
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
print("Removed app:", request.bundleIdentifier)
|
|
||||||
|
|
||||||
let response = RemoveAppResponse()
|
|
||||||
completionHandler(.success(response))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private extension RequestHandler
|
|
||||||
{
|
|
||||||
func receiveApp(for request: PrepareAppRequest, from connection: Connection, completionHandler: @escaping (Result<URL, ALTServerError>) -> Void)
|
|
||||||
{
|
|
||||||
connection.receiveData(expectedSize: request.contentSize) { (result) in
|
|
||||||
do
|
|
||||||
{
|
|
||||||
print("Received app data!")
|
|
||||||
|
|
||||||
let data = try result.get()
|
|
||||||
|
|
||||||
guard ALTDeviceManager.shared.availableDevices.contains(where: { $0.identifier == request.udid }) else { throw ALTServerError(.deviceNotFound) }
|
|
||||||
|
|
||||||
print("Writing app data...")
|
|
||||||
|
|
||||||
let temporaryURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString + ".ipa")
|
|
||||||
try data.write(to: temporaryURL, options: .atomic)
|
|
||||||
|
|
||||||
print("Wrote app to URL:", temporaryURL)
|
|
||||||
|
|
||||||
completionHandler(.success(temporaryURL))
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
print("Error processing app data:", error)
|
|
||||||
|
|
||||||
completionHandler(.failure(ALTServerError(error)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func installApp(at fileURL: URL, toDeviceWithUDID udid: String, activeProvisioningProfiles: Set<String>?, connection: Connection, completionHandler: @escaping (Result<Void, ALTServerError>) -> Void)
|
|
||||||
{
|
|
||||||
let serialQueue = DispatchQueue(label: "com.altstore.ConnectionManager.installQueue", qos: .default)
|
|
||||||
var isSending = false
|
|
||||||
|
|
||||||
var observation: NSKeyValueObservation?
|
|
||||||
|
|
||||||
let progress = ALTDeviceManager.shared.installApp(at: fileURL, toDeviceWithUDID: udid, activeProvisioningProfiles: activeProvisioningProfiles) { (success, error) in
|
|
||||||
print("Installed app with result:", error == nil ? "Success" : error!.localizedDescription)
|
|
||||||
|
|
||||||
if let error = error.map({ ALTServerError($0) })
|
|
||||||
{
|
|
||||||
completionHandler(.failure(error))
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
completionHandler(.success(()))
|
|
||||||
}
|
|
||||||
|
|
||||||
observation?.invalidate()
|
|
||||||
observation = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
observation = progress.observe(\.fractionCompleted, changeHandler: { (progress, change) in
|
|
||||||
serialQueue.async {
|
|
||||||
guard !isSending else { return }
|
|
||||||
isSending = true
|
|
||||||
|
|
||||||
print("Progress:", progress.fractionCompleted)
|
|
||||||
let response = InstallationProgressResponse(progress: progress.fractionCompleted)
|
|
||||||
|
|
||||||
connection.send(response) { (result) in
|
|
||||||
serialQueue.async {
|
|
||||||
isSending = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
//
|
|
||||||
// WiredConnectionHandler.swift
|
|
||||||
// AltServer
|
|
||||||
//
|
|
||||||
// Created by Riley Testut on 6/1/20.
|
|
||||||
// Copyright © 2020 Riley Testut. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
class WiredConnectionHandler: ConnectionHandler
|
|
||||||
{
|
|
||||||
var connectionHandler: ((Connection) -> Void)?
|
|
||||||
var disconnectionHandler: ((Connection) -> Void)?
|
|
||||||
|
|
||||||
private var notificationConnections = [ALTDevice: NotificationConnection]()
|
|
||||||
|
|
||||||
func startListening()
|
|
||||||
{
|
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(WiredConnectionHandler.deviceDidConnect(_:)), name: .deviceManagerDeviceDidConnect, object: nil)
|
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(WiredConnectionHandler.deviceDidDisconnect(_:)), name: .deviceManagerDeviceDidDisconnect, object: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func stopListening()
|
|
||||||
{
|
|
||||||
NotificationCenter.default.removeObserver(self, name: .deviceManagerDeviceDidConnect, object: nil)
|
|
||||||
NotificationCenter.default.removeObserver(self, name: .deviceManagerDeviceDidDisconnect, object: nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private extension WiredConnectionHandler
|
|
||||||
{
|
|
||||||
func startNotificationConnection(to device: ALTDevice)
|
|
||||||
{
|
|
||||||
ALTDeviceManager.shared.startNotificationConnection(to: device) { (connection, error) in
|
|
||||||
guard let connection = connection else { return }
|
|
||||||
|
|
||||||
let notifications: [CFNotificationName] = [.wiredServerConnectionAvailableRequest, .wiredServerConnectionStartRequest]
|
|
||||||
connection.startListening(forNotifications: notifications.map { String($0.rawValue) }) { (success, error) in
|
|
||||||
guard success else { return }
|
|
||||||
|
|
||||||
connection.receivedNotificationHandler = { [weak self, weak connection] (notification) in
|
|
||||||
guard let self = self, let connection = connection else { return }
|
|
||||||
self.handle(notification, for: connection)
|
|
||||||
}
|
|
||||||
|
|
||||||
self.notificationConnections[device] = connection
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func stopNotificationConnection(to device: ALTDevice)
|
|
||||||
{
|
|
||||||
guard let connection = self.notificationConnections[device] else { return }
|
|
||||||
connection.disconnect()
|
|
||||||
|
|
||||||
self.notificationConnections[device] = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func handle(_ notification: CFNotificationName, for connection: NotificationConnection)
|
|
||||||
{
|
|
||||||
switch notification
|
|
||||||
{
|
|
||||||
case .wiredServerConnectionAvailableRequest:
|
|
||||||
connection.sendNotification(.wiredServerConnectionAvailableResponse) { (success, error) in
|
|
||||||
if let error = error, !success
|
|
||||||
{
|
|
||||||
print("Error sending wired server connection response.", error)
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
print("Sent wired server connection available response!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case .wiredServerConnectionStartRequest:
|
|
||||||
ALTDeviceManager.shared.startWiredConnection(to: connection.device) { (wiredConnection, error) in
|
|
||||||
if let wiredConnection = wiredConnection
|
|
||||||
{
|
|
||||||
print("Started wired server connection!")
|
|
||||||
self.connectionHandler?(wiredConnection)
|
|
||||||
|
|
||||||
var observation: NSKeyValueObservation?
|
|
||||||
observation = wiredConnection.observe(\.isConnected) { [weak self] (connection, change) in
|
|
||||||
guard !connection.isConnected else { return }
|
|
||||||
self?.disconnectionHandler?(connection)
|
|
||||||
|
|
||||||
observation?.invalidate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if let error = error
|
|
||||||
{
|
|
||||||
print("Error starting wired server connection.", error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
default: break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private extension WiredConnectionHandler
|
|
||||||
{
|
|
||||||
@objc func deviceDidConnect(_ notification: Notification)
|
|
||||||
{
|
|
||||||
guard let device = notification.object as? ALTDevice else { return }
|
|
||||||
self.startNotificationConnection(to: device)
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func deviceDidDisconnect(_ notification: Notification)
|
|
||||||
{
|
|
||||||
guard let device = notification.object as? ALTDevice else { return }
|
|
||||||
self.stopNotificationConnection(to: device)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,148 +0,0 @@
|
|||||||
//
|
|
||||||
// WirelessConnectionHandler.swift
|
|
||||||
// AltKit
|
|
||||||
//
|
|
||||||
// Created by Riley Testut on 6/1/20.
|
|
||||||
// Copyright © 2020 Riley Testut. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import Network
|
|
||||||
|
|
||||||
extension WirelessConnectionHandler
|
|
||||||
{
|
|
||||||
public enum State
|
|
||||||
{
|
|
||||||
case notRunning
|
|
||||||
case connecting
|
|
||||||
case running(NWListener.Service)
|
|
||||||
case failed(Swift.Error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class WirelessConnectionHandler: ConnectionHandler
|
|
||||||
{
|
|
||||||
public var connectionHandler: ((Connection) -> Void)?
|
|
||||||
public var disconnectionHandler: ((Connection) -> Void)?
|
|
||||||
|
|
||||||
public var stateUpdateHandler: ((State) -> Void)?
|
|
||||||
|
|
||||||
public private(set) var state: State = .notRunning {
|
|
||||||
didSet {
|
|
||||||
self.stateUpdateHandler?(self.state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private lazy var listener = self.makeListener()
|
|
||||||
private let dispatchQueue = DispatchQueue(label: "io.altstore.WirelessConnectionListener", qos: .utility)
|
|
||||||
|
|
||||||
public func startListening()
|
|
||||||
{
|
|
||||||
switch self.state
|
|
||||||
{
|
|
||||||
case .notRunning, .failed: self.listener.start(queue: self.dispatchQueue)
|
|
||||||
default: break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func stopListening()
|
|
||||||
{
|
|
||||||
switch self.state
|
|
||||||
{
|
|
||||||
case .running: self.listener.cancel()
|
|
||||||
default: break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private extension WirelessConnectionHandler
|
|
||||||
{
|
|
||||||
func makeListener() -> NWListener
|
|
||||||
{
|
|
||||||
let listener = try! NWListener(using: .tcp)
|
|
||||||
|
|
||||||
let service: NWListener.Service
|
|
||||||
|
|
||||||
if let serverID = UserDefaults.standard.serverID?.data(using: .utf8)
|
|
||||||
{
|
|
||||||
let txtDictionary = ["serverID": serverID]
|
|
||||||
let txtData = NetService.data(fromTXTRecord: txtDictionary)
|
|
||||||
|
|
||||||
service = NWListener.Service(name: nil, type: ALTServerServiceType, domain: nil, txtRecord: txtData)
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
service = NWListener.Service(type: ALTServerServiceType)
|
|
||||||
}
|
|
||||||
|
|
||||||
listener.service = service
|
|
||||||
|
|
||||||
listener.serviceRegistrationUpdateHandler = { (serviceChange) in
|
|
||||||
switch serviceChange
|
|
||||||
{
|
|
||||||
case .add(.service(let name, let type, let domain, _)):
|
|
||||||
let service = NWListener.Service(name: name, type: type, domain: domain, txtRecord: nil)
|
|
||||||
self.state = .running(service)
|
|
||||||
|
|
||||||
default: break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
listener.stateUpdateHandler = { (state) in
|
|
||||||
switch state
|
|
||||||
{
|
|
||||||
case .ready: break
|
|
||||||
case .waiting, .setup: self.state = .connecting
|
|
||||||
case .cancelled: self.state = .notRunning
|
|
||||||
case .failed(let error): self.state = .failed(error)
|
|
||||||
@unknown default: break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
listener.newConnectionHandler = { [weak self] (connection) in
|
|
||||||
self?.prepare(connection)
|
|
||||||
}
|
|
||||||
|
|
||||||
return listener
|
|
||||||
}
|
|
||||||
|
|
||||||
func prepare(_ nwConnection: NWConnection)
|
|
||||||
{
|
|
||||||
print("Preparing:", nwConnection)
|
|
||||||
|
|
||||||
// Use same instance for all callbacks.
|
|
||||||
let connection = NetworkConnection(nwConnection)
|
|
||||||
|
|
||||||
nwConnection.stateUpdateHandler = { [weak self] (state) in
|
|
||||||
switch state
|
|
||||||
{
|
|
||||||
case .setup, .preparing: break
|
|
||||||
|
|
||||||
case .ready:
|
|
||||||
print("Connected to client:", connection)
|
|
||||||
self?.connectionHandler?(connection)
|
|
||||||
|
|
||||||
case .waiting:
|
|
||||||
print("Waiting for connection...")
|
|
||||||
|
|
||||||
case .failed(let error):
|
|
||||||
print("Failed to connect to service \(nwConnection.endpoint).", error)
|
|
||||||
self?.disconnect(connection)
|
|
||||||
|
|
||||||
case .cancelled:
|
|
||||||
self?.disconnect(connection)
|
|
||||||
|
|
||||||
@unknown default: break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
nwConnection.start(queue: self.dispatchQueue)
|
|
||||||
}
|
|
||||||
|
|
||||||
func disconnect(_ connection: Connection)
|
|
||||||
{
|
|
||||||
connection.disconnect()
|
|
||||||
|
|
||||||
self.disconnectionHandler?(connection)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,846 +0,0 @@
|
|||||||
//
|
|
||||||
// ALTDeviceManager+Installation.swift
|
|
||||||
// AltServer
|
|
||||||
//
|
|
||||||
// Created by Riley Testut on 7/1/19.
|
|
||||||
// Copyright © 2019 Riley Testut. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Cocoa
|
|
||||||
import UserNotifications
|
|
||||||
import ObjectiveC
|
|
||||||
|
|
||||||
private let appGroupsLock = NSLock()
|
|
||||||
|
|
||||||
enum InstallError: LocalizedError
|
|
||||||
{
|
|
||||||
case cancelled
|
|
||||||
case noTeam
|
|
||||||
case missingPrivateKey
|
|
||||||
case missingCertificate
|
|
||||||
|
|
||||||
var errorDescription: String? {
|
|
||||||
switch self
|
|
||||||
{
|
|
||||||
case .cancelled: return NSLocalizedString("The operation was cancelled.", comment: "")
|
|
||||||
case .noTeam: return "You are not a member of any developer teams."
|
|
||||||
case .missingPrivateKey: return "The developer certificate's private key could not be found."
|
|
||||||
case .missingCertificate: return "The developer certificate could not be found."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension ALTDeviceManager
|
|
||||||
{
|
|
||||||
func installApplication(at url: URL, to device: ALTDevice, appleID: String, password: String, completion: @escaping (Result<ALTApplication, Error>) -> Void)
|
|
||||||
{
|
|
||||||
let destinationDirectoryURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
|
|
||||||
|
|
||||||
func finish(_ result: Result<ALTApplication, Error>, title: String = "")
|
|
||||||
{
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
completion(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
try? FileManager.default.removeItem(at: destinationDirectoryURL)
|
|
||||||
}
|
|
||||||
|
|
||||||
AnisetteDataManager.shared.requestAnisetteData { (result) in
|
|
||||||
do
|
|
||||||
{
|
|
||||||
let anisetteData = try result.get()
|
|
||||||
|
|
||||||
self.authenticate(appleID: appleID, password: password, anisetteData: anisetteData) { (result) in
|
|
||||||
do
|
|
||||||
{
|
|
||||||
let (account, session) = try result.get()
|
|
||||||
|
|
||||||
self.fetchTeam(for: account, session: session) { (result) in
|
|
||||||
do
|
|
||||||
{
|
|
||||||
let team = try result.get()
|
|
||||||
|
|
||||||
self.register(device, team: team, session: session) { (result) in
|
|
||||||
do
|
|
||||||
{
|
|
||||||
let device = try result.get()
|
|
||||||
|
|
||||||
self.fetchCertificate(for: team, session: session) { (result) in
|
|
||||||
do
|
|
||||||
{
|
|
||||||
let certificate = try result.get()
|
|
||||||
|
|
||||||
if !url.isFileURL
|
|
||||||
{
|
|
||||||
// Show alert before downloading remote .ipa.
|
|
||||||
self.showInstallationAlert(appName: NSLocalizedString("AltStore", comment: ""), deviceName: device.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
self.downloadApp(from: url) { (result) in
|
|
||||||
do
|
|
||||||
{
|
|
||||||
let fileURL = try result.get()
|
|
||||||
|
|
||||||
try FileManager.default.createDirectory(at: destinationDirectoryURL, withIntermediateDirectories: true, attributes: nil)
|
|
||||||
|
|
||||||
let appBundleURL = try FileManager.default.unzipAppBundle(at: fileURL, toDirectory: destinationDirectoryURL)
|
|
||||||
guard let application = ALTApplication(fileURL: appBundleURL) else { throw ALTError(.invalidApp) }
|
|
||||||
|
|
||||||
if url.isFileURL
|
|
||||||
{
|
|
||||||
// Show alert after "downloading" local .ipa.
|
|
||||||
self.showInstallationAlert(appName: application.name, deviceName: device.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Refresh anisette data to prevent session timeouts.
|
|
||||||
AnisetteDataManager.shared.requestAnisetteData { (result) in
|
|
||||||
do
|
|
||||||
{
|
|
||||||
let anisetteData = try result.get()
|
|
||||||
session.anisetteData = anisetteData
|
|
||||||
|
|
||||||
self.prepareAllProvisioningProfiles(for: application, device: device, team: team, session: session) { (result) in
|
|
||||||
do
|
|
||||||
{
|
|
||||||
let profiles = try result.get()
|
|
||||||
|
|
||||||
self.install(application, to: device, team: team, certificate: certificate, profiles: profiles) { (result) in
|
|
||||||
finish(result.map { application }, title: "Failed to Install AltStore")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
finish(.failure(error), title: "Failed to Fetch Provisioning Profiles")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
finish(.failure(error), title: "Failed to Refresh Anisette Data")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
finish(.failure(error), title: "Failed to Download AltStore")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
finish(.failure(error), title: "Failed to Fetch Certificate")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
finish(.failure(error), title: "Failed to Register Device")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
finish(.failure(error), title: "Failed to Fetch Team")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
finish(.failure(error), title: "Failed to Authenticate")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
finish(.failure(error), title: "Failed to Fetch Anisette Data")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private extension ALTDeviceManager
|
|
||||||
{
|
|
||||||
func downloadApp(from url: URL, completionHandler: @escaping (Result<URL, Error>) -> Void)
|
|
||||||
{
|
|
||||||
guard !url.isFileURL else { return completionHandler(.success(url)) }
|
|
||||||
|
|
||||||
let downloadTask = URLSession.shared.downloadTask(with: url) { (fileURL, response, error) in
|
|
||||||
do
|
|
||||||
{
|
|
||||||
let (fileURL, _) = try Result((fileURL, response), error).get()
|
|
||||||
completionHandler(.success(fileURL))
|
|
||||||
|
|
||||||
do { try FileManager.default.removeItem(at: fileURL) }
|
|
||||||
catch { print("Failed to remove downloaded .ipa.", error) }
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
completionHandler(.failure(error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadTask.resume()
|
|
||||||
}
|
|
||||||
|
|
||||||
func authenticate(appleID: String, password: String, anisetteData: ALTAnisetteData, completionHandler: @escaping (Result<(ALTAccount, ALTAppleAPISession), Error>) -> Void)
|
|
||||||
{
|
|
||||||
func handleVerificationCode(_ completionHandler: @escaping (String?) -> Void)
|
|
||||||
{
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
let alert = NSAlert()
|
|
||||||
alert.messageText = NSLocalizedString("Two-Factor Authentication Enabled", comment: "")
|
|
||||||
alert.informativeText = NSLocalizedString("Please enter the 6-digit verification code that was sent to your Apple devices.", comment: "")
|
|
||||||
|
|
||||||
let textField = NSTextField(frame: NSRect(x: 0, y: 0, width: 300, height: 22))
|
|
||||||
textField.delegate = self
|
|
||||||
textField.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
textField.placeholderString = NSLocalizedString("123456", comment: "")
|
|
||||||
alert.accessoryView = textField
|
|
||||||
alert.window.initialFirstResponder = textField
|
|
||||||
|
|
||||||
alert.addButton(withTitle: NSLocalizedString("Continue", comment: ""))
|
|
||||||
alert.addButton(withTitle: NSLocalizedString("Cancel", comment: ""))
|
|
||||||
|
|
||||||
self.securityCodeAlert = alert
|
|
||||||
self.securityCodeTextField = textField
|
|
||||||
self.validate()
|
|
||||||
|
|
||||||
NSRunningApplication.current.activate(options: .activateIgnoringOtherApps)
|
|
||||||
|
|
||||||
let response = alert.runModal()
|
|
||||||
if response == .alertFirstButtonReturn
|
|
||||||
{
|
|
||||||
let code = textField.stringValue
|
|
||||||
completionHandler(code)
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
completionHandler(nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ALTAppleAPI.shared.authenticate(appleID: appleID, password: password, anisetteData: anisetteData, verificationHandler: handleVerificationCode) { (account, session, error) in
|
|
||||||
if let account = account, let session = session
|
|
||||||
{
|
|
||||||
completionHandler(.success((account, session)))
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
completionHandler(.failure(error ?? ALTAppleAPIError(.unknown)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func fetchTeam(for account: ALTAccount, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTTeam, Error>) -> Void)
|
|
||||||
{
|
|
||||||
ALTAppleAPI.shared.fetchTeams(for: account, session: session) { (teams, error) in
|
|
||||||
do
|
|
||||||
{
|
|
||||||
let teams = try Result(teams, error).get()
|
|
||||||
|
|
||||||
if let team = teams.first(where: { $0.type == .individual })
|
|
||||||
{
|
|
||||||
return completionHandler(.success(team))
|
|
||||||
}
|
|
||||||
else if let team = teams.first(where: { $0.type == .free })
|
|
||||||
{
|
|
||||||
return completionHandler(.success(team))
|
|
||||||
}
|
|
||||||
else if let team = teams.first
|
|
||||||
{
|
|
||||||
return completionHandler(.success(team))
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw InstallError.noTeam
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
completionHandler(.failure(error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func fetchCertificate(for team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTCertificate, Error>) -> Void)
|
|
||||||
{
|
|
||||||
ALTAppleAPI.shared.fetchCertificates(for: team, session: session) { (certificates, error) in
|
|
||||||
do
|
|
||||||
{
|
|
||||||
let certificates = try Result(certificates, error).get()
|
|
||||||
|
|
||||||
let applicationSupportDirectoryURL = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0]
|
|
||||||
let altserverDirectoryURL = applicationSupportDirectoryURL.appendingPathComponent("com.rileytestut.AltServer")
|
|
||||||
let certificatesDirectoryURL = altserverDirectoryURL.appendingPathComponent("Certificates")
|
|
||||||
|
|
||||||
try FileManager.default.createDirectory(at: certificatesDirectoryURL, withIntermediateDirectories: true, attributes: nil)
|
|
||||||
|
|
||||||
let certificateFileURL = certificatesDirectoryURL.appendingPathComponent(team.identifier + ".p12")
|
|
||||||
|
|
||||||
var isCancelled = false
|
|
||||||
|
|
||||||
// Check if there is another AltStore certificate, which means AltStore has been installed with this Apple ID before.
|
|
||||||
if let previousCertificate = certificates.first(where: { $0.machineName?.starts(with: "AltStore") == true })
|
|
||||||
{
|
|
||||||
if FileManager.default.fileExists(atPath: certificateFileURL.path),
|
|
||||||
let data = try? Data(contentsOf: certificateFileURL),
|
|
||||||
let certificate = ALTCertificate(p12Data: data, password: previousCertificate.machineIdentifier)
|
|
||||||
{
|
|
||||||
// Manually set machineIdentifier so we can encrypt + embed certificate if needed.
|
|
||||||
certificate.machineIdentifier = previousCertificate.machineIdentifier
|
|
||||||
return completionHandler(.success(certificate))
|
|
||||||
}
|
|
||||||
|
|
||||||
DispatchQueue.main.sync {
|
|
||||||
let alert = NSAlert()
|
|
||||||
alert.messageText = NSLocalizedString("Multiple AltServers Not Supported", comment: "")
|
|
||||||
alert.informativeText = NSLocalizedString("Please use the same AltServer you previously used with this Apple ID, or else apps installed with other AltServers will stop working.\n\nAre you sure you want to continue?", comment: "")
|
|
||||||
|
|
||||||
alert.addButton(withTitle: NSLocalizedString("Continue", comment: ""))
|
|
||||||
alert.addButton(withTitle: NSLocalizedString("Cancel", comment: ""))
|
|
||||||
|
|
||||||
NSRunningApplication.current.activate(options: .activateIgnoringOtherApps)
|
|
||||||
|
|
||||||
let buttonIndex = alert.runModal()
|
|
||||||
if buttonIndex == NSApplication.ModalResponse.alertSecondButtonReturn
|
|
||||||
{
|
|
||||||
isCancelled = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
guard !isCancelled else { return completionHandler(.failure(InstallError.cancelled)) }
|
|
||||||
}
|
|
||||||
|
|
||||||
if team.type != .free
|
|
||||||
{
|
|
||||||
DispatchQueue.main.sync {
|
|
||||||
let alert = NSAlert()
|
|
||||||
alert.messageText = NSLocalizedString("Installing this app will revoke your iOS development certificate.", comment: "")
|
|
||||||
alert.informativeText = NSLocalizedString("""
|
|
||||||
This will not affect apps you've submitted to the App Store, but may cause apps you've installed to your devices with Xcode to stop working until you reinstall them.
|
|
||||||
|
|
||||||
To prevent this from happening, feel free to try again with another Apple ID.
|
|
||||||
""", comment: "")
|
|
||||||
|
|
||||||
alert.addButton(withTitle: NSLocalizedString("Continue", comment: ""))
|
|
||||||
alert.addButton(withTitle: NSLocalizedString("Cancel", comment: ""))
|
|
||||||
|
|
||||||
NSRunningApplication.current.activate(options: .activateIgnoringOtherApps)
|
|
||||||
|
|
||||||
let buttonIndex = alert.runModal()
|
|
||||||
if buttonIndex == NSApplication.ModalResponse.alertSecondButtonReturn
|
|
||||||
{
|
|
||||||
isCancelled = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
guard !isCancelled else { return completionHandler(.failure(InstallError.cancelled)) }
|
|
||||||
}
|
|
||||||
|
|
||||||
if let certificate = certificates.first
|
|
||||||
{
|
|
||||||
ALTAppleAPI.shared.revoke(certificate, for: team, session: session) { (success, error) in
|
|
||||||
do
|
|
||||||
{
|
|
||||||
try Result(success, error).get()
|
|
||||||
self.fetchCertificate(for: team, session: session, completionHandler: completionHandler)
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
completionHandler(.failure(error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ALTAppleAPI.shared.addCertificate(machineName: "AltStore", to: team, session: session) { (certificate, error) in
|
|
||||||
do
|
|
||||||
{
|
|
||||||
let certificate = try Result(certificate, error).get()
|
|
||||||
guard let privateKey = certificate.privateKey else { throw InstallError.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 InstallError.missingCertificate
|
|
||||||
}
|
|
||||||
|
|
||||||
certificate.privateKey = privateKey
|
|
||||||
|
|
||||||
completionHandler(.success(certificate))
|
|
||||||
|
|
||||||
if let machineIdentifier = certificate.machineIdentifier,
|
|
||||||
let encryptedData = certificate.encryptedP12Data(withPassword: machineIdentifier)
|
|
||||||
{
|
|
||||||
// Cache certificate.
|
|
||||||
do { try encryptedData.write(to: certificateFileURL, options: .atomic) }
|
|
||||||
catch { print("Failed to cache certificate:", error) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
completionHandler(.failure(error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
completionHandler(.failure(error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
completionHandler(.failure(error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func prepareAllProvisioningProfiles(for application: ALTApplication, device: ALTDevice, team: ALTTeam, session: ALTAppleAPISession,
|
|
||||||
completion: @escaping (Result<[String: ALTProvisioningProfile], Error>) -> Void)
|
|
||||||
{
|
|
||||||
self.prepareProvisioningProfile(for: application, parentApp: nil, device: device, team: team, session: session) { (result) in
|
|
||||||
do
|
|
||||||
{
|
|
||||||
let profile = try result.get()
|
|
||||||
|
|
||||||
var profiles = [application.bundleIdentifier: profile]
|
|
||||||
var error: Error?
|
|
||||||
|
|
||||||
let dispatchGroup = DispatchGroup()
|
|
||||||
|
|
||||||
for appExtension in application.appExtensions
|
|
||||||
{
|
|
||||||
dispatchGroup.enter()
|
|
||||||
|
|
||||||
self.prepareProvisioningProfile(for: appExtension, parentApp: application, device: device, team: team, session: session) { (result) in
|
|
||||||
switch result
|
|
||||||
{
|
|
||||||
case .failure(let e): error = e
|
|
||||||
case .success(let profile): profiles[appExtension.bundleIdentifier] = profile
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatchGroup.leave()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatchGroup.notify(queue: .global()) {
|
|
||||||
if let error = error
|
|
||||||
{
|
|
||||||
completion(.failure(error))
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
completion(.success(profiles))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
completion(.failure(error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func prepareProvisioningProfile(for application: ALTApplication, parentApp: ALTApplication?, device: ALTDevice, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTProvisioningProfile, Error>) -> Void)
|
|
||||||
{
|
|
||||||
let parentBundleID = parentApp?.bundleIdentifier ?? application.bundleIdentifier
|
|
||||||
let updatedParentBundleID: String
|
|
||||||
|
|
||||||
if application.isAltStoreApp
|
|
||||||
{
|
|
||||||
// Use legacy bundle ID format for AltStore (and its extensions).
|
|
||||||
updatedParentBundleID = "com.\(team.identifier).\(parentBundleID)"
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
updatedParentBundleID = parentBundleID + "." + team.identifier // Append just team identifier to make it harder to track.
|
|
||||||
}
|
|
||||||
|
|
||||||
let bundleID = application.bundleIdentifier.replacingOccurrences(of: parentBundleID, with: updatedParentBundleID)
|
|
||||||
|
|
||||||
let preferredName: String
|
|
||||||
|
|
||||||
if let parentApp = parentApp
|
|
||||||
{
|
|
||||||
preferredName = parentApp.name + " " + application.name
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
preferredName = application.name
|
|
||||||
}
|
|
||||||
|
|
||||||
self.registerAppID(name: preferredName, bundleID: bundleID, team: team, session: session) { (result) in
|
|
||||||
do
|
|
||||||
{
|
|
||||||
let appID = try result.get()
|
|
||||||
|
|
||||||
self.updateFeatures(for: appID, app: application, team: team, session: session) { (result) in
|
|
||||||
do
|
|
||||||
{
|
|
||||||
let appID = try result.get()
|
|
||||||
|
|
||||||
self.updateAppGroups(for: appID, app: application, team: team, session: session) { (result) in
|
|
||||||
do
|
|
||||||
{
|
|
||||||
let appID = try result.get()
|
|
||||||
|
|
||||||
self.fetchProvisioningProfile(for: appID, device: device, team: team, session: session) { (result) in
|
|
||||||
completionHandler(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
completionHandler(.failure(error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
completionHandler(.failure(error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
completionHandler(.failure(error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func registerAppID(name appName: String, bundleID: String, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTAppID, Error>) -> Void)
|
|
||||||
{
|
|
||||||
ALTAppleAPI.shared.fetchAppIDs(for: team, session: session) { (appIDs, error) in
|
|
||||||
do
|
|
||||||
{
|
|
||||||
let appIDs = try Result(appIDs, error).get()
|
|
||||||
|
|
||||||
if let appID = appIDs.first(where: { $0.bundleIdentifier == bundleID })
|
|
||||||
{
|
|
||||||
completionHandler(.success(appID))
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ALTAppleAPI.shared.addAppID(withName: appName, bundleIdentifier: bundleID, team: team, session: session) { (appID, error) in
|
|
||||||
completionHandler(Result(appID, error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
completionHandler(.failure(error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateFeatures(for appID: ALTAppID, app: ALTApplication, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTAppID, Error>) -> Void)
|
|
||||||
{
|
|
||||||
let requiredFeatures = app.entitlements.compactMap { (entitlement, value) -> (ALTFeature, Any)? in
|
|
||||||
guard let feature = ALTFeature(entitlement: entitlement) else { return nil }
|
|
||||||
return (feature, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
var features = requiredFeatures.reduce(into: [ALTFeature: Any]()) { $0[$1.0] = $1.1 }
|
|
||||||
|
|
||||||
if let applicationGroups = app.entitlements[.appGroups] as? [String], !applicationGroups.isEmpty
|
|
||||||
{
|
|
||||||
features[.appGroups] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
var updateFeatures = false
|
|
||||||
|
|
||||||
// Determine whether the required features are already enabled for the AppID.
|
|
||||||
for (feature, value) in features
|
|
||||||
{
|
|
||||||
if let appIDValue = appID.features[feature] as AnyObject?, (value as AnyObject).isEqual(appIDValue)
|
|
||||||
{
|
|
||||||
// AppID already has this feature enabled and the values are the same.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// AppID either doesn't have this feature enabled or the value has changed,
|
|
||||||
// so we need to update it to reflect new values.
|
|
||||||
updateFeatures = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if updateFeatures
|
|
||||||
{
|
|
||||||
let appID = appID.copy() as! ALTAppID
|
|
||||||
appID.features = features
|
|
||||||
|
|
||||||
ALTAppleAPI.shared.update(appID, team: team, session: session) { (appID, error) in
|
|
||||||
completionHandler(Result(appID, error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
completionHandler(.success(appID))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateAppGroups(for appID: ALTAppID, app: ALTApplication, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTAppID, Error>) -> Void)
|
|
||||||
{
|
|
||||||
let applicationGroups = app.entitlements[.appGroups] as? [String] ?? []
|
|
||||||
if applicationGroups.isEmpty
|
|
||||||
{
|
|
||||||
guard let isAppGroupsEnabled = appID.features[.appGroups] as? Bool, isAppGroupsEnabled else {
|
|
||||||
// No app groups, and we also haven't enabled the feature, so don't continue.
|
|
||||||
// For apps with no app groups but have had the feature enabled already
|
|
||||||
// we'll continue and assign the app ID to an empty array
|
|
||||||
// in case we need to explicitly remove them.
|
|
||||||
return completionHandler(.success(appID))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dispatch onto global queue to prevent appGroupsLock deadlock.
|
|
||||||
DispatchQueue.global().async {
|
|
||||||
|
|
||||||
// Ensure we're not concurrently fetching and updating app groups,
|
|
||||||
// which can lead to race conditions such as adding an app group twice.
|
|
||||||
appGroupsLock.lock()
|
|
||||||
|
|
||||||
func finish(_ result: Result<ALTAppID, Error>)
|
|
||||||
{
|
|
||||||
appGroupsLock.unlock()
|
|
||||||
completionHandler(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
ALTAppleAPI.shared.fetchAppGroups(for: team, session: session) { (groups, error) in
|
|
||||||
switch Result(groups, error)
|
|
||||||
{
|
|
||||||
case .failure(let error): finish(.failure(error))
|
|
||||||
case .success(let fetchedGroups):
|
|
||||||
let dispatchGroup = DispatchGroup()
|
|
||||||
|
|
||||||
var groups = [ALTAppGroup]()
|
|
||||||
var errors = [Error]()
|
|
||||||
|
|
||||||
for groupIdentifier in applicationGroups
|
|
||||||
{
|
|
||||||
let adjustedGroupIdentifier = groupIdentifier + "." + team.identifier
|
|
||||||
|
|
||||||
if let group = fetchedGroups.first(where: { $0.groupIdentifier == adjustedGroupIdentifier })
|
|
||||||
{
|
|
||||||
groups.append(group)
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
dispatchGroup.enter()
|
|
||||||
|
|
||||||
// Not all characters are allowed in group names, so we replace periods with spaces (like Apple does).
|
|
||||||
let name = "AltStore " + groupIdentifier.replacingOccurrences(of: ".", with: " ")
|
|
||||||
|
|
||||||
ALTAppleAPI.shared.addAppGroup(withName: name, groupIdentifier: adjustedGroupIdentifier, team: team, session: session) { (group, error) in
|
|
||||||
switch Result(group, error)
|
|
||||||
{
|
|
||||||
case .success(let group): groups.append(group)
|
|
||||||
case .failure(let error): errors.append(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatchGroup.leave()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatchGroup.notify(queue: .global()) {
|
|
||||||
if let error = errors.first
|
|
||||||
{
|
|
||||||
finish(.failure(error))
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ALTAppleAPI.shared.assign(appID, to: Array(groups), team: team, session: session) { (success, error) in
|
|
||||||
let result = Result(success, error)
|
|
||||||
finish(result.map { _ in appID })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func register(_ device: ALTDevice, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTDevice, Error>) -> Void)
|
|
||||||
{
|
|
||||||
ALTAppleAPI.shared.fetchDevices(for: team, types: device.type, session: session) { (devices, error) in
|
|
||||||
do
|
|
||||||
{
|
|
||||||
let devices = try Result(devices, error).get()
|
|
||||||
|
|
||||||
if let device = devices.first(where: { $0.identifier == device.identifier })
|
|
||||||
{
|
|
||||||
completionHandler(.success(device))
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ALTAppleAPI.shared.registerDevice(name: device.name, identifier: device.identifier, type: device.type, team: team, session: session) { (device, error) in
|
|
||||||
completionHandler(Result(device, error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
completionHandler(.failure(error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func fetchProvisioningProfile(for appID: ALTAppID, device: ALTDevice, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTProvisioningProfile, Error>) -> Void)
|
|
||||||
{
|
|
||||||
ALTAppleAPI.shared.fetchProvisioningProfile(for: appID, deviceType: device.type, team: team, session: session) { (profile, error) in
|
|
||||||
completionHandler(Result(profile, error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func install(_ application: ALTApplication, to device: ALTDevice, team: ALTTeam, certificate: ALTCertificate, profiles: [String: ALTProvisioningProfile], completionHandler: @escaping (Result<Void, Error>) -> Void)
|
|
||||||
{
|
|
||||||
func prepare(_ bundle: Bundle, additionalInfoDictionaryValues: [String: Any] = [:]) throws
|
|
||||||
{
|
|
||||||
guard let identifier = bundle.bundleIdentifier else { throw ALTError(.missingAppBundle) }
|
|
||||||
guard let profile = profiles[identifier] else { throw ALTError(.missingProvisioningProfile) }
|
|
||||||
guard var infoDictionary = bundle.completeInfoDictionary else { throw ALTError(.missingInfoPlist) }
|
|
||||||
|
|
||||||
infoDictionary[kCFBundleIdentifierKey as String] = profile.bundleIdentifier
|
|
||||||
infoDictionary[Bundle.Info.altBundleID] = identifier
|
|
||||||
|
|
||||||
for (key, value) in additionalInfoDictionaryValues
|
|
||||||
{
|
|
||||||
infoDictionary[key] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
if let appGroups = profile.entitlements[.appGroups] as? [String]
|
|
||||||
{
|
|
||||||
infoDictionary[Bundle.Info.appGroups] = appGroups
|
|
||||||
}
|
|
||||||
|
|
||||||
try (infoDictionary as NSDictionary).write(to: bundle.infoPlistURL)
|
|
||||||
}
|
|
||||||
|
|
||||||
DispatchQueue.global().async {
|
|
||||||
do
|
|
||||||
{
|
|
||||||
guard let appBundle = Bundle(url: application.fileURL) else { throw ALTError(.missingAppBundle) }
|
|
||||||
guard let infoDictionary = appBundle.completeInfoDictionary else { throw ALTError(.missingInfoPlist) }
|
|
||||||
|
|
||||||
let openAppURL = URL(string: "altstore-" + application.bundleIdentifier + "://")!
|
|
||||||
|
|
||||||
var allURLSchemes = infoDictionary[Bundle.Info.urlTypes] as? [[String: Any]] ?? []
|
|
||||||
|
|
||||||
// Embed open URL so AltBackup can return to AltStore.
|
|
||||||
let altstoreURLScheme = ["CFBundleTypeRole": "Editor",
|
|
||||||
"CFBundleURLName": application.bundleIdentifier,
|
|
||||||
"CFBundleURLSchemes": [openAppURL.scheme!]] as [String : Any]
|
|
||||||
allURLSchemes.append(altstoreURLScheme)
|
|
||||||
|
|
||||||
var additionalValues: [String: Any] = [Bundle.Info.urlTypes: allURLSchemes]
|
|
||||||
|
|
||||||
if application.isAltStoreApp
|
|
||||||
{
|
|
||||||
additionalValues[Bundle.Info.deviceID] = device.identifier
|
|
||||||
additionalValues[Bundle.Info.serverID] = UserDefaults.standard.serverID
|
|
||||||
|
|
||||||
if
|
|
||||||
let machineIdentifier = certificate.machineIdentifier,
|
|
||||||
let encryptedData = certificate.encryptedP12Data(withPassword: machineIdentifier)
|
|
||||||
{
|
|
||||||
additionalValues[Bundle.Info.certificateID] = certificate.serialNumber
|
|
||||||
|
|
||||||
let certificateURL = application.fileURL.appendingPathComponent("ALTCertificate.p12")
|
|
||||||
try encryptedData.write(to: certificateURL, options: .atomic)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try prepare(appBundle, additionalInfoDictionaryValues: additionalValues)
|
|
||||||
|
|
||||||
for appExtension in application.appExtensions
|
|
||||||
{
|
|
||||||
guard let bundle = Bundle(url: appExtension.fileURL) else { throw ALTError(.missingAppBundle) }
|
|
||||||
try prepare(bundle)
|
|
||||||
}
|
|
||||||
|
|
||||||
let resigner = ALTSigner(team: team, certificate: certificate)
|
|
||||||
resigner.signApp(at: application.fileURL, provisioningProfiles: Array(profiles.values)) { (success, error) in
|
|
||||||
do
|
|
||||||
{
|
|
||||||
try Result(success, error).get()
|
|
||||||
|
|
||||||
let activeProfiles: Set<String>? = (team.type == .free && application.isAltStoreApp) ? Set(profiles.values.map(\.bundleIdentifier)) : nil
|
|
||||||
ALTDeviceManager.shared.installApp(at: application.fileURL, toDeviceWithUDID: device.identifier, activeProvisioningProfiles: activeProfiles) { (success, error) in
|
|
||||||
completionHandler(Result(success, error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
print("Failed to install app", error)
|
|
||||||
completionHandler(.failure(error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
print("Failed to install AltStore", error)
|
|
||||||
completionHandler(.failure(error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func showInstallationAlert(appName: String, deviceName: String)
|
|
||||||
{
|
|
||||||
let content = UNMutableNotificationContent()
|
|
||||||
content.title = String(format: NSLocalizedString("Installing %@ to %@...", comment: ""), appName, deviceName)
|
|
||||||
content.body = NSLocalizedString("This may take a few seconds.", comment: "")
|
|
||||||
|
|
||||||
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
|
|
||||||
UNUserNotificationCenter.current().add(request)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var securityCodeAlertKey = 0
|
|
||||||
private var securityCodeTextFieldKey = 0
|
|
||||||
|
|
||||||
extension ALTDeviceManager: NSTextFieldDelegate
|
|
||||||
{
|
|
||||||
var securityCodeAlert: NSAlert? {
|
|
||||||
get { return objc_getAssociatedObject(self, &securityCodeAlertKey) as? NSAlert }
|
|
||||||
set { objc_setAssociatedObject(self, &securityCodeAlertKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
|
|
||||||
}
|
|
||||||
|
|
||||||
var securityCodeTextField: NSTextField? {
|
|
||||||
get { return objc_getAssociatedObject(self, &securityCodeTextFieldKey) as? NSTextField }
|
|
||||||
set { objc_setAssociatedObject(self, &securityCodeTextFieldKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
|
|
||||||
}
|
|
||||||
|
|
||||||
public func controlTextDidChange(_ obj: Notification)
|
|
||||||
{
|
|
||||||
self.validate()
|
|
||||||
}
|
|
||||||
|
|
||||||
public func controlTextDidEndEditing(_ obj: Notification)
|
|
||||||
{
|
|
||||||
self.validate()
|
|
||||||
}
|
|
||||||
|
|
||||||
private func validate()
|
|
||||||
{
|
|
||||||
guard let code = self.securityCodeTextField?.stringValue.trimmingCharacters(in: .whitespacesAndNewlines) else { return }
|
|
||||||
|
|
||||||
if code.count == 6
|
|
||||||
{
|
|
||||||
self.securityCodeAlert?.buttons.first?.isEnabled = true
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
self.securityCodeAlert?.buttons.first?.isEnabled = false
|
|
||||||
}
|
|
||||||
|
|
||||||
self.securityCodeAlert?.layout()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
//
|
|
||||||
// ALTDeviceManager.h
|
|
||||||
// AltServer
|
|
||||||
//
|
|
||||||
// Created by Riley Testut on 5/24/19.
|
|
||||||
// Copyright © 2019 Riley Testut. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
|
||||||
#import "AltSign.h"
|
|
||||||
|
|
||||||
@class ALTWiredConnection;
|
|
||||||
@class ALTNotificationConnection;
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
|
||||||
|
|
||||||
extern NSNotificationName const ALTDeviceManagerDeviceDidConnectNotification NS_SWIFT_NAME(deviceManagerDeviceDidConnect);
|
|
||||||
extern NSNotificationName const ALTDeviceManagerDeviceDidDisconnectNotification NS_SWIFT_NAME(deviceManagerDeviceDidDisconnect);
|
|
||||||
|
|
||||||
@interface ALTDeviceManager : NSObject
|
|
||||||
|
|
||||||
@property (class, nonatomic, readonly) ALTDeviceManager *sharedManager;
|
|
||||||
|
|
||||||
@property (nonatomic, readonly) NSArray<ALTDevice *> *connectedDevices;
|
|
||||||
@property (nonatomic, readonly) NSArray<ALTDevice *> *availableDevices;
|
|
||||||
|
|
||||||
- (void)start;
|
|
||||||
|
|
||||||
/* App Installation */
|
|
||||||
- (NSProgress *)installAppAtURL:(NSURL *)fileURL toDeviceWithUDID:(NSString *)udid activeProvisioningProfiles:(nullable NSSet<NSString *> *)activeProvisioningProfiles completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler;
|
|
||||||
- (void)removeAppForBundleIdentifier:(NSString *)bundleIdentifier fromDeviceWithUDID:(NSString *)udid completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler;
|
|
||||||
|
|
||||||
- (void)installProvisioningProfiles:(NSSet<ALTProvisioningProfile *> *)provisioningProfiles toDeviceWithUDID:(NSString *)udid activeProvisioningProfiles:(nullable NSSet<NSString *> *)activeProvisioningProfiles completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler;
|
|
||||||
- (void)removeProvisioningProfilesForBundleIdentifiers:(NSSet<NSString *> *)bundleIdentifiers fromDeviceWithUDID:(NSString *)udid completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler;
|
|
||||||
|
|
||||||
/* Connections */
|
|
||||||
- (void)startWiredConnectionToDevice:(ALTDevice *)device completionHandler:(void (^)(ALTWiredConnection *_Nullable connection, NSError *_Nullable error))completionHandler;
|
|
||||||
- (void)startNotificationConnectionToDevice:(ALTDevice *)device completionHandler:(void (^)(ALTNotificationConnection *_Nullable connection, NSError *_Nullable error))completionHandler;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
//
|
|
||||||
// UserDefaults+AltServer.swift
|
|
||||||
// AltServer
|
|
||||||
//
|
|
||||||
// Created by Riley Testut on 7/31/19.
|
|
||||||
// Copyright © 2019 Riley Testut. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
extension UserDefaults
|
|
||||||
{
|
|
||||||
var serverID: String? {
|
|
||||||
get {
|
|
||||||
return self.string(forKey: "serverID")
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
self.set(newValue, forKey: "serverID")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var didPresentInitialNotification: Bool {
|
|
||||||
get {
|
|
||||||
return self.bool(forKey: "didPresentInitialNotification")
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
self.set(newValue, forKey: "didPresentInitialNotification")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func registerDefaults()
|
|
||||||
{
|
|
||||||
if self.serverID == nil
|
|
||||||
{
|
|
||||||
self.serverID = UUID().uuidString
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +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>CFBundleDevelopmentRegion</key>
|
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
|
||||||
<key>CFBundleExecutable</key>
|
|
||||||
<string>$(EXECUTABLE_NAME)</string>
|
|
||||||
<key>CFBundleIconFile</key>
|
|
||||||
<string></string>
|
|
||||||
<key>CFBundleIdentifier</key>
|
|
||||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
|
||||||
<string>6.0</string>
|
|
||||||
<key>CFBundleName</key>
|
|
||||||
<string>$(PRODUCT_NAME)</string>
|
|
||||||
<key>CFBundlePackageType</key>
|
|
||||||
<string>APPL</string>
|
|
||||||
<key>CFBundleShortVersionString</key>
|
|
||||||
<string>$(MARKETING_VERSION)</string>
|
|
||||||
<key>CFBundleVersion</key>
|
|
||||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
|
||||||
<key>LSMinimumSystemVersion</key>
|
|
||||||
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
|
||||||
<key>LSUIElement</key>
|
|
||||||
<true/>
|
|
||||||
<key>NSHumanReadableCopyright</key>
|
|
||||||
<string>Copyright © 2019 Riley Testut. All rights reserved.</string>
|
|
||||||
<key>NSMainStoryboardFile</key>
|
|
||||||
<string>Main</string>
|
|
||||||
<key>NSPrincipalClass</key>
|
|
||||||
<string>NSApplication</string>
|
|
||||||
<key>SUFeedURL</key>
|
|
||||||
<string>https://altstore.io/altserver/sparkle-macos.xml</string>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
@@ -1,310 +0,0 @@
|
|||||||
//
|
|
||||||
// PluginManager.swift
|
|
||||||
// AltServer
|
|
||||||
//
|
|
||||||
// Created by Riley Testut on 9/16/20.
|
|
||||||
// Copyright © 2020 Riley Testut. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import AppKit
|
|
||||||
import CryptoKit
|
|
||||||
|
|
||||||
import STPrivilegedTask
|
|
||||||
|
|
||||||
private let pluginDirectoryURL = URL(fileURLWithPath: "/Library/Mail/Bundles", isDirectory: true)
|
|
||||||
private let pluginURL = pluginDirectoryURL.appendingPathComponent("AltPlugin.mailbundle")
|
|
||||||
|
|
||||||
enum PluginError: LocalizedError
|
|
||||||
{
|
|
||||||
case cancelled
|
|
||||||
case unknown
|
|
||||||
case notFound
|
|
||||||
case mismatchedHash(hash: String, expectedHash: String)
|
|
||||||
case taskError(String)
|
|
||||||
case taskErrorCode(Int)
|
|
||||||
|
|
||||||
var errorDescription: String? {
|
|
||||||
switch self
|
|
||||||
{
|
|
||||||
case .cancelled: return NSLocalizedString("Mail plug-in installation was cancelled.", comment: "")
|
|
||||||
case .unknown: return NSLocalizedString("Failed to install Mail plug-in.", comment: "")
|
|
||||||
case .notFound: return NSLocalizedString("The Mail plug-in does not exist at the requested URL.", comment: "")
|
|
||||||
case .mismatchedHash(let hash, let expectedHash): return String(format: NSLocalizedString("The hash of the downloaded Mail plug-in does not match the expected hash.\n\nHash:\n%@\n\nExpected Hash:\n%@", comment: ""), hash, expectedHash)
|
|
||||||
case .taskError(let output): return output
|
|
||||||
case .taskErrorCode(let errorCode): return String(format: NSLocalizedString("There was an error installing the Mail plug-in. (Error Code: %@)", comment: ""), NSNumber(value: errorCode))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct PluginVersion
|
|
||||||
{
|
|
||||||
var url: URL
|
|
||||||
var sha256Hash: String
|
|
||||||
var version: String
|
|
||||||
|
|
||||||
static let v1_0 = PluginVersion(url: URL(string: "https://f000.backblazeb2.com/file/altstore/altserver/altplugin/1_0.zip")!,
|
|
||||||
sha256Hash: "070e9b7e1f74e7a6474d36253ab5a3623ff93892acc9e1043c3581f2ded12200",
|
|
||||||
version: "1.0")
|
|
||||||
|
|
||||||
static let v1_3 = PluginVersion(url: Bundle.main.url(forResource: "AltPlugin", withExtension: "zip")!,
|
|
||||||
sha256Hash: "6c939d6601ea9793f149e4f6dd4a154e8229a9b9cf7f4bea4a1d6bca7d433512",
|
|
||||||
version: "1.3")
|
|
||||||
}
|
|
||||||
|
|
||||||
class PluginManager
|
|
||||||
{
|
|
||||||
var isMailPluginInstalled: Bool {
|
|
||||||
let isMailPluginInstalled = FileManager.default.fileExists(atPath: pluginURL.path)
|
|
||||||
return isMailPluginInstalled
|
|
||||||
}
|
|
||||||
|
|
||||||
var isUpdateAvailable: Bool {
|
|
||||||
guard let bundle = Bundle(url: pluginURL) else { return false }
|
|
||||||
|
|
||||||
// Load Info.plist from disk because Bundle.infoDictionary is cached by system.
|
|
||||||
let infoDictionaryURL = bundle.bundleURL.appendingPathComponent("Contents/Info.plist")
|
|
||||||
guard let infoDictionary = NSDictionary(contentsOf: infoDictionaryURL) as? [String: Any],
|
|
||||||
let version = infoDictionary["CFBundleShortVersionString"] as? String
|
|
||||||
else { return false }
|
|
||||||
|
|
||||||
let isUpdateAvailable = (version != self.preferredVersion.version)
|
|
||||||
return isUpdateAvailable
|
|
||||||
}
|
|
||||||
|
|
||||||
private var preferredVersion: PluginVersion {
|
|
||||||
if #available(macOS 11, *)
|
|
||||||
{
|
|
||||||
return .v1_3
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return .v1_0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension PluginManager
|
|
||||||
{
|
|
||||||
func installMailPlugin(completionHandler: @escaping (Result<Void, Error>) -> Void)
|
|
||||||
{
|
|
||||||
do
|
|
||||||
{
|
|
||||||
let alert = NSAlert()
|
|
||||||
|
|
||||||
if self.isUpdateAvailable
|
|
||||||
{
|
|
||||||
alert.messageText = NSLocalizedString("Update Mail Plug-in", comment: "")
|
|
||||||
alert.informativeText = NSLocalizedString("An update is available for AltServer's Mail plug-in. Please update the plug-in now in order to keep using AltStore.", comment: "")
|
|
||||||
|
|
||||||
alert.addButton(withTitle: NSLocalizedString("Update Plug-in", comment: ""))
|
|
||||||
alert.addButton(withTitle: NSLocalizedString("Cancel", comment: ""))
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
alert.messageText = NSLocalizedString("Install Mail Plug-in", comment: "")
|
|
||||||
alert.informativeText = NSLocalizedString("AltServer requires a Mail plug-in in order to retrieve necessary information about your Apple ID. Would you like to install it now?", comment: "")
|
|
||||||
|
|
||||||
alert.addButton(withTitle: NSLocalizedString("Install Plug-in", comment: ""))
|
|
||||||
alert.addButton(withTitle: NSLocalizedString("Cancel", comment: ""))
|
|
||||||
}
|
|
||||||
|
|
||||||
NSRunningApplication.current.activate(options: .activateIgnoringOtherApps)
|
|
||||||
|
|
||||||
let response = alert.runModal()
|
|
||||||
guard response == .alertFirstButtonReturn else { throw PluginError.cancelled }
|
|
||||||
|
|
||||||
self.downloadPlugin { (result) in
|
|
||||||
do
|
|
||||||
{
|
|
||||||
let fileURL = try result.get()
|
|
||||||
|
|
||||||
// Ensure plug-in directory exists.
|
|
||||||
let authorization = try self.runAndKeepAuthorization("mkdir", arguments: ["-p", pluginDirectoryURL.path])
|
|
||||||
|
|
||||||
// Create temporary directory.
|
|
||||||
let temporaryDirectoryURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
|
|
||||||
try FileManager.default.createDirectory(at: temporaryDirectoryURL, withIntermediateDirectories: true, attributes: nil)
|
|
||||||
defer { try? FileManager.default.removeItem(at: temporaryDirectoryURL) }
|
|
||||||
|
|
||||||
// Unzip AltPlugin to temporary directory.
|
|
||||||
try self.runAndKeepAuthorization("unzip", arguments: ["-o", fileURL.path, "-d", temporaryDirectoryURL.path], authorization: authorization)
|
|
||||||
|
|
||||||
if FileManager.default.fileExists(atPath: pluginURL.path)
|
|
||||||
{
|
|
||||||
// Delete existing Mail plug-in.
|
|
||||||
try self.runAndKeepAuthorization("rm", arguments: ["-rf", pluginURL.path], authorization: authorization)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy AltPlugin to Mail plug-ins directory.
|
|
||||||
// Must be separate step than unzip to prevent macOS from considering plug-in corrupted.
|
|
||||||
let unzippedPluginURL = temporaryDirectoryURL.appendingPathComponent(pluginURL.lastPathComponent)
|
|
||||||
try self.runAndKeepAuthorization("cp", arguments: ["-R", unzippedPluginURL.path, pluginDirectoryURL.path], authorization: authorization)
|
|
||||||
|
|
||||||
guard self.isMailPluginInstalled else { throw PluginError.unknown }
|
|
||||||
|
|
||||||
// Enable Mail plug-in preferences.
|
|
||||||
try self.run("defaults", arguments: ["write", "/Library/Preferences/com.apple.mail", "EnableBundles", "-bool", "YES"], authorization: authorization)
|
|
||||||
|
|
||||||
print("Finished installing Mail plug-in!")
|
|
||||||
|
|
||||||
completionHandler(.success(()))
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
completionHandler(.failure(error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
completionHandler(.failure(PluginError.cancelled))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func uninstallMailPlugin(completionHandler: @escaping (Result<Void, Error>) -> Void)
|
|
||||||
{
|
|
||||||
let alert = NSAlert()
|
|
||||||
alert.messageText = NSLocalizedString("Uninstall Mail Plug-in", comment: "")
|
|
||||||
alert.informativeText = NSLocalizedString("Are you sure you want to uninstall the AltServer Mail plug-in? You will no longer be able to install or refresh apps with AltStore.", comment: "")
|
|
||||||
|
|
||||||
alert.addButton(withTitle: NSLocalizedString("Uninstall Plug-in", comment: ""))
|
|
||||||
alert.addButton(withTitle: NSLocalizedString("Cancel", comment: ""))
|
|
||||||
|
|
||||||
NSRunningApplication.current.activate(options: .activateIgnoringOtherApps)
|
|
||||||
|
|
||||||
let response = alert.runModal()
|
|
||||||
guard response == .alertFirstButtonReturn else { return completionHandler(.failure(PluginError.cancelled)) }
|
|
||||||
|
|
||||||
DispatchQueue.global().async {
|
|
||||||
do
|
|
||||||
{
|
|
||||||
if FileManager.default.fileExists(atPath: pluginURL.path)
|
|
||||||
{
|
|
||||||
// Delete Mail plug-in from privileged directory.
|
|
||||||
try self.run("rm", arguments: ["-rf", pluginURL.path])
|
|
||||||
}
|
|
||||||
|
|
||||||
completionHandler(.success(()))
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
completionHandler(.failure(error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private extension PluginManager
|
|
||||||
{
|
|
||||||
func downloadPlugin(completion: @escaping (Result<URL, Error>) -> Void)
|
|
||||||
{
|
|
||||||
let pluginVersion = self.preferredVersion
|
|
||||||
|
|
||||||
func finish(_ result: Result<URL, Error>)
|
|
||||||
{
|
|
||||||
do
|
|
||||||
{
|
|
||||||
let fileURL = try result.get()
|
|
||||||
|
|
||||||
if #available(OSX 10.15, *)
|
|
||||||
{
|
|
||||||
let data = try Data(contentsOf: fileURL)
|
|
||||||
let sha256Hash = SHA256.hash(data: data)
|
|
||||||
let hashString = sha256Hash.compactMap { String(format: "%02x", $0) }.joined()
|
|
||||||
|
|
||||||
print("Comparing Mail plug-in hash (\(hashString)) against expected hash (\(pluginVersion.sha256Hash))...")
|
|
||||||
guard hashString == pluginVersion.sha256Hash else { throw PluginError.mismatchedHash(hash: hashString, expectedHash: pluginVersion.sha256Hash) }
|
|
||||||
}
|
|
||||||
|
|
||||||
completion(.success(fileURL))
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
completion(.failure(error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if pluginVersion.url.isFileURL
|
|
||||||
{
|
|
||||||
finish(.success(pluginVersion.url))
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
let downloadTask = URLSession.shared.downloadTask(with: pluginVersion.url) { (fileURL, response, error) in
|
|
||||||
if let response = response as? HTTPURLResponse
|
|
||||||
{
|
|
||||||
guard response.statusCode != 404 else { return finish(.failure(PluginError.notFound)) }
|
|
||||||
}
|
|
||||||
|
|
||||||
let result = Result(fileURL, error)
|
|
||||||
finish(result)
|
|
||||||
|
|
||||||
if let fileURL = fileURL
|
|
||||||
{
|
|
||||||
try? FileManager.default.removeItem(at: fileURL)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadTask.resume()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func run(_ program: String, arguments: [String], authorization: AuthorizationRef? = nil) throws
|
|
||||||
{
|
|
||||||
_ = try self._run(program, arguments: arguments, authorization: authorization, freeAuthorization: true)
|
|
||||||
}
|
|
||||||
|
|
||||||
@discardableResult
|
|
||||||
func runAndKeepAuthorization(_ program: String, arguments: [String], authorization: AuthorizationRef? = nil) throws -> AuthorizationRef
|
|
||||||
{
|
|
||||||
return try self._run(program, arguments: arguments, authorization: authorization, freeAuthorization: false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _run(_ program: String, arguments: [String], authorization: AuthorizationRef? = nil, freeAuthorization: Bool) throws -> AuthorizationRef
|
|
||||||
{
|
|
||||||
var launchPath = "/usr/bin/" + program
|
|
||||||
if !FileManager.default.fileExists(atPath: launchPath)
|
|
||||||
{
|
|
||||||
launchPath = "/bin/" + program
|
|
||||||
}
|
|
||||||
|
|
||||||
print("Running program:", launchPath)
|
|
||||||
|
|
||||||
let task = STPrivilegedTask()
|
|
||||||
task.launchPath = launchPath
|
|
||||||
task.arguments = arguments
|
|
||||||
task.freeAuthorizationWhenDone = freeAuthorization
|
|
||||||
|
|
||||||
let errorCode: OSStatus
|
|
||||||
|
|
||||||
if let authorization = authorization
|
|
||||||
{
|
|
||||||
errorCode = task.launch(withAuthorization: authorization)
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
errorCode = task.launch()
|
|
||||||
}
|
|
||||||
|
|
||||||
guard errorCode == 0 else { throw PluginError.taskErrorCode(Int(errorCode)) }
|
|
||||||
|
|
||||||
task.waitUntilExit()
|
|
||||||
|
|
||||||
print("Exit code:", task.terminationStatus)
|
|
||||||
|
|
||||||
guard task.terminationStatus == 0 else {
|
|
||||||
let outputData = task.outputFileHandle.readDataToEndOfFile()
|
|
||||||
|
|
||||||
if let outputString = String(data: outputData, encoding: .utf8), !outputString.isEmpty
|
|
||||||
{
|
|
||||||
throw PluginError.taskError(outputString)
|
|
||||||
}
|
|
||||||
|
|
||||||
throw PluginError.taskErrorCode(Int(task.terminationStatus))
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let authorization = task.authorization else { throw PluginError.unknown }
|
|
||||||
return authorization
|
|
||||||
}
|
|
||||||
}
|
|
||||||
3
AltStore.xcconfig
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#include "Build.xcconfig"
|
||||||
|
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = $(PRODUCT_BUNDLE_IDENTIFIER)
|
||||||
@@ -2,6 +2,6 @@
|
|||||||
<Workspace
|
<Workspace
|
||||||
version = "1.0">
|
version = "1.0">
|
||||||
<FileRef
|
<FileRef
|
||||||
location = "self:AltStore.xcodeproj">
|
location = "self:">
|
||||||
</FileRef>
|
</FileRef>
|
||||||
</Workspace>
|
</Workspace>
|
||||||
|
|||||||
@@ -14,26 +14,24 @@
|
|||||||
buildForAnalyzing = "YES">
|
buildForAnalyzing = "YES">
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
BlueprintIdentifier = "E32E9B621EB87D7B000FEEE9"
|
BlueprintIdentifier = "BFD247692284B9A500981D42"
|
||||||
BuildableName = "LaunchAtLogin.framework"
|
BuildableName = "SideStore.app"
|
||||||
BlueprintName = "LaunchAtLogin"
|
BlueprintName = "SideStore"
|
||||||
ReferencedContainer = "container:LaunchAtLogin.xcodeproj">
|
ReferencedContainer = "container:AltStore.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</BuildActionEntry>
|
</BuildActionEntry>
|
||||||
</BuildActionEntries>
|
</BuildActionEntries>
|
||||||
</BuildAction>
|
</BuildAction>
|
||||||
<TestAction
|
<TestAction
|
||||||
buildConfiguration = "Debug"
|
buildConfiguration = "Release"
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
<Testables>
|
<Testables>
|
||||||
</Testables>
|
</Testables>
|
||||||
<AdditionalOptions>
|
|
||||||
</AdditionalOptions>
|
|
||||||
</TestAction>
|
</TestAction>
|
||||||
<LaunchAction
|
<LaunchAction
|
||||||
buildConfiguration = "Debug"
|
buildConfiguration = "Release"
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
launchStyle = "0"
|
launchStyle = "0"
|
||||||
@@ -42,17 +40,22 @@
|
|||||||
debugDocumentVersioning = "YES"
|
debugDocumentVersioning = "YES"
|
||||||
debugServiceExtension = "internal"
|
debugServiceExtension = "internal"
|
||||||
allowLocationSimulation = "YES">
|
allowLocationSimulation = "YES">
|
||||||
<MacroExpansion>
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
BlueprintIdentifier = "E32E9B621EB87D7B000FEEE9"
|
BlueprintIdentifier = "BFD247692284B9A500981D42"
|
||||||
BuildableName = "LaunchAtLogin.framework"
|
BuildableName = "SideStore.app"
|
||||||
BlueprintName = "LaunchAtLogin"
|
BlueprintName = "SideStore"
|
||||||
ReferencedContainer = "container:LaunchAtLogin.xcodeproj">
|
ReferencedContainer = "container:AltStore.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</MacroExpansion>
|
</BuildableProductRunnable>
|
||||||
<AdditionalOptions>
|
<CommandLineArguments>
|
||||||
</AdditionalOptions>
|
<CommandLineArgument
|
||||||
|
argument = "-com.apple.CoreData.ConcurrencyDebug 1"
|
||||||
|
isEnabled = "YES">
|
||||||
|
</CommandLineArgument>
|
||||||
|
</CommandLineArguments>
|
||||||
</LaunchAction>
|
</LaunchAction>
|
||||||
<ProfileAction
|
<ProfileAction
|
||||||
buildConfiguration = "Release"
|
buildConfiguration = "Release"
|
||||||
@@ -60,18 +63,19 @@
|
|||||||
savedToolIdentifier = ""
|
savedToolIdentifier = ""
|
||||||
useCustomWorkingDirectory = "NO"
|
useCustomWorkingDirectory = "NO"
|
||||||
debugDocumentVersioning = "YES">
|
debugDocumentVersioning = "YES">
|
||||||
<MacroExpansion>
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
BlueprintIdentifier = "E32E9B621EB87D7B000FEEE9"
|
BlueprintIdentifier = "BFD247692284B9A500981D42"
|
||||||
BuildableName = "LaunchAtLogin.framework"
|
BuildableName = "SideStore.app"
|
||||||
BlueprintName = "LaunchAtLogin"
|
BlueprintName = "SideStore"
|
||||||
ReferencedContainer = "container:LaunchAtLogin.xcodeproj">
|
ReferencedContainer = "container:AltStore.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</MacroExpansion>
|
</BuildableProductRunnable>
|
||||||
</ProfileAction>
|
</ProfileAction>
|
||||||
<AnalyzeAction
|
<AnalyzeAction
|
||||||
buildConfiguration = "Debug">
|
buildConfiguration = "Release">
|
||||||
</AnalyzeAction>
|
</AnalyzeAction>
|
||||||
<ArchiveAction
|
<ArchiveAction
|
||||||
buildConfiguration = "Release"
|
buildConfiguration = "Release"
|
||||||
@@ -15,8 +15,8 @@
|
|||||||
<BuildableReference
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
BlueprintIdentifier = "BFD247692284B9A500981D42"
|
BlueprintIdentifier = "BFD247692284B9A500981D42"
|
||||||
BuildableName = "AltStore.app"
|
BuildableName = "SideStore.app"
|
||||||
BlueprintName = "AltStore"
|
BlueprintName = "SideStore"
|
||||||
ReferencedContainer = "container:AltStore.xcodeproj">
|
ReferencedContainer = "container:AltStore.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</BuildActionEntry>
|
</BuildActionEntry>
|
||||||
@@ -45,8 +45,8 @@
|
|||||||
<BuildableReference
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
BlueprintIdentifier = "BFD247692284B9A500981D42"
|
BlueprintIdentifier = "BFD247692284B9A500981D42"
|
||||||
BuildableName = "AltStore.app"
|
BuildableName = "SideStore.app"
|
||||||
BlueprintName = "AltStore"
|
BlueprintName = "SideStore"
|
||||||
ReferencedContainer = "container:AltStore.xcodeproj">
|
ReferencedContainer = "container:AltStore.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</BuildableProductRunnable>
|
</BuildableProductRunnable>
|
||||||
@@ -68,8 +68,8 @@
|
|||||||
<BuildableReference
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
BlueprintIdentifier = "BFD247692284B9A500981D42"
|
BlueprintIdentifier = "BFD247692284B9A500981D42"
|
||||||
BuildableName = "AltStore.app"
|
BuildableName = "SideStore.app"
|
||||||
BlueprintName = "AltStore"
|
BlueprintName = "SideStore"
|
||||||
ReferencedContainer = "container:AltStore.xcodeproj">
|
ReferencedContainer = "container:AltStore.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</BuildableProductRunnable>
|
</BuildableProductRunnable>
|
||||||
|
|||||||
16
AltStore.xcworkspace/contents.xcworkspacedata
generated
@@ -1,16 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<Workspace
|
|
||||||
version = "1.0">
|
|
||||||
<FileRef
|
|
||||||
location = "container:AltStore.xcodeproj">
|
|
||||||
</FileRef>
|
|
||||||
<FileRef
|
|
||||||
location = "group:Dependencies/AltSign">
|
|
||||||
</FileRef>
|
|
||||||
<FileRef
|
|
||||||
location = "group:Dependencies/Roxas/Roxas.xcodeproj">
|
|
||||||
</FileRef>
|
|
||||||
<FileRef
|
|
||||||
location = "group:Pods/Pods.xcodeproj">
|
|
||||||
</FileRef>
|
|
||||||
</Workspace>
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>IDEDidComputeMac32BitWarning</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
@@ -3,3 +3,6 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#import "NSAttributedString+Markdown.h"
|
#import "NSAttributedString+Markdown.h"
|
||||||
|
#import "ALTAppPatcher.h"
|
||||||
|
|
||||||
|
#include "fragmentzip.h"
|
||||||
|
|||||||
@@ -2,13 +2,11 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>aps-environment</key>
|
|
||||||
<string>development</string>
|
|
||||||
<key>com.apple.developer.siri</key>
|
<key>com.apple.developer.siri</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>com.apple.security.application-groups</key>
|
<key>com.apple.security.application-groups</key>
|
||||||
<array>
|
<array>
|
||||||
<string>group.com.rileytestut.AltStore</string>
|
<string>group.$(APP_GROUP_IDENTIFIER)</string>
|
||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -14,13 +14,7 @@ import AppCenter
|
|||||||
import AppCenterAnalytics
|
import AppCenterAnalytics
|
||||||
import AppCenterCrashes
|
import AppCenterCrashes
|
||||||
|
|
||||||
#if DEBUG
|
private let appCenterAppSecret = "73532d3e-e573-4693-99a4-9f85840bbb44"
|
||||||
private let appCenterAppSecret = "bb08e9bb-c126-408d-bf3f-324c8473fd40"
|
|
||||||
#elseif RELEASE
|
|
||||||
private let appCenterAppSecret = "b6718932-294a-432b-81f2-be1e17ff85c5"
|
|
||||||
#else
|
|
||||||
private let appCenterAppSecret = "e873f6ca-75eb-4685-818f-801e0e375d60"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
extension AnalyticsManager
|
extension AnalyticsManager
|
||||||
{
|
{
|
||||||
@@ -77,7 +71,7 @@ extension AnalyticsManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AnalyticsManager
|
final class AnalyticsManager
|
||||||
{
|
{
|
||||||
static let shared = AnalyticsManager()
|
static let shared = AnalyticsManager()
|
||||||
|
|
||||||
@@ -90,9 +84,9 @@ extension AnalyticsManager
|
|||||||
{
|
{
|
||||||
func start()
|
func start()
|
||||||
{
|
{
|
||||||
MSAppCenter.start(appCenterAppSecret, withServices:[
|
AppCenter.start(withAppSecret: appCenterAppSecret, services: [
|
||||||
MSAnalytics.self,
|
Analytics.self,
|
||||||
MSCrashes.self
|
Crashes.self
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,6 +96,6 @@ extension AnalyticsManager
|
|||||||
properties[item.key.rawValue] = item.value
|
properties[item.key.rawValue] = item.value
|
||||||
}
|
}
|
||||||
|
|
||||||
MSAnalytics.trackEvent(event.name, withProperties: properties)
|
Analytics.trackEvent(event.name, withProperties: properties)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ extension AppContentViewController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AppContentViewController: UITableViewController
|
final class AppContentViewController: UITableViewController
|
||||||
{
|
{
|
||||||
var app: StoreApp!
|
var app: StoreApp!
|
||||||
|
|
||||||
@@ -80,10 +80,21 @@ class AppContentViewController: UITableViewController
|
|||||||
|
|
||||||
self.subtitleLabel.text = self.app.subtitle
|
self.subtitleLabel.text = self.app.subtitle
|
||||||
self.descriptionTextView.text = self.app.localizedDescription
|
self.descriptionTextView.text = self.app.localizedDescription
|
||||||
self.versionDescriptionTextView.text = self.app.versionDescription
|
|
||||||
self.versionLabel.text = String(format: NSLocalizedString("Version %@", comment: ""), self.app.version)
|
if let version = self.app.latestAvailableVersion
|
||||||
self.versionDateLabel.text = Date().relativeDateString(since: self.app.versionDate, dateFormatter: self.dateFormatter)
|
{
|
||||||
self.sizeLabel.text = self.byteCountFormatter.string(fromByteCount: Int64(self.app.size))
|
self.versionDescriptionTextView.text = version.localizedDescription
|
||||||
|
self.versionLabel.text = String(format: NSLocalizedString("Version %@", comment: ""), version.version)
|
||||||
|
self.versionDateLabel.text = Date().relativeDateString(since: version.date, dateFormatter: self.dateFormatter)
|
||||||
|
self.sizeLabel.text = self.byteCountFormatter.string(fromByteCount: version.size)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self.versionDescriptionTextView.text = nil
|
||||||
|
self.versionLabel.text = nil
|
||||||
|
self.versionDateLabel.text = nil
|
||||||
|
self.sizeLabel.text = self.byteCountFormatter.string(fromByteCount: 0)
|
||||||
|
}
|
||||||
|
|
||||||
self.descriptionTextView.maximumNumberOfLines = 5
|
self.descriptionTextView.maximumNumberOfLines = 5
|
||||||
self.descriptionTextView.moreButton.addTarget(self, action: #selector(AppContentViewController.toggleCollapsingSection(_:)), for: .primaryActionTriggered)
|
self.descriptionTextView.moreButton.addTarget(self, action: #selector(AppContentViewController.toggleCollapsingSection(_:)), for: .primaryActionTriggered)
|
||||||
@@ -138,7 +149,8 @@ private extension AppContentViewController
|
|||||||
}
|
}
|
||||||
dataSource.prefetchHandler = { (imageURL, indexPath, completionHandler) in
|
dataSource.prefetchHandler = { (imageURL, indexPath, completionHandler) in
|
||||||
return RSTAsyncBlockOperation() { (operation) in
|
return RSTAsyncBlockOperation() { (operation) in
|
||||||
ImagePipeline.shared.loadImage(with: imageURL as URL, progress: nil, completion: { (response, error) in
|
let request = ImageRequest(url: imageURL as URL, processor: .screenshot)
|
||||||
|
ImagePipeline.shared.loadImage(with: request, progress: nil, completion: { (response, error) in
|
||||||
guard !operation.isCancelled else { return operation.finish() }
|
guard !operation.isCancelled else { return operation.finish() }
|
||||||
|
|
||||||
if let image = response?.image
|
if let image = response?.image
|
||||||
@@ -172,7 +184,8 @@ private extension AppContentViewController
|
|||||||
dataSource.cellConfigurationHandler = { (cell, permission, indexPath) in
|
dataSource.cellConfigurationHandler = { (cell, permission, indexPath) in
|
||||||
let cell = cell as! PermissionCollectionViewCell
|
let cell = cell as! PermissionCollectionViewCell
|
||||||
cell.button.setImage(permission.type.icon, for: .normal)
|
cell.button.setImage(permission.type.icon, for: .normal)
|
||||||
cell.textLabel.text = permission.type.localizedShortName
|
cell.button.tintColor = .label
|
||||||
|
cell.textLabel.text = permission.type.localizedShortName ?? permission.type.localizedName
|
||||||
}
|
}
|
||||||
|
|
||||||
return dataSource
|
return dataSource
|
||||||
@@ -208,10 +221,19 @@ extension AppContentViewController
|
|||||||
|
|
||||||
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
|
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
|
||||||
{
|
{
|
||||||
guard indexPath.row == Row.screenshots.rawValue else { return super.tableView(tableView, heightForRowAt: indexPath) }
|
switch Row.allCases[indexPath.row]
|
||||||
|
{
|
||||||
guard let size = self.preferredScreenshotSize else { return 0.0 }
|
case .screenshots:
|
||||||
return size.height
|
guard let size = self.preferredScreenshotSize else { return 0.0 }
|
||||||
|
return size.height
|
||||||
|
|
||||||
|
case .permissions:
|
||||||
|
guard !self.app.permissions.isEmpty else { return 0.0 }
|
||||||
|
return super.tableView(tableView, heightForRowAt: indexPath)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return super.tableView(tableView, heightForRowAt: indexPath)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class PermissionCollectionViewCell: UICollectionViewCell
|
final class PermissionCollectionViewCell: UICollectionViewCell
|
||||||
{
|
{
|
||||||
@IBOutlet var button: UIButton!
|
@IBOutlet var button: UIButton!
|
||||||
@IBOutlet var textLabel: UILabel!
|
@IBOutlet var textLabel: UILabel!
|
||||||
@@ -29,7 +29,7 @@ class PermissionCollectionViewCell: UICollectionViewCell
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AppContentTableViewCell: UITableViewCell
|
final class AppContentTableViewCell: UITableViewCell
|
||||||
{
|
{
|
||||||
override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize
|
override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import Roxas
|
|||||||
|
|
||||||
import Nuke
|
import Nuke
|
||||||
|
|
||||||
class AppViewController: UIViewController
|
final class AppViewController: UIViewController
|
||||||
{
|
{
|
||||||
var app: StoreApp!
|
var app: StoreApp!
|
||||||
|
|
||||||
@@ -188,6 +188,13 @@ class AppViewController: UIViewController
|
|||||||
|
|
||||||
self.contentViewController = segue.destination as? AppContentViewController
|
self.contentViewController = segue.destination as? AppContentViewController
|
||||||
self.contentViewController.app = self.app
|
self.contentViewController.app = self.app
|
||||||
|
|
||||||
|
if #available(iOS 15, *)
|
||||||
|
{
|
||||||
|
// Fix navigation bar + tab bar appearance on iOS 15.
|
||||||
|
self.setContentScrollView(self.scrollView)
|
||||||
|
self.navigationItem.scrollEdgeAppearance = self.navigationController?.navigationBar.standardAppearance
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidLayoutSubviews()
|
override func viewDidLayoutSubviews()
|
||||||
@@ -210,8 +217,8 @@ class AppViewController: UIViewController
|
|||||||
|
|
||||||
self._shouldResetLayout = false
|
self._shouldResetLayout = false
|
||||||
}
|
}
|
||||||
|
|
||||||
let statusBarHeight = UIApplication.shared.statusBarFrame.height
|
let statusBarHeight = self.view.window?.windowScene?.statusBarManager?.statusBarFrame.height ?? 0
|
||||||
let cornerRadius = self.contentViewControllerShadowView.layer.cornerRadius
|
let cornerRadius = self.contentViewControllerShadowView.layer.cornerRadius
|
||||||
|
|
||||||
let inset = 12 as CGFloat
|
let inset = 12 as CGFloat
|
||||||
@@ -316,7 +323,7 @@ class AppViewController: UIViewController
|
|||||||
|
|
||||||
self.backButtonContainerView.layer.cornerRadius = self.backButtonContainerView.bounds.midY
|
self.backButtonContainerView.layer.cornerRadius = self.backButtonContainerView.bounds.midY
|
||||||
|
|
||||||
self.scrollView.scrollIndicatorInsets.top = statusBarHeight
|
self.scrollView.verticalScrollIndicatorInsets.top = statusBarHeight
|
||||||
|
|
||||||
// Adjust content offset + size.
|
// Adjust content offset + size.
|
||||||
let contentOffset = self.scrollView.contentOffset
|
let contentOffset = self.scrollView.contentOffset
|
||||||
@@ -345,7 +352,7 @@ class AppViewController: UIViewController
|
|||||||
|
|
||||||
extension AppViewController
|
extension AppViewController
|
||||||
{
|
{
|
||||||
class func makeAppViewController(app: StoreApp) -> AppViewController
|
final class func makeAppViewController(app: StoreApp) -> AppViewController
|
||||||
{
|
{
|
||||||
let storyboard = UIStoryboard(name: "Main", bundle: nil)
|
let storyboard = UIStoryboard(name: "Main", bundle: nil)
|
||||||
|
|
||||||
@@ -377,10 +384,10 @@ private extension AppViewController
|
|||||||
button.progress = progress
|
button.progress = progress
|
||||||
}
|
}
|
||||||
|
|
||||||
if Date() < self.app.versionDate
|
if let versionDate = self.app.latestAvailableVersion?.date, versionDate > Date()
|
||||||
{
|
{
|
||||||
self.bannerView.button.countdownDate = self.app.versionDate
|
self.bannerView.button.countdownDate = versionDate
|
||||||
self.navigationBarDownloadButton.countdownDate = self.app.versionDate
|
self.navigationBarDownloadButton.countdownDate = versionDate
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -491,7 +498,7 @@ extension AppViewController
|
|||||||
{
|
{
|
||||||
guard self.app.installedApp == nil else { return }
|
guard self.app.installedApp == nil else { return }
|
||||||
|
|
||||||
let progress = AppManager.shared.install(self.app, presentingViewController: self) { (result) in
|
let group = AppManager.shared.install(self.app, presentingViewController: self) { (result) in
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
_ = try result.get()
|
_ = try result.get()
|
||||||
@@ -503,7 +510,7 @@ extension AppViewController
|
|||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
let toastView = ToastView(error: error)
|
let toastView = ToastView(error: error, opensLog: true)
|
||||||
toastView.show(in: self)
|
toastView.show(in: self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -515,8 +522,8 @@ extension AppViewController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.bannerView.button.progress = progress
|
self.bannerView.button.progress = group.progress
|
||||||
self.navigationBarDownloadButton.progress = progress
|
self.navigationBarDownloadButton.progress = group.progress
|
||||||
}
|
}
|
||||||
|
|
||||||
func open(_ installedApp: InstalledApp)
|
func open(_ installedApp: InstalledApp)
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import UIKit
|
|||||||
|
|
||||||
import AltStoreCore
|
import AltStoreCore
|
||||||
|
|
||||||
class PermissionPopoverViewController: UIViewController
|
final class PermissionPopoverViewController: UIViewController
|
||||||
{
|
{
|
||||||
var permission: AppPermission!
|
var permission: AppPermission!
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import UIKit
|
|||||||
import AltStoreCore
|
import AltStoreCore
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
class AppIDsViewController: UICollectionViewController
|
final class AppIDsViewController: UICollectionViewController
|
||||||
{
|
{
|
||||||
private lazy var dataSource = self.makeDataSource()
|
private lazy var dataSource = self.makeDataSource()
|
||||||
|
|
||||||
@@ -90,14 +90,21 @@ private extension AppIDsViewController
|
|||||||
cell.bannerView.button.isUserInteractionEnabled = false
|
cell.bannerView.button.isUserInteractionEnabled = false
|
||||||
|
|
||||||
cell.bannerView.buttonLabel.isHidden = false
|
cell.bannerView.buttonLabel.isHidden = false
|
||||||
|
|
||||||
let currentDate = Date()
|
let currentDate = Date()
|
||||||
|
|
||||||
let numberOfDays = expirationDate.numberOfCalendarDays(since: currentDate)
|
let formatter = DateComponentsFormatter()
|
||||||
let numberOfDaysText = (numberOfDays == 1) ? NSLocalizedString("1 day", comment: "") : String(format: NSLocalizedString("%@ days", comment: ""), NSNumber(value: numberOfDays))
|
formatter.unitsStyle = .full
|
||||||
cell.bannerView.button.setTitle(numberOfDaysText.uppercased(), for: .normal)
|
formatter.includesApproximationPhrase = false
|
||||||
|
formatter.includesTimeRemainingPhrase = false
|
||||||
|
formatter.allowedUnits = [.minute, .hour, .day]
|
||||||
|
formatter.maximumUnitCount = 1
|
||||||
|
|
||||||
attributedAccessibilityLabel.mutableString.append(String(format: NSLocalizedString("Expires in %@.", comment: ""), numberOfDaysText) + " ")
|
cell.bannerView.button.setTitle((formatter.string(from: currentDate, to: expirationDate) ?? NSLocalizedString("Unknown", comment: "")).uppercased(), for: .normal)
|
||||||
|
|
||||||
|
// formatter.includesTimeRemainingPhrase = true
|
||||||
|
|
||||||
|
// attributedAccessibilityLabel.mutableString.append((formatter.string(from: currentDate, to: expirationDate) ?? NSLocalizedString("Unknown", comment: "")) + " ")
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -201,9 +208,9 @@ extension AppIDsViewController: UICollectionViewDelegateFlowLayout
|
|||||||
if let activeTeam = DatabaseManager.shared.activeTeam(), activeTeam.type == .free
|
if let activeTeam = DatabaseManager.shared.activeTeam(), activeTeam.type == .free
|
||||||
{
|
{
|
||||||
let text = NSLocalizedString("""
|
let text = NSLocalizedString("""
|
||||||
Each app and app extension installed with AltStore must register an App ID with Apple. Apple limits free developer accounts to 10 App IDs at a time.
|
Each app and app extension installed with SideStore must register an App ID with Apple. Apple limits non-developer Apple IDs to 10 App IDs at a time.
|
||||||
|
|
||||||
**App IDs can't be deleted**, but they do expire after one week. AltStore will automatically renew App IDs for all active apps once they've expired.
|
**App IDs can't be deleted**, but they do expire after one week. SideStore will automatically renew App IDs for all active apps once they've expired.
|
||||||
""", comment: "")
|
""", comment: "")
|
||||||
|
|
||||||
let attributedText = NSAttributedString(markdownRepresentation: text, attributes: [.font: headerView.textLabel.font as Any])
|
let attributedText = NSAttributedString(markdownRepresentation: text, attributes: [.font: headerView.textLabel.font as Any])
|
||||||
@@ -212,7 +219,7 @@ extension AppIDsViewController: UICollectionViewDelegateFlowLayout
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
headerView.textLabel.text = NSLocalizedString("""
|
headerView.textLabel.text = NSLocalizedString("""
|
||||||
Each app and app extension installed with AltStore must register an App ID with Apple.
|
Each app and app extension installed with SideStore must register an App ID with Apple.
|
||||||
|
|
||||||
App IDs for paid developer accounts never expire, and there is no limit to how many you can create.
|
App IDs for paid developer accounts never expire, and there is no limit to how many you can create.
|
||||||
""", comment: "")
|
""", comment: "")
|
||||||
|
|||||||
@@ -14,14 +14,15 @@ import Intents
|
|||||||
import AltStoreCore
|
import AltStoreCore
|
||||||
import AltSign
|
import AltSign
|
||||||
import Roxas
|
import Roxas
|
||||||
|
import EmotionalDamage
|
||||||
|
|
||||||
extension AppDelegate
|
extension AppDelegate
|
||||||
{
|
{
|
||||||
static let openPatreonSettingsDeepLinkNotification = Notification.Name("com.rileytestut.AltStore.OpenPatreonSettingsDeepLinkNotification")
|
static let openPatreonSettingsDeepLinkNotification = Notification.Name(Bundle.Info.appbundleIdentifier + ".OpenPatreonSettingsDeepLinkNotification")
|
||||||
static let importAppDeepLinkNotification = Notification.Name("com.rileytestut.AltStore.ImportAppDeepLinkNotification")
|
static let importAppDeepLinkNotification = Notification.Name(Bundle.Info.appbundleIdentifier + ".ImportAppDeepLinkNotification")
|
||||||
static let addSourceDeepLinkNotification = Notification.Name("com.rileytestut.AltStore.AddSourceDeepLinkNotification")
|
static let addSourceDeepLinkNotification = Notification.Name(Bundle.Info.appbundleIdentifier + ".AddSourceDeepLinkNotification")
|
||||||
|
|
||||||
static let appBackupDidFinish = Notification.Name("com.rileytestut.AltStore.AppBackupDidFinish")
|
static let appBackupDidFinish = Notification.Name(Bundle.Info.appbundleIdentifier + ".AppBackupDidFinish")
|
||||||
|
|
||||||
static let importAppDeepLinkURLKey = "fileURL"
|
static let importAppDeepLinkURLKey = "fileURL"
|
||||||
static let appBackupResultKey = "result"
|
static let appBackupResultKey = "result"
|
||||||
@@ -29,21 +30,39 @@ extension AppDelegate
|
|||||||
}
|
}
|
||||||
|
|
||||||
@UIApplicationMain
|
@UIApplicationMain
|
||||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
final class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
|
|
||||||
var window: UIWindow?
|
var window: UIWindow?
|
||||||
|
|
||||||
@available(iOS 14, *)
|
@available(iOS 14, *)
|
||||||
private lazy var intentHandler = IntentHandler()
|
private var intentHandler: IntentHandler {
|
||||||
|
get { _intentHandler as! IntentHandler }
|
||||||
|
set { _intentHandler = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
@available(iOS 14, *)
|
@available(iOS 14, *)
|
||||||
private lazy var viewAppIntentHandler = ViewAppIntentHandler()
|
private var viewAppIntentHandler: ViewAppIntentHandler {
|
||||||
|
get { _viewAppIntentHandler as! ViewAppIntentHandler }
|
||||||
|
set { _viewAppIntentHandler = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
private lazy var _intentHandler: Any = {
|
||||||
|
guard #available(iOS 14, *) else { fatalError() }
|
||||||
|
return IntentHandler()
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var _viewAppIntentHandler: Any = {
|
||||||
|
guard #available(iOS 14, *) else { fatalError() }
|
||||||
|
return ViewAppIntentHandler()
|
||||||
|
}()
|
||||||
|
|
||||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
|
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
|
||||||
{
|
{
|
||||||
// Register default settings before doing anything else.
|
// Register default settings before doing anything else.
|
||||||
UserDefaults.registerDefaults()
|
UserDefaults.registerDefaults()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
DatabaseManager.shared.start { (error) in
|
DatabaseManager.shared.start { (error) in
|
||||||
if let error = error
|
if let error = error
|
||||||
{
|
{
|
||||||
@@ -59,8 +78,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||||||
|
|
||||||
self.setTintColor()
|
self.setTintColor()
|
||||||
|
|
||||||
ServerManager.shared.startDiscovering()
|
|
||||||
|
|
||||||
SecureValueTransformer.register()
|
SecureValueTransformer.register()
|
||||||
|
|
||||||
if UserDefaults.standard.firstLaunch == nil
|
if UserDefaults.standard.firstLaunch == nil
|
||||||
@@ -82,15 +99,25 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||||||
|
|
||||||
func applicationDidEnterBackground(_ application: UIApplication)
|
func applicationDidEnterBackground(_ application: UIApplication)
|
||||||
{
|
{
|
||||||
ServerManager.shared.stopDiscovering()
|
// Make sure to update SceneDelegate.sceneDidEnterBackground() as well.
|
||||||
|
|
||||||
|
guard let oneMonthAgo = Calendar.current.date(byAdding: .month, value: -1, to: Date()) else { return }
|
||||||
|
|
||||||
|
let midnightOneMonthAgo = Calendar.current.startOfDay(for: oneMonthAgo)
|
||||||
|
DatabaseManager.shared.purgeLoggedErrors(before: midnightOneMonthAgo) { result in
|
||||||
|
switch result
|
||||||
|
{
|
||||||
|
case .success: break
|
||||||
|
case .failure(let error): print("[ALTLog] Failed to purge logged errors before \(midnightOneMonthAgo).", error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func applicationWillEnterForeground(_ application: UIApplication)
|
func applicationWillEnterForeground(_ application: UIApplication)
|
||||||
{
|
{
|
||||||
AppManager.shared.update()
|
AppManager.shared.update()
|
||||||
ServerManager.shared.startDiscovering()
|
start_em_proxy(bind_addr: Consts.Proxy.serverURL)
|
||||||
|
|
||||||
PatreonAPI.shared.refreshPatreonAccount()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any]) -> Bool
|
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any]) -> Bool
|
||||||
@@ -251,7 +278,7 @@ extension AppDelegate
|
|||||||
|
|
||||||
let content = UNMutableNotificationContent()
|
let content = UNMutableNotificationContent()
|
||||||
content.title = NSLocalizedString("App Refresh Tip", comment: "")
|
content.title = NSLocalizedString("App Refresh Tip", comment: "")
|
||||||
content.body = NSLocalizedString("The more you open AltStore, the more chances it's given to refresh apps in the background.", comment: "")
|
content.body = NSLocalizedString("The more you open SideStore, the more chances it's given to refresh apps in the background.", comment: "")
|
||||||
|
|
||||||
let request = UNNotificationRequest(identifier: "background-refresh-reminder5", content: content, trigger: trigger)
|
let request = UNNotificationRequest(identifier: "background-refresh-reminder5", content: content, trigger: trigger)
|
||||||
UNUserNotificationCenter.current().add(request)
|
UNUserNotificationCenter.current().add(request)
|
||||||
@@ -355,11 +382,11 @@ private extension AppDelegate
|
|||||||
for update in updates
|
for update in updates
|
||||||
{
|
{
|
||||||
guard !previousUpdates.contains(where: { $0[#keyPath(InstalledApp.bundleIdentifier)] == update.bundleIdentifier }) else { continue }
|
guard !previousUpdates.contains(where: { $0[#keyPath(InstalledApp.bundleIdentifier)] == update.bundleIdentifier }) else { continue }
|
||||||
guard let storeApp = update.storeApp else { continue }
|
guard let storeApp = update.storeApp, let version = storeApp.latestSupportedVersion else { continue }
|
||||||
|
|
||||||
let content = UNMutableNotificationContent()
|
let content = UNMutableNotificationContent()
|
||||||
content.title = NSLocalizedString("New Update Available", comment: "")
|
content.title = NSLocalizedString("New Update Available", comment: "")
|
||||||
content.body = String(format: NSLocalizedString("%@ %@ is now available for download.", comment: ""), update.name, storeApp.version)
|
content.body = String(format: NSLocalizedString("%@ %@ is now available for download.", comment: ""), update.name, version)
|
||||||
content.sound = .default
|
content.sound = .default
|
||||||
|
|
||||||
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
|
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
|
||||||
@@ -379,7 +406,7 @@ private extension AppDelegate
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
content.title = NSLocalizedString("AltStore News", comment: "")
|
content.title = NSLocalizedString("SideStore News", comment: "")
|
||||||
}
|
}
|
||||||
|
|
||||||
content.body = newsItem.title
|
content.body = newsItem.title
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="15701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
<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">
|
||||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<deployment identifier="iOS"/>
|
<deployment identifier="iOS"/>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15703"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22684"/>
|
||||||
<capability name="Named colors" minToolsVersion="9.0"/>
|
<capability name="Named colors" minToolsVersion="9.0"/>
|
||||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
@@ -13,8 +13,8 @@
|
|||||||
<scene sceneID="lNR-II-WoW">
|
<scene sceneID="lNR-II-WoW">
|
||||||
<objects>
|
<objects>
|
||||||
<navigationController storyboardIdentifier="navigationController" id="ZTo-53-dSL" sceneMemberID="viewController">
|
<navigationController storyboardIdentifier="navigationController" id="ZTo-53-dSL" sceneMemberID="viewController">
|
||||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" barStyle="black" largeTitles="YES" id="Aej-RF-PfV" customClass="NavigationBar" customModule="AltStore" customModuleProvider="target">
|
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" barStyle="black" largeTitles="YES" id="Aej-RF-PfV" 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"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<color key="barTintColor" name="SettingsBackground"/>
|
<color key="barTintColor" name="SettingsBackground"/>
|
||||||
@@ -36,19 +36,19 @@
|
|||||||
<!--Authentication View Controller-->
|
<!--Authentication View Controller-->
|
||||||
<scene sceneID="OCd-xc-Ms7">
|
<scene sceneID="OCd-xc-Ms7">
|
||||||
<objects>
|
<objects>
|
||||||
<viewController storyboardIdentifier="authenticationViewController" id="yO1-iT-7NP" customClass="AuthenticationViewController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
|
<viewController storyboardIdentifier="authenticationViewController" id="yO1-iT-7NP" customClass="AuthenticationViewController" customModule="SideStore" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
<view key="view" contentMode="scaleToFill" id="mjy-4S-hyH">
|
<view key="view" contentMode="scaleToFill" id="mjy-4S-hyH">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<view hidden="YES" userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="oyW-Fd-ojD" userLabel="Sizing View">
|
<view hidden="YES" userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="oyW-Fd-ojD" userLabel="Sizing View">
|
||||||
<rect key="frame" x="0.0" y="44" width="375" height="623"/>
|
<rect key="frame" x="0.0" y="64" width="375" height="603"/>
|
||||||
</view>
|
</view>
|
||||||
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" alwaysBounceVertical="YES" indicatorStyle="white" keyboardDismissMode="onDrag" translatesAutoresizingMaskIntoConstraints="NO" id="WXx-hX-AXv">
|
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" alwaysBounceVertical="YES" indicatorStyle="white" keyboardDismissMode="onDrag" translatesAutoresizingMaskIntoConstraints="NO" id="WXx-hX-AXv">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<view contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" translatesAutoresizingMaskIntoConstraints="NO" id="2wp-qG-f0Z">
|
<view contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" translatesAutoresizingMaskIntoConstraints="NO" id="2wp-qG-f0Z">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="623"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="603"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" axis="vertical" spacing="50" translatesAutoresizingMaskIntoConstraints="NO" id="YmX-7v-pxh">
|
<stackView opaque="NO" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" axis="vertical" spacing="50" translatesAutoresizingMaskIntoConstraints="NO" id="YmX-7v-pxh">
|
||||||
<rect key="frame" x="16" y="6" width="343" height="359.5"/>
|
<rect key="frame" x="16" y="6" width="343" height="359.5"/>
|
||||||
@@ -56,14 +56,14 @@
|
|||||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="top" spacing="6" translatesAutoresizingMaskIntoConstraints="NO" id="Yfu-hI-0B7" userLabel="Welcome">
|
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="top" spacing="6" translatesAutoresizingMaskIntoConstraints="NO" id="Yfu-hI-0B7" userLabel="Welcome">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="343" height="67.5"/>
|
<rect key="frame" x="0.0" y="0.0" width="343" height="67.5"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Welcome to AltStore." textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="EI2-V3-zQZ">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Welcome to SideStore." textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="EI2-V3-zQZ">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="333.5" height="41"/>
|
<rect key="frame" x="0.0" y="0.0" width="343" height="41"/>
|
||||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="34"/>
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="34"/>
|
||||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Sign in with your Apple ID to get started." textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="SNU-tv-8Au">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Sign in with your Apple ID to get started." textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="SNU-tv-8Au">
|
||||||
<rect key="frame" x="0.0" y="47" width="308.5" height="20.5"/>
|
<rect key="frame" x="0.0" y="47" width="306.5" height="20.5"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
@@ -160,7 +160,7 @@
|
|||||||
</stackView>
|
</stackView>
|
||||||
</subviews>
|
</subviews>
|
||||||
</stackView>
|
</stackView>
|
||||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="2N5-zd-fUj">
|
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="2N5-zd-fUj">
|
||||||
<rect key="frame" x="0.0" y="191" width="343" height="51"/>
|
<rect key="frame" x="0.0" y="191" width="343" height="51"/>
|
||||||
<color key="backgroundColor" name="SettingsHighlighted"/>
|
<color key="backgroundColor" name="SettingsHighlighted"/>
|
||||||
<constraints>
|
<constraints>
|
||||||
@@ -179,7 +179,7 @@
|
|||||||
</subviews>
|
</subviews>
|
||||||
</stackView>
|
</stackView>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" verticalCompressionResistancePriority="250" axis="vertical" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="DBk-rT-ZE8">
|
<stackView opaque="NO" contentMode="scaleToFill" verticalCompressionResistancePriority="250" axis="vertical" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="DBk-rT-ZE8">
|
||||||
<rect key="frame" x="16" y="518.5" width="343" height="96.5"/>
|
<rect key="frame" x="16" y="498.5" width="343" height="96.5"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Why do we need this?" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="p9U-0q-Kn8">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Why do we need this?" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="p9U-0q-Kn8">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="343" height="20.5"/>
|
<rect key="frame" x="0.0" y="0.0" width="343" height="20.5"/>
|
||||||
@@ -198,6 +198,10 @@
|
|||||||
</stackView>
|
</stackView>
|
||||||
</subviews>
|
</subviews>
|
||||||
<constraints>
|
<constraints>
|
||||||
|
<constraint firstItem="DBk-rT-ZE8" firstAttribute="leading" secondItem="2wp-qG-f0Z" secondAttribute="leadingMargin" id="5AT-nV-ZP9"/>
|
||||||
|
<constraint firstAttribute="bottomMargin" secondItem="DBk-rT-ZE8" secondAttribute="bottom" id="HgY-oY-8KM"/>
|
||||||
|
<constraint firstAttribute="trailingMargin" secondItem="DBk-rT-ZE8" secondAttribute="trailing" id="VCf-bW-2K4"/>
|
||||||
|
<constraint firstItem="YmX-7v-pxh" firstAttribute="top" secondItem="2wp-qG-f0Z" secondAttribute="top" constant="6" id="iUr-Nd-tkt"/>
|
||||||
<constraint firstItem="DBk-rT-ZE8" firstAttribute="top" relation="greaterThanOrEqual" secondItem="YmX-7v-pxh" secondAttribute="bottom" constant="8" symbolic="YES" id="zTU-eY-DWd"/>
|
<constraint firstItem="DBk-rT-ZE8" firstAttribute="top" relation="greaterThanOrEqual" secondItem="YmX-7v-pxh" secondAttribute="bottom" constant="8" symbolic="YES" id="zTU-eY-DWd"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
</view>
|
</view>
|
||||||
@@ -210,26 +214,22 @@
|
|||||||
</constraints>
|
</constraints>
|
||||||
</scrollView>
|
</scrollView>
|
||||||
</subviews>
|
</subviews>
|
||||||
|
<viewLayoutGuide key="safeArea" id="zMn-DV-fpy"/>
|
||||||
<color key="backgroundColor" name="SettingsBackground"/>
|
<color key="backgroundColor" name="SettingsBackground"/>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstAttribute="bottom" secondItem="WXx-hX-AXv" secondAttribute="bottom" id="0jL-Ky-ju6"/>
|
<constraint firstAttribute="bottom" secondItem="WXx-hX-AXv" secondAttribute="bottom" id="0jL-Ky-ju6"/>
|
||||||
<constraint firstAttribute="leadingMargin" secondItem="YmX-7v-pxh" secondAttribute="leading" id="2PO-lG-dmB"/>
|
<constraint firstAttribute="leadingMargin" secondItem="YmX-7v-pxh" secondAttribute="leading" id="2PO-lG-dmB"/>
|
||||||
<constraint firstItem="DBk-rT-ZE8" firstAttribute="leading" secondItem="2wp-qG-f0Z" secondAttribute="leadingMargin" id="5AT-nV-ZP9"/>
|
|
||||||
<constraint firstItem="oyW-Fd-ojD" firstAttribute="top" secondItem="zMn-DV-fpy" secondAttribute="top" id="730-db-ukB"/>
|
<constraint firstItem="oyW-Fd-ojD" firstAttribute="top" secondItem="zMn-DV-fpy" secondAttribute="top" id="730-db-ukB"/>
|
||||||
<constraint firstItem="2wp-qG-f0Z" firstAttribute="bottomMargin" secondItem="DBk-rT-ZE8" secondAttribute="bottom" id="HgY-oY-8KM"/>
|
|
||||||
<constraint firstItem="zMn-DV-fpy" firstAttribute="trailing" secondItem="oyW-Fd-ojD" secondAttribute="trailing" id="KGE-CN-SWf"/>
|
<constraint firstItem="zMn-DV-fpy" firstAttribute="trailing" secondItem="oyW-Fd-ojD" secondAttribute="trailing" id="KGE-CN-SWf"/>
|
||||||
<constraint firstItem="WXx-hX-AXv" firstAttribute="top" secondItem="mjy-4S-hyH" secondAttribute="top" id="LPQ-bF-ic0"/>
|
<constraint firstItem="WXx-hX-AXv" firstAttribute="top" secondItem="mjy-4S-hyH" secondAttribute="top" id="LPQ-bF-ic0"/>
|
||||||
<constraint firstItem="zMn-DV-fpy" firstAttribute="trailing" secondItem="WXx-hX-AXv" secondAttribute="trailing" id="MG7-A6-pKp"/>
|
<constraint firstItem="zMn-DV-fpy" firstAttribute="trailing" secondItem="WXx-hX-AXv" secondAttribute="trailing" id="MG7-A6-pKp"/>
|
||||||
<constraint firstAttribute="trailingMargin" secondItem="YmX-7v-pxh" secondAttribute="trailing" id="O4T-nu-o3e"/>
|
<constraint firstAttribute="trailingMargin" secondItem="YmX-7v-pxh" secondAttribute="trailing" id="O4T-nu-o3e"/>
|
||||||
<constraint firstItem="zMn-DV-fpy" firstAttribute="bottom" secondItem="oyW-Fd-ojD" secondAttribute="bottom" id="PuX-ab-cEq"/>
|
<constraint firstItem="zMn-DV-fpy" firstAttribute="bottom" secondItem="oyW-Fd-ojD" secondAttribute="bottom" id="PuX-ab-cEq"/>
|
||||||
<constraint firstItem="oyW-Fd-ojD" firstAttribute="leading" secondItem="zMn-DV-fpy" secondAttribute="leading" id="SzC-gC-Nvi"/>
|
<constraint firstItem="oyW-Fd-ojD" firstAttribute="leading" secondItem="zMn-DV-fpy" secondAttribute="leading" id="SzC-gC-Nvi"/>
|
||||||
<constraint firstItem="2wp-qG-f0Z" firstAttribute="trailingMargin" secondItem="DBk-rT-ZE8" secondAttribute="trailing" id="VCf-bW-2K4"/>
|
|
||||||
<constraint firstItem="WXx-hX-AXv" firstAttribute="leading" secondItem="zMn-DV-fpy" secondAttribute="leading" id="d08-zF-5X6"/>
|
<constraint firstItem="WXx-hX-AXv" firstAttribute="leading" secondItem="zMn-DV-fpy" secondAttribute="leading" id="d08-zF-5X6"/>
|
||||||
<constraint firstItem="2wp-qG-f0Z" firstAttribute="height" secondItem="oyW-Fd-ojD" secondAttribute="height" id="dFN-pw-TWt"/>
|
<constraint firstItem="2wp-qG-f0Z" firstAttribute="height" secondItem="oyW-Fd-ojD" secondAttribute="height" id="dFN-pw-TWt"/>
|
||||||
<constraint firstItem="YmX-7v-pxh" firstAttribute="top" secondItem="2wp-qG-f0Z" secondAttribute="top" constant="6" id="iUr-Nd-tkt"/>
|
|
||||||
<constraint firstItem="2wp-qG-f0Z" firstAttribute="width" secondItem="oyW-Fd-ojD" secondAttribute="width" id="rYO-GN-0Lk"/>
|
<constraint firstItem="2wp-qG-f0Z" firstAttribute="width" secondItem="oyW-Fd-ojD" secondAttribute="width" id="rYO-GN-0Lk"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
<viewLayoutGuide key="safeArea" id="zMn-DV-fpy"/>
|
|
||||||
</view>
|
</view>
|
||||||
<toolbarItems/>
|
<toolbarItems/>
|
||||||
<navigationItem key="navigationItem" largeTitleDisplayMode="never" id="jCf-N4-xVD">
|
<navigationItem key="navigationItem" largeTitleDisplayMode="never" id="jCf-N4-xVD">
|
||||||
@@ -258,13 +258,13 @@
|
|||||||
<!--How it works-->
|
<!--How it works-->
|
||||||
<scene sceneID="dMt-EA-SGy">
|
<scene sceneID="dMt-EA-SGy">
|
||||||
<objects>
|
<objects>
|
||||||
<viewController storyboardIdentifier="instructionsViewController" hidesBottomBarWhenPushed="YES" id="aFi-fb-W0B" customClass="InstructionsViewController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
|
<viewController storyboardIdentifier="instructionsViewController" hidesBottomBarWhenPushed="YES" id="aFi-fb-W0B" customClass="InstructionsViewController" customModule="SideStore" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
<view key="view" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" id="Otz-hn-WGS">
|
<view key="view" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" id="Otz-hn-WGS">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" axis="vertical" distribution="equalSpacing" translatesAutoresizingMaskIntoConstraints="NO" id="bp6-55-IG2">
|
<stackView opaque="NO" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" axis="vertical" distribution="equalSpacing" translatesAutoresizingMaskIntoConstraints="NO" id="bp6-55-IG2">
|
||||||
<rect key="frame" x="0.0" y="44" width="375" height="564"/>
|
<rect key="frame" x="0.0" y="64" width="375" height="544"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="FjP-tm-w7K">
|
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="FjP-tm-w7K">
|
||||||
<rect key="frame" x="16" y="35" width="343" height="95.5"/>
|
<rect key="frame" x="16" y="35" width="343" height="95.5"/>
|
||||||
@@ -281,13 +281,13 @@
|
|||||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="Q20-ml-9D0">
|
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="Q20-ml-9D0">
|
||||||
<rect key="frame" x="79" y="16" width="264" height="64"/>
|
<rect key="frame" x="79" y="16" width="264" height="64"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Launch AltServer" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="XKD-XH-eB0">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Launch SideStore" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="XKD-XH-eB0">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="264" height="20.5"/>
|
<rect key="frame" x="0.0" y="0.0" width="264" height="20.5"/>
|
||||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Leave AltServer running in the background on your computer." textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="6HP-Xh-sAH">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Leave SideStore running in the background on your idevice." textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="6HP-Xh-sAH">
|
||||||
<rect key="frame" x="0.0" y="25.5" width="264" height="38.5"/>
|
<rect key="frame" x="0.0" y="25.5" width="264" height="38.5"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="16"/>
|
<fontDescription key="fontDescription" type="system" pointSize="16"/>
|
||||||
<color key="textColor" white="1" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
@@ -298,7 +298,7 @@
|
|||||||
</subviews>
|
</subviews>
|
||||||
</stackView>
|
</stackView>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="LpI-Jt-SzX">
|
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="LpI-Jt-SzX">
|
||||||
<rect key="frame" x="16" y="168" width="343" height="95.5"/>
|
<rect key="frame" x="16" y="161" width="343" height="95.5"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="2" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="0LW-eE-qHa">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="2" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="0LW-eE-qHa">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="59" height="95.5"/>
|
<rect key="frame" x="0.0" y="0.0" width="59" height="95.5"/>
|
||||||
@@ -310,16 +310,16 @@
|
|||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="dMu-eg-gIO">
|
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="dMu-eg-gIO">
|
||||||
<rect key="frame" x="79" y="15.5" width="264" height="64"/>
|
<rect key="frame" x="79" y="17.5" width="264" height="60.5"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Connect to WiFi" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="esj-pD-D4A">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Connect to Wi-Fi and VPN" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="esj-pD-D4A">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="264" height="20.5"/>
|
<rect key="frame" x="0.0" y="0.0" width="264" height="20.5"/>
|
||||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Enable iTunes WiFi Sync and connect to the same WiFi as AltServer." textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="4rk-ge-FSj">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Enable SideStore VPN in Wireguard and be able to use Sidestore on the go." textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="4rk-ge-FSj">
|
||||||
<rect key="frame" x="0.0" y="25.5" width="264" height="38.5"/>
|
<rect key="frame" x="0.0" y="25.5" width="264" height="35"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="16"/>
|
<fontDescription key="fontDescription" type="system" pointSize="16"/>
|
||||||
<color key="textColor" white="1" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
@@ -329,7 +329,7 @@
|
|||||||
</subviews>
|
</subviews>
|
||||||
</stackView>
|
</stackView>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="tfb-ja-9UC">
|
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="tfb-ja-9UC">
|
||||||
<rect key="frame" x="16" y="300.5" width="343" height="95.5"/>
|
<rect key="frame" x="16" y="287.5" width="343" height="95.5"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="3" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="nVr-El-Csi">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="3" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="nVr-El-Csi">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="59" height="95.5"/>
|
<rect key="frame" x="0.0" y="0.0" width="59" height="95.5"/>
|
||||||
@@ -341,7 +341,7 @@
|
|||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="z6Y-zi-teL">
|
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="z6Y-zi-teL">
|
||||||
<rect key="frame" x="79" y="16" width="264" height="64"/>
|
<rect key="frame" x="79" y="15.5" width="264" height="64"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Download Apps" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="JeJ-bk-UCA">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Download Apps" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="JeJ-bk-UCA">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="264" height="20.5"/>
|
<rect key="frame" x="0.0" y="0.0" width="264" height="20.5"/>
|
||||||
@@ -349,7 +349,7 @@
|
|||||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Browse and download apps directly from AltStore." textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="M7T-9j-uyt">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Browse and download apps directly from SideStore." textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="M7T-9j-uyt">
|
||||||
<rect key="frame" x="0.0" y="25.5" width="264" height="38.5"/>
|
<rect key="frame" x="0.0" y="25.5" width="264" height="38.5"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="16"/>
|
<fontDescription key="fontDescription" type="system" pointSize="16"/>
|
||||||
<color key="textColor" white="1" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
@@ -360,7 +360,7 @@
|
|||||||
</subviews>
|
</subviews>
|
||||||
</stackView>
|
</stackView>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="X3r-G1-vf2">
|
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="X3r-G1-vf2">
|
||||||
<rect key="frame" x="16" y="433.5" width="343" height="95.5"/>
|
<rect key="frame" x="16" y="413.5" width="343" height="95.5"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="4" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="i2U-NL-plG">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="4" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="i2U-NL-plG">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="59" height="95.5"/>
|
<rect key="frame" x="0.0" y="0.0" width="59" height="95.5"/>
|
||||||
@@ -372,7 +372,7 @@
|
|||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="Xs6-pJ-PUz">
|
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="Xs6-pJ-PUz">
|
||||||
<rect key="frame" x="79" y="16" width="264" height="64"/>
|
<rect key="frame" x="79" y="17" width="264" height="62"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Apps Refresh Automatically" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="nvb-Aq-sYa">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Apps Refresh Automatically" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="nvb-Aq-sYa">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="264" height="20.5"/>
|
<rect key="frame" x="0.0" y="0.0" width="264" height="20.5"/>
|
||||||
@@ -380,8 +380,8 @@
|
|||||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Apps are refreshed in the background when on same WiFi as AltServer." textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="HU5-Hv-E3d">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Apps are refreshed in the background while you are on SideStore VPN!" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="HU5-Hv-E3d">
|
||||||
<rect key="frame" x="0.0" y="25.5" width="264" height="38.5"/>
|
<rect key="frame" x="0.0" y="25.5" width="264" height="36.5"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="16"/>
|
<fontDescription key="fontDescription" type="system" pointSize="16"/>
|
||||||
<color key="textColor" white="1" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
@@ -393,7 +393,7 @@
|
|||||||
</subviews>
|
</subviews>
|
||||||
<edgeInsets key="layoutMargins" top="35" left="0.0" bottom="35" right="0.0"/>
|
<edgeInsets key="layoutMargins" top="35" left="0.0" bottom="35" right="0.0"/>
|
||||||
</stackView>
|
</stackView>
|
||||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="qZ9-AR-2zK">
|
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="qZ9-AR-2zK">
|
||||||
<rect key="frame" x="16" y="608" width="343" height="51"/>
|
<rect key="frame" x="16" y="608" width="343" height="51"/>
|
||||||
<color key="backgroundColor" name="SettingsHighlighted"/>
|
<color key="backgroundColor" name="SettingsHighlighted"/>
|
||||||
<constraints>
|
<constraints>
|
||||||
@@ -408,6 +408,7 @@
|
|||||||
</connections>
|
</connections>
|
||||||
</button>
|
</button>
|
||||||
</subviews>
|
</subviews>
|
||||||
|
<viewLayoutGuide key="safeArea" id="Zek-aC-HOO"/>
|
||||||
<color key="backgroundColor" name="SettingsBackground"/>
|
<color key="backgroundColor" name="SettingsBackground"/>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstItem="qZ9-AR-2zK" firstAttribute="top" secondItem="bp6-55-IG2" secondAttribute="bottom" id="3yt-cr-swd"/>
|
<constraint firstItem="qZ9-AR-2zK" firstAttribute="top" secondItem="bp6-55-IG2" secondAttribute="bottom" id="3yt-cr-swd"/>
|
||||||
@@ -418,7 +419,6 @@
|
|||||||
<constraint firstAttribute="bottomMargin" secondItem="qZ9-AR-2zK" secondAttribute="bottom" id="e8e-9l-Mkt"/>
|
<constraint firstAttribute="bottomMargin" secondItem="qZ9-AR-2zK" secondAttribute="bottom" id="e8e-9l-Mkt"/>
|
||||||
<constraint firstItem="qZ9-AR-2zK" firstAttribute="leading" secondItem="Otz-hn-WGS" secondAttribute="leadingMargin" id="t2b-3e-6ld"/>
|
<constraint firstItem="qZ9-AR-2zK" firstAttribute="leading" secondItem="Otz-hn-WGS" secondAttribute="leadingMargin" id="t2b-3e-6ld"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
<viewLayoutGuide key="safeArea" id="Zek-aC-HOO"/>
|
|
||||||
</view>
|
</view>
|
||||||
<navigationItem key="navigationItem" title="How it works" largeTitleDisplayMode="always" id="bCq-Jq-gf1"/>
|
<navigationItem key="navigationItem" title="How it works" largeTitleDisplayMode="always" id="bCq-Jq-gf1"/>
|
||||||
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" prompted="NO"/>
|
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" prompted="NO"/>
|
||||||
@@ -434,7 +434,7 @@
|
|||||||
<!--Refresh AltStore-->
|
<!--Refresh AltStore-->
|
||||||
<scene sceneID="9Vh-dM-OqX">
|
<scene sceneID="9Vh-dM-OqX">
|
||||||
<objects>
|
<objects>
|
||||||
<viewController storyboardIdentifier="refreshAltStoreViewController" id="aoK-yE-UVT" customClass="RefreshAltStoreViewController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
|
<viewController storyboardIdentifier="refreshAltStoreViewController" id="aoK-yE-UVT" customClass="RefreshAltStoreViewController" customModule="SideStore" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
<view key="view" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" id="R83-kV-365">
|
<view key="view" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" id="R83-kV-365">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
@@ -445,7 +445,7 @@
|
|||||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="tDQ-ao-1Jg">
|
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="tDQ-ao-1Jg">
|
||||||
<rect key="frame" x="16" y="570" width="343" height="89"/>
|
<rect key="frame" x="16" y="570" width="343" height="89"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="xcg-hT-tDe" customClass="PillButton" customModule="AltStore" customModuleProvider="target">
|
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="xcg-hT-tDe" customClass="PillButton" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="343" height="51"/>
|
<rect key="frame" x="0.0" y="0.0" width="343" height="51"/>
|
||||||
<color key="backgroundColor" name="SettingsHighlighted"/>
|
<color key="backgroundColor" name="SettingsHighlighted"/>
|
||||||
<constraints>
|
<constraints>
|
||||||
@@ -460,7 +460,7 @@
|
|||||||
<action selector="refreshAltStore:" destination="aoK-yE-UVT" eventType="primaryActionTriggered" id="WQu-9b-Zgg"/>
|
<action selector="refreshAltStore:" destination="aoK-yE-UVT" eventType="primaryActionTriggered" id="WQu-9b-Zgg"/>
|
||||||
</connections>
|
</connections>
|
||||||
</button>
|
</button>
|
||||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="qua-VA-asJ">
|
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="qua-VA-asJ">
|
||||||
<rect key="frame" x="0.0" y="59" width="343" height="30"/>
|
<rect key="frame" x="0.0" y="59" width="343" height="30"/>
|
||||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
|
||||||
<state key="normal" title="Refresh Later">
|
<state key="normal" title="Refresh Later">
|
||||||
@@ -473,6 +473,7 @@
|
|||||||
</subviews>
|
</subviews>
|
||||||
</stackView>
|
</stackView>
|
||||||
</subviews>
|
</subviews>
|
||||||
|
<viewLayoutGuide key="safeArea" id="iwE-xE-ziz"/>
|
||||||
<color key="backgroundColor" name="SettingsBackground"/>
|
<color key="backgroundColor" name="SettingsBackground"/>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstItem="fpO-Bf-gFY" firstAttribute="leading" secondItem="iwE-xE-ziz" secondAttribute="leading" id="A77-nX-Wg2"/>
|
<constraint firstItem="fpO-Bf-gFY" firstAttribute="leading" secondItem="iwE-xE-ziz" secondAttribute="leading" id="A77-nX-Wg2"/>
|
||||||
@@ -483,7 +484,6 @@
|
|||||||
<constraint firstItem="fpO-Bf-gFY" firstAttribute="top" secondItem="R83-kV-365" secondAttribute="top" id="oKo-10-7kD"/>
|
<constraint firstItem="fpO-Bf-gFY" firstAttribute="top" secondItem="R83-kV-365" secondAttribute="top" id="oKo-10-7kD"/>
|
||||||
<constraint firstItem="tDQ-ao-1Jg" firstAttribute="leading" secondItem="R83-kV-365" secondAttribute="leadingMargin" id="zEt-Xr-kJx"/>
|
<constraint firstItem="tDQ-ao-1Jg" firstAttribute="leading" secondItem="R83-kV-365" secondAttribute="leadingMargin" id="zEt-Xr-kJx"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
<viewLayoutGuide key="safeArea" id="iwE-xE-ziz"/>
|
|
||||||
</view>
|
</view>
|
||||||
<navigationItem key="navigationItem" title="Refresh AltStore" largeTitleDisplayMode="always" id="5nk-NR-jtV"/>
|
<navigationItem key="navigationItem" title="Refresh AltStore" largeTitleDisplayMode="always" id="5nk-NR-jtV"/>
|
||||||
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" prompted="NO"/>
|
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" prompted="NO"/>
|
||||||
@@ -493,16 +493,76 @@
|
|||||||
</viewController>
|
</viewController>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="Chr-7g-qEw" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="Chr-7g-qEw" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||||
</objects>
|
</objects>
|
||||||
<point key="canvasLocation" x="2101.5999999999999" y="733.5832083958021"/>
|
<point key="canvasLocation" x="3025" y="734"/>
|
||||||
|
</scene>
|
||||||
|
<!--Select a Team-->
|
||||||
|
<scene sceneID="ioQ-WB-CLJ">
|
||||||
|
<objects>
|
||||||
|
<viewController storyboardIdentifier="selectTeamViewController" hidesBottomBarWhenPushed="YES" id="kOD-4P-a6L" customClass="SelectTeamViewController" customModule="SideStore" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
|
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" indicatorStyle="white" dataMode="prototypes" style="grouped" separatorStyle="none" rowHeight="60" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="fWW-kX-ifH">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<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"/>
|
||||||
|
<prototypes>
|
||||||
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="TeamCell" textLabel="6ip-34-gmM" detailTextLabel="knk-Wf-PKf" style="IBUITableViewCellStyleSubtitle" id="qeQ-eb-2SC" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
|
<rect key="frame" x="0.0" y="55.5" width="375" height="60"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="qeQ-eb-2SC" id="bT4-Fc-u6I">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="334.5" height="60"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<subviews>
|
||||||
|
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Team 1" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="6ip-34-gmM">
|
||||||
|
<rect key="frame" x="30" y="10" width="56.5" height="20.5"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||||
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Description" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="knk-Wf-PKf">
|
||||||
|
<rect key="frame" x="30" y="33.5" width="70" height="14.5"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="12"/>
|
||||||
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
</subviews>
|
||||||
|
</tableViewCellContentView>
|
||||||
|
<color key="backgroundColor" white="1" alpha="0.14999999999999999" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<edgeInsets key="layoutMargins" top="8" left="30" bottom="8" right="30"/>
|
||||||
|
<userDefinedRuntimeAttributes>
|
||||||
|
<userDefinedRuntimeAttribute type="number" keyPath="style">
|
||||||
|
<integer key="value" value="0"/>
|
||||||
|
</userDefinedRuntimeAttribute>
|
||||||
|
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="YES"/>
|
||||||
|
</userDefinedRuntimeAttributes>
|
||||||
|
</tableViewCell>
|
||||||
|
</prototypes>
|
||||||
|
<sections/>
|
||||||
|
<connections>
|
||||||
|
<outlet property="dataSource" destination="kOD-4P-a6L" id="OLE-fk-1MD"/>
|
||||||
|
<outlet property="delegate" destination="kOD-4P-a6L" id="t9T-jO-TrR"/>
|
||||||
|
</connections>
|
||||||
|
</tableView>
|
||||||
|
<navigationItem key="navigationItem" title="Select a Team" largeTitleDisplayMode="always" id="qxJ-Go-OPq"/>
|
||||||
|
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" prompted="NO"/>
|
||||||
|
</viewController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="yH5-jU-aez" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
<point key="canvasLocation" x="2114" y="734"/>
|
||||||
</scene>
|
</scene>
|
||||||
</scenes>
|
</scenes>
|
||||||
|
<color key="tintColor" name="Primary"/>
|
||||||
<resources>
|
<resources>
|
||||||
|
<namedColor name="Primary">
|
||||||
|
<color red="0.64313725490196083" green="0.019607843137254902" blue="0.98039215686274506" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
</namedColor>
|
||||||
<namedColor name="SettingsBackground">
|
<namedColor name="SettingsBackground">
|
||||||
<color red="0.0039215686274509803" green="0.50196078431372548" blue="0.51764705882352946" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color red="0.45098039215686275" green="0.015686274509803921" blue="0.68627450980392157" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
</namedColor>
|
</namedColor>
|
||||||
<namedColor name="SettingsHighlighted">
|
<namedColor name="SettingsHighlighted">
|
||||||
<color red="0.0080000003799796104" green="0.32199999690055847" blue="0.40400001406669617" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color red="0.38823529411764707" green="0.011764705882352941" blue="0.58823529411764708" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
</namedColor>
|
</namedColor>
|
||||||
</resources>
|
</resources>
|
||||||
<color key="tintColor" name="Primary"/>
|
|
||||||
</document>
|
</document>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import UIKit
|
|||||||
|
|
||||||
import AltSign
|
import AltSign
|
||||||
|
|
||||||
class AuthenticationViewController: UIViewController
|
final class AuthenticationViewController: UIViewController
|
||||||
{
|
{
|
||||||
var authenticationHandler: ((String, String, @escaping (Result<(ALTAccount, ALTAppleAPISession), Error>) -> Void) -> Void)?
|
var authenticationHandler: ((String, String, @escaping (Result<(ALTAccount, ALTAppleAPISession), Error>) -> Void) -> Void)?
|
||||||
var completionHandler: (((ALTAccount, ALTAppleAPISession, String)?) -> Void)?
|
var completionHandler: (((ALTAccount, ALTAppleAPISession, String)?) -> Void)?
|
||||||
@@ -31,7 +31,7 @@ class AuthenticationViewController: UIViewController
|
|||||||
{
|
{
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
self.signInButton.activityIndicatorView.style = .white
|
self.signInButton.activityIndicatorView.style = .medium
|
||||||
|
|
||||||
for view in [self.appleIDBackgroundView!, self.passwordBackgroundView!, self.signInButton!]
|
for view in [self.appleIDBackgroundView!, self.passwordBackgroundView!, self.signInButton!]
|
||||||
{
|
{
|
||||||
@@ -108,11 +108,9 @@ private extension AuthenticationViewController
|
|||||||
|
|
||||||
case .failure(let error as NSError):
|
case .failure(let error as NSError):
|
||||||
DispatchQueue.main.async {
|
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)
|
let toastView = ToastView(error: error)
|
||||||
toastView.textLabel.textColor = .altPink
|
|
||||||
toastView.detailTextLabel.textColor = .altPink
|
|
||||||
toastView.show(in: self)
|
toastView.show(in: self)
|
||||||
self.toastView = toastView
|
self.toastView = toastView
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class InstructionsViewController: UIViewController
|
final class InstructionsViewController: UIViewController
|
||||||
{
|
{
|
||||||
var completionHandler: (() -> Void)?
|
var completionHandler: (() -> Void)?
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import AltStoreCore
|
|||||||
import AltSign
|
import AltSign
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
class RefreshAltStoreViewController: UIViewController
|
final class RefreshAltStoreViewController: UIViewController
|
||||||
{
|
{
|
||||||
var context: AuthenticatedOperationContext!
|
var context: AuthenticatedOperationContext!
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ class RefreshAltStoreViewController: UIViewController
|
|||||||
|
|
||||||
self.placeholderView.detailTextLabel.textAlignment = .left
|
self.placeholderView.detailTextLabel.textAlignment = .left
|
||||||
self.placeholderView.detailTextLabel.textColor = UIColor.white.withAlphaComponent(0.6)
|
self.placeholderView.detailTextLabel.textColor = UIColor.white.withAlphaComponent(0.6)
|
||||||
self.placeholderView.detailTextLabel.text = NSLocalizedString("AltStore was unable to use an existing signing certificate, so it had to create a new one. This will cause any apps installed with an existing certificate to expire — including AltStore.\n\nTo prevent AltStore from expiring early, please refresh the app now. AltStore will quit once refreshing is complete.", comment: "")
|
self.placeholderView.detailTextLabel.text = NSLocalizedString("SideStore was unable to use an existing signing certificate, so it had to create a new one. This will cause any apps installed with an existing certificate to expire — including SideStore.\n\nTo prevent SideStore from expiring early, please refresh the app now. SideStore will quit once refreshing is complete.", comment: "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ private extension RefreshAltStoreViewController
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Install, _not_ refresh, to ensure we are installing with a non-revoked certificate.
|
// Install, _not_ refresh, to ensure we are installing with a non-revoked certificate.
|
||||||
let progress = AppManager.shared.install(altStore, presentingViewController: self, context: self.context) { (result) in
|
let group = AppManager.shared.install(altStore, presentingViewController: self, context: self.context) { (result) in
|
||||||
switch result
|
switch result
|
||||||
{
|
{
|
||||||
case .success: self.completionHandler?(.success(()))
|
case .success: self.completionHandler?(.success(()))
|
||||||
@@ -58,7 +58,7 @@ private extension RefreshAltStoreViewController
|
|||||||
sender.progress = nil
|
sender.progress = nil
|
||||||
sender.isIndicatingActivity = false
|
sender.isIndicatingActivity = false
|
||||||
|
|
||||||
let alertController = UIAlertController(title: NSLocalizedString("Failed to Refresh AltStore", comment: ""), message: error.localizedFailureReason ?? error.localizedDescription, preferredStyle: .alert)
|
let alertController = UIAlertController(title: NSLocalizedString("Failed to Refresh SideStore", comment: ""), message: error.localizedFailureReason ?? error.localizedDescription, preferredStyle: .alert)
|
||||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Try Again", comment: ""), style: .default, handler: { (action) in
|
alertController.addAction(UIAlertAction(title: NSLocalizedString("Try Again", comment: ""), style: .default, handler: { (action) in
|
||||||
refresh()
|
refresh()
|
||||||
}))
|
}))
|
||||||
@@ -71,7 +71,7 @@ private extension RefreshAltStoreViewController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sender.progress = progress
|
sender.progress = group.progress
|
||||||
}
|
}
|
||||||
|
|
||||||
refresh()
|
refresh()
|
||||||
|
|||||||
61
AltStore/Authentication/SelectTeamViewController.swift
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
//
|
||||||
|
// SelectTeamViewController.swift
|
||||||
|
// AltStore
|
||||||
|
//
|
||||||
|
// Created by Megarushing on 4/26/21.
|
||||||
|
// Copyright © 2021 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import SafariServices
|
||||||
|
import MessageUI
|
||||||
|
import Intents
|
||||||
|
import IntentsUI
|
||||||
|
|
||||||
|
import AltSign
|
||||||
|
|
||||||
|
final class SelectTeamViewController: UITableViewController
|
||||||
|
{
|
||||||
|
public var teams: [ALTTeam]?
|
||||||
|
public var completionHandler: ((Result<ALTTeam, Swift.Error>) -> Void)?
|
||||||
|
|
||||||
|
private var prototypeHeaderFooterView: SettingsHeaderFooterView!
|
||||||
|
|
||||||
|
override var preferredStatusBarStyle: UIStatusBarStyle {
|
||||||
|
return .lightContent
|
||||||
|
}
|
||||||
|
|
||||||
|
override func numberOfSections(in tableView: UITableView) -> Int {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||||
|
return teams?.count ?? 0
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||||
|
return self.completionHandler!(.success((self.teams?[indexPath.row])!))
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||||
|
let cell = tableView.dequeueReusableCell(withIdentifier: "TeamCell", for: indexPath) as! InsetGroupTableViewCell
|
||||||
|
|
||||||
|
cell.textLabel?.text = self.teams?[indexPath.row].name
|
||||||
|
cell.detailTextLabel?.text = self.teams?[indexPath.row].type.localizedDescription
|
||||||
|
if indexPath.row == 0
|
||||||
|
{
|
||||||
|
cell.style = InsetGroupTableViewCell.Style.top
|
||||||
|
} else if indexPath.row == self.tableView(self.tableView, numberOfRowsInSection: indexPath.section) - 1 {
|
||||||
|
cell.style = InsetGroupTableViewCell.Style.bottom
|
||||||
|
} else {
|
||||||
|
cell.style = InsetGroupTableViewCell.Style.middle
|
||||||
|
}
|
||||||
|
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||||
|
"Teams"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17503.1" 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"/>
|
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<deployment identifier="iOS"/>
|
<deployment identifier="iOS"/>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17502"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22684"/>
|
||||||
<capability name="Named colors" minToolsVersion="9.0"/>
|
<capability name="Named colors" minToolsVersion="9.0"/>
|
||||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
<!--Launch View Controller-->
|
<!--Launch View Controller-->
|
||||||
<scene sceneID="q24-yd-v7v">
|
<scene sceneID="q24-yd-v7v">
|
||||||
<objects>
|
<objects>
|
||||||
<viewController id="wKh-xq-NuP" customClass="LaunchViewController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
|
<viewController id="wKh-xq-NuP" customClass="LaunchViewController" customModule="SideStore" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
<view key="view" contentMode="scaleToFill" id="G9E-Qs-gFM">
|
<view key="view" contentMode="scaleToFill" id="G9E-Qs-gFM">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
<!--Tab Bar Controller-->
|
<!--Tab Bar Controller-->
|
||||||
<scene sceneID="yl2-sM-qoP">
|
<scene sceneID="yl2-sM-qoP">
|
||||||
<objects>
|
<objects>
|
||||||
<tabBarController storyboardIdentifier="tabBarController" modalPresentationStyle="fullScreen" id="49e-Tb-3d3" customClass="TabBarController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
|
<tabBarController storyboardIdentifier="tabBarController" modalPresentationStyle="fullScreen" id="49e-Tb-3d3" customClass="TabBarController" customModule="SideStore" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
<tabBar key="tabBar" contentMode="scaleToFill" id="W28-zg-YXA">
|
<tabBar key="tabBar" contentMode="scaleToFill" id="W28-zg-YXA">
|
||||||
<rect key="frame" x="0.0" y="975" width="768" height="49"/>
|
<rect key="frame" x="0.0" y="975" width="768" height="49"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||||
@@ -40,6 +40,7 @@
|
|||||||
<segue destination="3Ew-ox-i4n" kind="relationship" relationship="viewControllers" id="zii-dF-qEt"/>
|
<segue destination="3Ew-ox-i4n" kind="relationship" relationship="viewControllers" id="zii-dF-qEt"/>
|
||||||
<segue destination="p3d-dP-Swg" kind="relationship" relationship="viewControllers" id="4Nf-rL-P4c"/>
|
<segue destination="p3d-dP-Swg" kind="relationship" relationship="viewControllers" id="4Nf-rL-P4c"/>
|
||||||
<segue destination="Qo4-72-Hmr" kind="presentation" identifier="presentSources" id="Qd6-ba-dIo"/>
|
<segue destination="Qo4-72-Hmr" kind="presentation" identifier="presentSources" id="Qd6-ba-dIo"/>
|
||||||
|
<segue destination="bTL-bY-9Yq" kind="presentation" identifier="finishJailbreak" id="cIc-Ta-uNk"/>
|
||||||
</connections>
|
</connections>
|
||||||
</tabBarController>
|
</tabBarController>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="HuB-VB-40B" sceneMemberID="firstResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="HuB-VB-40B" sceneMemberID="firstResponder"/>
|
||||||
@@ -49,7 +50,7 @@
|
|||||||
<!--Browse-->
|
<!--Browse-->
|
||||||
<scene sceneID="rXq-UR-qQp">
|
<scene sceneID="rXq-UR-qQp">
|
||||||
<objects>
|
<objects>
|
||||||
<collectionViewController id="e3L-BF-iXp" customClass="BrowseViewController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
|
<collectionViewController id="e3L-BF-iXp" customClass="BrowseViewController" customModule="SideStore" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
<collectionView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="prototypes" id="CaT-1q-qnx">
|
<collectionView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="prototypes" id="CaT-1q-qnx">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
@@ -84,7 +85,7 @@
|
|||||||
<!--App View Controller-->
|
<!--App View Controller-->
|
||||||
<scene sceneID="TgT-LO-3Er">
|
<scene sceneID="TgT-LO-3Er">
|
||||||
<objects>
|
<objects>
|
||||||
<viewController storyboardIdentifier="appViewController" id="0V6-N4-hTO" customClass="AppViewController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
|
<viewController storyboardIdentifier="appViewController" id="0V6-N4-hTO" customClass="AppViewController" customModule="SideStore" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
<view key="view" contentMode="scaleToFill" id="0cR-li-tCB">
|
<view key="view" contentMode="scaleToFill" id="0cR-li-tCB">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
@@ -92,7 +93,7 @@
|
|||||||
<stackView opaque="NO" contentMode="scaleToFill" fixedFrame="YES" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="Bql-t3-Ndi">
|
<stackView opaque="NO" contentMode="scaleToFill" fixedFrame="YES" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="Bql-t3-Ndi">
|
||||||
<rect key="frame" x="47" y="238" width="85" height="35"/>
|
<rect key="frame" x="47" y="238" width="85" height="35"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="j1W-Jn-HFI" customClass="AppIconImageView" customModule="AltStore" customModuleProvider="target">
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="j1W-Jn-HFI" customClass="AppIconImageView" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="35" height="35"/>
|
<rect key="frame" x="0.0" y="0.0" width="35" height="35"/>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstAttribute="width" constant="35" id="aMS-N7-WYn"/>
|
<constraint firstAttribute="width" constant="35" id="aMS-N7-WYn"/>
|
||||||
@@ -130,7 +131,7 @@
|
|||||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Qlg-m3-lXg">
|
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Qlg-m3-lXg">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="NEy-yr-cLS" customClass="AppBannerView" customModule="AltStore" customModuleProvider="target">
|
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="NEy-yr-cLS" customClass="AppBannerView" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="37" y="287" width="300" height="93"/>
|
<rect key="frame" x="37" y="287" width="300" height="93"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
</view>
|
</view>
|
||||||
@@ -191,7 +192,7 @@
|
|||||||
</view>
|
</view>
|
||||||
<navigationItem key="navigationItem" largeTitleDisplayMode="never" id="maq-gT-QcS">
|
<navigationItem key="navigationItem" largeTitleDisplayMode="never" id="maq-gT-QcS">
|
||||||
<barButtonItem key="rightBarButtonItem" style="plain" id="FLf-DS-F77">
|
<barButtonItem key="rightBarButtonItem" style="plain" id="FLf-DS-F77">
|
||||||
<button key="customView" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" id="grk-xM-YWA" customClass="PillButton" customModule="AltStore" customModuleProvider="target">
|
<button key="customView" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" id="grk-xM-YWA" customClass="PillButton" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="287" y="6.5" width="72" height="31"/>
|
<rect key="frame" x="287" y="6.5" width="72" height="31"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
<color key="backgroundColor" red="0.22352941179999999" green="0.4941176471" blue="0.39607843139999999" alpha="0.10000000000000001" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="backgroundColor" red="0.22352941179999999" green="0.4941176471" blue="0.39607843139999999" alpha="0.10000000000000001" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
@@ -228,7 +229,7 @@
|
|||||||
<!--App-->
|
<!--App-->
|
||||||
<scene sceneID="CgX-7h-sRI">
|
<scene sceneID="CgX-7h-sRI">
|
||||||
<objects>
|
<objects>
|
||||||
<tableViewController id="kBq-V8-3XC" customClass="AppContentViewController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
|
<tableViewController id="kBq-V8-3XC" customClass="AppContentViewController" customModule="SideStore" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" contentInsetAdjustmentBehavior="never" dataMode="static" style="plain" separatorStyle="none" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" contentViewInsetsToSafeArea="NO" id="w5c-Q3-FcU">
|
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" contentInsetAdjustmentBehavior="never" dataMode="static" style="plain" separatorStyle="none" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" contentViewInsetsToSafeArea="NO" id="w5c-Q3-FcU">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
@@ -237,7 +238,7 @@
|
|||||||
<tableViewSection id="rfR-32-T0h">
|
<tableViewSection id="rfR-32-T0h">
|
||||||
<cells>
|
<cells>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" rowHeight="57" id="xef-ko-Qp1">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" rowHeight="57" id="xef-ko-Qp1">
|
||||||
<rect key="frame" x="0.0" y="28" width="375" height="57"/>
|
<rect key="frame" x="0.0" y="50" width="375" height="57"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="xef-ko-Qp1" id="8PX-jQ-nHd">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="xef-ko-Qp1" id="8PX-jQ-nHd">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="57"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="57"/>
|
||||||
@@ -261,7 +262,7 @@
|
|||||||
<inset key="separatorInset" minX="15" minY="0.0" maxX="15" maxY="0.0"/>
|
<inset key="separatorInset" minX="15" minY="0.0" maxX="15" maxY="0.0"/>
|
||||||
</tableViewCell>
|
</tableViewCell>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" id="nI6-wC-H2d">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" id="nI6-wC-H2d">
|
||||||
<rect key="frame" x="0.0" y="85" width="375" height="44"/>
|
<rect key="frame" x="0.0" y="107" width="375" height="44"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="nI6-wC-H2d" id="Z4y-vb-Z4Q">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="nI6-wC-H2d" id="Z4y-vb-Z4Q">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
|
||||||
@@ -298,14 +299,14 @@
|
|||||||
<color key="backgroundColor" name="Background"/>
|
<color key="backgroundColor" name="Background"/>
|
||||||
<inset key="separatorInset" minX="15" minY="0.0" maxX="15" maxY="0.0"/>
|
<inset key="separatorInset" minX="15" minY="0.0" maxX="15" maxY="0.0"/>
|
||||||
</tableViewCell>
|
</tableViewCell>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" id="EL5-UC-RIw" customClass="AppContentTableViewCell" customModule="AltStore" customModuleProvider="target">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" id="EL5-UC-RIw" customClass="AppContentTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="129" width="375" height="44"/>
|
<rect key="frame" x="0.0" y="151" width="375" height="98"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" ambiguous="YES" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="EL5-UC-RIw" id="D1G-nK-G0Z">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="EL5-UC-RIw" id="D1G-nK-G0Z">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="98"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" editable="NO" text="Hello Me" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="Pyt-8D-BZA" customClass="CollapsingTextView" customModule="AltStore" customModuleProvider="target">
|
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" editable="NO" text="Hello Me" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="Pyt-8D-BZA" customClass="CollapsingTextView" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="20" y="20" width="335" height="34"/>
|
<rect key="frame" x="20" y="20" width="335" height="34"/>
|
||||||
<color key="backgroundColor" name="Background"/>
|
<color key="backgroundColor" name="Background"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
||||||
@@ -313,8 +314,8 @@
|
|||||||
</textView>
|
</textView>
|
||||||
</subviews>
|
</subviews>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstItem="Pyt-8D-BZA" firstAttribute="top" secondItem="D1G-nK-G0Z" secondAttribute="top" constant="20" id="Lm9-lx-kJ8"/>
|
<constraint firstItem="Pyt-8D-BZA" firstAttribute="top" secondItem="D1G-nK-G0Z" secondAttribute="top" priority="999" constant="20" id="Lm9-lx-kJ8"/>
|
||||||
<constraint firstAttribute="bottom" secondItem="Pyt-8D-BZA" secondAttribute="bottom" constant="44" id="TSS-Au-gYx"/>
|
<constraint firstAttribute="bottom" secondItem="Pyt-8D-BZA" secondAttribute="bottom" priority="999" constant="44" id="TSS-Au-gYx"/>
|
||||||
<constraint firstItem="Pyt-8D-BZA" firstAttribute="leading" secondItem="D1G-nK-G0Z" secondAttribute="leading" constant="20" id="UlS-ct-L9Y"/>
|
<constraint firstItem="Pyt-8D-BZA" firstAttribute="leading" secondItem="D1G-nK-G0Z" secondAttribute="leading" constant="20" id="UlS-ct-L9Y"/>
|
||||||
<constraint firstAttribute="trailing" secondItem="Pyt-8D-BZA" secondAttribute="trailing" constant="20" id="Wq4-Ql-wvN"/>
|
<constraint firstAttribute="trailing" secondItem="Pyt-8D-BZA" secondAttribute="trailing" constant="20" id="Wq4-Ql-wvN"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
@@ -322,30 +323,30 @@
|
|||||||
<color key="backgroundColor" name="Background"/>
|
<color key="backgroundColor" name="Background"/>
|
||||||
<inset key="separatorInset" minX="1000" minY="0.0" maxX="0.0" maxY="0.0"/>
|
<inset key="separatorInset" minX="1000" minY="0.0" maxX="0.0" maxY="0.0"/>
|
||||||
</tableViewCell>
|
</tableViewCell>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" id="47M-El-a4G" customClass="AppContentTableViewCell" customModule="AltStore" customModuleProvider="target">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" id="47M-El-a4G" customClass="AppContentTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="173" width="375" height="44"/>
|
<rect key="frame" x="0.0" y="249" width="375" height="137.5"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="47M-El-a4G" id="f9D-OR-oGE">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="47M-El-a4G" id="f9D-OR-oGE">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="137.5"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" axis="vertical" distribution="equalSpacing" spacing="12" translatesAutoresizingMaskIntoConstraints="NO" id="n9R-39-Glq">
|
<stackView opaque="NO" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" axis="vertical" distribution="equalSpacing" spacing="12" translatesAutoresizingMaskIntoConstraints="NO" id="n9R-39-Glq">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="60"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="137.5"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="y3w-4S-e64">
|
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="y3w-4S-e64">
|
||||||
<rect key="frame" x="20" y="0.0" width="335" height="4"/>
|
<rect key="frame" x="20" y="0.0" width="335" height="47.5"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" distribution="equalSpacing" spacing="11" translatesAutoresizingMaskIntoConstraints="NO" id="PFD-gZ-77F">
|
<stackView opaque="NO" contentMode="scaleToFill" distribution="equalSpacing" spacing="11" translatesAutoresizingMaskIntoConstraints="NO" id="PFD-gZ-77F">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="335" height="0.0"/>
|
<rect key="frame" x="0.0" y="0.0" width="335" height="26.5"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="751" text="What's New" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="obM-TM-y2E">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="751" text="What's New" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="obM-TM-y2E">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="124" height="0.0"/>
|
<rect key="frame" x="0.0" y="0.0" width="124" height="26.5"/>
|
||||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="22"/>
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="22"/>
|
||||||
<nil key="textColor"/>
|
<nil key="textColor"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="751" text="2w ago" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="wGD-mS-8fO">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="751" text="2w ago" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="wGD-mS-8fO">
|
||||||
<rect key="frame" x="285" y="0.0" width="50" height="0.0"/>
|
<rect key="frame" x="285" y="0.0" width="50" height="26.5"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
||||||
<color key="textColor" white="0.5" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="0.5" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
@@ -353,16 +354,16 @@
|
|||||||
</subviews>
|
</subviews>
|
||||||
</stackView>
|
</stackView>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" distribution="equalSpacing" translatesAutoresizingMaskIntoConstraints="NO" id="ewH-gi-pyW">
|
<stackView opaque="NO" contentMode="scaleToFill" distribution="equalSpacing" translatesAutoresizingMaskIntoConstraints="NO" id="ewH-gi-pyW">
|
||||||
<rect key="frame" x="0.0" y="4" width="335" height="0.0"/>
|
<rect key="frame" x="0.0" y="30.5" width="335" height="17"/>
|
||||||
<subviews>
|
<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">
|
<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.5" height="0.0"/>
|
<rect key="frame" x="0.0" y="0.0" width="84" height="17"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||||
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="751" text="50.4 MB" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="DgM-bD-bBY">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="751" text="50.4 MB" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="DgM-bD-bBY">
|
||||||
<rect key="frame" x="280.5" y="0.0" width="54.5" height="0.0"/>
|
<rect key="frame" x="280.5" y="0.0" width="54.5" height="17"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||||
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
@@ -371,8 +372,8 @@
|
|||||||
</stackView>
|
</stackView>
|
||||||
</subviews>
|
</subviews>
|
||||||
</stackView>
|
</stackView>
|
||||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" editable="NO" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="wQF-WY-Gdz" customClass="CollapsingTextView" customModule="AltStore" customModuleProvider="target">
|
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" editable="NO" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="wQF-WY-Gdz" customClass="CollapsingTextView" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="20" y="16" width="335" height="0.0"/>
|
<rect key="frame" x="20" y="59.5" width="335" height="34"/>
|
||||||
<color key="backgroundColor" name="Background"/>
|
<color key="backgroundColor" name="Background"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
||||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||||
@@ -392,7 +393,7 @@
|
|||||||
<inset key="separatorInset" minX="1000" minY="0.0" maxX="0.0" maxY="0.0"/>
|
<inset key="separatorInset" minX="1000" minY="0.0" maxX="0.0" maxY="0.0"/>
|
||||||
</tableViewCell>
|
</tableViewCell>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" rowHeight="149" id="nM7-vJ-W8b">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" rowHeight="149" id="nM7-vJ-W8b">
|
||||||
<rect key="frame" x="0.0" y="217" width="375" height="149"/>
|
<rect key="frame" x="0.0" y="386.5" width="375" height="149"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="nM7-vJ-W8b" id="cQ2-Jd-pRK">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="nM7-vJ-W8b" id="cQ2-Jd-pRK">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="149"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="149"/>
|
||||||
@@ -411,7 +412,7 @@
|
|||||||
<rect key="frame" x="0.0" y="41" width="375" height="88"/>
|
<rect key="frame" x="0.0" y="41" width="375" height="88"/>
|
||||||
<color key="backgroundColor" name="Background"/>
|
<color key="backgroundColor" name="Background"/>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstAttribute="height" constant="88" id="6Lk-OO-MsA"/>
|
<constraint firstAttribute="height" priority="999" constant="88" id="6Lk-OO-MsA"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
<collectionViewFlowLayout key="collectionViewLayout" scrollDirection="horizontal" minimumLineSpacing="10" minimumInteritemSpacing="10" id="2HF-4d-3Im">
|
<collectionViewFlowLayout key="collectionViewLayout" scrollDirection="horizontal" minimumLineSpacing="10" minimumInteritemSpacing="10" id="2HF-4d-3Im">
|
||||||
<size key="itemSize" width="60" height="88"/>
|
<size key="itemSize" width="60" height="88"/>
|
||||||
@@ -420,15 +421,15 @@
|
|||||||
<inset key="sectionInset" minX="20" minY="0.0" maxX="20" maxY="0.0"/>
|
<inset key="sectionInset" minX="20" minY="0.0" maxX="20" maxY="0.0"/>
|
||||||
</collectionViewFlowLayout>
|
</collectionViewFlowLayout>
|
||||||
<cells>
|
<cells>
|
||||||
<collectionViewCell opaque="NO" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="Cell" id="WYy-bZ-h3T" customClass="PermissionCollectionViewCell" customModule="AltStore" customModuleProvider="target">
|
<collectionViewCell opaque="NO" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="Cell" id="WYy-bZ-h3T" customClass="PermissionCollectionViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="20" y="0.0" width="60" height="88"/>
|
<rect key="frame" x="20" y="0.0" width="60" height="88"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO">
|
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="60" height="88"/>
|
<rect key="frame" x="0.0" y="0.0" width="60" height="88"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" alignment="center" spacing="6" translatesAutoresizingMaskIntoConstraints="NO" id="fSx-We-L4W">
|
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="center" spacing="6" translatesAutoresizingMaskIntoConstraints="NO" id="fSx-We-L4W">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="56" height="56"/>
|
<rect key="frame" x="0.0" y="0.0" width="60" height="87.5"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<button opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="79g-9q-mE2">
|
<button opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="79g-9q-mE2">
|
||||||
<rect key="frame" x="5" y="0.0" width="50" height="50"/>
|
<rect key="frame" x="5" y="0.0" width="50" height="50"/>
|
||||||
@@ -510,7 +511,7 @@ World</string>
|
|||||||
<!--Permission Popover View Controller-->
|
<!--Permission Popover View Controller-->
|
||||||
<scene sceneID="24j-EJ-G4e">
|
<scene sceneID="24j-EJ-G4e">
|
||||||
<objects>
|
<objects>
|
||||||
<viewController id="Ojq-DN-xcF" customClass="PermissionPopoverViewController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
|
<viewController id="Ojq-DN-xcF" customClass="PermissionPopoverViewController" customModule="SideStore" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
<view key="view" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="IgU-aM-YrX">
|
<view key="view" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="IgU-aM-YrX">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="217"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="217"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
@@ -565,7 +566,7 @@ World</string>
|
|||||||
<!--News-->
|
<!--News-->
|
||||||
<scene sceneID="bqw-wB-hyB">
|
<scene sceneID="bqw-wB-hyB">
|
||||||
<objects>
|
<objects>
|
||||||
<collectionViewController id="3sa-FZ-PTg" customClass="NewsViewController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
|
<collectionViewController id="3sa-FZ-PTg" customClass="NewsViewController" customModule="SideStore" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
<collectionView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" id="736-lq-Aef">
|
<collectionView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" id="736-lq-Aef">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
@@ -591,11 +592,11 @@ World</string>
|
|||||||
<!--Browse-->
|
<!--Browse-->
|
||||||
<scene sceneID="VHa-uP-bFU">
|
<scene sceneID="VHa-uP-bFU">
|
||||||
<objects>
|
<objects>
|
||||||
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="faz-B4-Sub" customClass="ForwardingNavigationController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
|
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="faz-B4-Sub" customClass="ForwardingNavigationController" customModule="SideStore" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
<tabBarItem key="tabBarItem" title="Browse" image="Browse" id="Uwh-Bg-Ymq"/>
|
<tabBarItem key="tabBarItem" title="Browse" image="Browse" id="Uwh-Bg-Ymq"/>
|
||||||
<toolbarItems/>
|
<toolbarItems/>
|
||||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="dIv-qd-9L5" customClass="NavigationBar" customModule="AltStore" customModuleProvider="target">
|
<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"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<color key="tintColor" name="Primary"/>
|
<color key="tintColor" name="Primary"/>
|
||||||
</navigationBar>
|
</navigationBar>
|
||||||
@@ -608,16 +609,24 @@ World</string>
|
|||||||
</objects>
|
</objects>
|
||||||
<point key="canvasLocation" x="962" y="-17"/>
|
<point key="canvasLocation" x="962" y="-17"/>
|
||||||
</scene>
|
</scene>
|
||||||
|
<!--PatchApp-->
|
||||||
|
<scene sceneID="vdC-wt-hvX">
|
||||||
|
<objects>
|
||||||
|
<viewControllerPlaceholder storyboardName="PatchApp" id="bTL-bY-9Yq" sceneMemberID="viewController"/>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="NyZ-z6-R2q" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
<point key="canvasLocation" x="-1" y="545"/>
|
||||||
|
</scene>
|
||||||
<!--My Apps-->
|
<!--My Apps-->
|
||||||
<scene sceneID="nhh-BJ-XiT">
|
<scene sceneID="nhh-BJ-XiT">
|
||||||
<objects>
|
<objects>
|
||||||
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="3Ew-ox-i4n" customClass="ForwardingNavigationController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
|
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="3Ew-ox-i4n" customClass="ForwardingNavigationController" customModule="SideStore" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
<tabBarItem key="tabBarItem" title="My Apps" image="MyApps" id="4gT-9u-k7y">
|
<tabBarItem key="tabBarItem" title="My Apps" image="MyApps" id="4gT-9u-k7y">
|
||||||
<color key="badgeColor" name="Primary"/>
|
<color key="badgeColor" name="Primary"/>
|
||||||
</tabBarItem>
|
</tabBarItem>
|
||||||
<toolbarItems/>
|
<toolbarItems/>
|
||||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="CzO-Kt-BlZ" customClass="NavigationBar" customModule="AltStore" customModuleProvider="target">
|
<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"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
</navigationBar>
|
</navigationBar>
|
||||||
<nil name="viewControllers"/>
|
<nil name="viewControllers"/>
|
||||||
@@ -632,7 +641,7 @@ World</string>
|
|||||||
<!--My Apps-->
|
<!--My Apps-->
|
||||||
<scene sceneID="EC8-Sf-AF9">
|
<scene sceneID="EC8-Sf-AF9">
|
||||||
<objects>
|
<objects>
|
||||||
<collectionViewController id="hv7-Ar-voT" customClass="MyAppsViewController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
|
<collectionViewController id="hv7-Ar-voT" customClass="MyAppsViewController" customModule="SideStore" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
<collectionView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" id="Jrp-gi-4Df">
|
<collectionView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" id="Jrp-gi-4Df">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
@@ -644,14 +653,14 @@ World</string>
|
|||||||
<inset key="sectionInset" minX="0.0" minY="0.0" maxX="0.0" maxY="0.0"/>
|
<inset key="sectionInset" minX="0.0" minY="0.0" maxX="0.0" maxY="0.0"/>
|
||||||
</collectionViewFlowLayout>
|
</collectionViewFlowLayout>
|
||||||
<cells>
|
<cells>
|
||||||
<collectionViewCell opaque="NO" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" reuseIdentifier="AppCell" id="kMp-ym-2yu" customClass="InstalledAppCollectionViewCell" customModule="AltStore" customModuleProvider="target">
|
<collectionViewCell opaque="NO" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" reuseIdentifier="AppCell" id="kMp-ym-2yu" customClass="InstalledAppCollectionViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="60"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="60"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO">
|
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="60"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="60"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="mos-e4-dQ7" customClass="AppBannerView" customModule="AltStore" customModuleProvider="target">
|
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="mos-e4-dQ7" customClass="AppBannerView" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="8" y="0.0" width="359" height="60"/>
|
<rect key="frame" x="8" y="0.0" width="359" height="60"/>
|
||||||
</view>
|
</view>
|
||||||
</subviews>
|
</subviews>
|
||||||
@@ -670,7 +679,7 @@ World</string>
|
|||||||
</segue>
|
</segue>
|
||||||
</connections>
|
</connections>
|
||||||
</collectionViewCell>
|
</collectionViewCell>
|
||||||
<collectionViewCell opaque="NO" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="NoUpdatesCell" id="h0f-XI-UA5" customClass="NoUpdatesCollectionViewCell" customModule="AltStore" customModuleProvider="target">
|
<collectionViewCell opaque="NO" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="NoUpdatesCell" id="h0f-XI-UA5" customClass="NoUpdatesCollectionViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="75" width="375" height="60"/>
|
<rect key="frame" x="0.0" y="75" width="375" height="60"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO">
|
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO">
|
||||||
@@ -731,7 +740,7 @@ World</string>
|
|||||||
</connections>
|
</connections>
|
||||||
</collectionViewCell>
|
</collectionViewCell>
|
||||||
</cells>
|
</cells>
|
||||||
<collectionReusableView key="sectionFooterView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" reuseIdentifier="InstalledAppsFooter" id="HYs-co-nJZ" customClass="InstalledAppsCollectionFooterView" customModule="AltStore" customModuleProvider="target">
|
<collectionReusableView key="sectionFooterView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" reuseIdentifier="InstalledAppsFooter" id="HYs-co-nJZ" customClass="InstalledAppsCollectionFooterView" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="135" width="375" height="60.5"/>
|
<rect key="frame" x="0.0" y="135" width="375" height="60.5"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
@@ -788,7 +797,7 @@ World</string>
|
|||||||
<!--App IDs-->
|
<!--App IDs-->
|
||||||
<scene sceneID="kvf-US-rRe">
|
<scene sceneID="kvf-US-rRe">
|
||||||
<objects>
|
<objects>
|
||||||
<collectionViewController title="App IDs" id="y1A-Nm-mw7" customClass="AppIDsViewController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
|
<collectionViewController title="App IDs" id="y1A-Nm-mw7" customClass="AppIDsViewController" customModule="SideStore" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
<collectionView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" dataMode="prototypes" id="v1r-C8-h6h">
|
<collectionView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" dataMode="prototypes" id="v1r-C8-h6h">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="647"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="647"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
@@ -800,14 +809,14 @@ World</string>
|
|||||||
<inset key="sectionInset" minX="0.0" minY="10" maxX="0.0" maxY="20"/>
|
<inset key="sectionInset" minX="0.0" minY="10" maxX="0.0" maxY="20"/>
|
||||||
</collectionViewFlowLayout>
|
</collectionViewFlowLayout>
|
||||||
<cells>
|
<cells>
|
||||||
<collectionViewCell opaque="NO" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" reuseIdentifier="Cell" id="XWu-DU-xbh" customClass="BannerCollectionViewCell" customModule="AltStore" customModuleProvider="target">
|
<collectionViewCell opaque="NO" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" reuseIdentifier="Cell" id="XWu-DU-xbh" customClass="BannerCollectionViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="70" width="375" height="80"/>
|
<rect key="frame" x="0.0" y="70" width="375" height="80"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO">
|
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="80"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="80"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="1w8-fI-98T" customClass="AppBannerView" customModule="AltStore" customModuleProvider="target">
|
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="1w8-fI-98T" customClass="AppBannerView" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="8" y="0.0" width="359" height="80"/>
|
<rect key="frame" x="8" y="0.0" width="359" height="80"/>
|
||||||
<accessibility key="accessibilityConfiguration">
|
<accessibility key="accessibilityConfiguration">
|
||||||
<bool key="isElement" value="YES"/>
|
<bool key="isElement" value="YES"/>
|
||||||
@@ -826,7 +835,7 @@ World</string>
|
|||||||
</connections>
|
</connections>
|
||||||
</collectionViewCell>
|
</collectionViewCell>
|
||||||
</cells>
|
</cells>
|
||||||
<collectionReusableView key="sectionHeaderView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" reuseIdentifier="Header" id="th0-G6-bRt" customClass="TextCollectionReusableView" customModule="AltStore" customModuleProvider="target">
|
<collectionReusableView key="sectionHeaderView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" reuseIdentifier="Header" id="th0-G6-bRt" customClass="TextCollectionReusableView" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="60"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="60"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
@@ -847,7 +856,7 @@ World</string>
|
|||||||
<outlet property="textLabel" destination="83Z-Ih-nOW" id="xxM-HD-iJS"/>
|
<outlet property="textLabel" destination="83Z-Ih-nOW" id="xxM-HD-iJS"/>
|
||||||
</connections>
|
</connections>
|
||||||
</collectionReusableView>
|
</collectionReusableView>
|
||||||
<collectionReusableView key="sectionFooterView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" reuseIdentifier="Footer" id="xMh-lD-r6C" customClass="TextCollectionReusableView" customModule="AltStore" customModuleProvider="target">
|
<collectionReusableView key="sectionFooterView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" reuseIdentifier="Footer" id="xMh-lD-r6C" customClass="TextCollectionReusableView" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="170" width="375" height="50"/>
|
<rect key="frame" x="0.0" y="170" width="375" height="50"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
@@ -896,11 +905,11 @@ World</string>
|
|||||||
<!--News-->
|
<!--News-->
|
||||||
<scene sceneID="BV8-6J-nIv">
|
<scene sceneID="BV8-6J-nIv">
|
||||||
<objects>
|
<objects>
|
||||||
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="kjR-gi-fgT" customClass="ForwardingNavigationController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
|
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="kjR-gi-fgT" customClass="ForwardingNavigationController" customModule="SideStore" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
<tabBarItem key="tabBarItem" title="News" image="News" id="fVN-ed-uO1"/>
|
<tabBarItem key="tabBarItem" title="News" image="News" id="fVN-ed-uO1"/>
|
||||||
<toolbarItems/>
|
<toolbarItems/>
|
||||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="525-jF-uDK" customClass="NavigationBar" customModule="AltStore" customModuleProvider="target">
|
<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"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<edgeInsets key="layoutMargins" top="8" left="20" bottom="8" right="8"/>
|
<edgeInsets key="layoutMargins" top="8" left="20" bottom="8" right="8"/>
|
||||||
</navigationBar>
|
</navigationBar>
|
||||||
@@ -934,26 +943,26 @@ World</string>
|
|||||||
<!--Sources-->
|
<!--Sources-->
|
||||||
<scene sceneID="0S1-zn-9KZ">
|
<scene sceneID="0S1-zn-9KZ">
|
||||||
<objects>
|
<objects>
|
||||||
<collectionViewController title="Sources" id="cHC-TX-KzQ" customClass="SourcesViewController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
|
<collectionViewController title="Sources" id="cHC-TX-KzQ" customClass="SourcesViewController" customModule="SideStore" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
<collectionView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" dataMode="prototypes" id="S36-hD-vu2">
|
<collectionView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" alwaysBounceVertical="YES" dataMode="prototypes" id="S36-hD-vu2">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="647"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="647"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||||
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="15" minimumInteritemSpacing="10" id="X20-b5-XEP">
|
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="15" minimumInteritemSpacing="10" id="X20-b5-XEP">
|
||||||
<size key="itemSize" width="375" height="80"/>
|
<size key="itemSize" width="375" height="80"/>
|
||||||
<size key="headerReferenceSize" width="50" height="200"/>
|
<size key="headerReferenceSize" width="50" height="200"/>
|
||||||
<size key="footerReferenceSize" width="0.0" height="0.0"/>
|
<size key="footerReferenceSize" width="50" height="50"/>
|
||||||
<inset key="sectionInset" minX="0.0" minY="10" maxX="0.0" maxY="20"/>
|
<inset key="sectionInset" minX="0.0" minY="0.0" maxX="0.0" maxY="0.0"/>
|
||||||
</collectionViewFlowLayout>
|
</collectionViewFlowLayout>
|
||||||
<cells>
|
<cells>
|
||||||
<collectionViewCell opaque="NO" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" reuseIdentifier="Cell" id="XcN-o4-9qm" customClass="BannerCollectionViewCell" customModule="AltStore" customModuleProvider="target">
|
<collectionViewCell opaque="NO" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" reuseIdentifier="Cell" id="XcN-o4-9qm" customClass="BannerCollectionViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="210" width="375" height="80"/>
|
<rect key="frame" x="0.0" y="200" width="375" height="80"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO">
|
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="80"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="80"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="LW1-CC-bWu" customClass="AppBannerView" customModule="AltStore" customModuleProvider="target">
|
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="LW1-CC-bWu" customClass="AppBannerView" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="8" y="0.0" width="359" height="80"/>
|
<rect key="frame" x="8" y="0.0" width="359" height="80"/>
|
||||||
<accessibility key="accessibilityConfiguration">
|
<accessibility key="accessibilityConfiguration">
|
||||||
<bool key="isElement" value="YES"/>
|
<bool key="isElement" value="YES"/>
|
||||||
@@ -972,11 +981,11 @@ World</string>
|
|||||||
</connections>
|
</connections>
|
||||||
</collectionViewCell>
|
</collectionViewCell>
|
||||||
</cells>
|
</cells>
|
||||||
<collectionReusableView key="sectionHeaderView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" reuseIdentifier="Header" id="8N7-JY-mcA" customClass="TextCollectionReusableView" customModule="AltStore" customModuleProvider="target">
|
<collectionReusableView key="sectionHeaderView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" reuseIdentifier="Header" id="8N7-JY-mcA" customClass="TextCollectionReusableView" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="200"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="200"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Manage sources to control which apps are available to download through AltStore." textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="TZv-TM-uJj">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Manage sources to control which apps are available to download through SideStore." textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="TZv-TM-uJj">
|
||||||
<rect key="frame" x="8" y="14" width="359" height="171"/>
|
<rect key="frame" x="8" y="14" width="359" height="171"/>
|
||||||
<fontDescription key="fontDescription" style="UICTFontTextStyleCallout"/>
|
<fontDescription key="fontDescription" style="UICTFontTextStyleCallout"/>
|
||||||
<color key="textColor" white="0.5" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="0.5" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
@@ -990,7 +999,46 @@ World</string>
|
|||||||
<constraint firstAttribute="leadingMargin" secondItem="TZv-TM-uJj" secondAttribute="leading" id="aS5-6Y-rMd"/>
|
<constraint firstAttribute="leadingMargin" secondItem="TZv-TM-uJj" secondAttribute="leading" id="aS5-6Y-rMd"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
<connections>
|
<connections>
|
||||||
|
<outlet property="bottomLayoutConstraint" destination="Aml-PC-dko" id="I1s-ae-C8A"/>
|
||||||
|
<outlet property="leadingLayoutConstraint" destination="aS5-6Y-rMd" id="An8-KN-xfb"/>
|
||||||
<outlet property="textLabel" destination="TZv-TM-uJj" id="kWV-Wv-5gz"/>
|
<outlet property="textLabel" destination="TZv-TM-uJj" id="kWV-Wv-5gz"/>
|
||||||
|
<outlet property="topLayoutConstraint" destination="2zE-UV-24S" id="mjq-yH-v8J"/>
|
||||||
|
<outlet property="trailingLayoutConstraint" destination="V0U-al-5eb" id="z8b-2G-SgY"/>
|
||||||
|
</connections>
|
||||||
|
</collectionReusableView>
|
||||||
|
<collectionReusableView key="sectionFooterView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" reuseIdentifier="Footer" id="X5B-Kp-w1p" customClass="SourcesFooterView">
|
||||||
|
<rect key="frame" x="0.0" y="280" width="375" height="50"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<subviews>
|
||||||
|
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="15" translatesAutoresizingMaskIntoConstraints="NO" id="j0O-xE-gyd">
|
||||||
|
<rect key="frame" x="8" y="0.0" width="359" height="50"/>
|
||||||
|
<subviews>
|
||||||
|
<activityIndicatorView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" hidesWhenStopped="YES" animating="YES" style="medium" translatesAutoresizingMaskIntoConstraints="NO" id="PNx-uR-y2F">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="359" height="0.0"/>
|
||||||
|
</activityIndicatorView>
|
||||||
|
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" verticalCompressionResistancePriority="800" scrollEnabled="NO" contentInsetAdjustmentBehavior="never" editable="NO" text="Test" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="66c-H8-KJx">
|
||||||
|
<rect key="frame" x="0.0" y="15" width="359" height="35"/>
|
||||||
|
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||||
|
<color key="textColor" white="0.5" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<fontDescription key="fontDescription" style="UICTFontTextStyleSubhead"/>
|
||||||
|
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||||
|
</textView>
|
||||||
|
</subviews>
|
||||||
|
</stackView>
|
||||||
|
</subviews>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="bottom" secondItem="j0O-xE-gyd" secondAttribute="bottom" id="BQ5-11-BzK"/>
|
||||||
|
<constraint firstItem="j0O-xE-gyd" firstAttribute="top" secondItem="X5B-Kp-w1p" secondAttribute="top" id="KZg-fd-8Cp" propertyAccessControl="none"/>
|
||||||
|
<constraint firstItem="j0O-xE-gyd" firstAttribute="leading" secondItem="X5B-Kp-w1p" secondAttribute="leadingMargin" id="R2x-Io-bXD"/>
|
||||||
|
<constraint firstAttribute="trailingMargin" secondItem="j0O-xE-gyd" secondAttribute="trailing" id="aBK-Bq-P9O"/>
|
||||||
|
</constraints>
|
||||||
|
<connections>
|
||||||
|
<outlet property="activityIndicatorView" destination="PNx-uR-y2F" id="7Le-VW-GYK"/>
|
||||||
|
<outlet property="bottomLayoutConstraint" destination="BQ5-11-BzK" id="iJR-4o-u9l"/>
|
||||||
|
<outlet property="leadingLayoutConstraint" destination="R2x-Io-bXD" id="plZ-Yj-zTc"/>
|
||||||
|
<outlet property="textView" destination="66c-H8-KJx" id="kwc-OH-U6i"/>
|
||||||
|
<outlet property="topLayoutConstraint" destination="KZg-fd-8Cp" id="zNM-UU-feF"/>
|
||||||
|
<outlet property="trailingLayoutConstraint" destination="aBK-Bq-P9O" id="L2r-VL-ruT"/>
|
||||||
</connections>
|
</connections>
|
||||||
</collectionReusableView>
|
</collectionReusableView>
|
||||||
<connections>
|
<connections>
|
||||||
@@ -1036,7 +1084,7 @@ World</string>
|
|||||||
</scene>
|
</scene>
|
||||||
</scenes>
|
</scenes>
|
||||||
<inferredMetricsTieBreakers>
|
<inferredMetricsTieBreakers>
|
||||||
<segue reference="de9-NH-aec"/>
|
<segue reference="Qd6-ba-dIo"/>
|
||||||
<segue reference="cnd-KK-o60"/>
|
<segue reference="cnd-KK-o60"/>
|
||||||
</inferredMetricsTieBreakers>
|
</inferredMetricsTieBreakers>
|
||||||
<color key="tintColor" name="Primary"/>
|
<color key="tintColor" name="Primary"/>
|
||||||
@@ -1047,13 +1095,13 @@ World</string>
|
|||||||
<image name="News" width="19" height="20"/>
|
<image name="News" width="19" height="20"/>
|
||||||
<image name="Settings" width="20" height="20"/>
|
<image name="Settings" width="20" height="20"/>
|
||||||
<namedColor name="Background">
|
<namedColor name="Background">
|
||||||
<color red="0.0040000001899898052" green="0.50199997425079346" blue="0.51800000667572021" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color red="0.45098039215686275" green="0.015686274509803921" blue="0.68627450980392157" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
</namedColor>
|
</namedColor>
|
||||||
<namedColor name="BlurTint">
|
<namedColor name="BlurTint">
|
||||||
<color red="1" green="1" blue="1" alpha="0.30000001192092896" colorSpace="custom" customColorSpace="sRGB"/>
|
<color red="1" green="1" blue="1" alpha="0.30000001192092896" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
</namedColor>
|
</namedColor>
|
||||||
<namedColor name="Primary">
|
<namedColor name="Primary">
|
||||||
<color red="0.0040000001899898052" green="0.50199997425079346" blue="0.51800000667572021" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color red="0.64313725490196083" green="0.019607843137254902" blue="0.98039215686274506" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
</namedColor>
|
</namedColor>
|
||||||
<systemColor name="systemBackgroundColor">
|
<systemColor name="systemBackgroundColor">
|
||||||
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import Roxas
|
|||||||
|
|
||||||
import Nuke
|
import Nuke
|
||||||
|
|
||||||
@objc class BrowseCollectionViewCell: UICollectionViewCell
|
@objc final class BrowseCollectionViewCell: UICollectionViewCell
|
||||||
{
|
{
|
||||||
var imageURLs: [URL] = [] {
|
var imageURLs: [URL] = [] {
|
||||||
didSet {
|
didSet {
|
||||||
@@ -53,7 +53,8 @@ private extension BrowseCollectionViewCell
|
|||||||
}
|
}
|
||||||
dataSource.prefetchHandler = { (imageURL, indexPath, completionHandler) in
|
dataSource.prefetchHandler = { (imageURL, indexPath, completionHandler) in
|
||||||
return RSTAsyncBlockOperation() { (operation) in
|
return RSTAsyncBlockOperation() { (operation) in
|
||||||
ImagePipeline.shared.loadImage(with: imageURL as URL, progress: nil, completion: { (response, error) in
|
let request = ImageRequest(url: imageURL as URL, processor: .screenshot)
|
||||||
|
ImagePipeline.shared.loadImage(with: request, progress: nil, completion: { (response, error) in
|
||||||
guard !operation.isCancelled else { return operation.finish() }
|
guard !operation.isCancelled else { return operation.finish() }
|
||||||
|
|
||||||
if let image = response?.image
|
if let image = response?.image
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
|
import minimuxer
|
||||||
import AltStoreCore
|
import AltStoreCore
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
@@ -94,7 +95,7 @@ private extension BrowseViewController
|
|||||||
cell.bannerView.iconImageView.isIndicatingActivity = true
|
cell.bannerView.iconImageView.isIndicatingActivity = true
|
||||||
|
|
||||||
cell.bannerView.button.addTarget(self, action: #selector(BrowseViewController.performAppAction(_:)), for: .primaryActionTriggered)
|
cell.bannerView.button.addTarget(self, action: #selector(BrowseViewController.performAppAction(_:)), for: .primaryActionTriggered)
|
||||||
cell.bannerView.button.activityIndicatorView.style = .white
|
cell.bannerView.button.activityIndicatorView.style = .medium
|
||||||
|
|
||||||
// Explicitly set to false to ensure we're starting from a non-activity indicating state.
|
// Explicitly set to false to ensure we're starting from a non-activity indicating state.
|
||||||
// Otherwise, cell reuse can mess up some cached values.
|
// Otherwise, cell reuse can mess up some cached values.
|
||||||
@@ -113,9 +114,9 @@ private extension BrowseViewController
|
|||||||
let progress = AppManager.shared.installationProgress(for: app)
|
let progress = AppManager.shared.installationProgress(for: app)
|
||||||
cell.bannerView.button.progress = progress
|
cell.bannerView.button.progress = progress
|
||||||
|
|
||||||
if Date() < app.versionDate
|
if let versionDate = app.latestSupportedVersion?.date, versionDate > Date()
|
||||||
{
|
{
|
||||||
cell.bannerView.button.countdownDate = app.versionDate
|
cell.bannerView.button.countdownDate = versionDate
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -167,14 +168,7 @@ private extension BrowseViewController
|
|||||||
|
|
||||||
func updateDataSource()
|
func updateDataSource()
|
||||||
{
|
{
|
||||||
if let patreonAccount = DatabaseManager.shared.patreonAccount(), patreonAccount.isPatron, PatreonAPI.shared.isAuthenticated
|
|
||||||
{
|
|
||||||
self.dataSource.predicate = nil
|
self.dataSource.predicate = nil
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
self.dataSource.predicate = NSPredicate(format: "%K == NO", #keyPath(StoreApp.isBeta))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchSource()
|
func fetchSource()
|
||||||
@@ -242,12 +236,6 @@ private extension BrowseViewController
|
|||||||
|
|
||||||
self.placeholderView.activityIndicatorView.stopAnimating()
|
self.placeholderView.activityIndicatorView.stopAnimating()
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !BETA
|
|
||||||
// Hide Sources button for public version if there's only 1 source.
|
|
||||||
let sources = Source.all(in: DatabaseManager.shared.viewContext)
|
|
||||||
self.navigationItem.rightBarButtonItem = (sources.count > 1) ? self.sourcesBarButtonItem : nil
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,14 +265,20 @@ private extension BrowseViewController
|
|||||||
previousProgress?.cancel()
|
previousProgress?.cancel()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !minimuxer.ready() {
|
||||||
|
let toastView = ToastView(error: MinimuxerError.NoConnection)
|
||||||
|
toastView.show(in: self)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
_ = AppManager.shared.install(app, presentingViewController: self) { (result) in
|
_ = AppManager.shared.install(app, presentingViewController: self) { (result) in
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
switch result
|
switch result
|
||||||
{
|
{
|
||||||
case .failure(OperationError.cancelled): break // Ignore
|
case .failure(OperationError.cancelled): break // Ignore
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
let toastView = ToastView(error: error)
|
let toastView = ToastView(error: error, opensLog: true)
|
||||||
toastView.show(in: self)
|
toastView.show(in: self)
|
||||||
|
|
||||||
case .success: print("Installed app:", app.bundleIdentifier)
|
case .success: print("Installed app:", app.bundleIdentifier)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class AppIconImageView: UIImageView
|
final class AppIconImageView: UIImageView
|
||||||
{
|
{
|
||||||
override func awakeFromNib()
|
override func awakeFromNib()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
import AVFoundation
|
import AVFoundation
|
||||||
|
|
||||||
class BackgroundTaskManager
|
final class BackgroundTaskManager
|
||||||
{
|
{
|
||||||
static let shared = BackgroundTaskManager()
|
static let shared = BackgroundTaskManager()
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class BannerCollectionViewCell: UICollectionViewCell
|
final class BannerCollectionViewCell: UICollectionViewCell
|
||||||
{
|
{
|
||||||
private(set) var errorBadge: UIView?
|
private(set) var errorBadge: UIView?
|
||||||
@IBOutlet private(set) var bannerView: AppBannerView!
|
@IBOutlet private(set) var bannerView: AppBannerView!
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class Button: UIButton
|
final class Button: UIButton
|
||||||
{
|
{
|
||||||
override var intrinsicContentSize: CGSize {
|
override var intrinsicContentSize: CGSize {
|
||||||
var size = super.intrinsicContentSize
|
var size = super.intrinsicContentSize
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class CollapsingTextView: UITextView
|
final class CollapsingTextView: UITextView
|
||||||
{
|
{
|
||||||
var isCollapsed = true {
|
var isCollapsed = true {
|
||||||
didSet {
|
didSet {
|
||||||
@@ -22,7 +22,7 @@ class CollapsingTextView: UITextView
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var lineSpacing: CGFloat = 2 {
|
var lineSpacing: Double = 2 {
|
||||||
didSet {
|
didSet {
|
||||||
self.setNeedsLayout()
|
self.setNeedsLayout()
|
||||||
}
|
}
|
||||||
@@ -34,7 +34,19 @@ class CollapsingTextView: UITextView
|
|||||||
{
|
{
|
||||||
super.awakeFromNib()
|
super.awakeFromNib()
|
||||||
|
|
||||||
self.layoutManager.delegate = self
|
self.initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func initialize()
|
||||||
|
{
|
||||||
|
if #available(iOS 16, *)
|
||||||
|
{
|
||||||
|
self.updateText()
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self.layoutManager.delegate = self
|
||||||
|
}
|
||||||
|
|
||||||
self.textContainerInset = .zero
|
self.textContainerInset = .zero
|
||||||
self.textContainer.lineFragmentPadding = 0
|
self.textContainer.lineFragmentPadding = 0
|
||||||
@@ -71,8 +83,10 @@ class CollapsingTextView: UITextView
|
|||||||
{
|
{
|
||||||
self.textContainer.maximumNumberOfLines = self.maximumNumberOfLines
|
self.textContainer.maximumNumberOfLines = self.maximumNumberOfLines
|
||||||
|
|
||||||
let maximumCollapsedHeight = font.lineHeight * CGFloat(self.maximumNumberOfLines)
|
let boundingSize = self.attributedText.boundingRect(with: CGSize(width: self.textContainer.size.width, height: .infinity), options: [.usesLineFragmentOrigin, .usesFontLeading], context: nil)
|
||||||
if self.intrinsicContentSize.height > maximumCollapsedHeight
|
let maximumCollapsedHeight = font.lineHeight * Double(self.maximumNumberOfLines)
|
||||||
|
|
||||||
|
if boundingSize.height.rounded() > maximumCollapsedHeight.rounded()
|
||||||
{
|
{
|
||||||
var exclusionFrame = moreButtonFrame
|
var exclusionFrame = moreButtonFrame
|
||||||
exclusionFrame.origin.y += self.moreButton.bounds.midY
|
exclusionFrame.origin.y += self.moreButton.bounds.midY
|
||||||
@@ -106,6 +120,25 @@ private extension CollapsingTextView
|
|||||||
{
|
{
|
||||||
self.isCollapsed.toggle()
|
self.isCollapsed.toggle()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(iOS 16, *)
|
||||||
|
func updateText()
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
let style = NSMutableParagraphStyle()
|
||||||
|
style.lineSpacing = self.lineSpacing
|
||||||
|
|
||||||
|
var attributedText = try AttributedString(self.attributedText, including: \.uiKit)
|
||||||
|
attributedText[AttributeScopes.UIKitAttributes.ParagraphStyleAttribute.self] = style
|
||||||
|
|
||||||
|
self.attributedText = NSAttributedString(attributedText)
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
print("[ALTLog] Failed to update CollapsingTextView line spacing:", error)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension CollapsingTextView: NSLayoutManagerDelegate
|
extension CollapsingTextView: NSLayoutManagerDelegate
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class ForwardingNavigationController: UINavigationController
|
final class ForwardingNavigationController: UINavigationController
|
||||||
{
|
{
|
||||||
override var childForStatusBarStyle: UIViewController? {
|
override var childForStatusBarStyle: UIViewController? {
|
||||||
return self.topViewController
|
return self.topViewController
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import UIKit
|
|||||||
|
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
class NavigationBar: UINavigationBar
|
final class NavigationBar: UINavigationBar
|
||||||
{
|
{
|
||||||
@IBInspectable var automaticallyAdjustsItemPositions: Bool = true
|
@IBInspectable var automaticallyAdjustsItemPositions: Bool = true
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,13 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class PillButton: UIButton
|
extension PillButton
|
||||||
|
{
|
||||||
|
static let minimumSize = CGSize(width: 77, height: 31)
|
||||||
|
static let contentInsets = NSDirectionalEdgeInsets(top: 7, leading: 13, bottom: 7, trailing: 13)
|
||||||
|
}
|
||||||
|
|
||||||
|
final class PillButton: UIButton
|
||||||
{
|
{
|
||||||
override var accessibilityValue: String? {
|
override var accessibilityValue: String? {
|
||||||
get {
|
get {
|
||||||
@@ -70,9 +76,7 @@ class PillButton: UIButton
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
override var intrinsicContentSize: CGSize {
|
override var intrinsicContentSize: CGSize {
|
||||||
var size = super.intrinsicContentSize
|
let size = self.sizeThatFits(CGSize(width: Double.infinity, height: .infinity))
|
||||||
size.width += 26
|
|
||||||
size.height += 3
|
|
||||||
return size
|
return size
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +92,9 @@ class PillButton: UIButton
|
|||||||
self.layer.masksToBounds = true
|
self.layer.masksToBounds = true
|
||||||
self.accessibilityTraits.formUnion([.updatesFrequently, .button])
|
self.accessibilityTraits.formUnion([.updatesFrequently, .button])
|
||||||
|
|
||||||
self.activityIndicatorView.style = .white
|
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
|
self.activityIndicatorView.isUserInteractionEnabled = false
|
||||||
|
|
||||||
self.progressView.progress = 0
|
self.progressView.progress = 0
|
||||||
@@ -119,6 +125,15 @@ class PillButton: UIButton
|
|||||||
|
|
||||||
self.update()
|
self.update()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func sizeThatFits(_ size: CGSize) -> CGSize
|
||||||
|
{
|
||||||
|
var size = super.sizeThatFits(size)
|
||||||
|
size.width = max(size.width, PillButton.minimumSize.width)
|
||||||
|
size.height = max(size.height, PillButton.minimumSize.height)
|
||||||
|
|
||||||
|
return size
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension PillButton
|
private extension PillButton
|
||||||
|
|||||||
@@ -11,4 +11,9 @@ import UIKit
|
|||||||
class TextCollectionReusableView: UICollectionReusableView
|
class TextCollectionReusableView: UICollectionReusableView
|
||||||
{
|
{
|
||||||
@IBOutlet var textLabel: UILabel!
|
@IBOutlet var textLabel: UILabel!
|
||||||
|
|
||||||
|
@IBOutlet var topLayoutConstraint: NSLayoutConstraint!
|
||||||
|
@IBOutlet var bottomLayoutConstraint: NSLayoutConstraint!
|
||||||
|
@IBOutlet var leadingLayoutConstraint: NSLayoutConstraint!
|
||||||
|
@IBOutlet var trailingLayoutConstraint: NSLayoutConstraint!
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,10 +16,19 @@ extension TimeInterval
|
|||||||
static let longToastViewDuration = 8.0
|
static let longToastViewDuration = 8.0
|
||||||
}
|
}
|
||||||
|
|
||||||
class ToastView: RSTToastView
|
final class ToastView: RSTToastView
|
||||||
{
|
{
|
||||||
|
static let openErrorLogNotification = Notification.Name("ALTOpenErrorLogNotification")
|
||||||
|
|
||||||
var preferredDuration: TimeInterval
|
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?)
|
override init(text: String, detailText detailedText: String?)
|
||||||
{
|
{
|
||||||
if detailedText == nil
|
if detailedText == nil
|
||||||
@@ -43,53 +52,43 @@ class ToastView: RSTToastView
|
|||||||
// RSTToastView does not expose stack view containing labels,
|
// RSTToastView does not expose stack view containing labels,
|
||||||
// so we access it indirectly as the labels' superview.
|
// so we access it indirectly as the labels' superview.
|
||||||
stackView.spacing = (detailedText != nil) ? 4.0 : 0.0
|
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)
|
convenience init(error: Error)
|
||||||
{
|
{
|
||||||
var error = error as NSError
|
var error = error as NSError
|
||||||
var underlyingError = error.userInfo[NSUnderlyingErrorKey] as? NSError
|
var underlyingError = error.underlyingError
|
||||||
|
|
||||||
var preferredDuration: TimeInterval?
|
|
||||||
|
|
||||||
if
|
if
|
||||||
let unwrappedUnderlyingError = underlyingError,
|
let unwrappedUnderlyingError = underlyingError,
|
||||||
error.domain == AltServerErrorDomain && error.code == ALTServerError.Code.underlyingError.rawValue
|
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
|
error = unwrappedUnderlyingError as NSError
|
||||||
|
|
||||||
|
if let localizedTitle = nsError.localizedTitle {
|
||||||
|
error = error.withLocalizedTitle(localizedTitle)
|
||||||
|
}
|
||||||
|
if let localizedFailure = nsError.localizedFailure {
|
||||||
|
error = error.withLocalizedFailure(localizedFailure)
|
||||||
|
}
|
||||||
|
|
||||||
underlyingError = nil
|
underlyingError = nil
|
||||||
|
|
||||||
preferredDuration = .longToastViewDuration
|
|
||||||
}
|
}
|
||||||
|
let text = error.localizedTitle ?? NSLocalizedString("Operation Failed", comment: "")
|
||||||
let text: String
|
let detailText = error.localizedDescription
|
||||||
let detailText: String?
|
|
||||||
|
|
||||||
if let failure = error.localizedFailure
|
|
||||||
{
|
|
||||||
text = failure
|
|
||||||
detailText = error.localizedFailureReason ?? error.localizedRecoverySuggestion ?? underlyingError?.localizedDescription ?? error.localizedDescription
|
|
||||||
}
|
|
||||||
else if let reason = error.localizedFailureReason
|
|
||||||
{
|
|
||||||
text = reason
|
|
||||||
detailText = error.localizedRecoverySuggestion ?? underlyingError?.localizedDescription
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
text = error.localizedDescription
|
|
||||||
detailText = underlyingError?.localizedDescription ?? error.localizedRecoverySuggestion
|
|
||||||
}
|
|
||||||
|
|
||||||
self.init(text: text, detailText: detailText)
|
self.init(text: text, detailText: detailText)
|
||||||
|
|
||||||
if let preferredDuration = preferredDuration
|
|
||||||
{
|
|
||||||
self.preferredDuration = preferredDuration
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
required init(coder aDecoder: NSCoder) {
|
required init(coder aDecoder: NSCoder) {
|
||||||
@@ -112,6 +111,18 @@ class ToastView: RSTToastView
|
|||||||
|
|
||||||
override func show(in view: UIView, duration: TimeInterval)
|
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)
|
super.show(in: view, duration: duration)
|
||||||
|
|
||||||
let announcement = (self.textLabel.text ?? "") + ". " + (self.detailTextLabel.text ?? "")
|
let announcement = (self.textLabel.text ?? "") + ". " + (self.detailTextLabel.text ?? "")
|
||||||
@@ -127,4 +138,10 @@ class ToastView: RSTToastView
|
|||||||
{
|
{
|
||||||
self.show(in: view, duration: self.preferredDuration)
|
self.show(in: view, duration: self.preferredDuration)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc
|
||||||
|
func showErrorLog() {
|
||||||
|
guard self.opensErrorLog else { return }
|
||||||
|
NotificationCenter.default.post(name: ToastView.openErrorLogNotification, object: self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
17
AltStore/Consts/Consts+Proxy.swift
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
//
|
||||||
|
// Proxy.swift
|
||||||
|
// SideStore
|
||||||
|
//
|
||||||
|
// Created by Joseph Mattiello on 11/7/22.
|
||||||
|
// Copyright © 2022 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public extension Consts {
|
||||||
|
enum Proxy {
|
||||||
|
static let address = "127.0.0.1"
|
||||||
|
static let port = "51820"
|
||||||
|
static let serverURL = "\(address):\(port)"
|
||||||
|
}
|
||||||
|
}
|
||||||
13
AltStore/Consts/Consts.swift
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
//
|
||||||
|
// Consts.swift
|
||||||
|
// SideStore
|
||||||
|
//
|
||||||
|
// Created by Joseph Mattiello on 11/7/22.
|
||||||
|
// Copyright © 2022 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public enum Consts {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
//
|
|
||||||
// NSError+AltStore.swift
|
|
||||||
// AltStore
|
|
||||||
//
|
|
||||||
// Created by Riley Testut on 3/11/20.
|
|
||||||
// Copyright © 2020 Riley Testut. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
extension NSError
|
|
||||||
{
|
|
||||||
@objc(alt_localizedFailure)
|
|
||||||
var localizedFailure: String? {
|
|
||||||
let localizedFailure = (self.userInfo[NSLocalizedFailureErrorKey] as? String) ?? (NSError.userInfoValueProvider(forDomain: self.domain)?(self, NSLocalizedFailureErrorKey) as? String)
|
|
||||||
return localizedFailure
|
|
||||||
}
|
|
||||||
|
|
||||||
func withLocalizedFailure(_ failure: String) -> NSError
|
|
||||||
{
|
|
||||||
var userInfo = self.userInfo
|
|
||||||
userInfo[NSLocalizedFailureErrorKey] = failure
|
|
||||||
userInfo[NSLocalizedDescriptionKey] = self.localizedDescription
|
|
||||||
userInfo[NSLocalizedFailureReasonErrorKey] = self.localizedFailureReason
|
|
||||||
userInfo[NSLocalizedRecoverySuggestionErrorKey] = self.localizedRecoverySuggestion
|
|
||||||
|
|
||||||
let error = NSError(domain: self.domain, code: self.code, userInfo: userInfo)
|
|
||||||
return error
|
|
||||||
}
|
|
||||||
|
|
||||||
func sanitizedForCoreData() -> NSError
|
|
||||||
{
|
|
||||||
var userInfo = self.userInfo
|
|
||||||
userInfo[NSLocalizedFailureErrorKey] = self.localizedFailure
|
|
||||||
userInfo[NSLocalizedDescriptionKey] = self.localizedDescription
|
|
||||||
userInfo[NSLocalizedFailureReasonErrorKey] = self.localizedFailureReason
|
|
||||||
userInfo[NSLocalizedRecoverySuggestionErrorKey] = self.localizedRecoverySuggestion
|
|
||||||
|
|
||||||
// Remove non-ObjC-compliant userInfo values.
|
|
||||||
userInfo["NSCodingPath"] = nil
|
|
||||||
|
|
||||||
let error = NSError(domain: self.domain, code: self.code, userInfo: userInfo)
|
|
||||||
return error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protocol ALTLocalizedError: LocalizedError, CustomNSError
|
|
||||||
{
|
|
||||||
var errorFailure: String? { get }
|
|
||||||
}
|
|
||||||
|
|
||||||
extension ALTLocalizedError
|
|
||||||
{
|
|
||||||
var errorUserInfo: [String : Any] {
|
|
||||||
let userInfo = [NSLocalizedDescriptionKey: self.errorDescription,
|
|
||||||
NSLocalizedFailureReasonErrorKey: self.failureReason,
|
|
||||||
NSLocalizedFailureErrorKey: self.errorFailure].compactMapValues { $0 }
|
|
||||||
return userInfo
|
|
||||||
}
|
|
||||||
}
|
|
||||||
74
AltStore/Extensions/OSLog+SideStore.swift
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
//
|
||||||
|
// OSLog+SideStore.swift
|
||||||
|
// SideStore
|
||||||
|
//
|
||||||
|
// Created by Joseph Mattiello on 11/16/22.
|
||||||
|
// Copyright © 2022 SideStore. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import OSLog
|
||||||
|
|
||||||
|
public let customLog = OSLog(subsystem: "org.sidestore.sidestore",
|
||||||
|
category: "ios")
|
||||||
|
|
||||||
|
|
||||||
|
public extension OSLog {
|
||||||
|
/// Error logger extension
|
||||||
|
/// - Parameters:
|
||||||
|
/// - message: String or format string
|
||||||
|
/// - args: optional args for format string
|
||||||
|
@inlinable
|
||||||
|
static func error(_ message: StaticString, _ args: CVarArg...) {
|
||||||
|
os_log(message, log: customLog, type: .error, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Info logger extension
|
||||||
|
/// - Parameters:
|
||||||
|
/// - message: String or format string
|
||||||
|
/// - args: optional args for format string
|
||||||
|
@inlinable
|
||||||
|
static func info(_ message: StaticString, _ args: CVarArg...) {
|
||||||
|
os_log(message, log: customLog, type: .info, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Debug logger extension
|
||||||
|
/// - Parameters:
|
||||||
|
/// - message: String or format string
|
||||||
|
/// - args: optional args for format string
|
||||||
|
@inlinable
|
||||||
|
static func debug(_ message: StaticString, _ args: CVarArg...) {
|
||||||
|
os_log(message, log: customLog, type: .debug, args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Add file,line,function to messages? -- @JoeMatt
|
||||||
|
|
||||||
|
/// Error logger convenience method for SideStore logging
|
||||||
|
/// - Parameters:
|
||||||
|
/// - message: String or format string
|
||||||
|
/// - args: optional args for format string
|
||||||
|
@inlinable
|
||||||
|
public func ELOG(_ message: StaticString, file: StaticString = #file, function: StaticString = #function, line: UInt = #line, _ args: CVarArg...) {
|
||||||
|
OSLog.error(message, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Info logger convenience method for SideStore logging
|
||||||
|
/// - Parameters:
|
||||||
|
/// - message: String or format string
|
||||||
|
/// - args: optional args for format string
|
||||||
|
@inlinable
|
||||||
|
public func ILOG(_ message: StaticString, file: StaticString = #file, function: StaticString = #function, line: UInt = #line, _ args: CVarArg...) {
|
||||||
|
OSLog.info(message, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Debug logger convenience method for SideStore logging
|
||||||
|
/// - Parameters:
|
||||||
|
/// - message: String or format string
|
||||||
|
/// - args: optional args for format string
|
||||||
|
@inlinable
|
||||||
|
public func DLOG(_ message: StaticString, file: StaticString = #file, function: StaticString = #function, line: UInt = #line, _ args: CVarArg...) {
|
||||||
|
OSLog.debug(message, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
// mark: Helpers
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import ARKit
|
||||||
|
|
||||||
extension UIDevice
|
extension UIDevice
|
||||||
{
|
{
|
||||||
@@ -27,4 +28,23 @@ extension UIDevice
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(iOS 14, *)
|
||||||
|
var supportsFugu14: Bool {
|
||||||
|
#if targetEnvironment(simulator)
|
||||||
|
return true
|
||||||
|
#else
|
||||||
|
// Fugu14 is supported on devices with an A12 processor or better.
|
||||||
|
// ARKit 3 is only supported by devices with an A12 processor or better, according to the documentation.
|
||||||
|
return ARBodyTrackingConfiguration.isSupported
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 14, *)
|
||||||
|
var isUntetheredJailbreakRequired: Bool {
|
||||||
|
let ios14_4 = OperatingSystemVersion(majorVersion: 14, minorVersion: 4, patchVersion: 0)
|
||||||
|
|
||||||
|
let isUntetheredJailbreakRequired = ProcessInfo.processInfo.isOperatingSystemAtLeast(ios14_4)
|
||||||
|
return isUntetheredJailbreakRequired
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||