Compare commits
669 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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.
|
||||
15
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
### Changes
|
||||
|
||||
<!-- Fill this list with what your PR changes. Example: -->
|
||||
- Fix bug
|
||||
- Change UI for QOL
|
||||
|
||||
<!-- If your PR is ready to be merged, you can remove this section. -->
|
||||
### Todo before merge
|
||||
|
||||
<!-- Example: -->
|
||||
- [x] Finish UI changes
|
||||
- [ ] Test
|
||||
|
||||
<!-- If your PR doesn't close an issue, you can remove the next line. -->
|
||||
Closes #1234
|
||||
22
.github/workflows/attach_build_products.yml
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
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
|
||||
96
.github/workflows/beta.yml
vendored
Normal file
@@ -0,0 +1,96 @@
|
||||
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: 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
|
||||
|
||||
106
.github/workflows/nightly.yml
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
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: 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/
|
||||
|
||||
- name: Reset cache for apps.sidestore.io/nightly
|
||||
run: sleep 10 && curl https://apps.sidestore.io/reset-cache/nightly/${{ secrets.SIDESOURCE_KEY }}
|
||||
64
.github/workflows/pr.yml
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
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: 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/
|
||||
93
.github/workflows/stable.yml
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
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: 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/
|
||||
20
.gitignore
vendored
@@ -8,7 +8,7 @@
|
||||
## Build generated
|
||||
build/
|
||||
DerivedData
|
||||
|
||||
archive.xcarchive
|
||||
## Various settings
|
||||
*.pbxuser
|
||||
!default.pbxuser
|
||||
@@ -27,4 +27,20 @@ xcuserdata
|
||||
*.xcscmblueprint
|
||||
|
||||
## 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"]
|
||||
path = Dependencies/Roxas
|
||||
url = https://github.com/rileytestut/Roxas.git
|
||||
[submodule "Dependencies/AltSign"]
|
||||
path = Dependencies/AltSign
|
||||
url = https://github.com/rileytestut/AltSign.git
|
||||
[submodule "Dependencies/libimobiledevice"]
|
||||
path = Dependencies/libimobiledevice
|
||||
url = https://github.com/rileytestut/libimobiledevice.git
|
||||
url = https://github.com/libimobiledevice/libimobiledevice
|
||||
[submodule "Dependencies/libusbmuxd"]
|
||||
path = Dependencies/libusbmuxd
|
||||
url = https://github.com/libimobiledevice/libusbmuxd.git
|
||||
[submodule "Dependencies/libplist"]
|
||||
path = Dependencies/libplist
|
||||
url = https://github.com/libimobiledevice/libplist.git
|
||||
url = https://github.com/SideStore/libplist.git
|
||||
[submodule "Dependencies/MarkdownAttributedString"]
|
||||
path = Dependencies/MarkdownAttributedString
|
||||
url = https://github.com/chockenberry/MarkdownAttributedString.git
|
||||
[submodule "Dependencies/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>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.com.rileytestut.AltStore</string>
|
||||
<string>group.$(APP_GROUP_IDENTIFIER)</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.518",
|
||||
"green" : "0.502",
|
||||
"red" : "0.004"
|
||||
"blue" : "175",
|
||||
"green" : "4",
|
||||
"red" : "115"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
@@ -23,9 +23,9 @@
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.404",
|
||||
"green" : "0.322",
|
||||
"red" : "0.008"
|
||||
"blue" : "150",
|
||||
"green" : "3",
|
||||
"red" : "99"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
|
||||
@@ -38,7 +38,7 @@ struct BackupError: ALTLocalizedError
|
||||
let sourceFile: String
|
||||
let sourceFileLine: Int
|
||||
|
||||
var errorFailure: String?
|
||||
var failure: String?
|
||||
|
||||
var failureReason: String? {
|
||||
switch self.code
|
||||
@@ -60,7 +60,7 @@ struct BackupError: ALTLocalizedError
|
||||
var errorUserInfo: [String : Any] {
|
||||
let userInfo: [String: Any?] = [NSLocalizedDescriptionKey: self.errorDescription,
|
||||
NSLocalizedFailureReasonErrorKey: self.failureReason,
|
||||
NSLocalizedFailureErrorKey: self.errorFailure,
|
||||
NSLocalizedFailureErrorKey: self.failure,
|
||||
ErrorUserInfoKey.sourceFile: self.sourceFile,
|
||||
ErrorUserInfoKey.sourceFileLine: self.sourceFileLine]
|
||||
return userInfo.compactMapValues { $0 }
|
||||
@@ -69,7 +69,7 @@ struct BackupError: ALTLocalizedError
|
||||
init(_ code: Code, description: String? = nil, file: String = #file, line: Int = #line)
|
||||
{
|
||||
self.code = code
|
||||
self.errorFailure = description
|
||||
self.failure = description
|
||||
self.sourceFile = file
|
||||
self.sourceFileLine = line
|
||||
}
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
<dict>
|
||||
<key>ALTAppGroups</key>
|
||||
<array>
|
||||
<string>group.com.rileytestut.AltStore</string>
|
||||
<string>group.$(APP_GROUP_IDENTIFIER)</string>
|
||||
<string>group.com.SideStore.SideStore</string>
|
||||
</array>
|
||||
<key>ALTBundleIdentifier</key>
|
||||
<string>com.rileytestut.AltBackup</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
|
||||
@@ -160,7 +160,7 @@ private extension ViewController
|
||||
self.textLabel.text = String(format: NSLocalizedString("%@ is inactive.", 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: ""))
|
||||
|
||||
self.detailTextLabel.isHidden = false
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>application-identifier</key>
|
||||
<string>6XVY5G3U44.com.rileytestut.AltDaemon</string>
|
||||
<string>$(DEVELOPMENT_TEAM).$(ORG_IDENTIFIER).AltDaemon</string>
|
||||
<key>get-task-allow</key>
|
||||
<true/>
|
||||
<key>platform-application</key>
|
||||
|
||||
@@ -77,7 +77,7 @@ extension XPCConnectionHandler: NSXPCListenerDelegate
|
||||
guard
|
||||
let codeSigningInfo = signingInfo as? [String: Any],
|
||||
let bundleIdentifier = codeSigningInfo["identifier"] as? String,
|
||||
bundleIdentifier.contains("com.rileytestut.AltStore")
|
||||
bundleIdentifier.contains(Bundle.Info.appbundleIdentifier)
|
||||
else { return false }
|
||||
|
||||
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
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:AltStore.xcodeproj">
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
{
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "altsign",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/SideStore/AltSign",
|
||||
"state" : {
|
||||
"branch" : "master",
|
||||
"revision" : "cc6189f0f7cd8e5bd24943af9322e0ff9420e9f4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "appcenter-sdk-apple",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/microsoft/appcenter-sdk-apple.git",
|
||||
"state" : {
|
||||
"revision" : "b2dc99cfedead0bad4e6573d86c5228c89cff332",
|
||||
"version" : "4.4.3"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "imobiledevice.swift",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/SideStore/iMobileDevice.swift",
|
||||
"state" : {
|
||||
"revision" : "74e481106dd155c0cd21bca6795fd9fe5f751654",
|
||||
"version" : "1.0.5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "keychainaccess",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/kishikawakatsumi/KeychainAccess.git",
|
||||
"state" : {
|
||||
"revision" : "84e546727d66f1adc5439debad16270d0fdd04e7",
|
||||
"version" : "4.2.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "launchatlogin",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/sindresorhus/LaunchAtLogin.git",
|
||||
"state" : {
|
||||
"revision" : "e8171b3e38a2816f579f58f3dac1522aa39efe41",
|
||||
"version" : "4.2.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "nuke",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/kean/Nuke.git",
|
||||
"state" : {
|
||||
"revision" : "9318d02a8a6d20af56505c9673261c1fd3b3aebe",
|
||||
"version" : "7.6.3"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "openssl",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/krzyzanowskim/OpenSSL",
|
||||
"state" : {
|
||||
"revision" : "0faf71a188bcfdf0245cab42886b9b240ca71c52",
|
||||
"version" : "1.1.2200"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "plcrashreporter",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/microsoft/PLCrashReporter.git",
|
||||
"state" : {
|
||||
"revision" : "81cdec2b3827feb03286cb297f4c501a8eb98df1",
|
||||
"version" : "1.10.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "semanticversion",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/SwiftPackageIndex/SemanticVersion.git",
|
||||
"state" : {
|
||||
"revision" : "a70840d5fca686ae3bd2fcf8aecc5ded0bd4f125",
|
||||
"version" : "0.3.6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "sparkle",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/sparkle-project/Sparkle.git",
|
||||
"state" : {
|
||||
"revision" : "f0ceaf5cc9f3f23daa0ccb6dcebd79fc96ccc7d9",
|
||||
"version" : "2.5.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "starscream",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/daltoniam/Starscream.git",
|
||||
"state" : {
|
||||
"revision" : "ac6c0fc9da221873e01bd1a0d4818498a71eef33",
|
||||
"version" : "4.0.6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "stprivilegedtask",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/JoeMatt/STPrivilegedTask.git",
|
||||
"state" : {
|
||||
"branch" : "master",
|
||||
"revision" : "10a9150ef32d444af326beba76356ae9af95a3e7"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 2
|
||||
}
|
||||
@@ -14,26 +14,24 @@
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "E32E9B621EB87D7B000FEEE9"
|
||||
BuildableName = "LaunchAtLogin.framework"
|
||||
BlueprintName = "LaunchAtLogin"
|
||||
ReferencedContainer = "container:LaunchAtLogin.xcodeproj">
|
||||
BlueprintIdentifier = "BFD247692284B9A500981D42"
|
||||
BuildableName = "SideStore.app"
|
||||
BlueprintName = "SideStore"
|
||||
ReferencedContainer = "container:AltStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
buildConfiguration = "Release"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
buildConfiguration = "Release"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
@@ -42,17 +40,22 @@
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "E32E9B621EB87D7B000FEEE9"
|
||||
BuildableName = "LaunchAtLogin.framework"
|
||||
BlueprintName = "LaunchAtLogin"
|
||||
ReferencedContainer = "container:LaunchAtLogin.xcodeproj">
|
||||
BlueprintIdentifier = "BFD247692284B9A500981D42"
|
||||
BuildableName = "SideStore.app"
|
||||
BlueprintName = "SideStore"
|
||||
ReferencedContainer = "container:AltStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</BuildableProductRunnable>
|
||||
<CommandLineArguments>
|
||||
<CommandLineArgument
|
||||
argument = "-com.apple.CoreData.ConcurrencyDebug 1"
|
||||
isEnabled = "YES">
|
||||
</CommandLineArgument>
|
||||
</CommandLineArguments>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
@@ -60,18 +63,19 @@
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "E32E9B621EB87D7B000FEEE9"
|
||||
BuildableName = "LaunchAtLogin.framework"
|
||||
BlueprintName = "LaunchAtLogin"
|
||||
ReferencedContainer = "container:LaunchAtLogin.xcodeproj">
|
||||
BlueprintIdentifier = "BFD247692284B9A500981D42"
|
||||
BuildableName = "SideStore.app"
|
||||
BlueprintName = "SideStore"
|
||||
ReferencedContainer = "container:AltStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
buildConfiguration = "Release">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
@@ -15,8 +15,8 @@
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "BFD247692284B9A500981D42"
|
||||
BuildableName = "AltStore.app"
|
||||
BlueprintName = "AltStore"
|
||||
BuildableName = "SideStore.app"
|
||||
BlueprintName = "SideStore"
|
||||
ReferencedContainer = "container:AltStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
@@ -45,8 +45,8 @@
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "BFD247692284B9A500981D42"
|
||||
BuildableName = "AltStore.app"
|
||||
BlueprintName = "AltStore"
|
||||
BuildableName = "SideStore.app"
|
||||
BlueprintName = "SideStore"
|
||||
ReferencedContainer = "container:AltStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
@@ -68,8 +68,8 @@
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "BFD247692284B9A500981D42"
|
||||
BuildableName = "AltStore.app"
|
||||
BlueprintName = "AltStore"
|
||||
BuildableName = "SideStore.app"
|
||||
BlueprintName = "SideStore"
|
||||
ReferencedContainer = "container:AltStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</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 "ALTAppPatcher.h"
|
||||
|
||||
#include "fragmentzip.h"
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<true/>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.com.rileytestut.AltStore</string>
|
||||
<string>group.$(APP_GROUP_IDENTIFIER)</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -14,13 +14,7 @@ import AppCenter
|
||||
import AppCenterAnalytics
|
||||
import AppCenterCrashes
|
||||
|
||||
#if DEBUG
|
||||
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
|
||||
private let appCenterAppSecret = "73532d3e-e573-4693-99a4-9f85840bbb44"
|
||||
|
||||
extension AnalyticsManager
|
||||
{
|
||||
@@ -77,7 +71,7 @@ extension AnalyticsManager
|
||||
}
|
||||
}
|
||||
|
||||
class AnalyticsManager
|
||||
final class AnalyticsManager
|
||||
{
|
||||
static let shared = AnalyticsManager()
|
||||
|
||||
@@ -90,9 +84,9 @@ extension AnalyticsManager
|
||||
{
|
||||
func start()
|
||||
{
|
||||
MSAppCenter.start(appCenterAppSecret, withServices:[
|
||||
MSAnalytics.self,
|
||||
MSCrashes.self
|
||||
AppCenter.start(withAppSecret: appCenterAppSecret, services: [
|
||||
Analytics.self,
|
||||
Crashes.self
|
||||
])
|
||||
}
|
||||
|
||||
@@ -102,6 +96,6 @@ extension AnalyticsManager
|
||||
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!
|
||||
|
||||
@@ -80,10 +80,21 @@ class AppContentViewController: UITableViewController
|
||||
|
||||
self.subtitleLabel.text = self.app.subtitle
|
||||
self.descriptionTextView.text = self.app.localizedDescription
|
||||
self.versionDescriptionTextView.text = self.app.versionDescription
|
||||
self.versionLabel.text = String(format: NSLocalizedString("Version %@", comment: ""), self.app.version)
|
||||
self.versionDateLabel.text = Date().relativeDateString(since: self.app.versionDate, dateFormatter: self.dateFormatter)
|
||||
self.sizeLabel.text = self.byteCountFormatter.string(fromByteCount: Int64(self.app.size))
|
||||
|
||||
if let version = self.app.latestVersion
|
||||
{
|
||||
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.moreButton.addTarget(self, action: #selector(AppContentViewController.toggleCollapsingSection(_:)), for: .primaryActionTriggered)
|
||||
@@ -138,7 +149,8 @@ private extension AppContentViewController
|
||||
}
|
||||
dataSource.prefetchHandler = { (imageURL, indexPath, completionHandler) 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() }
|
||||
|
||||
if let image = response?.image
|
||||
@@ -172,7 +184,8 @@ private extension AppContentViewController
|
||||
dataSource.cellConfigurationHandler = { (cell, permission, indexPath) in
|
||||
let cell = cell as! PermissionCollectionViewCell
|
||||
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
|
||||
@@ -208,10 +221,19 @@ extension AppContentViewController
|
||||
|
||||
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
|
||||
{
|
||||
guard indexPath.row == Row.screenshots.rawValue else { return super.tableView(tableView, heightForRowAt: indexPath) }
|
||||
|
||||
guard let size = self.preferredScreenshotSize else { return 0.0 }
|
||||
return size.height
|
||||
switch Row.allCases[indexPath.row]
|
||||
{
|
||||
case .screenshots:
|
||||
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
|
||||
|
||||
class PermissionCollectionViewCell: UICollectionViewCell
|
||||
final class PermissionCollectionViewCell: UICollectionViewCell
|
||||
{
|
||||
@IBOutlet var button: UIButton!
|
||||
@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
|
||||
{
|
||||
|
||||
@@ -13,7 +13,7 @@ import Roxas
|
||||
|
||||
import Nuke
|
||||
|
||||
class AppViewController: UIViewController
|
||||
final class AppViewController: UIViewController
|
||||
{
|
||||
var app: StoreApp!
|
||||
|
||||
@@ -188,6 +188,13 @@ class AppViewController: UIViewController
|
||||
|
||||
self.contentViewController = segue.destination as? AppContentViewController
|
||||
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()
|
||||
@@ -210,8 +217,8 @@ class AppViewController: UIViewController
|
||||
|
||||
self._shouldResetLayout = false
|
||||
}
|
||||
|
||||
let statusBarHeight = UIApplication.shared.statusBarFrame.height
|
||||
|
||||
let statusBarHeight = self.view.window?.windowScene?.statusBarManager?.statusBarFrame.height ?? 0
|
||||
let cornerRadius = self.contentViewControllerShadowView.layer.cornerRadius
|
||||
|
||||
let inset = 12 as CGFloat
|
||||
@@ -316,7 +323,7 @@ class AppViewController: UIViewController
|
||||
|
||||
self.backButtonContainerView.layer.cornerRadius = self.backButtonContainerView.bounds.midY
|
||||
|
||||
self.scrollView.scrollIndicatorInsets.top = statusBarHeight
|
||||
self.scrollView.verticalScrollIndicatorInsets.top = statusBarHeight
|
||||
|
||||
// Adjust content offset + size.
|
||||
let contentOffset = self.scrollView.contentOffset
|
||||
@@ -345,7 +352,7 @@ class AppViewController: UIViewController
|
||||
|
||||
extension AppViewController
|
||||
{
|
||||
class func makeAppViewController(app: StoreApp) -> AppViewController
|
||||
final class func makeAppViewController(app: StoreApp) -> AppViewController
|
||||
{
|
||||
let storyboard = UIStoryboard(name: "Main", bundle: nil)
|
||||
|
||||
@@ -377,10 +384,10 @@ private extension AppViewController
|
||||
button.progress = progress
|
||||
}
|
||||
|
||||
if Date() < self.app.versionDate
|
||||
if let versionDate = self.app.latestVersion?.date, versionDate > Date()
|
||||
{
|
||||
self.bannerView.button.countdownDate = self.app.versionDate
|
||||
self.navigationBarDownloadButton.countdownDate = self.app.versionDate
|
||||
self.bannerView.button.countdownDate = versionDate
|
||||
self.navigationBarDownloadButton.countdownDate = versionDate
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -491,7 +498,7 @@ extension AppViewController
|
||||
{
|
||||
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
|
||||
{
|
||||
_ = try result.get()
|
||||
@@ -515,8 +522,8 @@ extension AppViewController
|
||||
}
|
||||
}
|
||||
|
||||
self.bannerView.button.progress = progress
|
||||
self.navigationBarDownloadButton.progress = progress
|
||||
self.bannerView.button.progress = group.progress
|
||||
self.navigationBarDownloadButton.progress = group.progress
|
||||
}
|
||||
|
||||
func open(_ installedApp: InstalledApp)
|
||||
|
||||
@@ -10,7 +10,7 @@ import UIKit
|
||||
|
||||
import AltStoreCore
|
||||
|
||||
class PermissionPopoverViewController: UIViewController
|
||||
final class PermissionPopoverViewController: UIViewController
|
||||
{
|
||||
var permission: AppPermission!
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import UIKit
|
||||
import AltStoreCore
|
||||
import Roxas
|
||||
|
||||
class AppIDsViewController: UICollectionViewController
|
||||
final class AppIDsViewController: UICollectionViewController
|
||||
{
|
||||
private lazy var dataSource = self.makeDataSource()
|
||||
|
||||
@@ -90,14 +90,21 @@ private extension AppIDsViewController
|
||||
cell.bannerView.button.isUserInteractionEnabled = false
|
||||
|
||||
cell.bannerView.buttonLabel.isHidden = false
|
||||
|
||||
|
||||
let currentDate = Date()
|
||||
|
||||
let numberOfDays = expirationDate.numberOfCalendarDays(since: currentDate)
|
||||
let numberOfDaysText = (numberOfDays == 1) ? NSLocalizedString("1 day", comment: "") : String(format: NSLocalizedString("%@ days", comment: ""), NSNumber(value: numberOfDays))
|
||||
cell.bannerView.button.setTitle(numberOfDaysText.uppercased(), for: .normal)
|
||||
let formatter = DateComponentsFormatter()
|
||||
formatter.unitsStyle = .full
|
||||
formatter.includesApproximationPhrase = false
|
||||
formatter.includesTimeRemainingPhrase = false
|
||||
formatter.allowedUnits = [.minute, .hour, .day]
|
||||
formatter.maximumUnitCount = 1
|
||||
|
||||
attributedAccessibilityLabel.mutableString.append(String(format: NSLocalizedString("Expires in %@.", comment: ""), numberOfDaysText) + " ")
|
||||
cell.bannerView.button.setTitle((formatter.string(from: currentDate, to: expirationDate) ?? NSLocalizedString("Unknown", comment: "")).uppercased(), for: .normal)
|
||||
|
||||
// formatter.includesTimeRemainingPhrase = true
|
||||
|
||||
// attributedAccessibilityLabel.mutableString.append((formatter.string(from: currentDate, to: expirationDate) ?? NSLocalizedString("Unknown", comment: "")) + " ")
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -201,9 +208,9 @@ extension AppIDsViewController: UICollectionViewDelegateFlowLayout
|
||||
if let activeTeam = DatabaseManager.shared.activeTeam(), activeTeam.type == .free
|
||||
{
|
||||
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: "")
|
||||
|
||||
let attributedText = NSAttributedString(markdownRepresentation: text, attributes: [.font: headerView.textLabel.font as Any])
|
||||
@@ -212,7 +219,7 @@ extension AppIDsViewController: UICollectionViewDelegateFlowLayout
|
||||
else
|
||||
{
|
||||
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.
|
||||
""", comment: "")
|
||||
|
||||
@@ -14,14 +14,15 @@ import Intents
|
||||
import AltStoreCore
|
||||
import AltSign
|
||||
import Roxas
|
||||
import EmotionalDamage
|
||||
|
||||
extension AppDelegate
|
||||
{
|
||||
static let openPatreonSettingsDeepLinkNotification = Notification.Name("com.rileytestut.AltStore.OpenPatreonSettingsDeepLinkNotification")
|
||||
static let importAppDeepLinkNotification = Notification.Name("com.rileytestut.AltStore.ImportAppDeepLinkNotification")
|
||||
static let addSourceDeepLinkNotification = Notification.Name("com.rileytestut.AltStore.AddSourceDeepLinkNotification")
|
||||
static let openPatreonSettingsDeepLinkNotification = Notification.Name(Bundle.Info.appbundleIdentifier + ".OpenPatreonSettingsDeepLinkNotification")
|
||||
static let importAppDeepLinkNotification = Notification.Name(Bundle.Info.appbundleIdentifier + ".ImportAppDeepLinkNotification")
|
||||
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 appBackupResultKey = "result"
|
||||
@@ -29,16 +30,32 @@ extension AppDelegate
|
||||
}
|
||||
|
||||
@UIApplicationMain
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
final class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
var window: UIWindow?
|
||||
|
||||
@available(iOS 14, *)
|
||||
private lazy var intentHandler = IntentHandler()
|
||||
private var intentHandler: IntentHandler {
|
||||
get { _intentHandler as! IntentHandler }
|
||||
set { _intentHandler = newValue }
|
||||
}
|
||||
|
||||
@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
|
||||
{
|
||||
// Register default settings before doing anything else.
|
||||
@@ -59,8 +76,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
self.setTintColor()
|
||||
|
||||
ServerManager.shared.startDiscovering()
|
||||
|
||||
SecureValueTransformer.register()
|
||||
|
||||
if UserDefaults.standard.firstLaunch == nil
|
||||
@@ -82,15 +97,25 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
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)
|
||||
{
|
||||
AppManager.shared.update()
|
||||
ServerManager.shared.startDiscovering()
|
||||
|
||||
PatreonAPI.shared.refreshPatreonAccount()
|
||||
start_em_proxy(bind_addr: Consts.Proxy.serverURL)
|
||||
}
|
||||
|
||||
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any]) -> Bool
|
||||
@@ -251,7 +276,7 @@ extension AppDelegate
|
||||
|
||||
let content = UNMutableNotificationContent()
|
||||
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)
|
||||
UNUserNotificationCenter.current().add(request)
|
||||
@@ -355,11 +380,11 @@ private extension AppDelegate
|
||||
for update in updates
|
||||
{
|
||||
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.version else { continue }
|
||||
|
||||
let content = UNMutableNotificationContent()
|
||||
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
|
||||
|
||||
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
|
||||
@@ -379,7 +404,7 @@ private extension AppDelegate
|
||||
}
|
||||
else
|
||||
{
|
||||
content.title = NSLocalizedString("AltStore News", comment: "")
|
||||
content.title = NSLocalizedString("SideStore News", comment: "")
|
||||
}
|
||||
|
||||
content.body = newsItem.title
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?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="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15703"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
|
||||
<capability name="Named colors" minToolsVersion="9.0"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
@@ -56,14 +56,14 @@
|
||||
<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"/>
|
||||
<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">
|
||||
<rect key="frame" x="0.0" y="0.0" width="333.5" height="41"/>
|
||||
<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="332" height="41"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="34"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</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">
|
||||
<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"/>
|
||||
<color key="textColor" white="1" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
@@ -160,7 +160,7 @@
|
||||
</stackView>
|
||||
</subviews>
|
||||
</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"/>
|
||||
<color key="backgroundColor" name="SettingsHighlighted"/>
|
||||
<constraints>
|
||||
@@ -210,6 +210,7 @@
|
||||
</constraints>
|
||||
</scrollView>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="zMn-DV-fpy"/>
|
||||
<color key="backgroundColor" name="SettingsBackground"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="bottom" secondItem="WXx-hX-AXv" secondAttribute="bottom" id="0jL-Ky-ju6"/>
|
||||
@@ -229,7 +230,6 @@
|
||||
<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"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="zMn-DV-fpy"/>
|
||||
</view>
|
||||
<toolbarItems/>
|
||||
<navigationItem key="navigationItem" largeTitleDisplayMode="never" id="jCf-N4-xVD">
|
||||
@@ -281,13 +281,13 @@
|
||||
<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"/>
|
||||
<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"/>
|
||||
<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" 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"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="16"/>
|
||||
<color key="textColor" white="1" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
@@ -310,16 +310,16 @@
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<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" width="264" height="61.5"/>
|
||||
<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"/>
|
||||
<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" 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">
|
||||
<rect key="frame" x="0.0" y="25.5" width="264" height="38.5"/>
|
||||
<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="36"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="16"/>
|
||||
<color key="textColor" white="1" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
@@ -349,7 +349,7 @@
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</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"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="16"/>
|
||||
<color key="textColor" white="1" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
@@ -372,7 +372,7 @@
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<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>
|
||||
<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"/>
|
||||
@@ -380,8 +380,8 @@
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</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">
|
||||
<rect key="frame" x="0.0" y="25.5" width="264" height="38.5"/>
|
||||
<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="36.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="16"/>
|
||||
<color key="textColor" white="1" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
@@ -393,7 +393,7 @@
|
||||
</subviews>
|
||||
<edgeInsets key="layoutMargins" top="35" left="0.0" bottom="35" right="0.0"/>
|
||||
</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"/>
|
||||
<color key="backgroundColor" name="SettingsHighlighted"/>
|
||||
<constraints>
|
||||
@@ -408,6 +408,7 @@
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="Zek-aC-HOO"/>
|
||||
<color key="backgroundColor" name="SettingsBackground"/>
|
||||
<constraints>
|
||||
<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 firstItem="qZ9-AR-2zK" firstAttribute="leading" secondItem="Otz-hn-WGS" secondAttribute="leadingMargin" id="t2b-3e-6ld"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="Zek-aC-HOO"/>
|
||||
</view>
|
||||
<navigationItem key="navigationItem" title="How it works" largeTitleDisplayMode="always" id="bCq-Jq-gf1"/>
|
||||
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" prompted="NO"/>
|
||||
@@ -445,7 +445,7 @@
|
||||
<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"/>
|
||||
<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="AltStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="343" height="51"/>
|
||||
<color key="backgroundColor" name="SettingsHighlighted"/>
|
||||
<constraints>
|
||||
@@ -460,7 +460,7 @@
|
||||
<action selector="refreshAltStore:" destination="aoK-yE-UVT" eventType="primaryActionTriggered" id="WQu-9b-Zgg"/>
|
||||
</connections>
|
||||
</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"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
|
||||
<state key="normal" title="Refresh Later">
|
||||
@@ -473,6 +473,7 @@
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="iwE-xE-ziz"/>
|
||||
<color key="backgroundColor" name="SettingsBackground"/>
|
||||
<constraints>
|
||||
<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="tDQ-ao-1Jg" firstAttribute="leading" secondItem="R83-kV-365" secondAttribute="leadingMargin" id="zEt-Xr-kJx"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="iwE-xE-ziz"/>
|
||||
</view>
|
||||
<navigationItem key="navigationItem" title="Refresh AltStore" largeTitleDisplayMode="always" id="5nk-NR-jtV"/>
|
||||
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" prompted="NO"/>
|
||||
@@ -493,10 +493,72 @@
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="Chr-7g-qEw" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="2101.5999999999999" y="733.5832083958021"/>
|
||||
<point key="canvasLocation" x="2967" y="736"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<!--Select a Team-->
|
||||
<scene sceneID="ioQ-WB-CLJ">
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="selectTeamViewController" hidesBottomBarWhenPushed="YES" id="kOD-4P-a6L" customClass="SelectTeamViewController" customModule="AltStore" 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="AltStore" 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" 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="1401" y="734"/>
|
||||
|
||||
</scene>
|
||||
</scenes>
|
||||
<color key="tintColor" name="Primary"/>
|
||||
<resources>
|
||||
<namedColor name="Primary">
|
||||
<color red="0.0040000001899898052" green="0.50199997425079346" blue="0.51800000667572021" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</namedColor>
|
||||
<namedColor name="SettingsBackground">
|
||||
<color red="0.0039215686274509803" green="0.50196078431372548" blue="0.51764705882352946" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</namedColor>
|
||||
@@ -504,5 +566,4 @@
|
||||
<color red="0.0080000003799796104" green="0.32199999690055847" blue="0.40400001406669617" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</namedColor>
|
||||
</resources>
|
||||
<color key="tintColor" name="Primary"/>
|
||||
</document>
|
||||
|
||||
@@ -10,7 +10,7 @@ import UIKit
|
||||
|
||||
import AltSign
|
||||
|
||||
class AuthenticationViewController: UIViewController
|
||||
final class AuthenticationViewController: UIViewController
|
||||
{
|
||||
var authenticationHandler: ((String, String, @escaping (Result<(ALTAccount, ALTAppleAPISession), Error>) -> Void) -> Void)?
|
||||
var completionHandler: (((ALTAccount, ALTAppleAPISession, String)?) -> Void)?
|
||||
@@ -31,7 +31,7 @@ class AuthenticationViewController: UIViewController
|
||||
{
|
||||
super.viewDidLoad()
|
||||
|
||||
self.signInButton.activityIndicatorView.style = .white
|
||||
self.signInButton.activityIndicatorView.style = .medium
|
||||
|
||||
for view in [self.appleIDBackgroundView!, self.passwordBackgroundView!, self.signInButton!]
|
||||
{
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class InstructionsViewController: UIViewController
|
||||
final class InstructionsViewController: UIViewController
|
||||
{
|
||||
var completionHandler: (() -> Void)?
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import AltStoreCore
|
||||
import AltSign
|
||||
import Roxas
|
||||
|
||||
class RefreshAltStoreViewController: UIViewController
|
||||
final class RefreshAltStoreViewController: UIViewController
|
||||
{
|
||||
var context: AuthenticatedOperationContext!
|
||||
|
||||
@@ -28,7 +28,7 @@ class RefreshAltStoreViewController: UIViewController
|
||||
|
||||
self.placeholderView.detailTextLabel.textAlignment = .left
|
||||
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.
|
||||
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
|
||||
{
|
||||
case .success: self.completionHandler?(.success(()))
|
||||
@@ -58,7 +58,7 @@ private extension RefreshAltStoreViewController
|
||||
sender.progress = nil
|
||||
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
|
||||
refresh()
|
||||
}))
|
||||
@@ -71,7 +71,7 @@ private extension RefreshAltStoreViewController
|
||||
}
|
||||
}
|
||||
|
||||
sender.progress = progress
|
||||
sender.progress = group.progress
|
||||
}
|
||||
|
||||
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"?>
|
||||
<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"/>
|
||||
<dependencies>
|
||||
<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="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
@@ -13,7 +13,7 @@
|
||||
<!--Launch View Controller-->
|
||||
<scene sceneID="q24-yd-v7v">
|
||||
<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">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
@@ -28,7 +28,7 @@
|
||||
<!--Tab Bar Controller-->
|
||||
<scene sceneID="yl2-sM-qoP">
|
||||
<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">
|
||||
<rect key="frame" x="0.0" y="975" width="768" height="49"/>
|
||||
<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="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="bTL-bY-9Yq" kind="presentation" identifier="finishJailbreak" id="cIc-Ta-uNk"/>
|
||||
</connections>
|
||||
</tabBarController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="HuB-VB-40B" sceneMemberID="firstResponder"/>
|
||||
@@ -49,7 +50,7 @@
|
||||
<!--Browse-->
|
||||
<scene sceneID="rXq-UR-qQp">
|
||||
<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">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
@@ -84,7 +85,7 @@
|
||||
<!--App View Controller-->
|
||||
<scene sceneID="TgT-LO-3Er">
|
||||
<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">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<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">
|
||||
<rect key="frame" x="47" y="238" width="85" height="35"/>
|
||||
<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"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="35" id="aMS-N7-WYn"/>
|
||||
@@ -130,7 +131,7 @@
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Qlg-m3-lXg">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<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"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
</view>
|
||||
@@ -191,7 +192,7 @@
|
||||
</view>
|
||||
<navigationItem key="navigationItem" largeTitleDisplayMode="never" id="maq-gT-QcS">
|
||||
<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"/>
|
||||
<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"/>
|
||||
@@ -228,7 +229,7 @@
|
||||
<!--App-->
|
||||
<scene sceneID="CgX-7h-sRI">
|
||||
<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">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
@@ -237,7 +238,7 @@
|
||||
<tableViewSection id="rfR-32-T0h">
|
||||
<cells>
|
||||
<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"/>
|
||||
<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"/>
|
||||
@@ -261,7 +262,7 @@
|
||||
<inset key="separatorInset" minX="15" minY="0.0" maxX="15" maxY="0.0"/>
|
||||
</tableViewCell>
|
||||
<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"/>
|
||||
<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"/>
|
||||
@@ -298,14 +299,14 @@
|
||||
<color key="backgroundColor" name="Background"/>
|
||||
<inset key="separatorInset" minX="15" minY="0.0" maxX="15" maxY="0.0"/>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" id="EL5-UC-RIw" customClass="AppContentTableViewCell" customModule="AltStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="129" width="375" height="44"/>
|
||||
<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="151" width="375" height="98"/>
|
||||
<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">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
|
||||
<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="98"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<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"/>
|
||||
<color key="backgroundColor" name="Background"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
||||
@@ -313,8 +314,8 @@
|
||||
</textView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="Pyt-8D-BZA" firstAttribute="top" secondItem="D1G-nK-G0Z" secondAttribute="top" constant="20" id="Lm9-lx-kJ8"/>
|
||||
<constraint firstAttribute="bottom" secondItem="Pyt-8D-BZA" secondAttribute="bottom" constant="44" id="TSS-Au-gYx"/>
|
||||
<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" 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 firstAttribute="trailing" secondItem="Pyt-8D-BZA" secondAttribute="trailing" constant="20" id="Wq4-Ql-wvN"/>
|
||||
</constraints>
|
||||
@@ -322,30 +323,30 @@
|
||||
<color key="backgroundColor" name="Background"/>
|
||||
<inset key="separatorInset" minX="1000" minY="0.0" maxX="0.0" maxY="0.0"/>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" id="47M-El-a4G" customClass="AppContentTableViewCell" customModule="AltStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="173" width="375" height="44"/>
|
||||
<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="249" width="375" height="137.5"/>
|
||||
<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">
|
||||
<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"/>
|
||||
<subviews>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</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">
|
||||
<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"/>
|
||||
<color key="textColor" white="0.5" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
@@ -353,16 +354,16 @@
|
||||
</subviews>
|
||||
</stackView>
|
||||
<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>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="751" text="Version 4.4.2" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="7E0-TV-G4l">
|
||||
<rect key="frame" x="0.0" y="0.0" width="84.5" height="0.0"/>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="751" text="Version 0.5.6" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="7E0-TV-G4l">
|
||||
<rect key="frame" x="0.0" y="0.0" width="84" height="17"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</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">
|
||||
<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"/>
|
||||
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
@@ -371,8 +372,8 @@
|
||||
</stackView>
|
||||
</subviews>
|
||||
</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">
|
||||
<rect key="frame" x="20" y="16" width="335" height="0.0"/>
|
||||
<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="59.5" width="335" height="34"/>
|
||||
<color key="backgroundColor" name="Background"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||
@@ -392,7 +393,7 @@
|
||||
<inset key="separatorInset" minX="1000" minY="0.0" maxX="0.0" maxY="0.0"/>
|
||||
</tableViewCell>
|
||||
<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"/>
|
||||
<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"/>
|
||||
@@ -411,7 +412,7 @@
|
||||
<rect key="frame" x="0.0" y="41" width="375" height="88"/>
|
||||
<color key="backgroundColor" name="Background"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="88" id="6Lk-OO-MsA"/>
|
||||
<constraint firstAttribute="height" priority="999" constant="88" id="6Lk-OO-MsA"/>
|
||||
</constraints>
|
||||
<collectionViewFlowLayout key="collectionViewLayout" scrollDirection="horizontal" minimumLineSpacing="10" minimumInteritemSpacing="10" id="2HF-4d-3Im">
|
||||
<size key="itemSize" width="60" height="88"/>
|
||||
@@ -420,15 +421,15 @@
|
||||
<inset key="sectionInset" minX="20" minY="0.0" maxX="20" maxY="0.0"/>
|
||||
</collectionViewFlowLayout>
|
||||
<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"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<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"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" 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"/>
|
||||
<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="60" height="87.5"/>
|
||||
<subviews>
|
||||
<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"/>
|
||||
@@ -510,7 +511,7 @@ World</string>
|
||||
<!--Permission Popover View Controller-->
|
||||
<scene sceneID="24j-EJ-G4e">
|
||||
<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">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="217"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
@@ -565,7 +566,7 @@ World</string>
|
||||
<!--News-->
|
||||
<scene sceneID="bqw-wB-hyB">
|
||||
<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">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
@@ -591,11 +592,11 @@ World</string>
|
||||
<!--Browse-->
|
||||
<scene sceneID="VHa-uP-bFU">
|
||||
<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"/>
|
||||
<toolbarItems/>
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="dIv-qd-9L5" customClass="NavigationBar" customModule="AltStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="96"/>
|
||||
<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="20" width="375" height="96"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<color key="tintColor" name="Primary"/>
|
||||
</navigationBar>
|
||||
@@ -608,16 +609,24 @@ World</string>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="962" y="-17"/>
|
||||
</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-->
|
||||
<scene sceneID="nhh-BJ-XiT">
|
||||
<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">
|
||||
<color key="badgeColor" name="Primary"/>
|
||||
</tabBarItem>
|
||||
<toolbarItems/>
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="CzO-Kt-BlZ" customClass="NavigationBar" customModule="AltStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="96"/>
|
||||
<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="20" width="375" height="96"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</navigationBar>
|
||||
<nil name="viewControllers"/>
|
||||
@@ -632,7 +641,7 @@ World</string>
|
||||
<!--My Apps-->
|
||||
<scene sceneID="EC8-Sf-AF9">
|
||||
<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">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<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"/>
|
||||
</collectionViewFlowLayout>
|
||||
<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"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<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"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<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"/>
|
||||
</view>
|
||||
</subviews>
|
||||
@@ -670,7 +679,7 @@ World</string>
|
||||
</segue>
|
||||
</connections>
|
||||
</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"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO">
|
||||
@@ -731,7 +740,7 @@ World</string>
|
||||
</connections>
|
||||
</collectionViewCell>
|
||||
</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"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
@@ -788,7 +797,7 @@ World</string>
|
||||
<!--App IDs-->
|
||||
<scene sceneID="kvf-US-rRe">
|
||||
<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">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="647"/>
|
||||
<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"/>
|
||||
</collectionViewFlowLayout>
|
||||
<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"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<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"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<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"/>
|
||||
<accessibility key="accessibilityConfiguration">
|
||||
<bool key="isElement" value="YES"/>
|
||||
@@ -826,7 +835,7 @@ World</string>
|
||||
</connections>
|
||||
</collectionViewCell>
|
||||
</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"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
@@ -847,7 +856,7 @@ World</string>
|
||||
<outlet property="textLabel" destination="83Z-Ih-nOW" id="xxM-HD-iJS"/>
|
||||
</connections>
|
||||
</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"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
@@ -896,11 +905,11 @@ World</string>
|
||||
<!--News-->
|
||||
<scene sceneID="BV8-6J-nIv">
|
||||
<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"/>
|
||||
<toolbarItems/>
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="525-jF-uDK" customClass="NavigationBar" customModule="AltStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="96"/>
|
||||
<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="20" width="375" height="96"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<edgeInsets key="layoutMargins" top="8" left="20" bottom="8" right="8"/>
|
||||
</navigationBar>
|
||||
@@ -934,26 +943,26 @@ World</string>
|
||||
<!--Sources-->
|
||||
<scene sceneID="0S1-zn-9KZ">
|
||||
<objects>
|
||||
<collectionViewController title="Sources" id="cHC-TX-KzQ" customClass="SourcesViewController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<collectionView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" dataMode="prototypes" id="S36-hD-vu2">
|
||||
<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" alwaysBounceVertical="YES" dataMode="prototypes" id="S36-hD-vu2">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="647"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="15" minimumInteritemSpacing="10" id="X20-b5-XEP">
|
||||
<size key="itemSize" width="375" height="80"/>
|
||||
<size key="headerReferenceSize" width="50" height="200"/>
|
||||
<size key="footerReferenceSize" width="0.0" height="0.0"/>
|
||||
<inset key="sectionInset" minX="0.0" minY="10" maxX="0.0" maxY="20"/>
|
||||
<size key="footerReferenceSize" width="50" height="50"/>
|
||||
<inset key="sectionInset" minX="0.0" minY="0.0" maxX="0.0" maxY="0.0"/>
|
||||
</collectionViewFlowLayout>
|
||||
<cells>
|
||||
<collectionViewCell opaque="NO" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" reuseIdentifier="Cell" id="XcN-o4-9qm" customClass="BannerCollectionViewCell" customModule="AltStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="210" width="375" height="80"/>
|
||||
<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="200" width="375" height="80"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<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"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<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"/>
|
||||
<accessibility key="accessibilityConfiguration">
|
||||
<bool key="isElement" value="YES"/>
|
||||
@@ -972,11 +981,11 @@ World</string>
|
||||
</connections>
|
||||
</collectionViewCell>
|
||||
</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"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<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"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleCallout"/>
|
||||
<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"/>
|
||||
</constraints>
|
||||
<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="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>
|
||||
</collectionReusableView>
|
||||
<connections>
|
||||
@@ -1036,7 +1084,7 @@ World</string>
|
||||
</scene>
|
||||
</scenes>
|
||||
<inferredMetricsTieBreakers>
|
||||
<segue reference="de9-NH-aec"/>
|
||||
<segue reference="Qd6-ba-dIo"/>
|
||||
<segue reference="cnd-KK-o60"/>
|
||||
</inferredMetricsTieBreakers>
|
||||
<color key="tintColor" name="Primary"/>
|
||||
@@ -1047,13 +1095,13 @@ World</string>
|
||||
<image name="News" width="19" height="20"/>
|
||||
<image name="Settings" width="20" height="20"/>
|
||||
<namedColor name="Background">
|
||||
<color red="0.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 name="BlurTint">
|
||||
<color red="1" green="1" blue="1" alpha="0.30000001192092896" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</namedColor>
|
||||
<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>
|
||||
<systemColor name="systemBackgroundColor">
|
||||
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
|
||||
@@ -12,7 +12,7 @@ import Roxas
|
||||
|
||||
import Nuke
|
||||
|
||||
@objc class BrowseCollectionViewCell: UICollectionViewCell
|
||||
@objc final class BrowseCollectionViewCell: UICollectionViewCell
|
||||
{
|
||||
var imageURLs: [URL] = [] {
|
||||
didSet {
|
||||
@@ -53,7 +53,8 @@ private extension BrowseCollectionViewCell
|
||||
}
|
||||
dataSource.prefetchHandler = { (imageURL, indexPath, completionHandler) 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() }
|
||||
|
||||
if let image = response?.image
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
import minimuxer
|
||||
import AltStoreCore
|
||||
import Roxas
|
||||
|
||||
@@ -94,7 +95,7 @@ private extension BrowseViewController
|
||||
cell.bannerView.iconImageView.isIndicatingActivity = true
|
||||
|
||||
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.
|
||||
// Otherwise, cell reuse can mess up some cached values.
|
||||
@@ -113,7 +114,7 @@ private extension BrowseViewController
|
||||
let progress = AppManager.shared.installationProgress(for: app)
|
||||
cell.bannerView.button.progress = progress
|
||||
|
||||
if Date() < app.versionDate
|
||||
if let versionDate = app.latestVersion?.date, versionDate > Date()
|
||||
{
|
||||
cell.bannerView.button.countdownDate = app.versionDate
|
||||
}
|
||||
@@ -167,14 +168,7 @@ private extension BrowseViewController
|
||||
|
||||
func updateDataSource()
|
||||
{
|
||||
if let patreonAccount = DatabaseManager.shared.patreonAccount(), patreonAccount.isPatron, PatreonAPI.shared.isAuthenticated
|
||||
{
|
||||
self.dataSource.predicate = nil
|
||||
}
|
||||
else
|
||||
{
|
||||
self.dataSource.predicate = NSPredicate(format: "%K == NO", #keyPath(StoreApp.isBeta))
|
||||
}
|
||||
}
|
||||
|
||||
func fetchSource()
|
||||
@@ -242,12 +236,6 @@ private extension BrowseViewController
|
||||
|
||||
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,7 +265,13 @@ private extension BrowseViewController
|
||||
previousProgress?.cancel()
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if !minimuxer.ready() {
|
||||
let toastView = ToastView(error: MinimuxerError.NoConnection)
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
|
||||
_ = AppManager.shared.install(app, presentingViewController: self) { (result) in
|
||||
DispatchQueue.main.async {
|
||||
switch result
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class AppIconImageView: UIImageView
|
||||
final class AppIconImageView: UIImageView
|
||||
{
|
||||
override func awakeFromNib()
|
||||
{
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import AVFoundation
|
||||
|
||||
class BackgroundTaskManager
|
||||
final class BackgroundTaskManager
|
||||
{
|
||||
static let shared = BackgroundTaskManager()
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class BannerCollectionViewCell: UICollectionViewCell
|
||||
final class BannerCollectionViewCell: UICollectionViewCell
|
||||
{
|
||||
private(set) var errorBadge: UIView?
|
||||
@IBOutlet private(set) var bannerView: AppBannerView!
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class Button: UIButton
|
||||
final class Button: UIButton
|
||||
{
|
||||
override var intrinsicContentSize: CGSize {
|
||||
var size = super.intrinsicContentSize
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class CollapsingTextView: UITextView
|
||||
final class CollapsingTextView: UITextView
|
||||
{
|
||||
var isCollapsed = true {
|
||||
didSet {
|
||||
@@ -22,7 +22,7 @@ class CollapsingTextView: UITextView
|
||||
}
|
||||
}
|
||||
|
||||
var lineSpacing: CGFloat = 2 {
|
||||
var lineSpacing: Double = 2 {
|
||||
didSet {
|
||||
self.setNeedsLayout()
|
||||
}
|
||||
@@ -34,7 +34,19 @@ class CollapsingTextView: UITextView
|
||||
{
|
||||
super.awakeFromNib()
|
||||
|
||||
self.layoutManager.delegate = self
|
||||
self.initialize()
|
||||
}
|
||||
|
||||
private func initialize()
|
||||
{
|
||||
if #available(iOS 16, *)
|
||||
{
|
||||
self.updateText()
|
||||
}
|
||||
else
|
||||
{
|
||||
self.layoutManager.delegate = self
|
||||
}
|
||||
|
||||
self.textContainerInset = .zero
|
||||
self.textContainer.lineFragmentPadding = 0
|
||||
@@ -71,8 +83,10 @@ class CollapsingTextView: UITextView
|
||||
{
|
||||
self.textContainer.maximumNumberOfLines = self.maximumNumberOfLines
|
||||
|
||||
let maximumCollapsedHeight = font.lineHeight * CGFloat(self.maximumNumberOfLines)
|
||||
if self.intrinsicContentSize.height > maximumCollapsedHeight
|
||||
let boundingSize = self.attributedText.boundingRect(with: CGSize(width: self.textContainer.size.width, height: .infinity), options: [.usesLineFragmentOrigin, .usesFontLeading], context: nil)
|
||||
let maximumCollapsedHeight = font.lineHeight * Double(self.maximumNumberOfLines)
|
||||
|
||||
if boundingSize.height.rounded() > maximumCollapsedHeight.rounded()
|
||||
{
|
||||
var exclusionFrame = moreButtonFrame
|
||||
exclusionFrame.origin.y += self.moreButton.bounds.midY
|
||||
@@ -106,6 +120,25 @@ private extension CollapsingTextView
|
||||
{
|
||||
self.isCollapsed.toggle()
|
||||
}
|
||||
|
||||
@available(iOS 16, *)
|
||||
func updateText()
|
||||
{
|
||||
do
|
||||
{
|
||||
let style = NSMutableParagraphStyle()
|
||||
style.lineSpacing = self.lineSpacing
|
||||
|
||||
var attributedText = try AttributedString(self.attributedText, including: \.uiKit)
|
||||
attributedText[AttributeScopes.UIKitAttributes.ParagraphStyleAttribute.self] = style
|
||||
|
||||
self.attributedText = NSAttributedString(attributedText)
|
||||
}
|
||||
catch
|
||||
{
|
||||
print("[ALTLog] Failed to update CollapsingTextView line spacing:", error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension CollapsingTextView: NSLayoutManagerDelegate
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class ForwardingNavigationController: UINavigationController
|
||||
final class ForwardingNavigationController: UINavigationController
|
||||
{
|
||||
override var childForStatusBarStyle: UIViewController? {
|
||||
return self.topViewController
|
||||
|
||||
@@ -10,7 +10,7 @@ import UIKit
|
||||
|
||||
import Roxas
|
||||
|
||||
class NavigationBar: UINavigationBar
|
||||
final class NavigationBar: UINavigationBar
|
||||
{
|
||||
@IBInspectable var automaticallyAdjustsItemPositions: Bool = true
|
||||
|
||||
|
||||
@@ -8,7 +8,13 @@
|
||||
|
||||
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? {
|
||||
get {
|
||||
@@ -70,9 +76,7 @@ class PillButton: UIButton
|
||||
}()
|
||||
|
||||
override var intrinsicContentSize: CGSize {
|
||||
var size = super.intrinsicContentSize
|
||||
size.width += 26
|
||||
size.height += 3
|
||||
let size = self.sizeThatFits(CGSize(width: Double.infinity, height: .infinity))
|
||||
return size
|
||||
}
|
||||
|
||||
@@ -88,7 +92,9 @@ class PillButton: UIButton
|
||||
self.layer.masksToBounds = true
|
||||
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.progressView.progress = 0
|
||||
@@ -119,6 +125,15 @@ class PillButton: UIButton
|
||||
|
||||
self.update()
|
||||
}
|
||||
|
||||
override func sizeThatFits(_ size: CGSize) -> CGSize
|
||||
{
|
||||
var size = super.sizeThatFits(size)
|
||||
size.width = max(size.width, PillButton.minimumSize.width)
|
||||
size.height = max(size.height, PillButton.minimumSize.height)
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
private extension PillButton
|
||||
|
||||
@@ -11,4 +11,9 @@ import UIKit
|
||||
class TextCollectionReusableView: UICollectionReusableView
|
||||
{
|
||||
@IBOutlet var textLabel: UILabel!
|
||||
|
||||
@IBOutlet var topLayoutConstraint: NSLayoutConstraint!
|
||||
@IBOutlet var bottomLayoutConstraint: NSLayoutConstraint!
|
||||
@IBOutlet var leadingLayoutConstraint: NSLayoutConstraint!
|
||||
@IBOutlet var trailingLayoutConstraint: NSLayoutConstraint!
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ extension TimeInterval
|
||||
static let longToastViewDuration = 8.0
|
||||
}
|
||||
|
||||
class ToastView: RSTToastView
|
||||
final class ToastView: RSTToastView
|
||||
{
|
||||
var preferredDuration: TimeInterval
|
||||
|
||||
@@ -49,7 +49,7 @@ class ToastView: RSTToastView
|
||||
convenience init(error: Error)
|
||||
{
|
||||
var error = error as NSError
|
||||
var underlyingError = error.userInfo[NSUnderlyingErrorKey] as? NSError
|
||||
var underlyingError = error.underlyingError
|
||||
|
||||
var preferredDuration: TimeInterval?
|
||||
|
||||
@@ -59,7 +59,7 @@ class ToastView: RSTToastView
|
||||
{
|
||||
// Treat underlyingError as the primary error.
|
||||
|
||||
error = unwrappedUnderlyingError
|
||||
error = unwrappedUnderlyingError as NSError
|
||||
underlyingError = nil
|
||||
|
||||
preferredDuration = .longToastViewDuration
|
||||
|
||||
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
|
||||