mirror of
https://github.com/FWGS/xash3d-fwgs.git
synced 2026-08-05 03:24:56 +08:00
Compare commits
1162 Commits
continuous
...
apidocs
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
03a9d08053 | ||
|
|
a75666a3cd | ||
|
|
49426721d8 | ||
|
|
04125284a3 | ||
|
|
1d4f7b2f94 | ||
|
|
38c82a3f76 | ||
|
|
785632a437 | ||
|
|
4be75af214 | ||
|
|
dcdca01e86 | ||
|
|
201d9c8f80 | ||
|
|
69341e36a3 | ||
|
|
3532a77643 | ||
|
|
35dc072b26 | ||
|
|
f92a3695b3 | ||
|
|
a7b776f3b9 | ||
|
|
f2fb668c9e | ||
|
|
90e493fa3a | ||
|
|
692bcc4073 | ||
|
|
823642b10d | ||
|
|
07189ca2f5 | ||
|
|
0d55441830 | ||
|
|
98ff33d9e2 | ||
|
|
baa8fbaaec | ||
|
|
a0b46da04b | ||
|
|
e70ba7ad64 | ||
|
|
8a41be9d47 | ||
|
|
47aff9e30b | ||
|
|
ced8744ac9 | ||
|
|
734c07ddab | ||
|
|
ceb603488a | ||
|
|
80bac157eb | ||
|
|
b45581c630 | ||
|
|
d4fe3d6f8a | ||
|
|
1907485895 | ||
|
|
cfebb3e1d6 | ||
|
|
f9a0057c28 | ||
|
|
ceb2b0c5e4 | ||
|
|
83b5edea88 | ||
|
|
9d2a85f11f | ||
|
|
93d3df81ee | ||
|
|
2a7f13151a | ||
|
|
0c4f6ae61d | ||
|
|
b7150c3208 | ||
|
|
d6ebff81e2 | ||
|
|
ce4148f351 | ||
|
|
08c2632c14 | ||
|
|
03eac904c1 | ||
|
|
62f3354f4e | ||
|
|
d5b6eff0c0 | ||
|
|
c9ee844a24 | ||
|
|
687c9b004f | ||
|
|
14e79f04ac | ||
|
|
5d6cf48687 | ||
|
|
88949a9ee6 | ||
|
|
2d1e76199b | ||
|
|
a5d78ecfd7 | ||
|
|
3576e474e6 | ||
|
|
d50927f21f | ||
|
|
c6e3146290 | ||
|
|
9811988cce | ||
|
|
48bdd4238e | ||
|
|
fd2b7e9197 | ||
|
|
0e42e5b9e3 | ||
|
|
65750f0cf3 | ||
|
|
6469b0a5f5 | ||
|
|
4f98187e9a | ||
|
|
74c19a1557 | ||
|
|
cfebee6ea8 | ||
|
|
bd0d6644e0 | ||
|
|
f41450127a | ||
|
|
70897af362 | ||
|
|
f1d7101ad8 | ||
|
|
ac9d960eef | ||
|
|
0c471aecb1 | ||
|
|
72d0d70587 | ||
|
|
bec526a77e | ||
|
|
4d1119391a | ||
|
|
1dd622fcbb | ||
|
|
af4b64983a | ||
|
|
e751092555 | ||
|
|
ce06b1d3d7 | ||
|
|
a834f4996f | ||
|
|
fdcf267fe3 | ||
|
|
7f2ebd1b5e | ||
|
|
68b2d9f269 | ||
|
|
3f1eed9f59 | ||
|
|
0637dbaab1 | ||
|
|
85388e97c1 | ||
|
|
de961eb45e | ||
|
|
3d60770b84 | ||
|
|
93c5853356 | ||
|
|
98e587285b | ||
|
|
2aa13c8347 | ||
|
|
53344771fd | ||
|
|
2373743920 | ||
|
|
90a3497afe | ||
|
|
339aebb08c | ||
|
|
b4afe390d6 | ||
|
|
ca0c5f929a | ||
|
|
48cc526c7e | ||
|
|
3d30dc8d02 | ||
|
|
75adb2e686 | ||
|
|
45787a87b3 | ||
|
|
0a85734ae7 | ||
|
|
b28c1186b2 | ||
|
|
5007f8488c | ||
|
|
f7062498c2 | ||
|
|
964744d330 | ||
|
|
f5d635b67b | ||
|
|
d0112a98b5 | ||
|
|
614b9113ad | ||
|
|
e14cd758ad | ||
|
|
de1361d99d | ||
|
|
e731775f70 | ||
|
|
342d61b283 | ||
|
|
b030cdb4e3 | ||
|
|
18a973d4f2 | ||
|
|
51fa7d2611 | ||
|
|
6b6d3b3770 | ||
|
|
fd5e3fbbb9 | ||
|
|
6247bbc629 | ||
|
|
0181e640c9 | ||
|
|
26230596ba | ||
|
|
884e052d98 | ||
|
|
5516180ed8 | ||
|
|
6d00aef398 | ||
|
|
2d52dae69c | ||
|
|
eb64ffcec9 | ||
|
|
7eba5a994d | ||
|
|
c39d2e243a | ||
|
|
3661af921e | ||
|
|
4581755f9b | ||
|
|
593b0f295a | ||
|
|
7c23b6473c | ||
|
|
960a3a1483 | ||
|
|
485d4324f8 | ||
|
|
881788c0c9 | ||
|
|
73cf55b958 | ||
|
|
b7c2c476a8 | ||
|
|
fa152bef20 | ||
|
|
770054daaf | ||
|
|
2ad5066271 | ||
|
|
99acd64203 | ||
|
|
86154f81aa | ||
|
|
e982b7911f | ||
|
|
4e138ee795 | ||
|
|
8e5d0a6425 | ||
|
|
33901afa57 | ||
|
|
8fce7dc78e | ||
|
|
d618b817ca | ||
|
|
914be8dbd7 | ||
|
|
9e67a90b25 | ||
|
|
44e67c987b | ||
|
|
3c6c90c1e7 | ||
|
|
3e8bd16d2b | ||
|
|
fb964b8686 | ||
|
|
38ef16b08b | ||
|
|
1f5c97964b | ||
|
|
4798cd6d1e | ||
|
|
df4194ca57 | ||
|
|
888599677e | ||
|
|
22da42b121 | ||
|
|
5ad8bc3ba4 | ||
|
|
0154ffa060 | ||
|
|
cb3f5a74a8 | ||
|
|
b3c7dbe3e4 | ||
|
|
6b24b263ab | ||
|
|
75b69fbf85 | ||
|
|
23b410ccb0 | ||
|
|
bf6a9cb0e7 | ||
|
|
689e29bc42 | ||
|
|
b316af13d6 | ||
|
|
e7f939c87b | ||
|
|
b94446161e | ||
|
|
567cf44111 | ||
|
|
9244bdc7db | ||
|
|
0db2597d1e | ||
|
|
744d5d1141 | ||
|
|
778315da5f | ||
|
|
1acda22ad4 | ||
|
|
4cb1f69475 | ||
|
|
f1806cbf2e | ||
|
|
a4beb7d493 | ||
|
|
880ae0fe7c | ||
|
|
cd3cc7e696 | ||
|
|
9e9a9847fc | ||
|
|
cb5f5baa2b | ||
|
|
edef41fb71 | ||
|
|
6bfa5ce600 | ||
|
|
d1f70bfe2c | ||
|
|
a56f512dc3 | ||
|
|
7c88fd0a17 | ||
|
|
bec23c6d57 | ||
|
|
81197b1aa4 | ||
|
|
8205e22465 | ||
|
|
8407af17f4 | ||
|
|
22fef6fa36 | ||
|
|
88f91d3959 | ||
|
|
b63abe1cf2 | ||
|
|
cd3dc78d4d | ||
|
|
ddf5a01dfc | ||
|
|
10e8790311 | ||
|
|
f04a26901c | ||
|
|
b9f4f1eba4 | ||
|
|
8b0d49f036 | ||
|
|
e94a2c900f | ||
|
|
bfb84a7ac4 | ||
|
|
fa272d9d93 | ||
|
|
87f0217588 | ||
|
|
c4f71f1a44 | ||
|
|
af332eb8ac | ||
|
|
ac6dc2c2d3 | ||
|
|
a29b6772b9 | ||
|
|
3691f02359 | ||
|
|
622d88eeba | ||
|
|
56c19c4f5a | ||
|
|
c5471fe6fd | ||
|
|
046b1da8a8 | ||
|
|
8fd5a5af96 | ||
|
|
229f1560b0 | ||
|
|
8f7f311f60 | ||
|
|
721c8043f1 | ||
|
|
31a86d8af2 | ||
|
|
ac7617990a | ||
|
|
0813d2c7ce | ||
|
|
0ceb0d9e11 | ||
|
|
30db748dcb | ||
|
|
399d549d0c | ||
|
|
77fb2d0eb2 | ||
|
|
ea88bb88b2 | ||
|
|
190e4a5e8a | ||
|
|
823dc3d46f | ||
|
|
779cb2f721 | ||
|
|
88c42d50f2 | ||
|
|
a749bb8ef1 | ||
|
|
723cd8e26c | ||
|
|
a6592e7776 | ||
|
|
4342689e73 | ||
|
|
8a2a7d1180 | ||
|
|
199e226758 | ||
|
|
16ed6cbdcb | ||
|
|
b51d1a4e2e | ||
|
|
534adc9630 | ||
|
|
d15949d2e8 | ||
|
|
43609bc77c | ||
|
|
3917e096ea | ||
|
|
d1a571201b | ||
|
|
8a8cdd2927 | ||
|
|
3ef4acecb5 | ||
|
|
e8c1fc958f | ||
|
|
08bc4ccec4 | ||
|
|
6ae62e3bb1 | ||
|
|
397f2f208a | ||
|
|
8bebf698ef | ||
|
|
6a2a2c33ee | ||
|
|
0adda483c5 | ||
|
|
d8eb918cea | ||
|
|
96b95db7c8 | ||
|
|
b1915106b0 | ||
|
|
0d2a8488ae | ||
|
|
a52c8aa291 | ||
|
|
fbbafb45a3 | ||
|
|
659adca473 | ||
|
|
38e8a581b6 | ||
|
|
06e476e8c8 | ||
|
|
f529f06ea1 | ||
|
|
159ded433d | ||
|
|
c3b6422ad0 | ||
|
|
41b78c26ef | ||
|
|
ca93c5b666 | ||
|
|
80f8b778a9 | ||
|
|
8c5db5f980 | ||
|
|
3756361cfe | ||
|
|
5f3e167799 | ||
|
|
b7ed779f71 | ||
|
|
fa6f564c12 | ||
|
|
ad457f717b | ||
|
|
f95ce7f52d | ||
|
|
ec73f23e4a | ||
|
|
2972d7c309 | ||
|
|
4d003bc13f | ||
|
|
4cb11861e4 | ||
|
|
addd50467e | ||
|
|
6cd2dbf178 | ||
|
|
f079e63737 | ||
|
|
d807041aa5 | ||
|
|
6153d7f168 | ||
|
|
e37feece4d | ||
|
|
6cf11cb6cf | ||
|
|
bb4e64ea58 | ||
|
|
9ed55776a9 | ||
|
|
b45bc9af67 | ||
|
|
2227e487c0 | ||
|
|
1f49ce599f | ||
|
|
8af995cc28 | ||
|
|
b39d660189 | ||
|
|
9608da5bf9 | ||
|
|
d3adcf3185 | ||
|
|
40c051fb6b | ||
|
|
015f1ba8c0 | ||
|
|
c3de4a5c3b | ||
|
|
09210e4316 | ||
|
|
93b382f174 | ||
|
|
42cf51c6aa | ||
|
|
f7bc258c2f | ||
|
|
9e1908fc6f | ||
|
|
6bdb3f6ea1 | ||
|
|
2fe94bceb6 | ||
|
|
371639f0dc | ||
|
|
f88d424ecd | ||
|
|
c2ca80c889 | ||
|
|
0313e19674 | ||
|
|
281d4359d0 | ||
|
|
7b02c37fa7 | ||
|
|
2ea0b0d60c | ||
|
|
d879e028cf | ||
|
|
3e7e81e078 | ||
|
|
cc53d59532 | ||
|
|
319dea1f7d | ||
|
|
48047c05b7 | ||
|
|
85b0273911 | ||
|
|
eb72b6baee | ||
|
|
422addcbb6 | ||
|
|
4a58be2aa0 | ||
|
|
562b54839c | ||
|
|
639eaa9416 | ||
|
|
6c6cb65b30 | ||
|
|
8ea8437e19 | ||
|
|
2656eb1aeb | ||
|
|
fa1e92b127 | ||
|
|
cff8ee13e6 | ||
|
|
3956e3863e | ||
|
|
9600218757 | ||
|
|
2db7cb0d64 | ||
|
|
267a70d621 | ||
|
|
667bedab82 | ||
|
|
49990a0859 | ||
|
|
f7c4cd9398 | ||
|
|
cd4f4d5ad4 | ||
|
|
fe429fe213 | ||
|
|
0a24ca3fe2 | ||
|
|
8c93a0d247 | ||
|
|
5c75dda864 | ||
|
|
55bdded6a4 | ||
|
|
25b2c0f7e3 | ||
|
|
ed522852cf | ||
|
|
224e42d345 | ||
|
|
128a1f59a9 | ||
|
|
40ffde0439 | ||
|
|
ec5578a437 | ||
|
|
1cf40a2d17 | ||
|
|
958ea0ac1d | ||
|
|
a9fdae28b2 | ||
|
|
3d9eb869b0 | ||
|
|
8162c6d42f | ||
|
|
48a9f17222 | ||
|
|
e381d04094 | ||
|
|
0e79326571 | ||
|
|
f7eb8cd913 | ||
|
|
a15c1f99b2 | ||
|
|
ba2191172d | ||
|
|
6ab2c9dbbf | ||
|
|
7da460d4b1 | ||
|
|
ffe7b1f4a1 | ||
|
|
6a1f502d25 | ||
|
|
434359ea01 | ||
|
|
ae2ad6ddf2 | ||
|
|
ec11683dc5 | ||
|
|
47318b0c14 | ||
|
|
60d6214ad7 | ||
|
|
a3e26a21a7 | ||
|
|
d0f3a6f74b | ||
|
|
fee236a050 | ||
|
|
e3e4bcc015 | ||
|
|
2d546ccbf7 | ||
|
|
b41804bb5b | ||
|
|
47a04df189 | ||
|
|
2efedaf46c | ||
|
|
49e43c830b | ||
|
|
763e101208 | ||
|
|
e39a62b1fd | ||
|
|
da76abda75 | ||
|
|
829d7d0a7e | ||
|
|
9f6372b305 | ||
|
|
12edd782c6 | ||
|
|
0b59fa9416 | ||
|
|
cc0f12ed26 | ||
|
|
f42553d0e1 | ||
|
|
e7564420c9 | ||
|
|
e81a0ded11 | ||
|
|
a32c702dbd | ||
|
|
23a5ed71de | ||
|
|
245dc97cfb | ||
|
|
ece204c30f | ||
|
|
1bd59096fd | ||
|
|
0b02c2c6a7 | ||
|
|
6a61a499e9 | ||
|
|
6b1c283082 | ||
|
|
5402a9611e | ||
|
|
43e06ccd45 | ||
|
|
f52c825bf5 | ||
|
|
bf8b709372 | ||
|
|
fdf82e5270 | ||
|
|
0224a1b66b | ||
|
|
505ad6d69b | ||
|
|
5bcd573821 | ||
|
|
c5a291d9c7 | ||
|
|
232a8c1148 | ||
|
|
663f6eaf5c | ||
|
|
9d3210259c | ||
|
|
4ec6c2f7cf | ||
|
|
8452bec877 | ||
|
|
0d4296e0e1 | ||
|
|
2b85da3bc9 | ||
|
|
17f194b9a3 | ||
|
|
ff47fbaedc | ||
|
|
87c88f7463 | ||
|
|
60fd5f7ab8 | ||
|
|
5ceab059dc | ||
|
|
67c898f9ad | ||
|
|
d903187eea | ||
|
|
653f0841e4 | ||
|
|
10bfadb556 | ||
|
|
0b2cc066dd | ||
|
|
60ab13d216 | ||
|
|
f90c658567 | ||
|
|
0c54895877 | ||
|
|
4eb54328f8 | ||
|
|
b1189db9c5 | ||
|
|
869578f1ba | ||
|
|
3320f776ce | ||
|
|
1d64635783 | ||
|
|
bbb076c0d4 | ||
|
|
fe2a4d0428 | ||
|
|
6bdc1941e6 | ||
|
|
9188905145 | ||
|
|
3f2fbb10d8 | ||
|
|
9c97235fe8 | ||
|
|
70a13b47db | ||
|
|
18afaf0eaf | ||
|
|
8b8c3d8116 | ||
|
|
514fc9fe43 | ||
|
|
5d8923d69c | ||
|
|
5db880af57 | ||
|
|
c05db9ce58 | ||
|
|
04079abe00 | ||
|
|
8054b69f73 | ||
|
|
b5ee777466 | ||
|
|
96f3061709 | ||
|
|
420c5a4ff3 | ||
|
|
e8f6476e8c | ||
|
|
65ec35b732 | ||
|
|
8d731b5d15 | ||
|
|
38c0dc4551 | ||
|
|
4d8cb0469f | ||
|
|
62cb8f5ba6 | ||
|
|
97c75d96d5 | ||
|
|
f5ae806b31 | ||
|
|
482dc3a5a5 | ||
|
|
a8539ac004 | ||
|
|
9970c56218 | ||
|
|
480218412a | ||
|
|
6ae09d2866 | ||
|
|
609680b328 | ||
|
|
ccbe370c8e | ||
|
|
9706e47643 | ||
|
|
d98361f782 | ||
|
|
ba7c789ec9 | ||
|
|
d128997d40 | ||
|
|
61342547ce | ||
|
|
c1a5f173b8 | ||
|
|
f5c5b7b2c8 | ||
|
|
057b47555d | ||
|
|
43299f6747 | ||
|
|
03bf6cb719 | ||
|
|
3a1bb12830 | ||
|
|
5efa68757e | ||
|
|
afaf5bf3ea | ||
|
|
50c805826f | ||
|
|
35ae9f4a64 | ||
|
|
d7d96c02a2 | ||
|
|
dd03723144 | ||
|
|
26f07196ea | ||
|
|
8c291fdfe3 | ||
|
|
d1e118f07c | ||
|
|
6c531c14e6 | ||
|
|
433213c915 | ||
|
|
ab0c0b18c2 | ||
|
|
29fd5f9fbe | ||
|
|
882d8afc93 | ||
|
|
e11635d15c | ||
|
|
5d79d93aac | ||
|
|
7ccf651fd0 | ||
|
|
add509bf9e | ||
|
|
e4bc767644 | ||
|
|
97ab977f58 | ||
|
|
058aca0ce3 | ||
|
|
8ab693f3fe | ||
|
|
71b2232995 | ||
|
|
a2d9730c60 | ||
|
|
4de1241713 | ||
|
|
55d78b0197 | ||
|
|
c3c277f03b | ||
|
|
ccb687e552 | ||
|
|
075dbbd08b | ||
|
|
24266c64ef | ||
|
|
f19430447a | ||
|
|
67df4861ce | ||
|
|
4ad3e52407 | ||
|
|
b5b2e322c2 | ||
|
|
2e39432e04 | ||
|
|
75e0eb8f7d | ||
|
|
4d7309b77d | ||
|
|
384cc8541e | ||
|
|
63bd4faa22 | ||
|
|
acba3e8c7b | ||
|
|
a4ec54551a | ||
|
|
155e766276 | ||
|
|
59ef64c821 | ||
|
|
9feaee3e28 | ||
|
|
5fd30876ab | ||
|
|
8d1288373d | ||
|
|
f487cf0763 | ||
|
|
bea0f46d31 | ||
|
|
a55282f2c8 | ||
|
|
09764c65ae | ||
|
|
6f29e2ea12 | ||
|
|
2ed18ddded | ||
|
|
9f45a1e8cb | ||
|
|
2e5fcd45bd | ||
|
|
2c8cf356f9 | ||
|
|
8b6243d872 | ||
|
|
8082d2092f | ||
|
|
c2840ce608 | ||
|
|
addd2f76ef | ||
|
|
d6c715c5c3 | ||
|
|
1e84082d81 | ||
|
|
8602bd4e9e | ||
|
|
ac0e9b633d | ||
|
|
d683f32a34 | ||
|
|
b92b13158b | ||
|
|
dea0c2ce43 | ||
|
|
0ec21fa424 | ||
|
|
0258055c1d | ||
|
|
9280d47766 | ||
|
|
63d6c2f1fd | ||
|
|
ad3ca3b304 | ||
|
|
1564dc4e8e | ||
|
|
7dbaf87cd4 | ||
|
|
5b0aed5035 | ||
|
|
72f3fc9578 | ||
|
|
00a0959e45 | ||
|
|
f587f6ecd6 | ||
|
|
2906ab7cd5 | ||
|
|
2b30141bcc | ||
|
|
7bcce219ed | ||
|
|
5a7ea67a0e | ||
|
|
c04631dad5 | ||
|
|
aca292da94 | ||
|
|
f1b4b01029 | ||
|
|
5d737987bb | ||
|
|
f84dafb7fe | ||
|
|
dc6397e607 | ||
|
|
a1f8416336 | ||
|
|
02f1a92a1b | ||
|
|
d5d3c6868e | ||
|
|
b9028879fb | ||
|
|
59411a214a | ||
|
|
b69649c50e | ||
|
|
dc13474729 | ||
|
|
7674964cc8 | ||
|
|
a8f3701e92 | ||
|
|
ad3157e940 | ||
|
|
332f81553a | ||
|
|
c22c227108 | ||
|
|
e72b89a88b | ||
|
|
3369cca3e6 | ||
|
|
906e6ece0b | ||
|
|
4179464de1 | ||
|
|
fe0661fde9 | ||
|
|
76cd263c83 | ||
|
|
fa4c023961 | ||
|
|
e18d0976ef | ||
|
|
ae23528e04 | ||
|
|
bee36ae6a8 | ||
|
|
397dbcad51 | ||
|
|
4eb42bf73a | ||
|
|
eb830889f9 | ||
|
|
98ba50c974 | ||
|
|
a692857fe8 | ||
|
|
4fa711bfd3 | ||
|
|
8d9d9fd682 | ||
|
|
949c9e278b | ||
|
|
74babbfe01 | ||
|
|
e260228e90 | ||
|
|
a0d0aac9da | ||
|
|
6755c8ef7f | ||
|
|
b7d97bc21b | ||
|
|
24a56795aa | ||
|
|
2fd2d749dc | ||
|
|
84e158da79 | ||
|
|
31edf71985 | ||
|
|
4f25b03c9c | ||
|
|
3da70aa4f7 | ||
|
|
74dd990f2f | ||
|
|
1aab248fa4 | ||
|
|
8582c505e9 | ||
|
|
57cb127d73 | ||
|
|
af9a759e9a | ||
|
|
c7b1f28590 | ||
|
|
37c84ad909 | ||
|
|
face262d32 | ||
|
|
c9716b87be | ||
|
|
5ec762b69c | ||
|
|
688fc87f48 | ||
|
|
0aaf624cd7 | ||
|
|
64403a5cd8 | ||
|
|
5efac59e8f | ||
|
|
2eae8a02e5 | ||
|
|
0cfc3966c5 | ||
|
|
a56fe98dc5 | ||
|
|
586a03d431 | ||
|
|
c175a1b949 | ||
|
|
3937520d3e | ||
|
|
4309604eca | ||
|
|
4360676f96 | ||
|
|
6ad205a619 | ||
|
|
24632c0e69 | ||
|
|
6d2d7a9d6d | ||
|
|
7e76089b76 | ||
|
|
73cbe46ecb | ||
|
|
385c557a60 | ||
|
|
6058828a33 | ||
|
|
cd11dd138e | ||
|
|
5b93c0651e | ||
|
|
63026564c2 | ||
|
|
f415ac379d | ||
|
|
7b62f5af21 | ||
|
|
bfef1cf301 | ||
|
|
0f02f3472e | ||
|
|
2f8ab7174b | ||
|
|
1b5e416988 | ||
|
|
03105e096d | ||
|
|
3d19551172 | ||
|
|
811de8d4e7 | ||
|
|
0d1d899d7c | ||
|
|
72e3313c30 | ||
|
|
b48d965111 | ||
|
|
08d2309986 | ||
|
|
cc0bf4bc43 | ||
|
|
fbb5301075 | ||
|
|
47433059b5 | ||
|
|
5aaa34dc7e | ||
|
|
f68aa134a1 | ||
|
|
8577f3bd79 | ||
|
|
8e441e2b96 | ||
|
|
2b0a4f552b | ||
|
|
aa3af3bf67 | ||
|
|
31573ccbeb | ||
|
|
9c086c503a | ||
|
|
27b2fb55c2 | ||
|
|
69081da566 | ||
|
|
2d04a3faa9 | ||
|
|
300e65e200 | ||
|
|
09a88e4cbc | ||
|
|
ffa411b874 | ||
|
|
cc25bb91bd | ||
|
|
a3e10ef9ac | ||
|
|
1c1cab6e30 | ||
|
|
88873cad9d | ||
|
|
48fe23b483 | ||
|
|
76f3b31eb4 | ||
|
|
ea4929c7e7 | ||
|
|
00d5b652c1 | ||
|
|
df311e861d | ||
|
|
e7a6988811 | ||
|
|
9c72a2ed05 | ||
|
|
ea085ae8d7 | ||
|
|
4cf28bb6b8 | ||
|
|
4e58654c10 | ||
|
|
57847a7dd3 | ||
|
|
a1af4dba99 | ||
|
|
96432c6ca9 | ||
|
|
268f4a8ec2 | ||
|
|
a80e3e3022 | ||
|
|
8e517beff8 | ||
|
|
108dc1243f | ||
|
|
3b3d82e097 | ||
|
|
94bdf0238a | ||
|
|
41fc8e1672 | ||
|
|
4d7018bfde | ||
|
|
0637008eec | ||
|
|
c658fe782d | ||
|
|
94ec3df282 | ||
|
|
90fa1c1fef | ||
|
|
78e36a2eab | ||
|
|
88df5e4631 | ||
|
|
124bedcc59 | ||
|
|
92423c8108 | ||
|
|
d7389e4511 | ||
|
|
b923b3d6b6 | ||
|
|
b7b468ba14 | ||
|
|
20f95b2104 | ||
|
|
c5900d761f | ||
|
|
a09abe4bcf | ||
|
|
85641c3e1d | ||
|
|
57ce27dfe7 | ||
|
|
2eb6fbbc42 | ||
|
|
37a4c921a6 | ||
|
|
6432a9f68d | ||
|
|
25608759e5 | ||
|
|
0213d3be89 | ||
|
|
8ec0283c06 | ||
|
|
bd17310bad | ||
|
|
67a296d304 | ||
|
|
2f4f36d407 | ||
|
|
828ede1e3a | ||
|
|
1c02cfb443 | ||
|
|
5885a6ccd4 | ||
|
|
be71073e93 | ||
|
|
dcd92e8a57 | ||
|
|
a1e8f0e1df | ||
|
|
d23797a244 | ||
|
|
0c5d9a8146 | ||
|
|
51b53e0f2c | ||
|
|
691fbe8615 | ||
|
|
c25ebc4adc | ||
|
|
b8b44ff3e3 | ||
|
|
16c91c4046 | ||
|
|
8dbe38ebfe | ||
|
|
a62975fe1f | ||
|
|
00ff0ced07 | ||
|
|
b0099a2c5d | ||
|
|
f0bd91fba7 | ||
|
|
2168912095 | ||
|
|
efc472cf84 | ||
|
|
1018169842 | ||
|
|
06359502e1 | ||
|
|
0949e89c11 | ||
|
|
c1da6335e4 | ||
|
|
4e07206c98 | ||
|
|
426b088e16 | ||
|
|
0e3b544534 | ||
|
|
45b51451ab | ||
|
|
9313824a5f | ||
|
|
6f5e809507 | ||
|
|
7c44e6451a | ||
|
|
27ca283446 | ||
|
|
4647feaf35 | ||
|
|
67c8527e2b | ||
|
|
3b6365a048 | ||
|
|
d216265519 | ||
|
|
ba38146726 | ||
|
|
17ce269513 | ||
|
|
8fcb6ca128 | ||
|
|
58cd129055 | ||
|
|
236586eed5 | ||
|
|
f51e6ab1e2 | ||
|
|
fbcf7758bf | ||
|
|
99454bac95 | ||
|
|
a4745e06fc | ||
|
|
9613276658 | ||
|
|
b3777bd7c0 | ||
|
|
fa43690519 | ||
|
|
aaba1b6db9 | ||
|
|
5368a7f56b | ||
|
|
6d9056c74c | ||
|
|
ea60581142 | ||
|
|
139972e5c8 | ||
|
|
24dd05dd36 | ||
|
|
b028a18d16 | ||
|
|
e2faa06de2 | ||
|
|
3b5e5462b4 | ||
|
|
323e314714 | ||
|
|
7867a53895 | ||
|
|
2e2fca08a3 | ||
|
|
770c72d0c5 | ||
|
|
66eacf4839 | ||
|
|
af63c6fd5b | ||
|
|
9c9f98623a | ||
|
|
4bfd57037b | ||
|
|
58deff4184 | ||
|
|
ec8393eccb | ||
|
|
dd1607f261 | ||
|
|
4138aaf19f | ||
|
|
de21d49463 | ||
|
|
b58214bc26 | ||
|
|
a95b345e33 | ||
|
|
9d94f5973b | ||
|
|
ceba3e90c6 | ||
|
|
a38603ebe1 | ||
|
|
a9bb69cd32 | ||
|
|
1a0700aafe | ||
|
|
690275a0de | ||
|
|
b98df4df07 | ||
|
|
fa91f6d279 | ||
|
|
3a4d5beabd | ||
|
|
ed66e84f71 | ||
|
|
d97cd43174 | ||
|
|
fae4e066e9 | ||
|
|
9c3ed99de2 | ||
|
|
b0a4b00eb0 | ||
|
|
bf245a5439 | ||
|
|
ac3d861df7 | ||
|
|
2e9b4bf07c | ||
|
|
f3ec856995 | ||
|
|
4aa0439be4 | ||
|
|
41ba97d426 | ||
|
|
e759bf819a | ||
|
|
e45165a958 | ||
|
|
f7a298e8b7 | ||
|
|
3fb08631c4 | ||
|
|
54166f6be4 | ||
|
|
abf3e99896 | ||
|
|
da3c921ea8 | ||
|
|
19bae91be1 | ||
|
|
58f8f87e61 | ||
|
|
4209dd9529 | ||
|
|
a207140613 | ||
|
|
34b12cdf8c | ||
|
|
7bdc2664d4 | ||
|
|
8de7adae11 | ||
|
|
006c301564 | ||
|
|
e917d13ce3 | ||
|
|
f8f8e5c743 | ||
|
|
6f16759f6c | ||
|
|
05ff89f1a3 | ||
|
|
6e3365a412 | ||
|
|
1e8888b2a4 | ||
|
|
a5a6dfb0df | ||
|
|
c9febff75f | ||
|
|
a5a51792dc | ||
|
|
df1dbbdd09 | ||
|
|
8d26578c33 | ||
|
|
27c1257d97 | ||
|
|
da85d25a02 | ||
|
|
667d410b9f | ||
|
|
d00b27d447 | ||
|
|
5784a2116f | ||
|
|
016be4ab0f | ||
|
|
3cda974475 | ||
|
|
78f8a7279a | ||
|
|
f1813fed8a | ||
|
|
adae21a549 | ||
|
|
456e391c75 | ||
|
|
ebba9effb7 | ||
|
|
12e6b7a9ab | ||
|
|
172d3f9e88 | ||
|
|
e471a9d586 | ||
|
|
9a78494be9 | ||
|
|
438d574714 | ||
|
|
dff2f20dbb | ||
|
|
1e3e1c574d | ||
|
|
b2d221e3df | ||
|
|
e8d0a3a5b6 | ||
|
|
6a3541f6d9 | ||
|
|
98898f852e | ||
|
|
5a2b0b1514 | ||
|
|
e76774ebe3 | ||
|
|
2e11e1cd30 | ||
|
|
77e209f5ed | ||
|
|
2c73f5a0fa | ||
|
|
d35d54afa4 | ||
|
|
2e42e30f1c | ||
|
|
6f5ccd4239 | ||
|
|
f44451e0f1 | ||
|
|
4fc4454a74 | ||
|
|
100f89b0e7 | ||
|
|
2916a1020e | ||
|
|
3192fd851d | ||
|
|
ce2e4767ad | ||
|
|
8133cd9d01 | ||
|
|
e75f120cf7 | ||
|
|
774ee7cf38 | ||
|
|
f6f2aede91 | ||
|
|
063dc599b9 | ||
|
|
0a35d46131 | ||
|
|
ad5147a53f | ||
|
|
c02a3167ca | ||
|
|
7dde5b83b1 | ||
|
|
7a10caf3df | ||
|
|
7db489df98 | ||
|
|
d2b0860577 | ||
|
|
0af71adf39 | ||
|
|
4697cda8b0 | ||
|
|
df0a0f2a64 | ||
|
|
106b193511 | ||
|
|
1195c1b96b | ||
|
|
e48b54d461 | ||
|
|
1c4c10e97e | ||
|
|
c4d3e4af20 | ||
|
|
d19f81fef1 | ||
|
|
e55c292c81 | ||
|
|
249bbf991a | ||
|
|
a7a1a2457c | ||
|
|
cc211aa82e | ||
|
|
530d68e43a | ||
|
|
276afa836b | ||
|
|
1fde2ba0dd | ||
|
|
c52453ea1a | ||
|
|
cd1c47245a | ||
|
|
4ccca49099 | ||
|
|
38ec856198 | ||
|
|
9b2c616baa | ||
|
|
e5c3122ba7 | ||
|
|
64b30c4bd6 | ||
|
|
2ddcaf37b4 | ||
|
|
072781f21f | ||
|
|
379266732e | ||
|
|
1333895aa6 | ||
|
|
fa1d58020b | ||
|
|
f1aa3d2832 | ||
|
|
dab20204ce | ||
|
|
87a536ffc0 | ||
|
|
77327d2c21 | ||
|
|
491d537384 | ||
|
|
8a599ee8e9 | ||
|
|
6f27a141a5 | ||
|
|
12115e1dd1 | ||
|
|
9e515756af | ||
|
|
e5268bcf3f | ||
|
|
0c16521ff4 | ||
|
|
9575c9a9b5 | ||
|
|
ff81e98bc2 | ||
|
|
b708774bcd | ||
|
|
7bcaa0db87 | ||
|
|
f4538c2eec | ||
|
|
19c8340b5c | ||
|
|
0735d551ff | ||
|
|
57f3f03236 | ||
|
|
848b1f8ae6 | ||
|
|
aba773d1c0 | ||
|
|
3903f06abb | ||
|
|
e365040659 | ||
|
|
0211b8720f | ||
|
|
2c8b1ee753 | ||
|
|
71f1aa6a2d | ||
|
|
3736938bea | ||
|
|
69b11fcf38 | ||
|
|
fe5b7dc7cf | ||
|
|
5b0ee9679c | ||
|
|
441beea431 | ||
|
|
46a9d3fac6 | ||
|
|
aeee1419a2 | ||
|
|
126433c42c | ||
|
|
11a29ac213 | ||
|
|
e33ceda57a | ||
|
|
cd81aa82ea | ||
|
|
76cac2a352 | ||
|
|
285c11522d | ||
|
|
bc1d9fed7f | ||
|
|
80a1f6c4c8 | ||
|
|
156171d77a | ||
|
|
cc21b87045 | ||
|
|
19de6ce5ff | ||
|
|
4f1bc7d68a | ||
|
|
5ef65c1d5b | ||
|
|
9a98aeb958 | ||
|
|
6ebca5c330 | ||
|
|
0cffe3afe9 | ||
|
|
726eab44d9 | ||
|
|
45bf7fa453 | ||
|
|
4195f90e0f | ||
|
|
8065092976 | ||
|
|
631c9c46c0 | ||
|
|
4560bd68a0 | ||
|
|
a7993ad1c3 | ||
|
|
a0b0c6154f | ||
|
|
a78c93a65a | ||
|
|
8459c0e644 | ||
|
|
7986e1631a | ||
|
|
d1e8dce81a | ||
|
|
94b33a11bb | ||
|
|
8c3772a9bf | ||
|
|
34ee7c1d0f | ||
|
|
870c58490b | ||
|
|
880f085b24 | ||
|
|
42bbdc2f3c | ||
|
|
bb58bdeec9 | ||
|
|
230c07cc98 | ||
|
|
b5950a2972 | ||
|
|
b09068e655 | ||
|
|
c8432f81bb | ||
|
|
2131dc7cfd | ||
|
|
52deb81710 | ||
|
|
5d1f77d324 | ||
|
|
5b117fe69a | ||
|
|
14ba7328af | ||
|
|
5a073557ad | ||
|
|
0554a17f67 | ||
|
|
6edbd62ba1 | ||
|
|
2f5536c7b4 | ||
|
|
5b8d6c114c | ||
|
|
e18fcc1e15 | ||
|
|
5aa12da666 | ||
|
|
498a1f4ac9 | ||
|
|
e5a8d1c2c9 | ||
|
|
3d5374a452 | ||
|
|
7d77d01552 | ||
|
|
2d47dcef2c | ||
|
|
4cdc7b93dd | ||
|
|
3794611567 | ||
|
|
8fa32c733c | ||
|
|
19e710f22c | ||
|
|
3ef85a5863 | ||
|
|
9a4468477d | ||
|
|
0978b30e34 | ||
|
|
c6d419594a | ||
|
|
3112adc305 | ||
|
|
b0f7475962 | ||
|
|
1ef511f476 | ||
|
|
cbd1905d69 | ||
|
|
86ac642258 | ||
|
|
2f9968742a | ||
|
|
f2a22f4017 | ||
|
|
963f53a6a4 | ||
|
|
bab2279029 | ||
|
|
ddfc6ee3cf | ||
|
|
67b7d97557 | ||
|
|
2d5682958d | ||
|
|
8ce7db0ff0 | ||
|
|
d58289ca78 | ||
|
|
6a296232bc | ||
|
|
2f11dd22ec | ||
|
|
28702daccc | ||
|
|
eb1305dbd8 | ||
|
|
f70a1d626e | ||
|
|
ee0d139844 | ||
|
|
9279cedba4 | ||
|
|
c6533d40f9 | ||
|
|
4cf64fb370 | ||
|
|
19fc0b3a36 | ||
|
|
aa327eae4b | ||
|
|
53c109cda0 | ||
|
|
7bb46ec007 | ||
|
|
dafb85eca9 | ||
|
|
d513081356 | ||
|
|
1972cd8d5f | ||
|
|
b09bc7d82f | ||
|
|
6306424102 | ||
|
|
fd4695bfbe | ||
|
|
38ab0d7325 | ||
|
|
91cc1af64c | ||
|
|
34c0b256f6 | ||
|
|
d2f80c14f9 | ||
|
|
3b74455dcb | ||
|
|
073e43a5a4 | ||
|
|
bee8f590a3 | ||
|
|
cd89009d6b | ||
|
|
44279c195f | ||
|
|
39c4cfdf81 | ||
|
|
7dfb65760d | ||
|
|
aa3e0b7416 | ||
|
|
35cd2cbd63 | ||
|
|
6331313d67 | ||
|
|
f8a0acd776 | ||
|
|
0e3e1d8af9 | ||
|
|
56c06d7c01 | ||
|
|
293805c657 | ||
|
|
19b2fb76f7 | ||
|
|
561c0d8264 | ||
|
|
6c1f867b55 | ||
|
|
99ff4934c6 | ||
|
|
abf0f9df5e | ||
|
|
4322764fe2 | ||
|
|
ef278d80a2 | ||
|
|
c613a1368d | ||
|
|
30ba1d301b | ||
|
|
26e6eef233 | ||
|
|
19b5ac81a3 | ||
|
|
5bda511154 | ||
|
|
1f330cb1a8 | ||
|
|
1e2d82ee5d | ||
|
|
e8d98a5d71 | ||
|
|
f0fd46bb60 | ||
|
|
556a814f7c | ||
|
|
98d60887b2 | ||
|
|
82c24fc4cb | ||
|
|
d1252c9a91 | ||
|
|
2b65b71538 | ||
|
|
b4ead9c0ac | ||
|
|
1b894336fc | ||
|
|
bf0a8bf50a | ||
|
|
fb70702f02 | ||
|
|
7b28ad7e28 | ||
|
|
d720e327c9 | ||
|
|
b0140727cc | ||
|
|
29aec91121 | ||
|
|
dba7d531e8 | ||
|
|
65f6fe58b7 | ||
|
|
2d9d05f00e | ||
|
|
d7c89d1d9b | ||
|
|
a22678fc46 | ||
|
|
1dda4a0468 | ||
|
|
38f4a7c144 | ||
|
|
a04843e766 | ||
|
|
09be317894 | ||
|
|
80621be0b1 | ||
|
|
432432adc1 | ||
|
|
c2d7ae230c | ||
|
|
6af532e3b3 | ||
|
|
f0275dcc92 | ||
|
|
b634582701 | ||
|
|
545f14a8b6 | ||
|
|
7dbe0f8c89 | ||
|
|
01b11823c2 | ||
|
|
15cb862bc2 | ||
|
|
e60adf229e | ||
|
|
ed3a634474 | ||
|
|
fb5efe3001 | ||
|
|
c73257d155 | ||
|
|
fd4c08fe76 | ||
|
|
6285b271b8 | ||
|
|
e840082cb1 | ||
|
|
8b92060588 | ||
|
|
cb07de4364 | ||
|
|
253997f228 | ||
|
|
2680c60215 | ||
|
|
2a465e2ee6 | ||
|
|
837c384e86 | ||
|
|
5b30cf5bc2 | ||
|
|
e87ac3da81 | ||
|
|
b20e90ea24 | ||
|
|
fa72090ea1 | ||
|
|
56670e9015 | ||
|
|
beb1dd4838 | ||
|
|
71a667effe | ||
|
|
7896fc7ea7 | ||
|
|
4c554937a0 | ||
|
|
ec977e1307 | ||
|
|
94f416ec20 | ||
|
|
f65e60703f | ||
|
|
b47b51e804 | ||
|
|
06a0f792ff | ||
|
|
9051f59c57 | ||
|
|
e3333ab578 | ||
|
|
2a3e142b7f | ||
|
|
9fd3917216 | ||
|
|
017797a668 | ||
|
|
c9e93071c2 | ||
|
|
08f1b882c8 | ||
|
|
39176afce4 | ||
|
|
e6eba27bf5 | ||
|
|
fe836000cf | ||
|
|
7615914e86 | ||
|
|
58295431a0 | ||
|
|
23f4f980ee | ||
|
|
0bcc0db03f | ||
|
|
6418df976c | ||
|
|
de3442951d | ||
|
|
c9c7c268d0 | ||
|
|
69974c6bf9 | ||
|
|
ca61f79c07 | ||
|
|
2621404695 | ||
|
|
0e99d0f280 | ||
|
|
a20e57ba59 | ||
|
|
fe39a90089 | ||
|
|
3f42c7f914 | ||
|
|
68fb5fc4bc | ||
|
|
f2dbcd9532 | ||
|
|
acbd370168 | ||
|
|
6501b40644 |
@@ -5,6 +5,7 @@ packages:
|
||||
- freetype-dev
|
||||
- fontconfig-dev
|
||||
- opus-dev
|
||||
- bzip2-dev
|
||||
sources:
|
||||
- https://git.sr.ht/~a1batross/xash3d-fwgs
|
||||
tasks:
|
||||
|
||||
@@ -7,6 +7,7 @@ packages:
|
||||
- libfreetype-dev
|
||||
- libfontconfig-dev
|
||||
- libopus-dev
|
||||
- libbz2-dev
|
||||
sources:
|
||||
- https://git.sr.ht/~a1batross/xash3d-fwgs
|
||||
tasks:
|
||||
|
||||
@@ -6,6 +6,7 @@ packages:
|
||||
- libfreetype-dev
|
||||
- libfontconfig-dev
|
||||
- libopus-dev
|
||||
- libbz2-dev
|
||||
sources:
|
||||
- https://git.sr.ht/~a1batross/xash3d-fwgs
|
||||
tasks:
|
||||
|
||||
@@ -6,6 +6,7 @@ packages:
|
||||
- print/freetype2
|
||||
- x11-fonts/fontconfig
|
||||
- audio/opus
|
||||
- archivers/bzip2
|
||||
sources:
|
||||
- https://git.sr.ht/~a1batross/xash3d-fwgs
|
||||
tasks:
|
||||
|
||||
@@ -6,6 +6,7 @@ packages:
|
||||
- fontconfig
|
||||
- libopus
|
||||
- pkgconf
|
||||
- bzip2
|
||||
sources:
|
||||
- https://git.sr.ht/~a1batross/xash3d-fwgs
|
||||
tasks:
|
||||
|
||||
@@ -3,6 +3,7 @@ packages:
|
||||
- python3
|
||||
- sdl2
|
||||
- opus
|
||||
- bzip2
|
||||
sources:
|
||||
- https://git.sr.ht/~a1batross/xash3d-fwgs
|
||||
tasks:
|
||||
|
||||
25
.github/workflows/c-cpp.yml
vendored
25
.github/workflows/c-cpp.yml
vendored
@@ -21,6 +21,22 @@ jobs:
|
||||
- os: ubuntu-20.04
|
||||
targetos: linux
|
||||
targetarch: i386
|
||||
- os: ubuntu-20.04
|
||||
targetos: linux
|
||||
targetarch: arm64
|
||||
cross: true
|
||||
- os: ubuntu-20.04
|
||||
targetos: linux
|
||||
targetarch: armhf
|
||||
cross: true
|
||||
- os: ubuntu-20.04
|
||||
targetos: linux
|
||||
targetarch: riscv64
|
||||
cross: true
|
||||
- os: ubuntu-20.04
|
||||
targetos: linux
|
||||
targetarch: ppc64el
|
||||
cross: true
|
||||
# - os: ubuntu-aarch64-20.04
|
||||
# targetos: linux
|
||||
# targetarch: aarch64
|
||||
@@ -43,15 +59,16 @@ jobs:
|
||||
- os: windows-2019 # always use the oldest possible for 32-bit because of older compilers, and better support of certain legacy OSes
|
||||
targetos: win32
|
||||
targetarch: i386
|
||||
- os: macos-14 # in GitHub documentation, it's an ARM64 machine
|
||||
- os: macos-14 # arm64 as per github documentation
|
||||
targetos: apple
|
||||
targetarch: arm64
|
||||
- os: macos-12 # in GitHub documentation, it's an x86 machine
|
||||
- os: macos-13 # x86 as per github documentation (will they fix it before they deprecate this version?..)
|
||||
targetos: apple
|
||||
targetarch: amd64
|
||||
env:
|
||||
SDL_VERSION: 2.30.3
|
||||
SDL_VERSION: 2.30.11
|
||||
GH_CPU_ARCH: ${{ matrix.targetarch }}
|
||||
GH_CROSSCOMPILING: ${{ matrix.cross }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -75,7 +92,7 @@ jobs:
|
||||
include:
|
||||
- app: su.xash.Engine.Compat.i386
|
||||
container:
|
||||
image: bilelmoussaoui/flatpak-github-actions:freedesktop-23.08
|
||||
image: bilelmoussaoui/flatpak-github-actions:freedesktop-24.08
|
||||
options: --privileged
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -342,3 +342,6 @@ enc_temp_folder/
|
||||
|
||||
# KDevelop4
|
||||
*.kdev4
|
||||
|
||||
# ccls langauge server
|
||||
.ccls-*
|
||||
|
||||
15
.gitmodules
vendored
15
.gitmodules
vendored
@@ -19,3 +19,18 @@
|
||||
[submodule "3rdparty/xash-extras"]
|
||||
path = 3rdparty/extras/xash-extras
|
||||
url = https://github.com/FWGS/xash-extras
|
||||
[submodule "3rdparty/bzip2/bzip2"]
|
||||
path = 3rdparty/bzip2/bzip2
|
||||
url = https://gitlab.com/bzip2/bzip2
|
||||
[submodule "3rdparty/MultiEmulator"]
|
||||
path = 3rdparty/MultiEmulator
|
||||
url = https://github.com/FWGS/MultiEmulator
|
||||
[submodule "3rdparty/libogg/libogg"]
|
||||
path = 3rdparty/libogg/libogg
|
||||
url = https://gitlab.xiph.org/xiph/ogg.git
|
||||
[submodule "3rdparty/vorbis/vorbis-src"]
|
||||
path = 3rdparty/vorbis/vorbis-src
|
||||
url = https://gitlab.xiph.org/xiph/vorbis.git
|
||||
[submodule "3rdparty/opusfile/opusfile"]
|
||||
path = 3rdparty/opusfile/opusfile
|
||||
url = https://gitlab.xiph.org/xiph/opusfile.git
|
||||
|
||||
1
3rdparty/MultiEmulator
vendored
Submodule
1
3rdparty/MultiEmulator
vendored
Submodule
Submodule 3rdparty/MultiEmulator added at 7e989e670e
1
3rdparty/bzip2/bzip2
vendored
Submodule
1
3rdparty/bzip2/bzip2
vendored
Submodule
Submodule 3rdparty/bzip2/bzip2 added at 66c46b8c94
27
3rdparty/bzip2/wscript
vendored
Normal file
27
3rdparty/bzip2/wscript
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
#! /usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
def options(opt):
|
||||
pass
|
||||
|
||||
def configure(conf):
|
||||
conf.define('_GNU_SOURCE', 1)
|
||||
conf.define('BZ_NO_STDIO', 1)
|
||||
|
||||
if conf.env.DEST_OS == 'win32':
|
||||
conf.define('BZ_LCCWIN32', 1)
|
||||
else:
|
||||
conf.define('BZ_UNIX', 1)
|
||||
|
||||
def build(bld):
|
||||
bld(features = 'subst', source = 'bzip2/bz_version.h.in', target = 'bzip2/bz_version.h', BZ_VERSION='1.1.0-fwgs', name = 'bz_version')
|
||||
|
||||
bz_sources = ['bzip2/blocksort.c', 'bzip2/huffman.c', 'bzip2/crctable.c', 'bzip2/randtable.c', 'bzip2/compress.c', 'bzip2/decompress.c', 'bzip2/bzlib.c']
|
||||
|
||||
bld.stlib(
|
||||
source = bz_sources,
|
||||
target = 'bzip2',
|
||||
use = 'bz_version',
|
||||
includes = 'bzip2/',
|
||||
export_includes = 'bzip2/'
|
||||
)
|
||||
2
3rdparty/extras/xash-extras
vendored
2
3rdparty/extras/xash-extras
vendored
Submodule 3rdparty/extras/xash-extras updated: f4ff263d42...1bdeab2d8f
2
3rdparty/gl-wes-v2
vendored
2
3rdparty/gl-wes-v2
vendored
Submodule 3rdparty/gl-wes-v2 updated: 2d9b5afe10...6b392aecbb
2
3rdparty/gl4es/wscript
vendored
2
3rdparty/gl4es/wscript
vendored
@@ -16,9 +16,7 @@ def build(bld):
|
||||
|
||||
bld.stlib(source = gl4es_srcdir.ant_glob(['gl/*.c', 'gl/*/*.c', 'glx/hardext.c']),
|
||||
target = 'gl4es',
|
||||
features = 'c',
|
||||
includes = ['gl4es/src', 'gl4es/src/gl', 'gl4es/src/glx', 'gl4es/include'],
|
||||
defines = ['NOX11', 'NO_GBM', 'NO_INIT_CONSTRUCTOR', 'DEFAULT_ES=2', 'NOEGL', 'NO_LOADER', 'STATICLIB'],
|
||||
cflags = ['-w', '-fvisibility=hidden', '-std=gnu99'],
|
||||
subsystem = bld.env.MSVC_SUBSYSTEM,
|
||||
export_includes = '.')
|
||||
|
||||
1
3rdparty/libogg/libogg
vendored
Submodule
1
3rdparty/libogg/libogg
vendored
Submodule
Submodule 3rdparty/libogg/libogg added at 7cf42ea17a
50
3rdparty/libogg/wscript
vendored
Normal file
50
3rdparty/libogg/wscript
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
#! /usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
def options(opt):
|
||||
pass
|
||||
|
||||
def configure(conf):
|
||||
if not conf.path.find_dir('libogg') or not conf.path.find_dir('libogg/src'):
|
||||
conf.fatal('Can\'t find libogg submodule. Run `git submodule update --init --recursive`.')
|
||||
return
|
||||
|
||||
conf.env.INCLUDE_INTTYPES_H = 0
|
||||
conf.env.INCLUDE_SYS_TYPES_H = 0
|
||||
conf.env.INCLUDE_STDINT_H = 0
|
||||
|
||||
if conf.check_cc(header_name='inttypes.h', mandatory = False):
|
||||
conf.env.INCLUDE_INTTYPES_H = 1
|
||||
elif conf.check_cc(header_name='sys/types.h', mandatory = False):
|
||||
conf.env.INCLUDE_SYS_TYPES_H = 1
|
||||
elif conf.check_cc(header_name='stdint.h', mandatory = False):
|
||||
conf.env.INCLUDE_STDINT_H = 1
|
||||
|
||||
def build(bld):
|
||||
sources = bld.path.ant_glob([
|
||||
'libogg/src/*.c'
|
||||
])
|
||||
|
||||
bld(
|
||||
features = 'subst',
|
||||
name = 'libogg_config_types',
|
||||
source = 'libogg/include/ogg/config_types.h.in',
|
||||
target = 'libogg/include/ogg/config_types.h',
|
||||
INCLUDE_INTTYPES_H = bld.env.INCLUDE_INTTYPES_H,
|
||||
INCLUDE_SYS_TYPES_H = bld.env.INCLUDE_SYS_TYPES_H,
|
||||
INCLUDE_STDINT_H = bld.env.INCLUDE_STDINT_H,
|
||||
SIZE16 = 'int16_t',
|
||||
USIZE16 = 'uint16_t',
|
||||
SIZE32 = 'int32_t',
|
||||
USIZE32 = 'uint32_t',
|
||||
SIZE64 = 'int64_t',
|
||||
USIZE64 = 'uint64_t'
|
||||
)
|
||||
|
||||
bld.stlib(
|
||||
source = sources,
|
||||
target = 'ogg',
|
||||
use = 'libogg_config_types',
|
||||
includes = 'libogg/include/',
|
||||
export_includes = 'libogg/include/'
|
||||
)
|
||||
2
3rdparty/mainui
vendored
2
3rdparty/mainui
vendored
Submodule 3rdparty/mainui updated: b7edbaf2de...734a6d1ca2
2
3rdparty/nanogl
vendored
2
3rdparty/nanogl
vendored
Submodule 3rdparty/nanogl updated: e75792de03...dbfe2e75b3
2
3rdparty/opus/opus
vendored
2
3rdparty/opus/opus
vendored
Submodule 3rdparty/opus/opus updated: 8cf872a186...82ac57d9f1
32
3rdparty/opus/wscript
vendored
32
3rdparty/opus/wscript
vendored
@@ -3,6 +3,26 @@
|
||||
|
||||
import os
|
||||
|
||||
FRAGMENT_VLA='''int main (int argc, char **argv) {
|
||||
char a[argc];
|
||||
a[sizeof( a ) - 1] = 0;
|
||||
int N;
|
||||
return a[0];
|
||||
}'''
|
||||
|
||||
FRAGMENT_ALLOCA_H='''#include <alloca.h>
|
||||
int main (void) {
|
||||
int foo=10;
|
||||
int * array = alloca(foo);
|
||||
}'''
|
||||
|
||||
FRAGMENT_STDLIB_H='''#include <malloc.h>
|
||||
#include <stdlib.h>
|
||||
int main (void) {
|
||||
int foo=10;
|
||||
int * array = alloca(foo);
|
||||
}'''
|
||||
|
||||
def options(opt):
|
||||
pass
|
||||
|
||||
@@ -11,6 +31,15 @@ def configure(conf):
|
||||
conf.fatal('Can\'t find opus submodule. Run `git submodule update --init --recursive`.')
|
||||
return
|
||||
|
||||
# Check for C99 variable-size arrays, or alloca() as fallback
|
||||
if conf.check_cc(fragment=FRAGMENT_VLA, msg = 'Checking for C99 VLA support', mandatory = False):
|
||||
conf.define('VAR_ARRAYS', 1)
|
||||
elif conf.check_cc(fragment=FRAGMENT_ALLOCA_H, msg = 'Checking for alloca in alloca.h header', mandatory = False):
|
||||
conf.define('USE_ALLOCA', 1)
|
||||
conf.define('HAVE_ALLOCA_H', 1)
|
||||
elif conf.check_cc(fragmenmt=FRAGMENT_STDLIB_H, msg = 'Checking for alloca.h in stdlib.h', mandatory = False):
|
||||
conf.define('USE_ALLOCA', 1)
|
||||
|
||||
# TODO: ARM/x86 intrinsics detection
|
||||
# TODO: maybe call autotools/cmake/meson instead?
|
||||
|
||||
@@ -27,7 +56,7 @@ def build(bld):
|
||||
'opus/celt/opus_custom_demo.c'
|
||||
])
|
||||
includes = ['opus/include/', 'opus/celt/', 'opus/silk/', 'opus/silk/float/']
|
||||
defines = ['USE_ALLOCA', 'OPUS_BUILD', 'FLOAT_APPROX', 'PACKAGE_VERSION="1.3.1"', 'CUSTOM_MODES']
|
||||
defines = ['OPUS_BUILD', 'FLOAT_APPROX', 'PACKAGE_VERSION="1.4.0"', 'CUSTOM_MODES']
|
||||
|
||||
bld.stlib(
|
||||
source = sources,
|
||||
@@ -35,6 +64,5 @@ def build(bld):
|
||||
features = 'c',
|
||||
includes = includes,
|
||||
defines = defines,
|
||||
subsystem = bld.env.MSVC_SUBSYSTEM,
|
||||
export_includes = ['opus/include/']
|
||||
)
|
||||
|
||||
1
3rdparty/opusfile/opusfile
vendored
Submodule
1
3rdparty/opusfile/opusfile
vendored
Submodule
Submodule 3rdparty/opusfile/opusfile added at 3ecc22aa0a
37
3rdparty/opusfile/wscript
vendored
Normal file
37
3rdparty/opusfile/wscript
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
#! /usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
def options(opt):
|
||||
pass
|
||||
|
||||
def configure(conf):
|
||||
if not conf.path.find_dir('opusfile') or not conf.path.find_dir('opusfile/src'):
|
||||
conf.fatal('Can\'t find opusfile submodule. Run `git submodule update --init --recursive`.')
|
||||
return
|
||||
|
||||
if conf.env.COMPILER_CC == 'msvc':
|
||||
conf.define('_CRT_SECURE_NO_WARNINGS', 1)
|
||||
conf.define('_CRT_SECURE_NO_DEPRECATE', 1)
|
||||
conf.define('_CRT_NONSTDC_NO_DEPRECATE', 1)
|
||||
|
||||
if conf.env.DEST_OS == 'android':
|
||||
# HACKHACK: set it to 32 here because opusfile can't be built on Android SDK < 24
|
||||
# with _FILE_OFFSET_BITS 64 (which it sets automatically in src/internal.h)
|
||||
# we are not (????) relying on this part of the API, so it should be harmless
|
||||
conf.define('_FILE_OFFSET_BITS', 32)
|
||||
|
||||
def build(bld):
|
||||
sources = [
|
||||
'opusfile/src/info.c',
|
||||
'opusfile/src/internal.c',
|
||||
'opusfile/src/opusfile.c',
|
||||
'opusfile/src/stream.c'
|
||||
]
|
||||
|
||||
bld.stlib(
|
||||
source = sources,
|
||||
target = 'opusfile',
|
||||
includes = 'opusfile/include/',
|
||||
use = 'ogg opus',
|
||||
export_includes = 'opusfile/include/'
|
||||
)
|
||||
2
3rdparty/vgui_support
vendored
2
3rdparty/vgui_support
vendored
Submodule 3rdparty/vgui_support updated: 7276993f3f...d585ab86da
1
3rdparty/vorbis/vorbis-src
vendored
Submodule
1
3rdparty/vorbis/vorbis-src
vendored
Submodule
Submodule 3rdparty/vorbis/vorbis-src added at bb4047de4c
71
3rdparty/vorbis/wscript
vendored
Normal file
71
3rdparty/vorbis/wscript
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
#! /usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
FRAGMENT_MEMORY_H='''#include <memory.h>
|
||||
int main (void) {
|
||||
return 0;
|
||||
}'''
|
||||
|
||||
FRAGMENT_ALLOCA_H='''#include <alloca.h>
|
||||
int main (void) {
|
||||
int foo=10;
|
||||
int * array = alloca(foo);
|
||||
}'''
|
||||
|
||||
def options(opt):
|
||||
pass
|
||||
|
||||
def configure(conf):
|
||||
if not conf.path.find_dir('vorbis-src') or not conf.path.find_dir('vorbis-src/lib'):
|
||||
conf.fatal('Can\'t find Vorbis submodule. Run `git submodule update --init --recursive`.')
|
||||
return
|
||||
|
||||
if conf.check_cc(fragment=FRAGMENT_MEMORY_H, msg = 'Checking for memory.h header', mandatory = False):
|
||||
conf.define('USE_MEMORY_H', 1)
|
||||
|
||||
conf.check_cc(fragment=FRAGMENT_ALLOCA_H, msg = 'Checking for alloca in alloca.h header', mandatory = False)
|
||||
|
||||
if conf.env.COMPILER_CC == 'msvc':
|
||||
conf.define('_CRT_SECURE_NO_WARNINGS', 1)
|
||||
conf.define('_CRT_SECURE_NO_DEPRECATE', 1)
|
||||
conf.define('_CRT_NONSTDC_NO_DEPRECATE', 1)
|
||||
|
||||
def build(bld):
|
||||
libvorbis_sources = [
|
||||
'vorbis-src/lib/mdct.c',
|
||||
'vorbis-src/lib/smallft.c',
|
||||
'vorbis-src/lib/block.c',
|
||||
'vorbis-src/lib/envelope.c',
|
||||
'vorbis-src/lib/window.c',
|
||||
'vorbis-src/lib/lsp.c',
|
||||
'vorbis-src/lib/lpc.c',
|
||||
'vorbis-src/lib/analysis.c',
|
||||
'vorbis-src/lib/synthesis.c',
|
||||
'vorbis-src/lib/psy.c',
|
||||
'vorbis-src/lib/info.c',
|
||||
'vorbis-src/lib/floor1.c',
|
||||
'vorbis-src/lib/floor0.c',
|
||||
'vorbis-src/lib/res0.c',
|
||||
'vorbis-src/lib/mapping0.c',
|
||||
'vorbis-src/lib/registry.c',
|
||||
'vorbis-src/lib/codebook.c',
|
||||
'vorbis-src/lib/sharedbook.c',
|
||||
'vorbis-src/lib/lookup.c',
|
||||
'vorbis-src/lib/bitrate.c'
|
||||
]
|
||||
|
||||
bld.stlib(
|
||||
source = libvorbis_sources,
|
||||
target = 'vorbis',
|
||||
includes = 'vorbis-src/include/',
|
||||
use = 'ogg',
|
||||
export_includes = 'vorbis-src/include/'
|
||||
)
|
||||
|
||||
bld.stlib(
|
||||
source = 'vorbis-src/lib/vorbisfile.c',
|
||||
target = 'vorbisfile',
|
||||
includes = 'vorbis-src/include/',
|
||||
use = 'vorbis',
|
||||
export_includes = 'vorbis-src/include/'
|
||||
)
|
||||
5
Documentation/api/README.md
Normal file
5
Documentation/api/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Welcome to the GoldSource API docs
|
||||
|
||||
This documentation is an attempt on explaining inner workings of Valve's Half-Life (R) engine, also known as GoldSource, from programmer's perspective. A deep knowledge of Quake (NQ, QW and Q2) engines is required.
|
||||
|
||||
Everything discussed here is it's author assumption based on studying Xash3D source code and reverse engineering Half-Life mods. Xash3D API extensions is out of scope of this document and will be explained separately.
|
||||
20
Documentation/api/client/01-init.md
Normal file
20
Documentation/api/client/01-init.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# Initializing client.dll
|
||||
|
||||
`client.dll` has got multiple ways of initialization during it's life but we will go through the simplest and the most common route here.
|
||||
|
||||
Here we expect that reader has knowledge of loading dynamic shared objects on their platform. On Windows it's done through `LoadLibrary`/`GetProcAddress`/`FreeLibrary` functions, on POSIX-complaint systems it's done through `dlopen`/`dlsym`/`dlclose` functions.
|
||||
|
||||
## client.dll lifetime
|
||||
|
||||
`client.dll` is expected to be loaded during client initialization and unloaded on client shutdown. Essentially, in non-dedicated builds, it always exists during the engine lifetime. Judging by the API, it might look like `client.dll` might be safely unloaded and loaded again, for example for implementing classic `Change Game` functionality from WON versions of Half-Life, but in practice it's nearly impossible to do this clean in standard and portable manner. If you want to implement changing games, consider using `execv`-like functions.
|
||||
|
||||
## client.dll exported functions
|
||||
|
||||
The first thing you should do, is to acquire pointers to all exported functions, which you can find in the next chapter. Some of them are optional, and might not present in the `client.dll`, and will be labeled as such.
|
||||
|
||||
## client.dll initialization process
|
||||
|
||||
1. The first function you call is `Initialize` function, which lets `client.dll` to store a copy of an engine API functions.
|
||||
2. Since SDK 2.0, `client.dll` have player movement code in it, which you must initialize through `HUD_PlayerMoveInit` function. `client.dll` might want to override player hulls, which can be grabbed with `HUD_GetHullBounds` function.
|
||||
3. HUD functionality must be started up with `HUD_Init` function and can be de-initialized with `HUD_Shutdown` export function.
|
||||
4. And finally, SDK 2.1 brings studio model renderer, which has separate set of API functions, which must be initialized with `HUD_GetStudioModelInterface` function.
|
||||
286
Documentation/api/client/02-exports.md
Normal file
286
Documentation/api/client/02-exports.md
Normal file
@@ -0,0 +1,286 @@
|
||||
# List of client.dll exported functions
|
||||
|
||||
### `int Initialize( cl_enginefuncs_t *enginefuncs, int version )`
|
||||
|
||||
> [!IMPORTANT]
|
||||
> This function is only called once.
|
||||
|
||||
Called after loading DLL and exports engine API to the `client.dll`.
|
||||
* `enginefuncs` must be set to a pointer to a struct filled with engine function pointers, it will be described in next chapters.
|
||||
* `version` must be always set to `7`. (HLSDK 1.0 uses version `6`, and is binary incompatible with `7`).
|
||||
Return value: `0` on error, otherwise success.
|
||||
|
||||
### `void HUD_PlayerMoveInit( struct playermove_s *ppmove, int server )`
|
||||
|
||||
> [!IMPORTANT]
|
||||
> This function is only called once.
|
||||
|
||||
Called on player movement prediction initialization, before HUD. In GoldSrc, engine only runs prediction loop, the players physics are implemented in `client.dll`.
|
||||
* `ppmove` must be set to a pointer to a client instance of player movement structure, which also exports it's own API and will be discussed in the next chapters.
|
||||
* `server` must be always set to `0` on client side.
|
||||
This function is called only once.
|
||||
|
||||
### `void HUD_Init( void )`
|
||||
|
||||
> [!IMPORTANT]
|
||||
> This function is only called once.
|
||||
|
||||
Called to initialize the HUD. At this moment engine should be ready to register new commands, console variables and user messages. No rendering or loading graphics done at this moment.
|
||||
|
||||
### `int HUD_GetStudioModelInterface( int version, struct r_studio_interface_s **ppinterface, struct engine_studio_api_s *pstudio )`
|
||||
|
||||
> [!IMPORTANT]
|
||||
> This function is only called once.
|
||||
|
||||
This function is called after initializing HUD and exports studio model interface to the `client.dll`.
|
||||
* `version` must be always set to `1`.
|
||||
* `ppinterface` will be set by `client.dll` to a pointer to `r_studio_interface_s` structure.
|
||||
* `pstudio` must be set to a pointer to `engine_studio_api_s` structure. The studio model interface will be discussed in the next chapters.
|
||||
Return value: `0` on error, otherwise success.
|
||||
|
||||
### `void HUD_Shutdown( void )`
|
||||
|
||||
> [!IMPORTANT]
|
||||
> This function is only called once.
|
||||
|
||||
Called at client shutdown.
|
||||
|
||||
### `int HUD_GetHullBounds( int hull, vec3_t mins, vec3_t maxs )`
|
||||
|
||||
> [!IMPORTANT]
|
||||
> This function is only called once.
|
||||
|
||||
Called by the engine after `HUD_PlayerMoveInit` to let `client.dll` override hull bounds used for in player movement prediction.
|
||||
|
||||
* `hull` is the hull index (0: player standing, 1: player crouched, 2: point hull, 3: large hull).
|
||||
* `mins` will contain hull mins.
|
||||
* `maxs` will contain hull maxs.
|
||||
Return value: if 0, don't override this hull and stop reading, non-zero means hull is valid _and_ there is more.
|
||||
|
||||
> [!NOTE]
|
||||
> This function is broken in most implementations. It might return non-zero value, but don't write anything to the `mins` and `maxs` vectors, so be prepared to have some default values.
|
||||
|
||||
### `int HUD_VidInit( void )`
|
||||
|
||||
Called when client receives `svc_serverdata` message. It is called before any parsing of that message is done, thus the state is preserved from the previous connection. It lets `client.dll` to re-initialize graphics, if required. At this point, engine is expected to have video subsystem running, but no rendering is done here.
|
||||
|
||||
### `int HUD_Redraw( float flTime, int intermission )`
|
||||
|
||||
Called each frame to redraw the HUD. Only 2D is drawn here.
|
||||
* `flTime` must be set to `cl.time`, i.e. synchronized with server.
|
||||
* `intermission` must be set to `1` during intermission (set through `svc_intermission` message), otherwise it's set to `0`.
|
||||
Return value: ignored.
|
||||
|
||||
### `void HUD_Reset( void )`
|
||||
|
||||
Called on demo recording or playback start and stop.
|
||||
|
||||
### `int HUD_UpdateClientData( client_data_t *cdata, float flTime )`
|
||||
|
||||
Called each frame after taking input.
|
||||
* `cdata` contains pointer to `client_data_t` structure, populated by engine.
|
||||
* `flTime` must be set to `cl.time`, i.e. synchronized with server.
|
||||
Return value: if non-zero, will override engine viewangles and FOV value.
|
||||
|
||||
### `void HUD_PlayerMove( struct playermove_s *pmove, int server )`
|
||||
|
||||
When prediction is enabled, use this function to run player movement prediction. Note that this correlates to QW/Q2's prediction mechanism. It does not include weapon prediction.
|
||||
|
||||
* `pmove` is a pointer to playermove object
|
||||
* `server` must be always set to `0`
|
||||
|
||||
### `char HUD_PlayerMoveTexture( char *name )`
|
||||
|
||||
Not used in the engine.
|
||||
|
||||
### `int HUD_ConnectionlessPacket( const netadr_t *from, const char *args, char *response_buffer, int *response_buffer_size )`
|
||||
|
||||
Called by the engine on unknown connectionless packets. Lets `client.dll` and `server.dll` have custom query protocol.
|
||||
|
||||
* `from` is set to network address where this packet is coming from.
|
||||
* `args` raw network buffer, minus the connectionless packet header (0xFFFFFFFF).
|
||||
* `response_buffer` set by `client.dll` if there is a response.
|
||||
* `response_buffer_size` is initialized by the engine with buffer maximum size. Set by `client.dll` if there is a response.
|
||||
Return value: non-zero if handled.
|
||||
|
||||
### `void HUD_Frame( double time )`
|
||||
|
||||
Called each frame after sending command to the remote server. No rendering is normally done in this function.
|
||||
|
||||
* `time` is the local delta time between previous and this frame (i.e. `host.frametime`)
|
||||
|
||||
### `void HUD_PostRunCmd( struct local_state_s *from, struct local_state_s *to, usercmd_t *cmd, int runfuncs, double time, unsigned int random_seed )`
|
||||
|
||||
Always called after `HUD_PlayerMove`, even if movement prediction is disabled. Used for weapon prediction stuff.
|
||||
|
||||
* `from` is a pointer to `local_state_s` object of the previous predicted frame
|
||||
* `to` is a pointer to `local_state_s` object of this current frame
|
||||
* `cmd` is current user command
|
||||
* `runfuncs` is set to `1` if this frame was never predicted before, and to `0` if it is being predicted again
|
||||
* `random_seed` is set to `incoming_acknowledged` plus number of predicted frames starting with `1`. This way it's synchronized between client and server.
|
||||
|
||||
### `int HUD_Key_Event( int down, int key, const char *current_binding )`
|
||||
|
||||
Called on keyboard event.
|
||||
|
||||
* `down` is set to `1` if key is being pressed or set to `0` on being released.
|
||||
* `key` is set to the key number. The key IDs are predefined.
|
||||
* `current_binding` is set to a null-terminated string with commands bound to this key
|
||||
Return value: `0` if `client.dll` wants engine to ignore that key.
|
||||
|
||||
### `int HUD_AddEntity( int type, struct cl_entity_s *ent, const char *modelname )`
|
||||
|
||||
Called before adding entity to the rendering list.
|
||||
|
||||
* `type` is set to an entity type, see `entity_state_t::entityType`
|
||||
* `ent` is a pointer to `cl_entity_t` object.
|
||||
* `modelname` is a null-terminated string with that entity model name.
|
||||
Return value: `0` if `client.dll` wants engine to not draw this entity.
|
||||
|
||||
### `void HUD_CreateEntities( void )`
|
||||
|
||||
Called each frame after all network entities (including players) are linked to let `client.dll` spawn client-side entities.
|
||||
|
||||
### `void HUD_StudioEvent( const struct mstudioevent_s *event, const struct cl_entity_s *ent )`
|
||||
|
||||
Studio models have events tied to the frame, on which engine calls this function.
|
||||
|
||||
* `event` is a pointer to a studio event object
|
||||
* `ent` is a pointer to a client entity object
|
||||
|
||||
### `void HUD_TxferLocalOverrides( struct entity_state_s *state, const struct clientdata_s *client )`
|
||||
|
||||
When client processes entity updates, for local client it might be truncated, don't have enough precision, miss some critical info, so engine calls this function as `client.dll` might choose to use client data came from `svc_clientdata` message. Note that it is called on raw, non-interpolated networked entity state.
|
||||
|
||||
* `state` is a pointer to the local client entity state coming from the network
|
||||
* `client` is a pointer to local client data object
|
||||
|
||||
### `void HUD_ProcessPlayerState( struct entity_state_s *dst, const struct entity_state_s *src )`
|
||||
|
||||
When client processes entity updates, it calls this function for player entities as `client.dll` might want to override some data or fill the missing parts, but usually it just copies from `src` to `dst`.
|
||||
|
||||
* `src` is a target pointer to the player entity data (stored in frames, for example)
|
||||
* `dst` is a source pointer to the player entity data coming from network
|
||||
|
||||
### `void HUD_TxferPredictionData( struct entity_state_s *ps, const struct entity_state_s *pps, struct clientdata_s *pcd, const struct clientdata_s *ppcd, struct weapon_data_s *wd, const weapon_data_s *pwd )`
|
||||
|
||||
When client receives `svc_clientdata` message, this function is called before any parsing is done, so that the `client.dll` fills in data from prediction.
|
||||
|
||||
* `ps`, `pcd`, `wd` are pointers to current network frame data.
|
||||
* `pps`, `ppcd`, `pwd` are pointers to predicted frame data.
|
||||
|
||||
### `void HUD_TempEntUpdate( double frametime, double client_time, double cl_gravity, TEMPENTITY **ppTempEntFree, TEMPENTITY **ppTempEntActive, int ( *AddVisibleEntity )( cl_entity_t *pEntity ), void ( *TempEntPlaySound)( TEMPENTITY *pTemp, float damp ))`
|
||||
|
||||
Called each frame after network entities are processed and after `HUD_CreateEntities`, to let `client.dll` process temporary entities logic.
|
||||
|
||||
* `frametime` is the delta between `cl.time` and `cl.oldtime`, i.e. server time.
|
||||
* `client_time` is `cl.time`
|
||||
* `gravity` is the synchronized gravity value from the server
|
||||
* `ppTempEntFree` is a pointer to the head of linked list of free temp entities
|
||||
* `ppTempEntActive` is a pointer to the head of linked list of active temp entities
|
||||
* `AddVisibleEntity` is a pointer to function that lets `client.dll` add this entity to the rendering list.
|
||||
* `TempEntPlaySound` is a pointer to function that's called by `client.dll` when temp entity needs to play predefined hit sound. The `damp` argument of this argument only makes sound to NOT play, if it's zero or negative.
|
||||
|
||||
### `void HUD_DrawNormalTriangles( void )`
|
||||
|
||||
Called each rendering frame to let `client.dll` draw custom solid triangles through TriAPI or direct OpenGL calls.
|
||||
|
||||
### `void HUD_DrawTransparentTriangles( void )`
|
||||
|
||||
Called each rendering frame to let `client.dll` draw custom transparent triangles through TriAPI or direct OpenGL calls.
|
||||
|
||||
### `struct cl_entity_s *HUD_GetUserEntity( int index )`
|
||||
|
||||
Called by engine when beam start/end indices are negative, thus allowing attaching beams to a temporary or client-only entity.
|
||||
|
||||
* `index` is the fixed up entity index, as beam start/end indices encode real entity index in low 12 bits (i.e. `beament_start & 0xFFF`).
|
||||
|
||||
### `void Demo_ReadBuffer( int size, unsigned char *buffer )`
|
||||
|
||||
Called by engine on demo playback, if `client.dll` saved some custom data on demo recording prior.
|
||||
|
||||
* `size` is the size of buffer in bytes
|
||||
* `buffer` is the pointer to custom data stored by `client.dll` in demo
|
||||
|
||||
### `void CAM_Think( void )`
|
||||
|
||||
Called each frame before rendering starts to let `client.dll` run custom camera logic, like advanced thirdperson follow camera for example.
|
||||
|
||||
### `int CL_IsThirdPerson( void )`
|
||||
|
||||
Returns non-zero value if camera is in thirdperson mode, lets engine figure out whether add local client entity to the rendering list or not.
|
||||
|
||||
### `void CL_CameraOffset( vec3_t offset )`
|
||||
|
||||
Not used in the engine.
|
||||
|
||||
### `void CL_CreateMove( float frametime, usercmd_t *cmd, int active )`
|
||||
|
||||
Called when `usercmd_t` is being created to let `client.dll` record user commands before they are being sent over the network.
|
||||
|
||||
* `frametime` is the delta time between previous and current frames.
|
||||
* `cmd` is the pointer to `usercmd_t` object, where player's intentions and impulses are added.
|
||||
* `active` is set to `1` when client is finished signing on to the server (as movement commands are being sent even if client is not fully spawned yet).
|
||||
|
||||
### `void IN_ActivateMouse( void )`
|
||||
|
||||
Called from similarly named NQ/QW function.
|
||||
|
||||
### `void IN_DeactivateMouse( void )`
|
||||
|
||||
Called from similarly named NQ/QW function.
|
||||
|
||||
### `void IN_MouseEvent( int mstate )`
|
||||
|
||||
Called from similarly named NQ/QW function.
|
||||
|
||||
### `void IN_Accumulate( void )`
|
||||
|
||||
Called from similarly named NQ/QW function.
|
||||
|
||||
### `void IN_ClearStates( void )`
|
||||
|
||||
Called from similarly named NQ/QW function.
|
||||
|
||||
### `void V_CalcRefdef( struct ref_params_s *params )`
|
||||
|
||||
Called each frame before rendering starts. This is close to the similarly named function found in NQ/QW and lets `client.dll` to run custom view logic.
|
||||
|
||||
* `params` is the refdef parameters object. It is also used as return value and might request from the engine to not draw anything (by `onlyClientDraws` field) or to run this multiple times (by `nextview` field)
|
||||
|
||||
### `kbutton_t *KB_Find( const char *name )`
|
||||
|
||||
Called by engine to find extra keys and their state.
|
||||
|
||||
Only few are really used by the engine:
|
||||
* `in_mlook` for mouse look
|
||||
* `in_jlook` for joystick look
|
||||
* `in_graph` for net_graph toggle
|
||||
|
||||
Return value: returns pointer to `kbutton_t` if found. `kbutton_t` structure matches the same structure that can be found in NQ and QW.
|
||||
|
||||
### `void HUD_DirectorMessage( int size, void *buf )`
|
||||
|
||||
> [!IMPORTANT]
|
||||
> This function is optional.
|
||||
|
||||
Called to notify `client.dll` about an `svc_director` message. This feature is used for HLTV, though mods use it to spawn text messages or execute commands (bypassing `svc_stufftext` filter in old `client.dll`) but `svc_director` message structure isn't enforced by engine, so it in theory mods might modify it for their own needs.
|
||||
|
||||
* `size` is the size of the payload
|
||||
* `buf` raw `svc_director` payload
|
||||
|
||||
### `void HUD_VoiceStatus( int entindex, qboolean talking )`
|
||||
|
||||
> [!IMPORTANT]
|
||||
> This function is optional.
|
||||
|
||||
Called to notify `client.dll` about a client status of using voice chat.
|
||||
* `entindex` an entity index (client index plus one, because zero is always world). When set to -1, notifies `client.dll` about local client recording. When set to -2, notifies `client.dll` about a loopback (i.e. local client's voice message was sent to server and received back)
|
||||
* `talking` if true, this client is talking
|
||||
|
||||
### `void HUD_ChatInputPosition( int *x, int *y )`
|
||||
|
||||
> [!IMPORTANT]
|
||||
> This function is optional.
|
||||
|
||||
When called, returns desired X and Y positions of chat box.
|
||||
9
Documentation/api/client/README.md
Normal file
9
Documentation/api/client/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# What is client.dll in GoldSource?
|
||||
|
||||
One of technical differences a programmer working with Quake engine might notice is the client.dll file. Historically, it appeared somewhere in between Alpha 0.52 and NetTest1 development and judging by Half-Life SDK 1.0 was meant to handle HUD rendering. The time moved forward, SDK 2.0 finalized `client.dll` API at version 7, added an in-game UI, custom input processing, player movement and weapon prediction, very basic rendering through TriAPI. SDK 2.1 added ability to re-define studio model rendering, and so on.
|
||||
|
||||
It somewhat resembles `cgame` module from Quake 3, but more crudely designed and sometimes feels like an afterthought, considering how many engine internal structures it exposes and the main point of incompatibilities with mods, which these days sometimes use `client.dll` as a way to inject custom rendering into the game.
|
||||
|
||||
In this document I will try to go through each step, letting you, dear reader, implement your own GoldSrc compatible API in your Quake fork, targetting vanilla Half-Life `client.dll` from latest update, which at the time of writing, is 25-th anniversary update.
|
||||
|
||||
Despite that we call it `client.dll`, since SDK 2.4 (unofficial naming, it's the first SDK Valve published on GitHub) it is considered portable and only has SDL2 and VGUI libraries in it's external dependences. It's only called this way to avoid possible misunderstandings with engine developers, who might interpret client as `cl_` prefixed part of Quake engine.
|
||||
120
Documentation/api/client/xx-structs.md
Normal file
120
Documentation/api/client/xx-structs.md
Normal file
@@ -0,0 +1,120 @@
|
||||
# `client.dll` shared structs and enums
|
||||
|
||||
GoldSource exposes a lot of structs to `client.dll`. Some of them are specific to `client.dll` and only used for passing data between engine and client, some of them are used in the engine everywhere, but still exposed in the public SDK. However, many mods rely on internal structs as well, and I will try to shed light on them as well.
|
||||
|
||||
When implementing them in C, consider the default to 32-bit systems alignment of 4 bytes and ILP32 data type model.
|
||||
|
||||
This file won't have API structs, for that, there should be separate chapters.
|
||||
|
||||
## Enums
|
||||
|
||||
### Entity types enum
|
||||
|
||||
This enum has no real name.
|
||||
|
||||
| Value | Description |
|
||||
|-------|-----------------|
|
||||
| `0` | Normal entities |
|
||||
| `1` | Players |
|
||||
| `2` | Temp entities |
|
||||
| `3` | Beams |
|
||||
| `4` | Fragmented entities. |
|
||||
|
||||
### edict->solid
|
||||
|
||||
It matches QuakeWorld definition.
|
||||
|
||||
## `client_data_t`
|
||||
|
||||
Used only to pass data to `client.dll` through `HUD_UpdateClientData`.
|
||||
|
||||
| Type | Name | Description |
|
||||
|----------|---------------|--------------------------------------------------------------|
|
||||
| `vec3_t` | `origin` | Local client origin. Cannot be changed by `client.dll` here. |
|
||||
| `vec3_t` | `viewangles` | Local client viewangles. This and next fields can be changed by `client.dll` |
|
||||
| `int` | `iWeaponBits` | Bit vector of weapons held by client. |
|
||||
| `float` | `fov` | Local client's field of view. |
|
||||
|
||||
## `netadr_t`
|
||||
|
||||
Matches Quake-2 structure with the similar name.
|
||||
|
||||
## `local_state_t`
|
||||
|
||||
Only consists of another structs. All of these structs are used to store prediction data, so one `local_state_t` for each predicted frame.
|
||||
|
||||
| Type | Name | Description |
|
||||
|---------------------|---------------|--------------------------------------|
|
||||
| `entity_state_t` | `playerstate` | Contains local player's entity state |
|
||||
| `clientdata_t` | `client` | Additional information about local client. _Do not get confused with `client_data_t`, note the underscore._ |
|
||||
| `weapon_data_t[64]` | `weapondata` | Array of 64 predictable weapons. |
|
||||
|
||||
## `entity_state_t`
|
||||
|
||||
This structure is similar to the one that can be found in QW, but contains much more data, though most of these fields are not used by the engine, but might be used by mods.
|
||||
|
||||
| Type | Name | Description |
|
||||
|-------------|----------------|------------------------------------------------------|
|
||||
| `int` | `entityType` | Entity type (see entity type enum above) |
|
||||
| `int` | `number` | Index of this entity on the server |
|
||||
| `float` | `msg_time` | Server time at which this entity had been updated |
|
||||
| `int` | `messagenum` | `parsecount` at which this entity had been updated |
|
||||
| `vec3_t` | `origin` | Non-interpolated entity position |
|
||||
| `vec3_t` | `angles` | Non-interpolated entity angles |
|
||||
| `int` | `modelindex` | Server model index |
|
||||
| `int` | `sequence` | Model animation sequence |
|
||||
| `int` | `frame` | Model animation frame |
|
||||
| `int` | `colormap` | Texture's top and bottom colors, like Quake |
|
||||
| `short` | `skin` | Texture number |
|
||||
| `short` | `solid` | `edict->solid` enum |
|
||||
| `int` | `effects` | Bitmask of entity effects |
|
||||
| `float` | `scale` | Entity scale value |
|
||||
| `byte` | `eflags` | |
|
||||
| `int` | `eflags` | |
|
||||
| `int` | `eflags` | |
|
||||
| `color24` | `eflags` | |
|
||||
| `int` | `eflags` | |
|
||||
| `int` | `eflags` | |
|
||||
| `float` | `eflags` | |
|
||||
| `float` | `eflags` | |
|
||||
| `int` | `eflags` | |
|
||||
| `byte[4]` | `eflags` | |
|
||||
| `byte[4]` | `eflags` | |
|
||||
| `vec3_t` | `eflags` | |
|
||||
| `vec3_t` | `eflags` | |
|
||||
| `vec3_t` | `eflags` | |
|
||||
| `vec3_t` | `maxs` | |
|
||||
| `int` | `eflags` | |
|
||||
| `int` | `eflags` | |
|
||||
| `float` | `eflags` | |
|
||||
| `float` | `eflags` | |
|
||||
| `int` | `eflags` | |
|
||||
| `int` | `eflags` | |
|
||||
| `int` | `eflags` | |
|
||||
| `qboolean` | `eflags` | |
|
||||
| `int` | `eflags` | |
|
||||
| `int` | `eflags` | |
|
||||
| `vec3_t` | `eflags` | |
|
||||
| `int` | `eflags` | |
|
||||
| `int` | `eflags` | |
|
||||
| `int` | `eflags` | |
|
||||
| `int` | `eflags` | |
|
||||
| `float` | `eflags` | |
|
||||
| `float` | `eflags` | |
|
||||
| `int` | `eflags` | |
|
||||
| `vec3_t` | `eflags` | |
|
||||
| `vec3_t` | `eflags` | |
|
||||
| `float` | `eflags` | |
|
||||
| `float` | `eflags` | |
|
||||
| `int` | `iuser1` | Few more extra fields for mods |
|
||||
| `int` | `iuser2` | |
|
||||
| `int` | `iuser3` | |
|
||||
| `int` | `iuser4` | |
|
||||
| `float` | `fuser1` | |
|
||||
| `float` | `fuser2` | |
|
||||
| `float` | `fuser3` | |
|
||||
| `float` | `fuser4` | |
|
||||
| `vec3_t` | `vuser1` | |
|
||||
| `vec3_t` | `vuser2` | |
|
||||
| `vec3_t` | `vuser3` | |
|
||||
| `vec3_t` | `vuser4` | |
|
||||
@@ -13,5 +13,6 @@ When `-bugcomp` is specified with argument, it interpreted as flags separated wi
|
||||
| Flag | Description | Games that require this flag |
|
||||
| ------- | ----------- | ---------------------------- |
|
||||
| `peoei` | Reverts `pfnPEntityOfEntIndex` behavior to GoldSrc, where it returns NULL for last player due to incorrect player index comparison | * Counter-Strike: Condition Zero - Deleted Scenes |
|
||||
| `gsmrf` | Rewrites message at the moment when Game DLL attempts to write an internal engine message, usually specific to GoldSrc protocol. Right now only supports `svc_spawnstaticsound`, more messages added by request. | * MetaMod/AMXModX based mods |
|
||||
| `gsmrf` | Rewrites message at the moment when Game DLL attempts to write an internal engine message, usually specific to GoldSrc protocol.<br>Right now only supports `svc_spawnstaticsound`, more messages added by request. | * MetaMod/AMXModX based mods |
|
||||
| `sp_attn_none` | Makes sounds with attenuation zero spatialized, i.e. have a stereo effect. | Possibly, every game that was made for GoldSrc. |
|
||||
| `get_game_dir_full` | Makes server return full path in server's `pfnGetGameDir` API function | Mods targetting engine before HL 1.1.1.1, according to MetaMod [documentation](http://metamod.org/engine_notes.html#GetGameDir) |
|
||||
|
||||
@@ -5,5 +5,10 @@ This can be useful to test engine in Wine without using virtual machines or dual
|
||||
0. Clone and install https://github.com/mstorsjo/msvc-wine (you can skip CMake part)
|
||||
1. Set environment variable MSVC_WINE_PATH to the path to installed MSVC toolchain
|
||||
2. Pre-load wine: `wineserver -k; wineserver -p; wine64 wineboot`
|
||||
3. Run `./waf configure -T <build-type> --enable-wine-msvc --sdl2=../SDL2_VC`. Configuration step will take more time than usual.
|
||||
3. Run `PKGCONFIG=/bin/false ./waf configure -T <build-type> --enable-wine-msvc --sdl2=../SDL2_VC`. Configuration step will take more time than usual.
|
||||
4. .. other typical steps to build from console ...
|
||||
|
||||
> [!NOTE]
|
||||
> Notice the usage of PKGCONFIG=/bin/false here. We're disabling pkg-config so we don't accidentally pull
|
||||
> system-wide dependencies and force building them from source. In future builds we might set custom
|
||||
> directory to pull dependencies from, like ffmpeg...
|
||||
|
||||
@@ -1,150 +0,0 @@
|
||||
# There are few new commands availiable in xash3d fork:
|
||||
|
||||
## Commands:
|
||||
### ent_create
|
||||
Create entity with specified classname and key/values
|
||||
|
||||
`ent_create <classname> <key> <value> <key> <value> ...`
|
||||
|
||||
for example:
|
||||
|
||||
`ent_create monster_zombie targetname zomb1`
|
||||
|
||||
after creating entity, ent_last_xxx cvars are set to new entity and ent_last_cb called, look at ent_getvars description
|
||||
|
||||
### ent_fire
|
||||
|
||||
Make some actions on entity
|
||||
|
||||
`ent_fire <pattern> <command> <args>`
|
||||
Availiavle commands:
|
||||
* Set fields (Only set entity field, does not call any functions):
|
||||
* health
|
||||
* gravity
|
||||
* movetype
|
||||
* solid
|
||||
* rendermode
|
||||
* rendercolor (vector)
|
||||
* renderfx
|
||||
* renderamt
|
||||
* hullmin (vector)
|
||||
* hullmax (vector)
|
||||
* Actions
|
||||
* rename: set entity targetname
|
||||
* settarget: set entity target (only targetnames)
|
||||
* setmodel: set entity model (does not update)
|
||||
* set: set key/value by server library
|
||||
* See game FGD to get list.
|
||||
* command takes two arguments
|
||||
* touch: touch entity by current player.
|
||||
* use: use entity by current player.
|
||||
* movehere: place entity in player fov.
|
||||
* drop2floor: place entity to nearest floor surface
|
||||
* moveup: move entity to 25 units up
|
||||
* moveup (value): move by y axis relatively to specified value
|
||||
* Flags (Set/clear specified flag bit, arg is bit number):
|
||||
* setflag
|
||||
* clearflag
|
||||
* setspawnflag
|
||||
* clearspawnflag
|
||||
|
||||
### ent_info
|
||||
Print information about entity by identificator
|
||||
|
||||
`ent_info <identificator>`
|
||||
|
||||
### ent_getvars
|
||||
Set client cvars containing entity information (useful for [[Scripting]]) and call ent_last_cb
|
||||
|
||||
`ent_getvars <identificator>`
|
||||
|
||||
These cvars are set:
|
||||
```
|
||||
ent_last_name
|
||||
ent_last_num
|
||||
ent_last_inst
|
||||
ent_last_origin
|
||||
ent_last_class
|
||||
```
|
||||
|
||||
### ent_list
|
||||
Print short information about antities, filtered by pattern
|
||||
|
||||
`ent_list <pattern>`
|
||||
|
||||
## Syntax description
|
||||
|
||||
### \<identificator\>
|
||||
|
||||
* !cross: entity under aim
|
||||
* Instance code: !\<number\>_\<seria\l>
|
||||
* set by ent_getvars command
|
||||
* Entity index
|
||||
* targetname pattern
|
||||
|
||||
### \<pattern\>
|
||||
|
||||
Pattern is like identificator, but may filter many entities by classname
|
||||
|
||||
### (vector)
|
||||
|
||||
used by ent_fire command. vector means three float values, entered without quotes
|
||||
|
||||
### key/value
|
||||
|
||||
All entities parameters may be set by specifiing key and value strings.
|
||||
|
||||
Originally, this mechanizm is used in map/bsp format, but it can be used in enttools too.
|
||||
|
||||
Keys and values are passed to server library and processed by entity keyvalue function, setting edict and entity owns parameters.
|
||||
|
||||
If value contains spaces, it must be put in quotes:
|
||||
|
||||
`ent_fire !cross set origin "0 0 0"`
|
||||
|
||||
## Using with scripting
|
||||
|
||||
ent_create and ent_getvars commands are setting cvars on client
|
||||
|
||||
It can be used with ent_last_cb alias that is executed after setting cvars.
|
||||
|
||||
Simple example:
|
||||
|
||||
```
|
||||
ent_create weapon_c4
|
||||
alias ent_last_cb "ent_fire \$ent_last_inst use"
|
||||
```
|
||||
|
||||
Use weapon_c4 after creating it.
|
||||
|
||||
Note that you cannot use many dfferent callbacks at the same time.
|
||||
|
||||
You can set entity name by by pattern and create special script, contatning all callbacks.
|
||||
|
||||
Example:
|
||||
|
||||
example.cfg
|
||||
```
|
||||
alias ent_last_cb exec entity_cb.cfg
|
||||
ent create \<class\> targetname my_ent1_$name
|
||||
ent_create \<class\> targetname my_ent2_$name
|
||||
```
|
||||
entity_cb.cfg
|
||||
```
|
||||
if $ent_last_name == my_ent1_$name
|
||||
:(ent1 actions)
|
||||
if $ent_last_name == my_ent2_$name
|
||||
:(ent2 actions)
|
||||
```
|
||||
Note that scripting cannot be blocking. You cannot wait for server answer and continue. But you can use small scripts, connected with ent_last_cb command. The best usage is user interaction. You can add touch buttons to screen or call user command menu actions by callbacks.
|
||||
## Server side
|
||||
|
||||
To enable entity tools on server, set sv_enttools_enable to 1
|
||||
|
||||
To change maximum number of entities, touched by ent_fire, change sv_enttools_maxfire to required number.
|
||||
|
||||
To enable actions on players, set sv_enttools_players to 1.
|
||||
|
||||
To enable entity tools for player by nickname, set sv_enttools_godplayer to nickname. Useful to temporary enable from rcon.
|
||||
|
||||
To prevent crash on some actions, set host_mapdesign_fatal to 0
|
||||
152
Documentation/extensions/entity-tools.md
Normal file
152
Documentation/extensions/entity-tools.md
Normal file
@@ -0,0 +1,152 @@
|
||||
# Entity tools
|
||||
|
||||
For some features described below, you need to enable [console scripting](https://github.com/FWGS/xash3d-fwgs/blob/master/Documentation/extensions/console-scripting.md) with command `cmd_scripting 1` in console.
|
||||
To get more information about it, check another according page related to console scripting.
|
||||
|
||||
## Commands description
|
||||
|
||||
### ent_create
|
||||
Create entity with specified classname and key/values.
|
||||
|
||||
`ent_create <classname> <key> <value> <key> <value> ...`
|
||||
|
||||
For example:
|
||||
|
||||
`ent_create monster_zombie targetname zomb1`
|
||||
|
||||
After creating entity, ent_last_xxx cvars are set to new entity and ent_last_cb called, look at ent_getvars description.
|
||||
|
||||
### ent_fire
|
||||
|
||||
Makes some actions on entity.
|
||||
|
||||
`ent_fire <pattern> <command> <args>`
|
||||
|
||||
#### Available commands:
|
||||
|
||||
Set fields (only set entity field, does not call any functions)
|
||||
* health
|
||||
* gravity
|
||||
* movetype
|
||||
* solid
|
||||
* rendermode
|
||||
* rendercolor (vector)
|
||||
* renderfx
|
||||
* renderamt
|
||||
* hullmin (vector)
|
||||
* hullmax (vector)
|
||||
|
||||
Actions
|
||||
* rename: set entity targetname
|
||||
* settarget: set entity target (only targetnames)
|
||||
* setmodel: set entity model (does not update)
|
||||
* set: set key/value by server library
|
||||
* See game FGD to get list.
|
||||
* command takes two arguments
|
||||
* touch: touch entity by current player.
|
||||
* use: use entity by current player.
|
||||
* movehere: place entity in player fov.
|
||||
* drop2floor: place entity to nearest floor surface
|
||||
* moveup: move entity to 25 units up
|
||||
* moveup (value): move by y axis relatively to specified value
|
||||
|
||||
Flags (set/clear specified flag bit, arg is bit number):
|
||||
* setflag
|
||||
* clearflag
|
||||
* setspawnflag
|
||||
* clearspawnflag
|
||||
|
||||
### ent_info
|
||||
Print information about entity by identificator.
|
||||
|
||||
`ent_info <identificator>`
|
||||
|
||||
### ent_getvars
|
||||
Set client cvars containing entity information (useful for [scripting](extensions/console-scripting.md)) and then calls ent_last_cb.
|
||||
|
||||
`ent_getvars <identificator>`
|
||||
|
||||
These cvars are set:
|
||||
```
|
||||
ent_last_name
|
||||
ent_last_num
|
||||
ent_last_inst
|
||||
ent_last_origin
|
||||
ent_last_class
|
||||
```
|
||||
|
||||
### ent_list
|
||||
Print short information about antities, filtered by pattern.
|
||||
|
||||
`ent_list <pattern>`
|
||||
|
||||
## Syntax description
|
||||
|
||||
#### \<identificator\>
|
||||
* !cross: entity under aim
|
||||
* !\<number\>_\<serial\>: instance code
|
||||
* Set by ent_getvars command
|
||||
* Entity index
|
||||
* Targetname pattern
|
||||
|
||||
#### \<pattern\>
|
||||
|
||||
Pattern is like identificator, but may filter many entities by classname.
|
||||
|
||||
#### (vector)
|
||||
|
||||
Used by ent_fire command. vector means three float values, entered without quotes.
|
||||
|
||||
#### key/value
|
||||
|
||||
All entities parameters may be set by specifiing key and value strings.
|
||||
|
||||
Originally, this mechanizm is used in map/bsp format, but it can be used in enttools too.
|
||||
|
||||
Keys and values are passed to server library and processed by entity keyvalue function, setting edict and entity owns parameters.
|
||||
|
||||
If value contains spaces, it must be put in quotes:
|
||||
|
||||
`ent_fire !cross set origin "0 0 0"`
|
||||
|
||||
## Using with scripting
|
||||
|
||||
ent_create and ent_getvars commands are setting cvars on client
|
||||
|
||||
It can be used with ent_last_cb alias that is executed after setting cvars.
|
||||
|
||||
Simple example:
|
||||
|
||||
```
|
||||
ent_create weapon_c4
|
||||
alias ent_last_cb "ent_fire \$ent_last_inst use"
|
||||
```
|
||||
|
||||
Use weapon_c4 after creating it.
|
||||
|
||||
Note that you cannot use many different callbacks at the same time.
|
||||
|
||||
You can set entity name by by pattern and create special script, contatning all callbacks.
|
||||
|
||||
Example:
|
||||
|
||||
> example.cfg
|
||||
```
|
||||
alias ent_last_cb exec entity_cb.cfg
|
||||
ent create \<class\> targetname my_ent1_$name
|
||||
ent_create \<class\> targetname my_ent2_$name
|
||||
```
|
||||
> entity_cb.cfg
|
||||
```
|
||||
if $ent_last_name == my_ent1_$name
|
||||
:(ent1 actions)
|
||||
if $ent_last_name == my_ent2_$name
|
||||
:(ent2 actions)
|
||||
```
|
||||
Note that scripting cannot be blocking. You cannot wait for server answer and continue. But you can use small scripts, connected with ent_last_cb command. The best usage is user interaction. You can add touch buttons to screen or call user command menu actions by callbacks.
|
||||
|
||||
## Server side configuration
|
||||
|
||||
To enable entity tools on server, set sv_enttools_enable to 1
|
||||
|
||||
To change maximum number of entities, touched by ent_fire, change sv_enttools_maxfire to required number.
|
||||
@@ -85,6 +85,9 @@ These strings are specific to Xash3D FWGS.
|
||||
As Xash3D accidentally supports GoldSrc games, it also supports parsing liblist.gam.\
|
||||
Xash3D will use this file if gameinfo.txt is absent, or if its modification timestamp is older than liblist.gam.
|
||||
|
||||
> [!NOTE]
|
||||
> Starting from January 2025, Xash3D FWGS doesn't automatically generate gameinfo.txt from liblist.gam. The key conversion table still remains but if you wish to use gameinfo.txt instead of liblist.gam, you can execute `fs_make_gameinfo` in console.
|
||||
|
||||
For game creators who plan supporting only Xash3D, using this file is not recommended.
|
||||
|
||||
The table below defines conversion rules from liblist.gam to gameinfo.txt. Some keys' interpretation does differ from `gameinfo.txt`, in this case a note will be left. If `liblist.gam` key isn't present in this table, it's ignored.
|
||||
|
||||
15
Documentation/goldsrc-protocol-support.md
Normal file
15
Documentation/goldsrc-protocol-support.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# Support for GoldSrc network protocol
|
||||
This feature is still work-in-progress, but for now it's available for all users, and we appreciate any bug-reports and contributions around it.
|
||||
|
||||
For connecting to GoldSrc-based servers, use this command:
|
||||
```
|
||||
connect ip:port gs
|
||||
```
|
||||
|
||||
But keep in mind, there are requirement for server to be able to accept connections from Xash3D-based clients: it should use Reunion or Dproto.
|
||||
Without this requirement, you will just get "Steam validation rejected" error on connecting.
|
||||
|
||||
That is because proper authorization with Steam API is not implemented in engine yet (but we have plans on it).
|
||||
|
||||
Also, we encountered that some GoldSrc-based servers are recognizing Xash3D clients as "fake clients" and banning/kicking them. Maybe this problem will be
|
||||
solved along with better compatibility with GoldSrc behavior, but may be not - we don't know logic behind this fake client checks.
|
||||
32
Documentation/nat-bypass-usage.md
Normal file
32
Documentation/nat-bypass-usage.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# NAT bypass feature in Xash3D FWGS
|
||||
Since IPv6 not as widespread as we would like, NAT (Network Address Translation) still being actively used by many internet service providers in an attempts to
|
||||
mitigate IPv4 addresses exhaustion. In short, they uses one IPv4 address and doing some tricks with ports to represent many other users behind this address.
|
||||
|
||||
But this leads to a problem for users: they cannot accept direct
|
||||
incoming connections anymore. This means that if you are behind provider's NAT and will try to setup Xash3D FWGS server - nobody will be able to connect to it,
|
||||
and the server will not show up in servers public list.
|
||||
|
||||
## Is it possible to avoid this problem?
|
||||
In most cases, it is possible to bypass NAT with UDP hole punching, and Xash3D FWGS uses this method too. But this method is not 100% guaranteed to work - it depends
|
||||
on NAT configurations on both server and client side, and there is no way to control it.
|
||||
|
||||
First of all, server should not be behind symmetric NAT. You can check your NAT type on [this page](https://www.checkmynat.com/).
|
||||
If you get "Symmetric NAT" result in this test, that means you cannot setup publicly available server with internet connection that you are using.
|
||||
|
||||
Here is more detailed scheme of different NAT types compatibility, it explains are users with different NAT types can connect to each other or not.
|
||||
Client NAT type on rows, server NAT type on columns:
|
||||
|
||||
| *NAT Type* | Full Cone | Restricted Cone | Port Restricted Cone | Symmetric |
|
||||
| -------------------- | --------- | --------------- | -------------------- | --------- |
|
||||
| Full Cone | ✔️ | ✔️ | ✔️ | ✔️ |
|
||||
| Restricted Cone | ✔️ | ✔️ | ✔️ | ✔️ |
|
||||
| Port Restricted Cone | ✔️ | ✔️ | ✔️ | ❌ |
|
||||
| Symmetric | ✔️ | ✔️ | ❌ | ❌ |
|
||||
|
||||
## How to use NAT bypass feature?
|
||||
If you are starting server within the game, you need to enable option "*Use NAT Bypass instead of direct mode*" in bottom left corner of screen.
|
||||
|
||||
If you are starting dedicated server, you should add console variable `sv_nat 1` to server.cfg file, or add `+sv_nat 1` to server startup parameters.
|
||||
|
||||
If you need to connect to server behind NAT, there is separate tab named *NAT* for such servers in server browser. Note that it is useless to add NAT server to favorites,
|
||||
or somehow trying manually to store it, because it has changing address and port.
|
||||
@@ -38,6 +38,9 @@ Uploaded to github by Oleg Cherkasky - https://github.com/gunrunners-paradise/Ct
|
||||
## Deathmatch Classic
|
||||
Available in Valve's Half-Life repository - https://github.com/ValveSoftware/halflife/tree/master/dmc
|
||||
|
||||
## Earth Special Forces
|
||||
Alpha 2.0 - https://www.gamers-desire.de/details/2830
|
||||
|
||||
## ESHQ
|
||||
Official github repository - https://github.com/adslbarxatov/xash3d-for-ESHQ
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
Xash3D FWGS is intended to be easily ported to specific platform, however main issue is maintaining such ports.
|
||||
Xash3D FWGS is intended to be easily portable for various platforms, however main issue is maintaining such ports.
|
||||
|
||||
This page is about merged ports to main source tree and responsible for it developers.
|
||||
|
||||
For porting guidelines, look engine-porting-guide.md.
|
||||
For porting guidelines, read engine-porting-guide.md.
|
||||
|
||||
Status:
|
||||
* **Supported**: active, was confirmed to be fully functional.
|
||||
* **Supported**: active, confirmed to be fully functional, gets built on CI.
|
||||
* **Orphaned**: some work was done but not finished or actively tested due to lack of human resources.
|
||||
* **In progress**: active, under development.
|
||||
* **Old Engine**: port was for old engine fork
|
||||
* **Not maintained**: lack of human resources
|
||||
* **Deprecated**: not supported anymore
|
||||
* **Old Engine**: port was for old engine fork.
|
||||
* **Deprecated**: not supported anymore.
|
||||
|
||||
Table is sorted by status and platform.
|
||||
|
||||
@@ -17,17 +17,22 @@ Table is sorted by status and platform.
|
||||
| -------- | ------ | ---------- | ----
|
||||
| Android | Supported | @Velaron |
|
||||
| *BSD | Supported | @nekonomicon |
|
||||
| DOS4GW | Supported | @mittorn |
|
||||
| GNU/Linux | Supported | @a1batross, @mittorn |
|
||||
| Haiku | Supported | not maintained | Was added by #478 and #483
|
||||
| macOS | Supported | @sofakng |
|
||||
| MotoMAGX | Supported | @a1batross |
|
||||
| PSVita | Supported | @fgsfdsfgs |
|
||||
| Switch | Supported | @fgsfdsfgs |
|
||||
| Windows | Supported | @a1batross, @SNMetamorph |
|
||||
| DOS4GW | Orphaned | N/A | Haven't been confirmed to work for a very long time
|
||||
| Haiku | Orphaned | N/A | Was added by #478 and #483
|
||||
| IRIX | Orphaned | N/A | Undone, compiles but requires big endian port
|
||||
| MotoMAGX | Orphaned | N/A | Should work but the compiler used for this platform is very unstable and easy to crash (it's GCC 3.4)
|
||||
| SerenityOS | Orphaned | N/A | Works but not throughly tested
|
||||
| Solaris | Orphaned | N/A | Works but not throughly tested
|
||||
| WebAssembly System Interface | Orphaned | N/A | Undone, WASI is missing a lot of APIs we want to use
|
||||
| Dreamcast | In progress | @maximqaxd | [GitHub Repository](https://github.com/maximqaxd/xash3d-fwgs_dc/)
|
||||
| PSP | In progress | @Crow_bar, @Velaron | [GitHub Repository](https://github.com/Crow-bar/xash3d-fwgs)
|
||||
| Wii | In progress | Collaborative effort | [GitHub Repository](https://github.com/saucesaft/xash3d-wii)
|
||||
| Emscripten | Old Engine | not maintained |
|
||||
| 3DS | Old Engine fork | not maintained | [GitHub Repository](https://github.com/masterfeizz/Xash3DS)
|
||||
| Oculus Quest | Old Engine fork | @DrBeef | [GitHub Repository](https://github.com/DrBeef/Lambda1VR)
|
||||
| iOS | Deprecated | not maintained | See GitHub issue #61
|
||||
| Emscripten | Old Engine | N/A |
|
||||
| 3DS | Old Engine fork | N/A | [GitHub Repository](https://github.com/masterfeizz/Xash3DS)
|
||||
| Oculus Quest | Old Engine fork | N/A | [GitHub Repository](https://github.com/DrBeef/Lambda1VR)
|
||||
| iOS | Deprecated | N/A | See GitHub issue #61
|
||||
|
||||
65
README.md
65
README.md
@@ -2,7 +2,7 @@
|
||||
[](https://builds.sr.ht/~a1batross/xash3d-fwgs?) [](https://github.com/FWGS/xash3d-fwgs/actions/workflows/c-cpp.yml) [](https://cirrus-ci.com/github/FWGS/xash3d-fwgs) [](http://fwgsdiscord.mentality.rip/) \
|
||||
[](https://github.com/FWGS/xash3d-fwgs/releases/latest) [](https://github.com/FWGS/xash3d-fwgs/releases/tag/continuous)
|
||||
|
||||
Xash3D (pronounced `[ksɑʂ]`) FWGS is a game engine, aimed to provide compatibility with Half-Life Engine and extend it, as well as to give game developers well known workflow.
|
||||
Xash3D ([pronounced](https://ipa-reader.com/?text=ks%C9%91%CA%82) `[ksɑʂ]`) FWGS is a game engine, aimed to provide compatibility with Half-Life Engine and extend it, as well as to give game developers well known workflow.
|
||||
|
||||
Xash3D FWGS is a heavily modified fork of an original [Xash3D Engine](https://www.moddb.com/engines/xash3d-engine) by Unkle Mike.
|
||||
|
||||
@@ -11,16 +11,17 @@ Xash3D FWGS is a heavily modified fork of an original [Xash3D Engine](https://ww
|
||||
If you like Xash3D FWGS, consider supporting individual engine maintainers. By supporting us, you help to continue developing this game engine further. The sponsorship links are available in [documentation](Documentation/donate.md).
|
||||
|
||||
## Fork features
|
||||
* Steam Half-Life (HLSDK 2.4) support.
|
||||
* Steam Half-Life (HLSDK 2.5) support.
|
||||
* Crossplatform and modern compilers support: supports Windows, Linux, BSD & Android on x86 & ARM and [many more](Documentation/ports.md).
|
||||
* Better multiplayer support: multiple master servers, headless dedicated server, voice chat and IPv6 support.
|
||||
* Better multiplayer: multiple master servers, headless dedicated server, voice chat, [GoldSrc protocol support](Documentation/goldsrc-protocol-support.md) and IPv6 support.
|
||||
* Multiple renderers support: OpenGL, GLESv1, GLESv2 and Software.
|
||||
* Advanced virtual filesystem: `.pk3` and `.pk3dir` support, compatibility with GoldSrc FS module, fast case-insensitivity emulation for crossplatform.
|
||||
* Mobility API: better game integration on mobile devices (vibration, touch controls)
|
||||
* Mobility API: better game integration on mobile devices (vibration, touch controls).
|
||||
* Different input methods: touch and gamepad in addition to mouse & keyboard.
|
||||
* TrueType font rendering, as a part of mainui_cpp.
|
||||
* External VGUI support module.
|
||||
* PNG & KTX2 image format support.
|
||||
* Ogg Vorbis (`.ogg`) & Ogg Opus (`.opus`) audio formats support.
|
||||
* [A set of small improvements](Documentation/), without broken compatibility.
|
||||
|
||||
## Installation & Running
|
||||
@@ -35,17 +36,16 @@ You still needed to copy `valve` directory as all game resources located there.
|
||||
For additional info, run Xash3D with `-help` command line key.
|
||||
|
||||
## Contributing
|
||||
* Before sending an issue, check if someone already reported your issue. Make sure you're following "How To Ask Questions The Smart Way" guide by Eric Steven Raymond. Read more: http://www.catb.org/~esr/faqs/smart-questions.html
|
||||
* Issues are accepted in both English and Russian
|
||||
* Before sending an issue, check if someone already reported your issue. Make sure you're following "How To Ask Questions The Smart Way" guide by Eric Steven Raymond. Read more: http://www.catb.org/~esr/faqs/smart-questions.html.
|
||||
* Issues are accepted in both English and Russian.
|
||||
* Before sending a PR, check if you followed our contribution guide in CONTRIBUTING.md file.
|
||||
|
||||
## Build instructions
|
||||
We are using Waf build system. If you have some Waf-related questions, I recommend you to read https://waf.io/book/
|
||||
We are using Waf build system. If you have some Waf-related questions, I recommend you to read [Waf Book](https://waf.io/book/).
|
||||
|
||||
NOTE: NEVER USE GitHub's ZIP ARCHIVES. GitHub doesn't include external dependencies we're using!
|
||||
|
||||
### Prerequisites
|
||||
|
||||
If your CPU is x86 compatible and you're on Windows or Linux, we are building 32-bit code by default. This was done to maintain compatibility with Steam releases of Half-Life and based on it's engine games.
|
||||
Even if Xash3D FWGS does support targetting 64-bit, you can't load games without recompiling them from source code!
|
||||
|
||||
@@ -62,31 +62,40 @@ This repository contains our fork of HLSDK and restored source code for Half-Lif
|
||||
|
||||
#### GNU/Linux
|
||||
##### Debian/Ubuntu
|
||||
* Enable i386 on your system, if you're compiling 32-bit engine on amd64. If not, skip this
|
||||
* Only for 32-bit engine on 64-bit x86 operating system:
|
||||
* Enable i386 on your system: `$ sudo dpkg --add-architecture i386`.
|
||||
* Install `aptitude` ([why?](https://github.com/FWGS/xash3d-fwgs/issues/1828#issuecomment-2415131759)): `$ sudo apt update && sudo apt upgrade && sudo apt install aptitude`
|
||||
* Install development tools: `$ sudo aptitude --without-recommends install git build-essential gcc-multilib g++-multilib libsdl2-dev:i386 libfontconfig-dev:i386 libfreetype-dev:i386 libopus-dev:i386 libbz2-dev:i386`.
|
||||
* Set PKG_CONFIG_PATH environment variable to point at 32-bit libraries: `$ export PKG_CONFIG_PATH=/usr/lib/i386-linux-gnu/pkgconfig`.
|
||||
|
||||
`$ sudo dpkg --add-architecture i386`
|
||||
* Install development tools
|
||||
* For 32-bit engine on amd64: \
|
||||
`$ sudo apt install build-essential gcc-multilib g++-multilib python libsdl2-dev:i386 libfontconfig-dev:i386 libfreetype6-dev:i386`
|
||||
* For everything else: \
|
||||
`$ sudo apt install build-essential python libsdl2-dev libfontconfig-dev libfreetype6-dev`
|
||||
* Clone this repostory:
|
||||
`$ git clone --recursive https://github.com/FWGS/xash3d-fwgs`
|
||||
* For 64-bit engine on 64-bit x86 and other non-x86 systems:
|
||||
* Install development tools: `$ sudo apt install git build-essential python libsdl2-dev libfontconfig-dev libfreetype6-dev libopus-dev libbz2-dev`.
|
||||
|
||||
* Clone this repostory: `$ git clone --recursive https://github.com/FWGS/xash3d-fwgs`.
|
||||
|
||||
##### RedHat/Fedora
|
||||
* Only for 32-bit engine on 64-bit x86 operating system:
|
||||
* Install development tools: `$ sudo dnf install git gcc gcc-c++ glibc-devel.i686 SDL2-devel.i686 opus-devel.i686 fontconfig-devel.i686 freetype-devel.i686 bzip2-devel.i686`.
|
||||
* Set PKG_CONFIG_PATH environment variable to point at 32-bit libraries: `$ export PKG_CONFIG_PATH=/usr/lib/pkgconfig`.
|
||||
|
||||
* For 64-bit engine on 64-bit x86 and other non-x86 systems:
|
||||
* Install development tools: `$ sudo dnf install git gcc gcc-c++ SDL2-devel opus-devel fontconfig-devel freetype-devel bzip2-devel`.
|
||||
|
||||
* Clone this repostory: `$ git clone --recursive https://github.com/FWGS/xash3d-fwgs`.
|
||||
|
||||
### Building
|
||||
#### Windows (Visual Studio)
|
||||
0) Open command line
|
||||
0) Open command line.
|
||||
1) Navigate to `xash3d-fwgs` directory.
|
||||
2) Carefully examine which build options are available: `waf --help`
|
||||
3) Configure build: `waf configure -T release --sdl2=c:/path/to/SDL2`
|
||||
4) Compile: `waf build`
|
||||
5) Install: `waf install --destdir=c:/path/to/any/output/directory`
|
||||
2) (optional) Examine which build options are available: `waf --help`.
|
||||
3) Configure build: `waf configure --sdl2=c:/path/to/SDL2`.
|
||||
4) Compile: `waf build`.
|
||||
5) Install: `waf install --destdir=c:/path/to/any/output/directory`.
|
||||
|
||||
#### Linux
|
||||
If compiling 32-bit on amd64, you may need to supply `export PKG_CONFIG_PATH=/usr/lib/i386-linux-gnu/pkgconfig` prior to running configure.
|
||||
If compiling 32-bit on amd64, make sure `PKG_CONFIG_PATH` from the previous step is set correctly, prior to running configure.
|
||||
|
||||
0) Examine which build options are available: `./waf --help`
|
||||
1) Configure build: `./waf configure -T release`
|
||||
(You need to pass `-8` to compile 64-bit engine on 64-bit x86 processor)
|
||||
2) Compile: `./waf build`
|
||||
3) Install(optional): `./waf install --destdir=/path/to/any/output/directory`
|
||||
0) (optional) Examine which build options are available: `./waf --help`.
|
||||
1) Configure build: `./waf configure` (you need to pass `-8` to compile 64-bit engine on 64-bit x86 processor).
|
||||
2) Compile: `./waf build`.
|
||||
3) Install: `./waf install --destdir=/path/to/any/output/directory`.
|
||||
|
||||
14
android/.gitignore
vendored
Normal file
14
android/.gitignore
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
.gradle/
|
||||
build/
|
||||
.externalNativeBuild
|
||||
.cxx/
|
||||
.idea/
|
||||
local.properties
|
||||
.project
|
||||
.classpath
|
||||
.gradle
|
||||
.settings
|
||||
release/
|
||||
*.hprof
|
||||
.vscode/
|
||||
*.bak
|
||||
60
android/app/CMakeLists.txt
Normal file
60
android/app/CMakeLists.txt
Normal file
@@ -0,0 +1,60 @@
|
||||
cmake_minimum_required(VERSION 3.6)
|
||||
|
||||
# Only used to build Android project
|
||||
|
||||
project(XASH_ANDROID)
|
||||
|
||||
# armeabi-v7a requires cpufeatures library
|
||||
include(AndroidNdkModules)
|
||||
android_ndk_import_module_cpufeatures()
|
||||
|
||||
find_package(PythonInterp 2.7 REQUIRED)
|
||||
|
||||
get_filename_component(C_COMPILER_ID ${CMAKE_C_COMPILER} NAME_WE)
|
||||
get_filename_component(CXX_COMPILER_ID ${CMAKE_CXX_COMPILER} NAME_WE)
|
||||
|
||||
if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
|
||||
set(BUILD_TYPE "debug")
|
||||
else()
|
||||
set(BUILD_TYPE "release")
|
||||
list(APPEND WAF_EXTRA_ARGS --enable-poly-opt --enable-lto)
|
||||
endif()
|
||||
|
||||
if(CMAKE_SIZEOF_VOID_P MATCHES "8")
|
||||
set(64BIT ON CACHE BOOL "" FORCE)
|
||||
list(APPEND WAF_EXTRA_ARGS -8) # only required for x86 when testing this cmakelist under linux
|
||||
endif()
|
||||
|
||||
set(CMAKE_VERBOSE_MAKEFILE ON)
|
||||
|
||||
set(WAF_CC "${CMAKE_C_COMPILER} --target=${CMAKE_C_COMPILER_TARGET}")
|
||||
set(WAF_CXX "${CMAKE_CXX_COMPILER} --target=${CMAKE_CXX_COMPILER_TARGET}")
|
||||
|
||||
# not cleanest way to get upper directory
|
||||
set(ENGINE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../")
|
||||
|
||||
execute_process(
|
||||
COMMAND ${CMAKE_COMMAND} -E env
|
||||
CC=${WAF_CC} CXX=${WAF_CXX}
|
||||
AR=${CMAKE_AR} STRIP=${CMAKE_STRIP}
|
||||
${PYTHON_EXECUTABLE} waf configure -T ${BUILD_TYPE} ${WAF_EXTRA_ARGS} cmake
|
||||
--check-c-compiler=${C_COMPILER_ID} --check-cxx-compiler=${CXX_COMPILER_ID}
|
||||
-s "${ENGINE_SOURCE_DIR}/SDL" --skip-sdl2-sanity-check --enable-bundled-deps
|
||||
WORKING_DIRECTORY "${ENGINE_SOURCE_DIR}"
|
||||
)
|
||||
|
||||
add_subdirectory("${ENGINE_SOURCE_DIR}/3rdparty/hlsdk-portable" hlsdk-portable)
|
||||
|
||||
# try to build minimal SDL. Enable features as we're gonna use them
|
||||
set(SDL_RENDER OFF)
|
||||
set(SDL_POWER OFF)
|
||||
set(SDL_VULKAN OFF)
|
||||
set(SDL_DISKAUDIO OFF)
|
||||
set(SDL_DUMMYAUDIO OFF)
|
||||
set(SDL_DUMMYVIDEO OFF)
|
||||
set(SDL_VULKAN OFF)
|
||||
set(SDL_OFFSCREEN OFF)
|
||||
set(SDL_STATIC OFF)
|
||||
add_subdirectory("${ENGINE_SOURCE_DIR}/3rdparty/SDL" SDL)
|
||||
add_subdirectory("${ENGINE_SOURCE_DIR}/" xash3d-fwgs)
|
||||
add_subdirectory("${ENGINE_SOURCE_DIR}/3rdparty/mainui" mainui)
|
||||
129
android/app/build.gradle.kts
Normal file
129
android/app/build.gradle.kts
Normal file
@@ -0,0 +1,129 @@
|
||||
import java.time.LocalDateTime
|
||||
import java.time.Month
|
||||
import java.time.temporal.ChronoUnit
|
||||
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("org.jetbrains.kotlin.android")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "su.xash.engine"
|
||||
ndkVersion = "27.2.12479018"
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "su.xash"
|
||||
applicationIdSuffix = "engine"
|
||||
versionName = "0.21"
|
||||
versionCode = getBuildNum()
|
||||
minSdk = 21
|
||||
targetSdk = 34
|
||||
compileSdk = 34
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
abiFilters("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
|
||||
arguments("-DANDROID_USE_LEGACY_TOOLCHAIN_FILE=OFF")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
version = "3.22.1"
|
||||
path = file("CMakeLists.txt")
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
isMinifyEnabled = false
|
||||
isShrinkResources = false
|
||||
isDebuggable = true
|
||||
applicationIdSuffix = ".test"
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
|
||||
release {
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
|
||||
register("asan") {
|
||||
initWith(getByName("debug"))
|
||||
}
|
||||
|
||||
register("continuous") {
|
||||
initWith(getByName("release"))
|
||||
applicationIdSuffix = ".test"
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
getByName("main") {
|
||||
assets.srcDirs("../../3rdparty/extras/xash-extras", "../moddb")
|
||||
java.srcDir("../../3rdparty/SDL/android-project/app/src/main/java")
|
||||
}
|
||||
}
|
||||
|
||||
lint {
|
||||
abortOnError = false
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
viewBinding = true
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
androidResources {
|
||||
noCompress += ""
|
||||
}
|
||||
|
||||
packaging {
|
||||
jniLibs {
|
||||
useLegacyPackaging = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("com.google.android.material:material:1.11.0")
|
||||
implementation("androidx.appcompat:appcompat:1.6.1")
|
||||
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
||||
implementation("androidx.navigation:navigation-fragment-ktx:2.7.7")
|
||||
implementation("androidx.navigation:navigation-ui-ktx:2.7.7")
|
||||
implementation("androidx.cardview:cardview:1.0.0")
|
||||
implementation("androidx.annotation:annotation:1.7.1")
|
||||
implementation("androidx.fragment:fragment-ktx:1.6.2")
|
||||
implementation("androidx.preference:preference-ktx:1.2.1")
|
||||
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
|
||||
implementation("androidx.work:work-runtime-ktx:2.9.0")
|
||||
// implementation "androidx.legacy:legacy-support-v4:1.0.0"
|
||||
|
||||
implementation("com.madgag.spongycastle:prov:1.58.0.0")
|
||||
implementation("in.dragonbra:javasteam:1.2.0")
|
||||
|
||||
implementation("ch.acra:acra-http:5.11.2")
|
||||
}
|
||||
|
||||
fun getBuildNum(): Int {
|
||||
val now = LocalDateTime.now()
|
||||
val releaseDate = LocalDateTime.of(2015, Month.APRIL, 1, 0, 0, 0)
|
||||
val qBuildNum = releaseDate.until(now, ChronoUnit.DAYS)
|
||||
val minuteOfDay = now.hour * 60 + now.minute
|
||||
return (qBuildNum * 10000 + minuteOfDay).toInt()
|
||||
}
|
||||
89
android/app/proguard-rules.pro
vendored
Normal file
89
android/app/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
-keep class su.xash.engine.XashActivity {
|
||||
java.lang.String loadAndroidID();
|
||||
java.lang.String getAndroidID();
|
||||
void saveAndroidID(java.lang.String);
|
||||
java.lang.String getCallingPackage();
|
||||
java.lang.String[] getAssetsList(boolean, java.lang.String);
|
||||
android.content.res.AssetManager getAssets(boolean);
|
||||
}
|
||||
|
||||
-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLInputConnection {
|
||||
void nativeCommitText(java.lang.String, int);
|
||||
void nativeGenerateScancodeForUnichar(char);
|
||||
}
|
||||
|
||||
-keep,includedescriptorclasses class org.libsdl.app.SDLActivity {
|
||||
# for some reason these aren't compatible with allowoptimization modifier
|
||||
boolean supportsRelativeMouse();
|
||||
void setWindowStyle(boolean);
|
||||
}
|
||||
|
||||
-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLActivity {
|
||||
java.lang.String nativeGetHint(java.lang.String); # Java-side doesn't use this, so it gets minified, but C-side still tries to register it
|
||||
boolean onNativeSoftReturnKey();
|
||||
void onNativeKeyboardFocusLost();
|
||||
boolean isScreenKeyboardShown();
|
||||
android.util.DisplayMetrics getDisplayDPI();
|
||||
java.lang.String clipboardGetText();
|
||||
boolean clipboardHasText();
|
||||
void clipboardSetText(java.lang.String);
|
||||
int createCustomCursor(int[], int, int, int, int);
|
||||
void destroyCustomCursor(int);
|
||||
android.content.Context getContext();
|
||||
boolean getManifestEnvironmentVariables();
|
||||
android.view.Surface getNativeSurface();
|
||||
void initTouch();
|
||||
boolean isAndroidTV();
|
||||
boolean isChromebook();
|
||||
boolean isDeXMode();
|
||||
boolean isTablet();
|
||||
void manualBackButton();
|
||||
int messageboxShowMessageBox(int, java.lang.String, java.lang.String, int[], int[], java.lang.String[], int[]);
|
||||
void minimizeWindow();
|
||||
int openURL(java.lang.String);
|
||||
void requestPermission(java.lang.String, int);
|
||||
int showToast(java.lang.String, int, int, int, int);
|
||||
boolean sendMessage(int, int);
|
||||
boolean setActivityTitle(java.lang.String);
|
||||
boolean setCustomCursor(int);
|
||||
void setOrientation(int, int, boolean, java.lang.String);
|
||||
boolean setRelativeMouseEnabled(boolean);
|
||||
boolean setSystemCursor(int);
|
||||
boolean shouldMinimizeOnFocusLoss();
|
||||
boolean showTextInput(int, int, int, int);
|
||||
}
|
||||
|
||||
-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.HIDDeviceManager {
|
||||
boolean initialize(boolean, boolean);
|
||||
boolean openDevice(int);
|
||||
int sendOutputReport(int, byte[]);
|
||||
int sendFeatureReport(int, byte[]);
|
||||
boolean getFeatureReport(int, byte[]);
|
||||
void closeDevice(int);
|
||||
}
|
||||
|
||||
-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLAudioManager {
|
||||
int[] getAudioOutputDevices();
|
||||
int[] getAudioInputDevices();
|
||||
int[] audioOpen(int, int, int, int, int);
|
||||
void audioWriteFloatBuffer(float[]);
|
||||
void audioWriteShortBuffer(short[]);
|
||||
void audioWriteByteBuffer(byte[]);
|
||||
void audioClose();
|
||||
int[] captureOpen(int, int, int, int, int);
|
||||
int captureReadFloatBuffer(float[], boolean);
|
||||
int captureReadShortBuffer(short[], boolean);
|
||||
int captureReadByteBuffer(byte[], boolean);
|
||||
void captureClose();
|
||||
void audioSetThreadPriority(boolean, int);
|
||||
native int nativeSetupJNI();
|
||||
native void removeAudioDevice(boolean, int);
|
||||
native void addAudioDevice(boolean, int);
|
||||
}
|
||||
|
||||
-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLControllerManager {
|
||||
void pollInputDevices();
|
||||
void pollHapticDevices();
|
||||
void hapticRun(int, float, int);
|
||||
void hapticStop(int);
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
5
android/app/src/asan/res/values/strings.xml
Normal file
5
android/app/src/asan/res/values/strings.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name" translatable="false">Xash3D FWGS (Test)</string>
|
||||
<string name="authority" translatable="false">su.xash.engine.test.documents</string>
|
||||
</resources>
|
||||
26
android/app/src/asan/resources/lib/arm64-v8a/wrap.sh
Normal file
26
android/app/src/asan/resources/lib/arm64-v8a/wrap.sh
Normal file
@@ -0,0 +1,26 @@
|
||||
|
||||
#!/system/bin/sh
|
||||
HERE=$(cd "$(dirname "$0")" && pwd)
|
||||
cmd=$1
|
||||
shift
|
||||
# This must be called *before* `LD_PRELOAD` is set. Otherwise, if this is a 32-
|
||||
# bit app running on a 64-bit device, the 64-bit getprop will fail to load
|
||||
# because it will preload a 32-bit ASan runtime.
|
||||
# https://github.com/android/ndk/issues/1744
|
||||
os_version=$(getprop ro.build.version.sdk)
|
||||
if [ "$os_version" -eq "27" ]; then
|
||||
cmd="$cmd -Xrunjdwp:transport=dt_android_adb,suspend=n,server=y -Xcompiler-option --debuggable $@"
|
||||
elif [ "$os_version" -eq "28" ]; then
|
||||
cmd="$cmd -XjdwpProvider:adbconnection -XjdwpOptions:suspend=n,server=y -Xcompiler-option --debuggable $@"
|
||||
else
|
||||
cmd="$cmd -XjdwpProvider:adbconnection -XjdwpOptions:suspend=n,server=y $@"
|
||||
fi
|
||||
export ASAN_OPTIONS=log_to_syslog=false,allow_user_segv_handler=1
|
||||
ASAN_LIB=$(ls "$HERE"/libclang_rt.asan-*-android.so)
|
||||
if [ -f "$HERE/libc++_shared.so" ]; then
|
||||
# Workaround for https://github.com/android-ndk/ndk/issues/988.
|
||||
export LD_PRELOAD="$ASAN_LIB $HERE/libc++_shared.so"
|
||||
else
|
||||
export LD_PRELOAD="$ASAN_LIB"
|
||||
fi
|
||||
exec $cmd
|
||||
26
android/app/src/asan/resources/lib/armeabi-v7a/wrap.sh
Normal file
26
android/app/src/asan/resources/lib/armeabi-v7a/wrap.sh
Normal file
@@ -0,0 +1,26 @@
|
||||
|
||||
#!/system/bin/sh
|
||||
HERE=$(cd "$(dirname "$0")" && pwd)
|
||||
cmd=$1
|
||||
shift
|
||||
# This must be called *before* `LD_PRELOAD` is set. Otherwise, if this is a 32-
|
||||
# bit app running on a 64-bit device, the 64-bit getprop will fail to load
|
||||
# because it will preload a 32-bit ASan runtime.
|
||||
# https://github.com/android/ndk/issues/1744
|
||||
os_version=$(getprop ro.build.version.sdk)
|
||||
if [ "$os_version" -eq "27" ]; then
|
||||
cmd="$cmd -Xrunjdwp:transport=dt_android_adb,suspend=n,server=y -Xcompiler-option --debuggable $@"
|
||||
elif [ "$os_version" -eq "28" ]; then
|
||||
cmd="$cmd -XjdwpProvider:adbconnection -XjdwpOptions:suspend=n,server=y -Xcompiler-option --debuggable $@"
|
||||
else
|
||||
cmd="$cmd -XjdwpProvider:adbconnection -XjdwpOptions:suspend=n,server=y $@"
|
||||
fi
|
||||
export ASAN_OPTIONS=log_to_syslog=false,allow_user_segv_handler=1
|
||||
ASAN_LIB=$(ls "$HERE"/libclang_rt.asan-*-android.so)
|
||||
if [ -f "$HERE/libc++_shared.so" ]; then
|
||||
# Workaround for https://github.com/android-ndk/ndk/issues/988.
|
||||
export LD_PRELOAD="$ASAN_LIB $HERE/libc++_shared.so"
|
||||
else
|
||||
export LD_PRELOAD="$ASAN_LIB"
|
||||
fi
|
||||
exec $cmd
|
||||
26
android/app/src/asan/resources/lib/x86/wrap.sh
Normal file
26
android/app/src/asan/resources/lib/x86/wrap.sh
Normal file
@@ -0,0 +1,26 @@
|
||||
|
||||
#!/system/bin/sh
|
||||
HERE=$(cd "$(dirname "$0")" && pwd)
|
||||
cmd=$1
|
||||
shift
|
||||
# This must be called *before* `LD_PRELOAD` is set. Otherwise, if this is a 32-
|
||||
# bit app running on a 64-bit device, the 64-bit getprop will fail to load
|
||||
# because it will preload a 32-bit ASan runtime.
|
||||
# https://github.com/android/ndk/issues/1744
|
||||
os_version=$(getprop ro.build.version.sdk)
|
||||
if [ "$os_version" -eq "27" ]; then
|
||||
cmd="$cmd -Xrunjdwp:transport=dt_android_adb,suspend=n,server=y -Xcompiler-option --debuggable $@"
|
||||
elif [ "$os_version" -eq "28" ]; then
|
||||
cmd="$cmd -XjdwpProvider:adbconnection -XjdwpOptions:suspend=n,server=y -Xcompiler-option --debuggable $@"
|
||||
else
|
||||
cmd="$cmd -XjdwpProvider:adbconnection -XjdwpOptions:suspend=n,server=y $@"
|
||||
fi
|
||||
export ASAN_OPTIONS=log_to_syslog=false,allow_user_segv_handler=1
|
||||
ASAN_LIB=$(ls "$HERE"/libclang_rt.asan-*-android.so)
|
||||
if [ -f "$HERE/libc++_shared.so" ]; then
|
||||
# Workaround for https://github.com/android-ndk/ndk/issues/988.
|
||||
export LD_PRELOAD="$ASAN_LIB $HERE/libc++_shared.so"
|
||||
else
|
||||
export LD_PRELOAD="$ASAN_LIB"
|
||||
fi
|
||||
exec $cmd
|
||||
26
android/app/src/asan/resources/lib/x86_64/wrap.sh
Normal file
26
android/app/src/asan/resources/lib/x86_64/wrap.sh
Normal file
@@ -0,0 +1,26 @@
|
||||
|
||||
#!/system/bin/sh
|
||||
HERE=$(cd "$(dirname "$0")" && pwd)
|
||||
cmd=$1
|
||||
shift
|
||||
# This must be called *before* `LD_PRELOAD` is set. Otherwise, if this is a 32-
|
||||
# bit app running on a 64-bit device, the 64-bit getprop will fail to load
|
||||
# because it will preload a 32-bit ASan runtime.
|
||||
# https://github.com/android/ndk/issues/1744
|
||||
os_version=$(getprop ro.build.version.sdk)
|
||||
if [ "$os_version" -eq "27" ]; then
|
||||
cmd="$cmd -Xrunjdwp:transport=dt_android_adb,suspend=n,server=y -Xcompiler-option --debuggable $@"
|
||||
elif [ "$os_version" -eq "28" ]; then
|
||||
cmd="$cmd -XjdwpProvider:adbconnection -XjdwpOptions:suspend=n,server=y -Xcompiler-option --debuggable $@"
|
||||
else
|
||||
cmd="$cmd -XjdwpProvider:adbconnection -XjdwpOptions:suspend=n,server=y $@"
|
||||
fi
|
||||
export ASAN_OPTIONS=log_to_syslog=false,allow_user_segv_handler=1
|
||||
ASAN_LIB=$(ls "$HERE"/libclang_rt.asan-*-android.so)
|
||||
if [ -f "$HERE/libc++_shared.so" ]; then
|
||||
# Workaround for https://github.com/android-ndk/ndk/issues/988.
|
||||
export LD_PRELOAD="$ASAN_LIB $HERE/libc++_shared.so"
|
||||
else
|
||||
export LD_PRELOAD="$ASAN_LIB"
|
||||
fi
|
||||
exec $cmd
|
||||
5
android/app/src/continuous/res/values/strings.xml
Normal file
5
android/app/src/continuous/res/values/strings.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name" translatable="false">Xash3D FWGS (Test)</string>
|
||||
<string name="authority" translatable="false">su.xash.engine.test.documents</string>
|
||||
</resources>
|
||||
5
android/app/src/debug/res/values/strings.xml
Normal file
5
android/app/src/debug/res/values/strings.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name" translatable="false">Xash3D FWGS (Test)</string>
|
||||
<string name="authority" translatable="false">su.xash.engine.test.documents</string>
|
||||
</resources>
|
||||
101
android/app/src/main/AndroidManifest.xml
Normal file
101
android/app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,101 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:allowAudioPlaybackCapture="true"
|
||||
android:installLocation="preferExternal"
|
||||
tools:targetApi="q">
|
||||
<!-- OpenGL ES 1.1 -->
|
||||
<uses-feature android:glEsVersion="0x00010000" />
|
||||
<!-- Touchscreen support -->
|
||||
<uses-feature
|
||||
android:name="android.hardware.touchscreen"
|
||||
android:required="false" />
|
||||
<!-- Game controller support -->
|
||||
<uses-feature
|
||||
android:name="android.hardware.bluetooth"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.gamepad"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.usb.host"
|
||||
android:required="false" />
|
||||
<!-- External mouse input events -->
|
||||
<uses-feature
|
||||
android:name="android.hardware.type.pc"
|
||||
android:required="false" />
|
||||
<!-- Audio recording support -->
|
||||
<uses-feature
|
||||
android:name="android.hardware.microphone"
|
||||
android:required="false" />
|
||||
<!-- Allow downloading to the external storage on Android 5.1 and older -->
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="22" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32" />
|
||||
<!-- Allow access to Bluetooth devices -->
|
||||
<!-- Currently this is just for Steam Controller support and requires setting SDL_HINT_JOYSTICK_HIDAPI_STEAM -->
|
||||
<uses-permission
|
||||
android:name="android.permission.BLUETOOTH"
|
||||
android:maxSdkVersion="30" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||
<!-- Allow access to the vibrator -->
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<!-- Allow recording audio -->
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<!-- Allow internet access -->
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<!-- Dedicated server -->
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||
|
||||
<application
|
||||
android:name=".MainApplication"
|
||||
android:allowBackup="true"
|
||||
android:hardwareAccelerated="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher"
|
||||
android:theme="@style/Theme.App"
|
||||
android:usesCleartextTraffic="true">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".XashActivity"
|
||||
android:alwaysRetainTaskState="true"
|
||||
android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation|touchscreen"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTask"
|
||||
android:preferMinimalPostProcessing="true"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
tools:targetApi="r">
|
||||
<intent-filter>
|
||||
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<provider
|
||||
android:name=".XashDocumentsProvider"
|
||||
android:authorities="@string/authority"
|
||||
android:exported="true"
|
||||
android:grantUriPermissions="true"
|
||||
android:permission="android.permission.MANAGE_DOCUMENTS">
|
||||
<intent-filter>
|
||||
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
|
||||
</intent-filter>
|
||||
</provider>
|
||||
</application>
|
||||
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="su.xash.engine.MOD" />
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
@@ -0,0 +1,49 @@
|
||||
package su.xash.engine;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.graphics.Rect;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
public class AndroidBug5497Workaround {
|
||||
// For more information, see https://code.google.com/p/android/issues/detail?id=5497
|
||||
// To use this class, simply invoke assistActivity() on an Activity that already has its content view set.
|
||||
|
||||
public static void assistActivity(Activity activity) {
|
||||
new AndroidBug5497Workaround(activity);
|
||||
}
|
||||
|
||||
private View mChildOfContent;
|
||||
private int usableHeightPrevious;
|
||||
private FrameLayout.LayoutParams frameLayoutParams;
|
||||
|
||||
private AndroidBug5497Workaround(Activity activity) {
|
||||
FrameLayout content = activity.findViewById(android.R.id.content);
|
||||
mChildOfContent = content.getChildAt(0);
|
||||
mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(this::possiblyResizeChildOfContent);
|
||||
frameLayoutParams = (FrameLayout.LayoutParams) mChildOfContent.getLayoutParams();
|
||||
}
|
||||
|
||||
private void possiblyResizeChildOfContent() {
|
||||
int usableHeightNow = computeUsableHeight();
|
||||
if (usableHeightNow != usableHeightPrevious) {
|
||||
int usableHeightSansKeyboard = mChildOfContent.getRootView().getHeight();
|
||||
int heightDifference = usableHeightSansKeyboard - usableHeightNow;
|
||||
if (heightDifference > (usableHeightSansKeyboard / 4)) {
|
||||
// keyboard probably just became visible
|
||||
frameLayoutParams.height = usableHeightSansKeyboard - heightDifference;
|
||||
} else {
|
||||
// keyboard probably just became hidden
|
||||
frameLayoutParams.height = usableHeightSansKeyboard;
|
||||
}
|
||||
mChildOfContent.requestLayout();
|
||||
usableHeightPrevious = usableHeightNow;
|
||||
}
|
||||
}
|
||||
|
||||
private int computeUsableHeight() {
|
||||
Rect r = new Rect();
|
||||
mChildOfContent.getWindowVisibleDisplayFrame(r);
|
||||
return (r.bottom - r.top);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package su.xash.engine
|
||||
|
||||
class DedicatedActivity {
|
||||
}
|
||||
11
android/app/src/main/java/su/xash/engine/DedicatedService.kt
Normal file
11
android/app/src/main/java/su/xash/engine/DedicatedService.kt
Normal file
@@ -0,0 +1,11 @@
|
||||
package su.xash.engine
|
||||
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.os.IBinder
|
||||
|
||||
class DedicatedService: Service() {
|
||||
override fun onBind(intent: Intent?): IBinder? {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
35
android/app/src/main/java/su/xash/engine/MainActivity.kt
Normal file
35
android/app/src/main/java/su/xash/engine/MainActivity.kt
Normal file
@@ -0,0 +1,35 @@
|
||||
package su.xash.engine
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.fragment.NavHostFragment
|
||||
import androidx.navigation.ui.AppBarConfiguration
|
||||
import androidx.navigation.ui.navigateUp
|
||||
import androidx.navigation.ui.setupActionBarWithNavController
|
||||
import su.xash.engine.databinding.ActivityMainBinding
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
private lateinit var binding: ActivityMainBinding
|
||||
private lateinit var appBarConfiguration: AppBarConfiguration
|
||||
private lateinit var navController: NavController
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
setSupportActionBar(binding.toolbar)
|
||||
|
||||
val navHostFragment =
|
||||
supportFragmentManager.findFragmentById(R.id.fragmentContainerView) as NavHostFragment
|
||||
navController = navHostFragment.navController
|
||||
appBarConfiguration = AppBarConfiguration(navController.graph)
|
||||
setupActionBarWithNavController(navController, appBarConfiguration)
|
||||
}
|
||||
|
||||
override fun onSupportNavigateUp(): Boolean {
|
||||
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
|
||||
}
|
||||
}
|
||||
28
android/app/src/main/java/su/xash/engine/MainApplication.kt
Normal file
28
android/app/src/main/java/su/xash/engine/MainApplication.kt
Normal file
@@ -0,0 +1,28 @@
|
||||
package su.xash.engine
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.os.StrictMode
|
||||
import org.acra.config.httpSender
|
||||
import org.acra.data.StringFormat
|
||||
import org.acra.ktx.initAcra
|
||||
|
||||
class MainApplication : Application() {
|
||||
override fun attachBaseContext(base: Context?) {
|
||||
super.attachBaseContext(base)
|
||||
|
||||
if (!BuildConfig.DEBUG) {
|
||||
initAcra {
|
||||
buildConfigClass = BuildConfig::class.java
|
||||
reportFormat = StringFormat.JSON
|
||||
|
||||
httpSender {
|
||||
uri = "http://bodis.pp.ua:5000/report"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// enable strict mode to detect memory leaks etc.
|
||||
StrictMode.enableDefaults();
|
||||
}
|
||||
}
|
||||
}
|
||||
148
android/app/src/main/java/su/xash/engine/XashActivity.java
Normal file
148
android/app/src/main/java/su/xash/engine/XashActivity.java
Normal file
@@ -0,0 +1,148 @@
|
||||
package su.xash.engine;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.res.AssetManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings.Secure;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import org.libsdl.app.SDLActivity;
|
||||
|
||||
public class XashActivity extends SDLActivity {
|
||||
private boolean mUseVolumeKeys;
|
||||
private String mPackageName;
|
||||
private static final String TAG = "XashActivity";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
//getWindow().addFlags(WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES);
|
||||
getWindow().getAttributes().layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
|
||||
}
|
||||
|
||||
AndroidBug5497Workaround.assistActivity(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy()
|
||||
{
|
||||
super.onDestroy();
|
||||
|
||||
// Now that we don't exit from native code, we need to exit here, resetting
|
||||
// application state (actually global variables that we don't cleanup on exit)
|
||||
//
|
||||
// When the issue with global variables will be resolved, remove that exit() call
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] getLibraries() {
|
||||
return new String[]{"SDL2", "xash"};
|
||||
}
|
||||
|
||||
@SuppressLint("HardwareIds")
|
||||
private String getAndroidID() {
|
||||
return Secure.getString(getContentResolver(), Secure.ANDROID_ID);
|
||||
}
|
||||
|
||||
@SuppressLint("ApplySharedPref")
|
||||
private void saveAndroidID(String id) {
|
||||
getSharedPreferences("xash_preferences", MODE_PRIVATE).edit().putString("xash_id", id).commit();
|
||||
}
|
||||
|
||||
private String loadAndroidID() {
|
||||
return getSharedPreferences("xash_preferences", MODE_PRIVATE).getString("xash_id", "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCallingPackage() {
|
||||
if (mPackageName != null) {
|
||||
return mPackageName;
|
||||
}
|
||||
|
||||
return super.getCallingPackage();
|
||||
}
|
||||
|
||||
private AssetManager getAssets(boolean isEngine) {
|
||||
AssetManager am = null;
|
||||
|
||||
if (isEngine) {
|
||||
am = getAssets();
|
||||
} else {
|
||||
try {
|
||||
am = getPackageManager().getResourcesForApplication(getCallingPackage()).getAssets();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Unable to load mod assets!");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
return am;
|
||||
}
|
||||
|
||||
private String[] getAssetsList(boolean isEngine, String path) {
|
||||
AssetManager am = getAssets(isEngine);
|
||||
|
||||
try {
|
||||
return am.list(path);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return new String[]{};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchKeyEvent(KeyEvent event) {
|
||||
if (SDLActivity.mBrokenLibraries) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int keyCode = event.getKeyCode();
|
||||
if (!mUseVolumeKeys) {
|
||||
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ||
|
||||
keyCode == KeyEvent.KEYCODE_VOLUME_UP ||
|
||||
keyCode == KeyEvent.KEYCODE_CAMERA ||
|
||||
keyCode == KeyEvent.KEYCODE_ZOOM_IN ||
|
||||
keyCode == KeyEvent.KEYCODE_ZOOM_OUT) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return getWindow().superDispatchKeyEvent(event);
|
||||
}
|
||||
|
||||
// TODO: REMOVE LATER, temporary launchers support?
|
||||
@Override
|
||||
protected String[] getArguments() {
|
||||
String gamedir = getIntent().getStringExtra("gamedir");
|
||||
if (gamedir == null) gamedir = "valve";
|
||||
nativeSetenv("XASH3D_GAME", gamedir);
|
||||
|
||||
String gamelibdir = getIntent().getStringExtra("gamelibdir");
|
||||
if (gamelibdir != null) nativeSetenv("XASH3D_GAMELIBDIR", gamelibdir);
|
||||
|
||||
String pakfile = getIntent().getStringExtra("pakfile");
|
||||
if (pakfile != null) nativeSetenv("XASH3D_EXTRAS_PAK2", pakfile);
|
||||
|
||||
mUseVolumeKeys = getIntent().getBooleanExtra("usevolume", false);
|
||||
mPackageName = getIntent().getStringExtra("package");
|
||||
|
||||
String[] env = getIntent().getStringArrayExtra("env");
|
||||
if (env != null) {
|
||||
for (int i = 0; i < env.length; i += 2)
|
||||
nativeSetenv(env[i], env[i + 1]);
|
||||
}
|
||||
|
||||
String argv = getIntent().getStringExtra("argv");
|
||||
if (argv == null) argv = "-console -log";
|
||||
return argv.split(" ");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
package su.xash.engine;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.res.AssetFileDescriptor;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.graphics.Point;
|
||||
import android.os.Build;
|
||||
import android.os.CancellationSignal;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.provider.DocumentsContract.Document;
|
||||
import android.provider.DocumentsContract.Root;
|
||||
import android.provider.DocumentsProvider;
|
||||
import android.webkit.MimeTypeMap;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.KITKAT)
|
||||
public class XashDocumentsProvider extends DocumentsProvider {
|
||||
private static final String ALL_MIME_TYPES = "*/*";
|
||||
private File mRootDir;
|
||||
|
||||
private static final String TAG = "XashDocumentsProvider";
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
mRootDir = getContext().getExternalFilesDir(null);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static final String[] DEFAULT_ROOT_PROJECTION = new String[]{Root.COLUMN_ROOT_ID,
|
||||
Root.COLUMN_MIME_TYPES,
|
||||
Root.COLUMN_FLAGS,
|
||||
Root.COLUMN_ICON,
|
||||
Root.COLUMN_TITLE,
|
||||
Root.COLUMN_SUMMARY,
|
||||
Root.COLUMN_DOCUMENT_ID,
|
||||
Root.COLUMN_AVAILABLE_BYTES};
|
||||
|
||||
private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[]{Document.COLUMN_DOCUMENT_ID,
|
||||
Document.COLUMN_MIME_TYPE,
|
||||
Document.COLUMN_DISPLAY_NAME,
|
||||
Document.COLUMN_LAST_MODIFIED,
|
||||
Document.COLUMN_FLAGS,
|
||||
Document.COLUMN_SIZE};
|
||||
|
||||
@Override
|
||||
public Cursor queryRoots(String[] projection) {
|
||||
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_ROOT_PROJECTION);
|
||||
|
||||
final String appName = getContext().getString(R.string.app_name);
|
||||
final String docId = getDocIdForFile(mRootDir);
|
||||
|
||||
int flags;
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_SEARCH;
|
||||
} else {
|
||||
flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_SEARCH | Root.FLAG_SUPPORTS_IS_CHILD;
|
||||
}
|
||||
|
||||
final MatrixCursor.RowBuilder row = result.newRow();
|
||||
row.add(Root.COLUMN_ROOT_ID, docId);
|
||||
row.add(Root.COLUMN_DOCUMENT_ID, docId);
|
||||
row.add(Root.COLUMN_SUMMARY, null);
|
||||
row.add(Root.COLUMN_FLAGS, flags);
|
||||
row.add(Root.COLUMN_TITLE, appName);
|
||||
row.add(Root.COLUMN_MIME_TYPES, ALL_MIME_TYPES);
|
||||
row.add(Root.COLUMN_AVAILABLE_BYTES, mRootDir.getFreeSpace());
|
||||
row.add(Root.COLUMN_ICON, R.mipmap.ic_launcher);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException {
|
||||
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
|
||||
|
||||
includeFile(result, documentId, null);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder) throws FileNotFoundException {
|
||||
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
|
||||
|
||||
final File parent = getFileForDocId(parentDocumentId);
|
||||
final File[] filesList = parent.listFiles();
|
||||
|
||||
if (filesList != null) {
|
||||
for (File file : filesList) {
|
||||
includeFile(result, null, file);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParcelFileDescriptor openDocument(final String documentId, String mode, CancellationSignal signal) throws FileNotFoundException {
|
||||
final File file = getFileForDocId(documentId);
|
||||
|
||||
final int accessMode = ParcelFileDescriptor.parseMode(mode);
|
||||
|
||||
return ParcelFileDescriptor.open(file, accessMode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AssetFileDescriptor openDocumentThumbnail(String documentId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException {
|
||||
final File file = getFileForDocId(documentId);
|
||||
|
||||
final ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
|
||||
|
||||
return new AssetFileDescriptor(pfd, 0, file.length());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createDocument(String parentDocumentId, String mimeType, String displayName) throws FileNotFoundException {
|
||||
File newFile = new File(parentDocumentId, displayName);
|
||||
|
||||
int noConflictId = 1;
|
||||
|
||||
while (newFile.exists()) {
|
||||
newFile = new File(parentDocumentId, displayName + " (" + noConflictId++ + ")");
|
||||
}
|
||||
|
||||
try {
|
||||
boolean succeeded;
|
||||
|
||||
if (Document.MIME_TYPE_DIR.equals(mimeType)) {
|
||||
succeeded = newFile.mkdir();
|
||||
} else {
|
||||
succeeded = newFile.createNewFile();
|
||||
}
|
||||
|
||||
if (!succeeded) {
|
||||
throw new FileNotFoundException("Failed to create document with id " + newFile.getPath());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new FileNotFoundException("Failed to create document with id " + newFile.getPath());
|
||||
}
|
||||
|
||||
return newFile.getPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteDocument(String documentId) throws FileNotFoundException {
|
||||
File file = getFileForDocId(documentId);
|
||||
|
||||
if (file.isDirectory()) {
|
||||
if (!deleteDirectory(file)) {
|
||||
throw new FileNotFoundException("Failed to delete document with id " + documentId);
|
||||
}
|
||||
} else if (!file.delete()) {
|
||||
throw new FileNotFoundException("Failed to delete document with id " + documentId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDocumentType(String documentId) throws FileNotFoundException {
|
||||
File file = getFileForDocId(documentId);
|
||||
|
||||
return getMimeType(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChildDocument(String parentDocumentId, String documentId) {
|
||||
return documentId.startsWith(parentDocumentId);
|
||||
}
|
||||
|
||||
private static File getFileForDocId(String docId) throws FileNotFoundException {
|
||||
final File f = new File(docId);
|
||||
|
||||
if (!f.exists()) throw new FileNotFoundException(f.getAbsolutePath() + " not found");
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
private static String getDocIdForFile(File file) {
|
||||
return file.getAbsolutePath();
|
||||
}
|
||||
|
||||
private static String getMimeType(File file) {
|
||||
if (file.isDirectory()) {
|
||||
return Document.MIME_TYPE_DIR;
|
||||
} else {
|
||||
final String name = file.getName();
|
||||
final int lastDot = name.lastIndexOf('.');
|
||||
|
||||
if (lastDot >= 0) {
|
||||
final String extension = name.substring(lastDot + 1).toLowerCase();
|
||||
final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
|
||||
|
||||
if (mime != null) return mime;
|
||||
}
|
||||
|
||||
return "application/octet-stream";
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean deleteDirectory(File dir) {
|
||||
final File[] allContents = dir.listFiles();
|
||||
|
||||
if (allContents != null) {
|
||||
for (File file : allContents) {
|
||||
deleteDirectory(file);
|
||||
}
|
||||
}
|
||||
return dir.delete();
|
||||
}
|
||||
|
||||
private void includeFile(MatrixCursor result, String docId, File file) throws FileNotFoundException {
|
||||
if (docId == null) {
|
||||
docId = getDocIdForFile(file);
|
||||
} else {
|
||||
file = getFileForDocId(docId);
|
||||
}
|
||||
|
||||
int flags = 0;
|
||||
|
||||
if (file.isDirectory()) {
|
||||
if (file.canWrite()) {
|
||||
flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
|
||||
}
|
||||
} else if (file.canWrite()) {
|
||||
flags |= Document.FLAG_SUPPORTS_WRITE;
|
||||
}
|
||||
|
||||
File parentFile = file.getParentFile();
|
||||
if (parentFile != null && parentFile.canWrite()) {
|
||||
flags |= Document.FLAG_SUPPORTS_DELETE;
|
||||
}
|
||||
|
||||
final String displayName = file.getName();
|
||||
final String mimeType = getMimeType(file);
|
||||
|
||||
if (mimeType.startsWith("image/")) {
|
||||
flags |= Document.FLAG_SUPPORTS_THUMBNAIL;
|
||||
}
|
||||
|
||||
final MatrixCursor.RowBuilder row = result.newRow();
|
||||
row.add(Document.COLUMN_DOCUMENT_ID, docId);
|
||||
row.add(Document.COLUMN_DISPLAY_NAME, displayName);
|
||||
row.add(Document.COLUMN_SIZE, file.length());
|
||||
row.add(Document.COLUMN_MIME_TYPE, mimeType);
|
||||
row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified());
|
||||
row.add(Document.COLUMN_FLAGS, flags);
|
||||
row.add(Document.COLUMN_ICON, R.mipmap.ic_launcher);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package su.xash.engine.adapters
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import su.xash.engine.R
|
||||
import su.xash.engine.databinding.CardGameBinding
|
||||
import su.xash.engine.model.Game
|
||||
import su.xash.engine.ui.library.LibraryViewModel
|
||||
|
||||
|
||||
class GameAdapter(private val libraryViewModel: LibraryViewModel) :
|
||||
ListAdapter<Game, GameAdapter.GameViewHolder>(DiffCallback()) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GameAdapter.GameViewHolder {
|
||||
val binding = CardGameBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
return GameViewHolder(binding)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: GameAdapter.GameViewHolder, position: Int) {
|
||||
return holder.bind(getItem(position))
|
||||
}
|
||||
|
||||
private class DiffCallback : DiffUtil.ItemCallback<Game>() {
|
||||
override fun areItemsTheSame(oldItem: Game, newItem: Game): Boolean {
|
||||
return oldItem.basedir.name == newItem.basedir.name
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean {
|
||||
return oldItem.basedir.name == newItem.basedir.name && oldItem.installed == newItem.installed
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
inner class GameViewHolder(val binding: CardGameBinding) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
fun bind(game: Game) {
|
||||
binding.apply {
|
||||
gameTitle.text = game.title
|
||||
|
||||
if (game.icon != null) {
|
||||
gameIcon.setImageBitmap(game.icon)
|
||||
} else {
|
||||
gameIcon.visibility = View.GONE
|
||||
}
|
||||
|
||||
if (game.cover != null) {
|
||||
gameCover.setImageBitmap(game.cover)
|
||||
} else {
|
||||
gameCover.visibility = View.GONE
|
||||
}
|
||||
|
||||
if (!game.installed) {
|
||||
launchButton.visibility = View.GONE
|
||||
settingsButton.visibility = View.GONE
|
||||
progressIndicator.visibility = View.VISIBLE
|
||||
return
|
||||
}
|
||||
|
||||
settingsButton.setOnClickListener {
|
||||
libraryViewModel.setSelectedGame(game)
|
||||
it.findNavController()
|
||||
.navigate(R.id.action_libraryFragment_to_gameSettingsFragment)
|
||||
}
|
||||
|
||||
root.setOnClickListener { libraryViewModel.startEngine(it.context, game) }
|
||||
launchButton.setOnClickListener {
|
||||
libraryViewModel.startEngine(it.context, game)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package su.xash.engine.model
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import su.xash.engine.util.TGAReader
|
||||
import java.util.Scanner
|
||||
|
||||
|
||||
object BackgroundBitmap {
|
||||
private const val BACKGROUND_ROWS = 3
|
||||
private const val BACKGROUND_COLUMNS = 4
|
||||
private const val BACKGROUND_WIDTH = 800
|
||||
private const val BACKGROUND_HEIGHT = 600
|
||||
|
||||
fun createBackground(ctx: Context, file: DocumentFile): Bitmap {
|
||||
var bitmap =
|
||||
Bitmap.createBitmap(BACKGROUND_WIDTH, BACKGROUND_HEIGHT, Bitmap.Config.ARGB_8888)
|
||||
var canvas = Canvas(bitmap)
|
||||
var x: Int
|
||||
var y = 0
|
||||
var width: Int
|
||||
var height = 0
|
||||
|
||||
var bgLayout = file.findFile("resource")?.findFile("HD_BackgroundLayout.txt")
|
||||
if (bgLayout == null) {
|
||||
bgLayout = file.findFile("resource")?.findFile("BackgroundLayout.txt")
|
||||
}
|
||||
|
||||
if (bgLayout == null) {
|
||||
val dir = file.findFile("resource")?.findFile("background")
|
||||
for (i in 0 until BACKGROUND_ROWS) {
|
||||
x = 0
|
||||
for (j in 0 until BACKGROUND_COLUMNS) {
|
||||
val filename = "${BACKGROUND_WIDTH}_${i + 1}_${'a' + j}_loading.tga"
|
||||
val bmpFile = dir?.findFile(filename)
|
||||
val bmpImage = loadTga(ctx, bmpFile!!)
|
||||
|
||||
canvas.drawBitmap(bmpImage, x.toFloat(), y.toFloat(), null)
|
||||
x += bmpImage.width
|
||||
height = bmpImage.height
|
||||
|
||||
}
|
||||
y += height
|
||||
}
|
||||
return bitmap
|
||||
}
|
||||
|
||||
ctx.contentResolver.openInputStream(bgLayout.uri).use { inputStream ->
|
||||
Scanner(inputStream).use { scanner ->
|
||||
while (scanner.hasNext()) {
|
||||
when (val str = scanner.next()) {
|
||||
"resolution" -> {
|
||||
width = scanner.nextInt()
|
||||
height = scanner.nextInt()
|
||||
bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
||||
canvas = Canvas(bitmap)
|
||||
}
|
||||
|
||||
else -> {
|
||||
var bmpFile = file
|
||||
str.split("/").forEach { bmpFile = bmpFile.findFile(it)!! }
|
||||
//skip
|
||||
scanner.next()
|
||||
x = scanner.nextInt()
|
||||
y = scanner.nextInt()
|
||||
val bmp = loadTga(ctx, bmpFile)
|
||||
canvas.drawBitmap(bmp, x.toFloat(), y.toFloat(), null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return bitmap
|
||||
}
|
||||
|
||||
private fun loadTga(ctx: Context, file: DocumentFile): Bitmap {
|
||||
ctx.contentResolver.openInputStream(file.uri).use {
|
||||
val buffer = it?.readBytes()
|
||||
val pixels = TGAReader.read(buffer, TGAReader.ARGB)
|
||||
|
||||
val width = TGAReader.getWidth(buffer)
|
||||
val height = TGAReader.getHeight(buffer)
|
||||
|
||||
return Bitmap.createBitmap(pixels, 0, width, width, height, Bitmap.Config.ARGB_8888)
|
||||
}
|
||||
}
|
||||
}
|
||||
133
android/app/src/main/java/su/xash/engine/model/Game.kt
Normal file
133
android/app/src/main/java/su/xash/engine/model/Game.kt
Normal file
@@ -0,0 +1,133 @@
|
||||
package su.xash.engine.model
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
import android.provider.MediaStore
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import su.xash.engine.XashActivity
|
||||
|
||||
|
||||
class Game(val ctx: Context, val basedir: DocumentFile, var installed: Boolean = true) {
|
||||
private var iconName = "game.ico"
|
||||
var title = "Unknown Game"
|
||||
var icon: Bitmap? = null
|
||||
var cover: Bitmap? = null
|
||||
|
||||
private val pref = ctx.getSharedPreferences(basedir.name, Context.MODE_PRIVATE)
|
||||
|
||||
init {
|
||||
basedir.findFile("gameinfo.txt")?.let {
|
||||
parseGameInfo(it)
|
||||
} ?: basedir.findFile("liblist.gam")?.let { parseGameInfo(it) }
|
||||
|
||||
basedir.findFile(iconName)
|
||||
?.let { icon = MediaStore.Images.Media.getBitmap(ctx.contentResolver, it.uri) }
|
||||
|
||||
try {
|
||||
cover = BackgroundBitmap.createBackground(ctx, basedir)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
fun startEngine(ctx: Context) {
|
||||
ctx.startActivity(Intent(ctx, XashActivity::class.java).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
putExtra("gamedir", basedir.name)
|
||||
putExtra("argv", pref.getString("arguments", "-console -log"))
|
||||
putExtra("usevolume", pref.getBoolean("use_volume_buttons", false))
|
||||
//.putExtra("gamelibdir", getGameLibDir(context))
|
||||
//.putExtra("package", getPackageName()) }
|
||||
})
|
||||
}
|
||||
|
||||
private fun parseGameInfo(file: DocumentFile) {
|
||||
ctx.contentResolver.openInputStream(file.uri).use { inputStream ->
|
||||
inputStream?.bufferedReader().use { reader ->
|
||||
reader?.forEachLine {
|
||||
val tokens = it.split("\\s+".toRegex(), limit = 2)
|
||||
if (tokens.size >= 2) {
|
||||
val k = tokens[0]
|
||||
val v = tokens[1].trim('"')
|
||||
|
||||
if (k == "title" || k == "game") title = v
|
||||
if (k == "icon") iconName = v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getPackageName(): String? {
|
||||
// return if (mDbEntry != null) {
|
||||
// mDbEntry.getPackageName()
|
||||
// } else null
|
||||
return null
|
||||
}
|
||||
|
||||
private fun getGameLibDir(ctx: Context): String? {
|
||||
val pkgName = getPackageName()
|
||||
if (pkgName != null) {
|
||||
val pkgInfo: PackageInfo = try {
|
||||
ctx.packageManager.getPackageInfo(pkgName, 0)
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
e.printStackTrace()
|
||||
ctx.startActivity(
|
||||
Intent(
|
||||
Intent.ACTION_VIEW, Uri.parse("market://details?id=$pkgName")
|
||||
).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
||||
)
|
||||
return null
|
||||
}
|
||||
return pkgInfo.applicationInfo.nativeLibraryDir
|
||||
}
|
||||
return ctx.applicationInfo.nativeLibraryDir
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun getGames(ctx: Context, file: DocumentFile): List<Game> {
|
||||
val games = mutableListOf<Game>()
|
||||
|
||||
if (checkIfGamedir(file)) {
|
||||
games.add(Game(ctx, file))
|
||||
} else {
|
||||
file.listFiles().forEach {
|
||||
if (it.isDirectory) {
|
||||
if (checkIfGamedir(it)) {
|
||||
games.add(Game(ctx, it))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return games
|
||||
}
|
||||
|
||||
fun checkIfGamedir(file: DocumentFile): Boolean {
|
||||
// exclude unfinished downloads
|
||||
if (file.name?.startsWith('.') == true)
|
||||
return false
|
||||
|
||||
file.findFile("liblist.gam")?.let { return true }
|
||||
file.findFile("gameinfo.txt")?.let { return true }
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Intent intent = new Intent("su.xash.engine.MOD");
|
||||
// for (ResolveInfo info : context.getPackageManager()
|
||||
// .queryIntentActivities(intent, PackageManager.GET_META_DATA)) {
|
||||
// String packageName = info.activityInfo.applicationInfo.packageName;
|
||||
// String gameDir = info.activityInfo.applicationInfo.metaData.getString(
|
||||
// "su.xash.engine.gamedir");
|
||||
// Log.d(TAG, "package = " + packageName + " gamedir = " + gameDir);
|
||||
// }
|
||||
|
||||
//public void startEngine(Context context) {
|
||||
// context.startActivity(new Intent(context, XashActivity.class).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP).putExtra("gamedir", getGameDir()).putExtra("argv", getArguments()).putExtra("usevolume", getVolumeState()).putExtra("gamelibdir", getGameLibDir(context)).putExtra("package", getPackageName()));
|
||||
//}
|
||||
@@ -0,0 +1,59 @@
|
||||
package su.xash.engine.model
|
||||
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
import java.io.InputStream
|
||||
|
||||
class ModDatabase(inputStream: InputStream) {
|
||||
val entries = mutableListOf<Entry>()
|
||||
|
||||
companion object {
|
||||
const val VERSION = 1
|
||||
|
||||
fun getFilename(): String {
|
||||
return "v${VERSION}.json"
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
inputStream.bufferedReader().use {
|
||||
val jsonArray = JSONArray(it.readText())
|
||||
|
||||
for (i in 0..<jsonArray.length()) {
|
||||
entries.add(Entry(jsonArray.getJSONObject(i)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getByGameDir(gamedir: String): Entry? {
|
||||
return entries.filter { it.gamedir.equals(gamedir) }.firstOrNull()
|
||||
}
|
||||
|
||||
|
||||
inner class Entry(jsonObject: JSONObject) {
|
||||
var name: String? = null
|
||||
var appid: Int? = null
|
||||
var gamedir: String? = null
|
||||
|
||||
// TODO Depots
|
||||
var pkgname: String? = null
|
||||
|
||||
init {
|
||||
if (jsonObject.has("name")) {
|
||||
name = jsonObject.getString("name");
|
||||
}
|
||||
|
||||
if (jsonObject.has("app_id")) {
|
||||
appid = jsonObject.getInt("app_id");
|
||||
}
|
||||
|
||||
if (jsonObject.has("gamedir")) {
|
||||
gamedir = jsonObject.getString("gamedir");
|
||||
}
|
||||
|
||||
if (jsonObject.has("package_name")) {
|
||||
pkgname = jsonObject.getString("package_name");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package su.xash.engine.ui.library
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.MenuProvider
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import kotlinx.coroutines.launch
|
||||
import su.xash.engine.R
|
||||
import su.xash.engine.adapters.GameAdapter
|
||||
import su.xash.engine.databinding.FragmentLibraryBinding
|
||||
|
||||
class LibraryFragment : Fragment(), MenuProvider {
|
||||
private var _binding: FragmentLibraryBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
private val libraryViewModel: LibraryViewModel by activityViewModels()
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
|
||||
): View? {
|
||||
_binding = FragmentLibraryBinding.inflate(inflater, container, false)
|
||||
|
||||
val adapter = GameAdapter(libraryViewModel)
|
||||
binding.gamesList.adapter = adapter
|
||||
|
||||
requireActivity().addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED)
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
binding.swipeRefresh.setOnRefreshListener { libraryViewModel.reloadGames(requireContext()) }
|
||||
|
||||
libraryViewModel.isReloading.observe(viewLifecycleOwner) {
|
||||
binding.swipeRefresh.isRefreshing = it
|
||||
}
|
||||
|
||||
libraryViewModel.installedGames.observe(viewLifecycleOwner) {
|
||||
(binding.gamesList.adapter as GameAdapter).submitList(it)
|
||||
}
|
||||
|
||||
libraryViewModel.workInfos.observe(viewLifecycleOwner) {
|
||||
libraryViewModel.refreshDownloads(requireContext())
|
||||
}
|
||||
|
||||
libraryViewModel.downloads.observe(viewLifecycleOwner) {
|
||||
libraryViewModel.reloadGames(requireContext())
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
|
||||
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
|
||||
menuInflater.inflate(R.menu.menu_library, menu)
|
||||
}
|
||||
|
||||
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
|
||||
when (menuItem.itemId) {
|
||||
R.id.action_browse -> {
|
||||
startActivity(
|
||||
Intent(Intent.ACTION_VIEW).setDataAndType(
|
||||
null, "vnd.android.document/directory"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
R.id.action_install -> {
|
||||
findNavController().navigate(R.id.action_libraryFragment_to_setupFragment)
|
||||
}
|
||||
|
||||
R.id.action_settings -> {
|
||||
findNavController().navigate(R.id.action_libraryFragment_to_appSettingsFragment)
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package su.xash.engine.ui.library
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.net.Uri
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.work.Data
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.OutOfQuotaPolicy
|
||||
import androidx.work.WorkInfo
|
||||
import androidx.work.WorkManager
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import su.xash.engine.model.Game
|
||||
import su.xash.engine.model.ModDatabase
|
||||
import su.xash.engine.workers.FileCopyWorker
|
||||
import su.xash.engine.workers.KEY_FILE_URI
|
||||
import java.util.Locale
|
||||
|
||||
const val TAG_INSTALL = "TAG_INSTALL"
|
||||
|
||||
class LibraryViewModel(application: Application) : AndroidViewModel(application) {
|
||||
val installedGames: LiveData<List<Game>> get() = _installedGames
|
||||
private val _installedGames = MutableLiveData(emptyList<Game>())
|
||||
|
||||
val downloads: LiveData<List<Game>> get() = _downloads
|
||||
private val _downloads = MutableLiveData(emptyList<Game>())
|
||||
|
||||
val isReloading: LiveData<Boolean> get() = _isReloading
|
||||
private val _isReloading = MutableLiveData(false)
|
||||
|
||||
private val workManager = WorkManager.getInstance(application.applicationContext)
|
||||
val workInfos: LiveData<List<WorkInfo>> = workManager.getWorkInfosByTagLiveData(TAG_INSTALL)
|
||||
|
||||
val selectedItem: LiveData<Game> get() = _selectedItem
|
||||
private val _selectedItem = MutableLiveData<Game>()
|
||||
|
||||
val appPreferences = application.getSharedPreferences("app_preferences", Context.MODE_PRIVATE)
|
||||
val modDb: ModDatabase
|
||||
|
||||
init {
|
||||
modDb = application.assets.open(ModDatabase.getFilename()).use { ModDatabase(it) }
|
||||
reloadGames(application.applicationContext)
|
||||
}
|
||||
|
||||
fun reloadGames(ctx: Context) {
|
||||
if (isReloading.value == true) {
|
||||
return
|
||||
}
|
||||
_isReloading.value = true
|
||||
|
||||
viewModelScope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
val games = mutableListOf<Game>()
|
||||
val root = DocumentFile.fromFile(ctx.getExternalFilesDir(null)!!)
|
||||
|
||||
val installedGames = Game.getGames(ctx, root)
|
||||
.filter { p -> _downloads.value?.any { p.basedir.name == it.basedir.name } == false }
|
||||
|
||||
games.addAll(installedGames)
|
||||
downloads.value?.let { games.addAll(it) }
|
||||
|
||||
_installedGames.postValue(games)
|
||||
_isReloading.postValue(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun refreshDownloads(ctx: Context) {
|
||||
viewModelScope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
val games = mutableListOf<Game>()
|
||||
|
||||
workInfos.value?.filter {
|
||||
it.state == WorkInfo.State.RUNNING && !it.progress.getString(FileCopyWorker.Input)
|
||||
.isNullOrEmpty()
|
||||
}?.forEach {
|
||||
val uri = Uri.parse(it.progress.getString(FileCopyWorker.Input))
|
||||
val file = DocumentFile.fromTreeUri(ctx, uri)
|
||||
games.addAll(Game.getGames(ctx, file!!))
|
||||
games.forEach { g -> g.installed = false }
|
||||
}
|
||||
|
||||
_downloads.postValue(games)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun installGame(uri: Uri) {
|
||||
val data = Data.Builder().putString(KEY_FILE_URI, uri.toString()).build()
|
||||
val request = OneTimeWorkRequestBuilder<FileCopyWorker>().run {
|
||||
setInputData(data)
|
||||
addTag(TAG_INSTALL)
|
||||
build()
|
||||
}
|
||||
workManager.enqueue(request)
|
||||
}
|
||||
|
||||
fun setSelectedGame(game: Game) {
|
||||
_selectedItem.value = game
|
||||
}
|
||||
|
||||
fun uninstallGame(game: Game) {
|
||||
viewModelScope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
game.installed = false
|
||||
game.basedir.delete()
|
||||
_installedGames.postValue(_installedGames.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun startEngine(ctx: Context, game: Game) {
|
||||
game.startEngine(ctx)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package su.xash.engine.ui.settings
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import su.xash.engine.databinding.FragmentAppSettingsBinding
|
||||
|
||||
class AppSettingsFragment : Fragment() {
|
||||
private var _binding: FragmentAppSettingsBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
|
||||
): View? {
|
||||
_binding = FragmentAppSettingsBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
childFragmentManager.beginTransaction()
|
||||
.add(binding.settingsFragment.id, AppSettingsPreferenceFragment()).commit();
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package su.xash.engine.ui.settings
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import su.xash.engine.R
|
||||
|
||||
class AppSettingsPreferenceFragment() : PreferenceFragmentCompat() {
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
preferenceManager.sharedPreferencesName = "app_preferences";
|
||||
setPreferencesFromResource(R.xml.app_preferences, rootKey);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package su.xash.engine.ui.settings
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import su.xash.engine.R
|
||||
import su.xash.engine.databinding.FragmentGameSettingsBinding
|
||||
import su.xash.engine.ui.library.LibraryViewModel
|
||||
|
||||
class GameSettingsFragment : Fragment() {
|
||||
private var _binding: FragmentGameSettingsBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
private val libraryViewModel: LibraryViewModel by activityViewModels()
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
|
||||
): View? {
|
||||
_binding = FragmentGameSettingsBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val game = libraryViewModel.selectedItem.value!!
|
||||
|
||||
binding.gameCard.apply {
|
||||
gameTitle.text = game.title
|
||||
|
||||
if (game.icon != null) {
|
||||
gameIcon.setImageBitmap(game.icon)
|
||||
} else {
|
||||
gameIcon.visibility = View.GONE
|
||||
}
|
||||
|
||||
if (game.cover != null) {
|
||||
gameCover.setImageBitmap(game.cover)
|
||||
} else {
|
||||
gameCover.visibility = View.GONE
|
||||
}
|
||||
|
||||
buttonsContainer.visibility = View.GONE
|
||||
}
|
||||
|
||||
childFragmentManager.beginTransaction()
|
||||
.add(binding.settingsFragment.id, GameSettingsPreferenceFragment(game))
|
||||
.commit();
|
||||
|
||||
binding.bottomNavigation.menu.findItem(R.id.action_uninstall).setOnMenuItemClickListener {
|
||||
libraryViewModel.uninstallGame(game)
|
||||
findNavController().popBackStack()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package su.xash.engine.ui.settings
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import su.xash.engine.R
|
||||
import su.xash.engine.model.Game
|
||||
|
||||
class GameSettingsPreferenceFragment(val game: Game) : PreferenceFragmentCompat() {
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
preferenceManager.sharedPreferencesName = game.basedir.name;
|
||||
setPreferencesFromResource(R.xml.game_preferences, rootKey);
|
||||
|
||||
val packageList = findPreference<ListPreference>("package_name")!!
|
||||
packageList.entries = arrayOf(getString(R.string.app_name))
|
||||
packageList.entryValues = arrayOf(requireContext().packageName)
|
||||
|
||||
if (packageList.value == null) {
|
||||
packageList.setValueIndex(0);
|
||||
}
|
||||
|
||||
val separatePackages = findPreference<SwitchPreferenceCompat>("separate_libraries")!!
|
||||
val clientPackage = findPreference<ListPreference>("client_package")!!
|
||||
val serverPackage = findPreference<ListPreference>("server_package")!!
|
||||
separatePackages.setOnPreferenceChangeListener { _, newValue ->
|
||||
if (newValue == true) {
|
||||
packageList.isVisible = false
|
||||
clientPackage.isVisible = true
|
||||
serverPackage.isVisible = true
|
||||
} else {
|
||||
packageList.isVisible = true
|
||||
clientPackage.isVisible = false
|
||||
serverPackage.isVisible = false
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package su.xash.engine.ui.setup
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
import su.xash.engine.databinding.FragmentSetupBinding
|
||||
import su.xash.engine.ui.setup.pages.LocationPageFragment
|
||||
import su.xash.engine.ui.setup.pages.WelcomePageFragment
|
||||
|
||||
class SetupFragment : Fragment() {
|
||||
private var _binding: FragmentSetupBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
private val setupViewModel: SetupViewModel by activityViewModels()
|
||||
|
||||
private lateinit var setupPageAdapter: SetupPageAdapter
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
|
||||
): View? {
|
||||
_binding = FragmentSetupBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
setupPageAdapter = SetupPageAdapter(this)
|
||||
binding.viewPager.isUserInputEnabled = false
|
||||
binding.viewPager.adapter = setupPageAdapter
|
||||
|
||||
setupViewModel.pageNumber.observe(viewLifecycleOwner) {
|
||||
binding.viewPager.setCurrentItem(it, true)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
}
|
||||
|
||||
class SetupPageAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) {
|
||||
val pages = listOf(WelcomePageFragment(), LocationPageFragment())
|
||||
override fun getItemCount(): Int = 2
|
||||
override fun createFragment(position: Int): Fragment = pages[position]
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package su.xash.engine.ui.setup
|
||||
|
||||
import android.app.Application
|
||||
import android.net.Uri
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import su.xash.engine.MainApplication
|
||||
import su.xash.engine.model.Game
|
||||
|
||||
class SetupViewModel(application: Application) : AndroidViewModel(application) {
|
||||
val pageNumber: LiveData<Int> get() = _pageNumber
|
||||
private val _pageNumber = MutableLiveData(0)
|
||||
|
||||
fun checkIfGameDir(uri: Uri): Boolean {
|
||||
val ctx = getApplication<MainApplication>().applicationContext
|
||||
val file = DocumentFile.fromTreeUri(ctx, uri)!!
|
||||
return Game.checkIfGamedir(file)
|
||||
}
|
||||
|
||||
fun setPageNumber(pos: Int) {
|
||||
_pageNumber.value = pos
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package su.xash.engine.ui.setup.pages
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import su.xash.engine.R
|
||||
import su.xash.engine.databinding.PageLocationBinding
|
||||
import su.xash.engine.ui.library.LibraryViewModel
|
||||
import su.xash.engine.ui.setup.SetupViewModel
|
||||
|
||||
class LocationPageFragment : Fragment() {
|
||||
private var _binding: PageLocationBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
private val setupViewModel: SetupViewModel by activityViewModels()
|
||||
private val libraryViewModel: LibraryViewModel by activityViewModels()
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
|
||||
): View? {
|
||||
_binding = PageLocationBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
binding.pageButton.setOnClickListener {
|
||||
getGamesDirectory.launch(null)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
|
||||
private val getGamesDirectory =
|
||||
registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) {
|
||||
it?.let {
|
||||
if (!setupViewModel.checkIfGameDir(it)) {
|
||||
MaterialAlertDialogBuilder(requireContext()).apply {
|
||||
setTitle(R.string.error)
|
||||
setMessage(R.string.setup_location_empty)
|
||||
setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() }
|
||||
show()
|
||||
}
|
||||
} else {
|
||||
requireContext().contentResolver.takePersistableUriPermission(it, Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
libraryViewModel.installGame(it)
|
||||
findNavController().navigate(R.id.action_setupFragment_to_libraryFragment)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package su.xash.engine.ui.setup.pages
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import su.xash.engine.databinding.PageWelcomeBinding
|
||||
import su.xash.engine.ui.setup.SetupViewModel
|
||||
|
||||
class WelcomePageFragment : Fragment() {
|
||||
private var _binding: PageWelcomeBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
private val setupViewModel: SetupViewModel by activityViewModels()
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
|
||||
): View? {
|
||||
_binding = PageWelcomeBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
binding.pageButton.setOnClickListener {
|
||||
setupViewModel.setPageNumber(1)
|
||||
}
|
||||
setupViewModel.setPageNumber(0)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
}
|
||||
589
android/app/src/main/java/su/xash/engine/util/TGAReader.java
Normal file
589
android/app/src/main/java/su/xash/engine/util/TGAReader.java
Normal file
@@ -0,0 +1,589 @@
|
||||
/**
|
||||
* TGAReader.java
|
||||
* <p>
|
||||
* Copyright (c) 2014 Kenji Sasaki
|
||||
* Released under the MIT license.
|
||||
* https://github.com/npedotnet/TGAReader/blob/master/LICENSE
|
||||
* <p>
|
||||
* English document
|
||||
* https://github.com/npedotnet/TGAReader/blob/master/README.md
|
||||
* <p>
|
||||
* Japanese document
|
||||
* http://3dtech.jp/wiki/index.php?TGAReader
|
||||
*/
|
||||
|
||||
package su.xash.engine.util;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public final class TGAReader {
|
||||
|
||||
public static final Order ARGB = new Order(16, 8, 0, 24);
|
||||
public static final Order ABGR = new Order(0, 8, 16, 24);
|
||||
|
||||
public static int getWidth(byte[] buffer) {
|
||||
return (buffer[12] & 0xFF) | (buffer[13] & 0xFF) << 8;
|
||||
}
|
||||
|
||||
public static int getHeight(byte[] buffer) {
|
||||
return (buffer[14] & 0xFF) | (buffer[15] & 0xFF) << 8;
|
||||
}
|
||||
|
||||
public static int[] read(byte[] buffer, Order order) throws IOException {
|
||||
|
||||
// header
|
||||
// int idFieldLength = buffer[0] & 0xFF;
|
||||
// int colormapType = buffer[1] & 0xFF;
|
||||
int type = buffer[2] & 0xFF;
|
||||
int colormapOrigin = (buffer[3] & 0xFF) | (buffer[4] & 0xFF) << 8;
|
||||
int colormapLength = (buffer[5] & 0xFF) | (buffer[6] & 0xFF) << 8;
|
||||
int colormapDepth = buffer[7] & 0xFF;
|
||||
// int originX = (buffer[8] & 0xFF) | (buffer[9] & 0xFF) << 8; // unsupported
|
||||
// int originY = (buffer[10] & 0xFF) | (buffer[11] & 0xFF) << 8; // unsupported
|
||||
int width = getWidth(buffer);
|
||||
int height = getHeight(buffer);
|
||||
int depth = buffer[16] & 0xFF;
|
||||
int descriptor = buffer[17] & 0xFF;
|
||||
|
||||
int[] pixels;
|
||||
|
||||
// data
|
||||
switch (type) {
|
||||
case COLORMAP: {
|
||||
int imageDataOffset = 18 + (colormapDepth / 8) * colormapLength;
|
||||
pixels = createPixelsFromColormap(width, height, colormapDepth, buffer, imageDataOffset, buffer, colormapOrigin, descriptor, order);
|
||||
}
|
||||
break;
|
||||
case RGB:
|
||||
pixels = createPixelsFromRGB(width, height, depth, buffer, 18, descriptor, order);
|
||||
break;
|
||||
case GRAYSCALE:
|
||||
pixels = createPixelsFromGrayscale(width, height, depth, buffer, 18, descriptor, order);
|
||||
break;
|
||||
case COLORMAP_RLE: {
|
||||
int imageDataOffset = 18 + (colormapDepth / 8) * colormapLength;
|
||||
byte[] decodeBuffer = decodeRLE(width, height, depth, buffer, imageDataOffset);
|
||||
pixels = createPixelsFromColormap(width, height, colormapDepth, decodeBuffer, 0, buffer, colormapOrigin, descriptor, order);
|
||||
}
|
||||
break;
|
||||
case RGB_RLE: {
|
||||
byte[] decodeBuffer = decodeRLE(width, height, depth, buffer, 18);
|
||||
pixels = createPixelsFromRGB(width, height, depth, decodeBuffer, 0, descriptor, order);
|
||||
}
|
||||
break;
|
||||
case GRAYSCALE_RLE: {
|
||||
byte[] decodeBuffer = decodeRLE(width, height, depth, buffer, 18);
|
||||
pixels = createPixelsFromGrayscale(width, height, depth, decodeBuffer, 0, descriptor, order);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IOException("Unsupported image type: " + type);
|
||||
}
|
||||
|
||||
return pixels;
|
||||
|
||||
}
|
||||
|
||||
private static final int COLORMAP = 1;
|
||||
private static final int RGB = 2;
|
||||
private static final int GRAYSCALE = 3;
|
||||
private static final int COLORMAP_RLE = 9;
|
||||
private static final int RGB_RLE = 10;
|
||||
private static final int GRAYSCALE_RLE = 11;
|
||||
|
||||
private static final int RIGHT_ORIGIN = 0x10;
|
||||
private static final int UPPER_ORIGIN = 0x20;
|
||||
|
||||
private static byte[] decodeRLE(int width, int height, int depth, byte[] buffer, int offset) {
|
||||
int elementCount = depth / 8;
|
||||
byte[] elements = new byte[elementCount];
|
||||
int decodeBufferLength = elementCount * width * height;
|
||||
byte[] decodeBuffer = new byte[decodeBufferLength];
|
||||
int decoded = 0;
|
||||
while (decoded < decodeBufferLength) {
|
||||
int packet = buffer[offset++] & 0xFF;
|
||||
if ((packet & 0x80) != 0) { // RLE
|
||||
for (int i = 0; i < elementCount; i++) {
|
||||
elements[i] = buffer[offset++];
|
||||
}
|
||||
int count = (packet & 0x7F) + 1;
|
||||
for (int i = 0; i < count; i++) {
|
||||
for (int j = 0; j < elementCount; j++) {
|
||||
decodeBuffer[decoded++] = elements[j];
|
||||
}
|
||||
}
|
||||
} else { // RAW
|
||||
int count = (packet + 1) * elementCount;
|
||||
for (int i = 0; i < count; i++) {
|
||||
decodeBuffer[decoded++] = buffer[offset++];
|
||||
}
|
||||
}
|
||||
}
|
||||
return decodeBuffer;
|
||||
}
|
||||
|
||||
private static int[] createPixelsFromColormap(int width, int height, int depth, byte[] bytes, int offset, byte[] palette, int colormapOrigin, int descriptor, Order order) throws IOException {
|
||||
int[] pixels;
|
||||
int rs = order.redShift;
|
||||
int gs = order.greenShift;
|
||||
int bs = order.blueShift;
|
||||
int as = order.alphaShift;
|
||||
switch (depth) {
|
||||
case 24:
|
||||
pixels = new int[width * height];
|
||||
if ((descriptor & RIGHT_ORIGIN) != 0) {
|
||||
if ((descriptor & UPPER_ORIGIN) != 0) {
|
||||
// UpperRight
|
||||
for (int i = 0; i < height; i++) {
|
||||
for (int j = 0; j < width; j++) {
|
||||
int colormapIndex = bytes[offset + width * i + j] &
|
||||
0xFF - colormapOrigin;
|
||||
int color = 0xFFFFFFFF;
|
||||
if (colormapIndex >= 0) {
|
||||
int index = 3 * colormapIndex + 18;
|
||||
int b = palette[index] & 0xFF;
|
||||
int g = palette[index + 1] & 0xFF;
|
||||
int r = palette[index + 2] & 0xFF;
|
||||
int a = 0xFF;
|
||||
color = (r << rs) | (g << gs) | (b << bs) | (a << as);
|
||||
}
|
||||
pixels[width * i + (width - j - 1)] = color;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// LowerRight
|
||||
for (int i = 0; i < height; i++) {
|
||||
for (int j = 0; j < width; j++) {
|
||||
int colormapIndex = bytes[offset + width * i + j] &
|
||||
0xFF - colormapOrigin;
|
||||
int color = 0xFFFFFFFF;
|
||||
if (colormapIndex >= 0) {
|
||||
int index = 3 * colormapIndex + 18;
|
||||
int b = palette[index] & 0xFF;
|
||||
int g = palette[index + 1] & 0xFF;
|
||||
int r = palette[index + 2] & 0xFF;
|
||||
int a = 0xFF;
|
||||
color = (r << rs) | (g << gs) | (b << bs) | (a << as);
|
||||
}
|
||||
pixels[width * (height - i - 1) + (width - j - 1)] = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ((descriptor & UPPER_ORIGIN) != 0) {
|
||||
// UpperLeft
|
||||
for (int i = 0; i < height; i++) {
|
||||
for (int j = 0; j < width; j++) {
|
||||
int colormapIndex = bytes[offset + width * i + j] &
|
||||
0xFF - colormapOrigin;
|
||||
int color = 0xFFFFFFFF;
|
||||
if (colormapIndex >= 0) {
|
||||
int index = 3 * colormapIndex + 18;
|
||||
int b = palette[index] & 0xFF;
|
||||
int g = palette[index + 1] & 0xFF;
|
||||
int r = palette[index + 2] & 0xFF;
|
||||
int a = 0xFF;
|
||||
color = (r << rs) | (g << gs) | (b << bs) | (a << as);
|
||||
}
|
||||
pixels[width * i + j] = color;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// LowerLeft
|
||||
for (int i = 0; i < height; i++) {
|
||||
for (int j = 0; j < width; j++) {
|
||||
int colormapIndex = bytes[offset + width * i + j] &
|
||||
0xFF - colormapOrigin;
|
||||
int color = 0xFFFFFFFF;
|
||||
if (colormapIndex >= 0) {
|
||||
int index = 3 * colormapIndex + 18;
|
||||
int b = palette[index] & 0xFF;
|
||||
int g = palette[index + 1] & 0xFF;
|
||||
int r = palette[index + 2] & 0xFF;
|
||||
int a = 0xFF;
|
||||
color = (r << rs) | (g << gs) | (b << bs) | (a << as);
|
||||
}
|
||||
pixels[width * (height - i - 1) + j] = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 32:
|
||||
pixels = new int[width * height];
|
||||
if ((descriptor & RIGHT_ORIGIN) != 0) {
|
||||
if ((descriptor & UPPER_ORIGIN) != 0) {
|
||||
// UpperRight
|
||||
for (int i = 0; i < height; i++) {
|
||||
for (int j = 0; j < width; j++) {
|
||||
int colormapIndex = bytes[offset + width * i + j] &
|
||||
0xFF - colormapOrigin;
|
||||
int color = 0xFFFFFFFF;
|
||||
if (colormapIndex >= 0) {
|
||||
int index = 4 * colormapIndex + 18;
|
||||
int b = palette[index] & 0xFF;
|
||||
int g = palette[index + 1] & 0xFF;
|
||||
int r = palette[index + 2] & 0xFF;
|
||||
int a = palette[index + 3] & 0xFF;
|
||||
color = (r << rs) | (g << gs) | (b << bs) | (a << as);
|
||||
}
|
||||
pixels[width * i + (width - j - 1)] = color;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// LowerRight
|
||||
for (int i = 0; i < height; i++) {
|
||||
for (int j = 0; j < width; j++) {
|
||||
int colormapIndex = bytes[offset + width * i + j] &
|
||||
0xFF - colormapOrigin;
|
||||
int color = 0xFFFFFFFF;
|
||||
if (colormapIndex >= 0) {
|
||||
int index = 4 * colormapIndex + 18;
|
||||
int b = palette[index] & 0xFF;
|
||||
int g = palette[index + 1] & 0xFF;
|
||||
int r = palette[index + 2] & 0xFF;
|
||||
int a = palette[index + 3] & 0xFF;
|
||||
color = (r << rs) | (g << gs) | (b << bs) | (a << as);
|
||||
}
|
||||
pixels[width * (height - i - 1) + (width - j - 1)] = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ((descriptor & UPPER_ORIGIN) != 0) {
|
||||
// UpperLeft
|
||||
for (int i = 0; i < height; i++) {
|
||||
for (int j = 0; j < width; j++) {
|
||||
int colormapIndex = bytes[offset + width * i + j] &
|
||||
0xFF - colormapOrigin;
|
||||
int color = 0xFFFFFFFF;
|
||||
if (colormapIndex >= 0) {
|
||||
int index = 4 * colormapIndex + 18;
|
||||
int b = palette[index] & 0xFF;
|
||||
int g = palette[index + 1] & 0xFF;
|
||||
int r = palette[index + 2] & 0xFF;
|
||||
int a = palette[index + 3] & 0xFF;
|
||||
color = (r << rs) | (g << gs) | (b << bs) | (a << as);
|
||||
}
|
||||
pixels[width * i + j] = color;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// LowerLeft
|
||||
for (int i = 0; i < height; i++) {
|
||||
for (int j = 0; j < width; j++) {
|
||||
int colormapIndex = bytes[offset + width * i + j] &
|
||||
0xFF - colormapOrigin;
|
||||
int color = 0xFFFFFFFF;
|
||||
if (colormapIndex >= 0) {
|
||||
int index = 4 * colormapIndex + 18;
|
||||
int b = palette[index] & 0xFF;
|
||||
int g = palette[index + 1] & 0xFF;
|
||||
int r = palette[index + 2] & 0xFF;
|
||||
int a = palette[index + 3] & 0xFF;
|
||||
color = (r << rs) | (g << gs) | (b << bs) | (a << as);
|
||||
}
|
||||
pixels[width * (height - i - 1) + j] = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IOException("Unsupported depth:" + depth);
|
||||
}
|
||||
return pixels;
|
||||
}
|
||||
|
||||
private static int[] createPixelsFromRGB(int width, int height, int depth, byte[] bytes, int offset, int descriptor, Order order) throws IOException {
|
||||
int[] pixels;
|
||||
int rs = order.redShift;
|
||||
int gs = order.greenShift;
|
||||
int bs = order.blueShift;
|
||||
int as = order.alphaShift;
|
||||
switch (depth) {
|
||||
case 24:
|
||||
pixels = new int[width * height];
|
||||
if ((descriptor & RIGHT_ORIGIN) != 0) {
|
||||
if ((descriptor & UPPER_ORIGIN) != 0) {
|
||||
// UpperRight
|
||||
for (int i = 0; i < height; i++) {
|
||||
for (int j = 0; j < width; j++) {
|
||||
int index = offset + 3 * width * i + 3 * j;
|
||||
int b = bytes[index] & 0xFF;
|
||||
int g = bytes[index + 1] & 0xFF;
|
||||
int r = bytes[index + 2] & 0xFF;
|
||||
int a = 0xFF;
|
||||
pixels[width * i + (width - j - 1)] = (r << rs) |
|
||||
(g << gs) |
|
||||
(b << bs) |
|
||||
(a << as);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// LowerRight
|
||||
for (int i = 0; i < height; i++) {
|
||||
for (int j = 0; j < width; j++) {
|
||||
int index = offset + 3 * width * i + 3 * j;
|
||||
int b = bytes[index] & 0xFF;
|
||||
int g = bytes[index + 1] & 0xFF;
|
||||
int r = bytes[index + 2] & 0xFF;
|
||||
int a = 0xFF;
|
||||
pixels[width * (height - i - 1) + (width - j - 1)] = (r << rs) |
|
||||
(g << gs) |
|
||||
(b << bs) |
|
||||
(a << as);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ((descriptor & UPPER_ORIGIN) != 0) {
|
||||
// UpperLeft
|
||||
for (int i = 0; i < height; i++) {
|
||||
for (int j = 0; j < width; j++) {
|
||||
int index = offset + 3 * width * i + 3 * j;
|
||||
int b = bytes[index] & 0xFF;
|
||||
int g = bytes[index + 1] & 0xFF;
|
||||
int r = bytes[index + 2] & 0xFF;
|
||||
int a = 0xFF;
|
||||
pixels[width * i + j] = (r << rs) |
|
||||
(g << gs) |
|
||||
(b << bs) |
|
||||
(a << as);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// LowerLeft
|
||||
for (int i = 0; i < height; i++) {
|
||||
for (int j = 0; j < width; j++) {
|
||||
int index = offset + 3 * width * i + 3 * j;
|
||||
int b = bytes[index] & 0xFF;
|
||||
int g = bytes[index + 1] & 0xFF;
|
||||
int r = bytes[index + 2] & 0xFF;
|
||||
int a = 0xFF;
|
||||
pixels[width * (height - i - 1) + j] = (r << rs) |
|
||||
(g << gs) |
|
||||
(b << bs) |
|
||||
(a << as);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 32:
|
||||
pixels = new int[width * height];
|
||||
if ((descriptor & RIGHT_ORIGIN) != 0) {
|
||||
if ((descriptor & UPPER_ORIGIN) != 0) {
|
||||
// UpperRight
|
||||
for (int i = 0; i < height; i++) {
|
||||
for (int j = 0; j < width; j++) {
|
||||
int index = offset + 4 * width * i + 4 * j;
|
||||
int b = bytes[index] & 0xFF;
|
||||
int g = bytes[index + 1] & 0xFF;
|
||||
int r = bytes[index + 2] & 0xFF;
|
||||
int a = bytes[index + 3] & 0xFF;
|
||||
pixels[width * i + (width - j - 1)] = (r << rs) |
|
||||
(g << gs) |
|
||||
(b << bs) |
|
||||
(a << as);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// LowerRight
|
||||
for (int i = 0; i < height; i++) {
|
||||
for (int j = 0; j < width; j++) {
|
||||
int index = offset + 4 * width * i + 4 * j;
|
||||
int b = bytes[index] & 0xFF;
|
||||
int g = bytes[index + 1] & 0xFF;
|
||||
int r = bytes[index + 2] & 0xFF;
|
||||
int a = bytes[index + 3] & 0xFF;
|
||||
pixels[width * (height - i - 1) + (width - j - 1)] = (r << rs) |
|
||||
(g << gs) |
|
||||
(b << bs) |
|
||||
(a << as);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ((descriptor & UPPER_ORIGIN) != 0) {
|
||||
// UpperLeft
|
||||
for (int i = 0; i < height; i++) {
|
||||
for (int j = 0; j < width; j++) {
|
||||
int index = offset + 4 * width * i + 4 * j;
|
||||
int b = bytes[index] & 0xFF;
|
||||
int g = bytes[index + 1] & 0xFF;
|
||||
int r = bytes[index + 2] & 0xFF;
|
||||
int a = bytes[index + 3] & 0xFF;
|
||||
pixels[width * i + j] = (r << rs) |
|
||||
(g << gs) |
|
||||
(b << bs) |
|
||||
(a << as);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// LowerLeft
|
||||
for (int i = 0; i < height; i++) {
|
||||
for (int j = 0; j < width; j++) {
|
||||
int index = offset + 4 * width * i + 4 * j;
|
||||
int b = bytes[index] & 0xFF;
|
||||
int g = bytes[index + 1] & 0xFF;
|
||||
int r = bytes[index + 2] & 0xFF;
|
||||
int a = bytes[index + 3] & 0xFF;
|
||||
pixels[width * (height - i - 1) + j] = (r << rs) |
|
||||
(g << gs) |
|
||||
(b << bs) |
|
||||
(a << as);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IOException("Unsupported depth:" + depth);
|
||||
}
|
||||
return pixels;
|
||||
}
|
||||
|
||||
private static int[] createPixelsFromGrayscale(int width, int height, int depth, byte[] bytes, int offset, int descriptor, Order order) throws IOException {
|
||||
int[] pixels;
|
||||
int rs = order.redShift;
|
||||
int gs = order.greenShift;
|
||||
int bs = order.blueShift;
|
||||
int as = order.alphaShift;
|
||||
switch (depth) {
|
||||
case 8:
|
||||
pixels = new int[width * height];
|
||||
if ((descriptor & RIGHT_ORIGIN) != 0) {
|
||||
if ((descriptor & UPPER_ORIGIN) != 0) {
|
||||
// UpperRight
|
||||
for (int i = 0; i < height; i++) {
|
||||
for (int j = 0; j < width; j++) {
|
||||
int e = bytes[offset + width * i + j] & 0xFF;
|
||||
int a = 0xFF;
|
||||
pixels[width * i + (width - j - 1)] = (e << rs) |
|
||||
(e << gs) |
|
||||
(e << bs) |
|
||||
(a << as);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// LowerRight
|
||||
for (int i = 0; i < height; i++) {
|
||||
for (int j = 0; j < width; j++) {
|
||||
int e = bytes[offset + width * i + j] & 0xFF;
|
||||
int a = 0xFF;
|
||||
pixels[width * (height - i - 1) + (width - j - 1)] = (e << rs) |
|
||||
(e << gs) |
|
||||
(e << bs) |
|
||||
(a << as);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ((descriptor & UPPER_ORIGIN) != 0) {
|
||||
// UpperLeft
|
||||
for (int i = 0; i < height; i++) {
|
||||
for (int j = 0; j < width; j++) {
|
||||
int e = bytes[offset + width * i + j] & 0xFF;
|
||||
int a = 0xFF;
|
||||
pixels[width * i + j] = (e << rs) |
|
||||
(e << gs) |
|
||||
(e << bs) |
|
||||
(a << as);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// LowerLeft
|
||||
for (int i = 0; i < height; i++) {
|
||||
for (int j = 0; j < width; j++) {
|
||||
int e = bytes[offset + width * i + j] & 0xFF;
|
||||
int a = 0xFF;
|
||||
pixels[width * (height - i - 1) + j] = (e << rs) |
|
||||
(e << gs) |
|
||||
(e << bs) |
|
||||
(a << as);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 16:
|
||||
pixels = new int[width * height];
|
||||
if ((descriptor & RIGHT_ORIGIN) != 0) {
|
||||
if ((descriptor & UPPER_ORIGIN) != 0) {
|
||||
// UpperRight
|
||||
for (int i = 0; i < height; i++) {
|
||||
for (int j = 0; j < width; j++) {
|
||||
int e = bytes[offset + 2 * width * i + 2 * j] & 0xFF;
|
||||
int a = bytes[offset + 2 * width * i + 2 * j + 1] & 0xFF;
|
||||
pixels[width * i + (width - j - 1)] = (e << rs) |
|
||||
(e << gs) |
|
||||
(e << bs) |
|
||||
(a << as);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// LowerRight
|
||||
for (int i = 0; i < height; i++) {
|
||||
for (int j = 0; j < width; j++) {
|
||||
int e = bytes[offset + 2 * width * i + 2 * j] & 0xFF;
|
||||
int a = bytes[offset + 2 * width * i + 2 * j + 1] & 0xFF;
|
||||
pixels[width * (height - i - 1) + (width - j - 1)] = (e << rs) |
|
||||
(e << gs) |
|
||||
(e << bs) |
|
||||
(a << as);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ((descriptor & UPPER_ORIGIN) != 0) {
|
||||
// UpperLeft
|
||||
for (int i = 0; i < height; i++) {
|
||||
for (int j = 0; j < width; j++) {
|
||||
int e = bytes[offset + 2 * width * i + 2 * j] & 0xFF;
|
||||
int a = bytes[offset + 2 * width * i + 2 * j + 1] & 0xFF;
|
||||
pixels[width * i + j] = (e << rs) |
|
||||
(e << gs) |
|
||||
(e << bs) |
|
||||
(a << as);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// LowerLeft
|
||||
for (int i = 0; i < height; i++) {
|
||||
for (int j = 0; j < width; j++) {
|
||||
int e = bytes[offset + 2 * width * i + 2 * j] & 0xFF;
|
||||
int a = bytes[offset + 2 * width * i + 2 * j + 1] & 0xFF;
|
||||
pixels[width * (height - i - 1) + j] = (e << rs) |
|
||||
(e << gs) |
|
||||
(e << bs) |
|
||||
(a << as);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IOException("Unsupported depth:" + depth);
|
||||
}
|
||||
return pixels;
|
||||
}
|
||||
|
||||
private TGAReader() {
|
||||
}
|
||||
|
||||
public static final class Order {
|
||||
Order(int redShift, int greenShift, int blueShift, int alphaShift) {
|
||||
this.redShift = redShift;
|
||||
this.greenShift = greenShift;
|
||||
this.blueShift = blueShift;
|
||||
this.alphaShift = alphaShift;
|
||||
}
|
||||
|
||||
public int redShift;
|
||||
public int greenShift;
|
||||
public int blueShift;
|
||||
public int alphaShift;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package su.xash.engine.workers
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.WorkerParameters
|
||||
import androidx.work.workDataOf
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
const val KEY_FILE_URI = "KEY_FILE_URI"
|
||||
|
||||
class FileCopyWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
|
||||
companion object {
|
||||
const val Input = "Input"
|
||||
const val Progress = "Progress"
|
||||
}
|
||||
|
||||
private var fileCount = 0
|
||||
private var fileCopied = 0
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
withContext(Dispatchers.IO) {
|
||||
val fileUri = inputData.getString(KEY_FILE_URI)
|
||||
setProgress(workDataOf(Input to fileUri))
|
||||
|
||||
val uri = Uri.parse(fileUri)
|
||||
val source = DocumentFile.fromTreeUri(applicationContext, uri)
|
||||
|
||||
fileCount = source?.countDirFiles() ?: return@withContext Result.failure()
|
||||
|
||||
setProgress(workDataOf(Progress to 0f))
|
||||
|
||||
val gamedir = source.name!!
|
||||
val externalFilesDir = DocumentFile.fromFile(applicationContext.getExternalFilesDir(null)!!)
|
||||
|
||||
// create a directory to store staged files
|
||||
val target = externalFilesDir.createDirectory(".$gamedir")!!
|
||||
|
||||
source.copyDirTo(applicationContext, this@FileCopyWorker, target)
|
||||
|
||||
target.renameTo(gamedir)
|
||||
|
||||
setProgress(workDataOf(Progress to 1f))
|
||||
}
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
suspend fun fileCopied(count: Int) {
|
||||
if(count == 0)
|
||||
return
|
||||
|
||||
fileCopied += count
|
||||
val percentage: Float = fileCopied.toFloat() / fileCount.toFloat();
|
||||
setProgress(workDataOf(Progress to percentage))
|
||||
}
|
||||
}
|
||||
|
||||
fun DocumentFile.countDirFiles(): Int {
|
||||
var count: Int = 0
|
||||
|
||||
listFiles().forEach {
|
||||
if (it.isDirectory)
|
||||
count += it.countDirFiles()
|
||||
else
|
||||
count++
|
||||
}
|
||||
|
||||
return count
|
||||
}
|
||||
|
||||
fun DocumentFile.copyFileTo(ctx: Context, file: DocumentFile) {
|
||||
val outFile = file.createFile("application", name!!)!!
|
||||
|
||||
ctx.contentResolver.openOutputStream(outFile.uri).use { os ->
|
||||
ctx.contentResolver.openInputStream(uri).use {
|
||||
it?.copyTo(os!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun DocumentFile.copyDirTo(ctx: Context, worker: FileCopyWorker, dir: DocumentFile) {
|
||||
var count: Int = 0
|
||||
|
||||
listFiles().forEach {
|
||||
if (it.isDirectory) {
|
||||
val outDir = dir.createDirectory(it.name!!)!!
|
||||
it.copyDirTo(ctx, worker, outDir)
|
||||
} else {
|
||||
it.copyFileTo(ctx, dir)
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
worker.fileCopied(count)
|
||||
}
|
||||
10
android/app/src/main/res/drawable/ic_baseline_add_24.xml
Normal file
10
android/app/src/main/res/drawable/ic_baseline_add_24.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
|
||||
</vector>
|
||||
10
android/app/src/main/res/drawable/ic_baseline_delete_24.xml
Normal file
10
android/app/src/main/res/drawable/ic_baseline_delete_24.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M20,6h-8l-2,-2L4,4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,8c0,-1.1 -0.9,-2 -2,-2zM20,18L4,18L4,8h16v10z" />
|
||||
</vector>
|
||||
5
android/app/src/main/res/drawable/ic_baseline_key_24.xml
Normal file
5
android/app/src/main/res/drawable/ic_baseline_key_24.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#000000"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M21,10h-8.35C11.83,7.67 9.61,6 7,6c-3.31,0 -6,2.69 -6,6s2.69,6 6,6c2.61,0 4.83,-1.67 5.65,-4H13l2,2l2,-2l2,2l4,-4.04L21,10zM7,15c-1.65,0 -3,-1.35 -3,-3c0,-1.65 1.35,-3 3,-3s3,1.35 3,3C10,13.65 8.65,15 7,15z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#000000"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M12,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zM12,14c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="#000000"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M8,5v14l11,-7z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M19.14,12.94c0.04,-0.3 0.06,-0.61 0.06,-0.94c0,-0.32 -0.02,-0.64 -0.07,-0.94l2.03,-1.58c0.18,-0.14 0.23,-0.41 0.12,-0.61l-1.92,-3.32c-0.12,-0.22 -0.37,-0.29 -0.59,-0.22l-2.39,0.96c-0.5,-0.38 -1.03,-0.7 -1.62,-0.94L14.4,2.81c-0.04,-0.24 -0.24,-0.41 -0.48,-0.41h-3.84c-0.24,0 -0.43,0.17 -0.47,0.41L9.25,5.35C8.66,5.59 8.12,5.92 7.63,6.29L5.24,5.33c-0.22,-0.08 -0.47,0 -0.59,0.22L2.74,8.87C2.62,9.08 2.66,9.34 2.86,9.48l2.03,1.58C4.84,11.36 4.8,11.69 4.8,12s0.02,0.64 0.07,0.94l-2.03,1.58c-0.18,0.14 -0.23,0.41 -0.12,0.61l1.92,3.32c0.12,0.22 0.37,0.29 0.59,0.22l2.39,-0.96c0.5,0.38 1.03,0.7 1.62,0.94l0.36,2.54c0.05,0.24 0.24,0.41 0.48,0.41h3.84c0.24,0 0.44,-0.17 0.47,-0.41l0.36,-2.54c0.59,-0.24 1.13,-0.56 1.62,-0.94l2.39,0.96c0.22,0.08 0.47,0 0.59,-0.22l1.92,-3.32c0.12,-0.22 0.07,-0.47 -0.12,-0.61L19.14,12.94zM12,15.6c-1.98,0 -3.6,-1.62 -3.6,-3.6s1.62,-3.6 3.6,-3.6s3.6,1.62 3.6,3.6S13.98,15.6 12,15.6z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M20,4H4C2.89,4 2,4.9 2,6v12c0,1.1 0.89,2 2,2h16c1.1,0 2,-0.9 2,-2V6C22,4.9 21.11,4 20,4zM20,18H4V8h16V18zM18,17h-6v-2h6V17zM7.5,17l-1.41,-1.41L8.67,13l-2.59,-2.59L7.5,9l4,4L7.5,17z" />
|
||||
</vector>
|
||||
15
android/app/src/main/res/drawable/steam_button_gradient.xml
Normal file
15
android/app/src/main/res/drawable/steam_button_gradient.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_enabled="true">
|
||||
<shape android:shape="rectangle">
|
||||
<gradient android:endColor="#2D73FF" android:startColor="#06BFFF" />
|
||||
<corners android:radius="4dp" />
|
||||
</shape>
|
||||
</item>
|
||||
<item android:state_enabled="false">
|
||||
<shape android:shape="rectangle">
|
||||
<gradient android:endColor="#6D6D6D" android:startColor="#8F8F8F" />
|
||||
<corners android:radius="4dp" />
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
||||
9
android/app/src/main/res/drawable/steam_icon.xml
Normal file
9
android/app/src/main/res/drawable/steam_icon.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M12.004,2c-5.25,0 -9.556,4.05 -9.964,9.197l5.36,2.216c0.454,-0.31 1.002,-0.492 1.593,-0.492 0.053,0 0.104,0.003 0.157,0.005l2.384,-3.452v-0.049c0,-2.08 1.69,-3.77 3.77,-3.77 2.079,0 3.77,1.692 3.77,3.772s-1.692,3.771 -3.77,3.771h-0.087l-3.397,2.426c0,0.043 0.003,0.088 0.003,0.133 0,1.562 -1.262,2.83 -2.825,2.83 -1.362,0 -2.513,-0.978 -2.775,-2.273l-3.838,-1.589C3.573,18.922 7.427,22 12.005,22c5.522,0 9.998,-4.477 9.998,-10 0,-5.522 -4.477,-10 -9.999,-10zM7.078,16.667c0.218,0.452 0.595,0.832 1.094,1.041 1.081,0.45 2.328,-0.063 2.777,-1.145 0.22,-0.525 0.22,-1.1 0.004,-1.625 -0.215,-0.525 -0.625,-0.934 -1.147,-1.152 -0.52,-0.217 -1.075,-0.208 -1.565,-0.025l1.269,0.525c0.797,0.333 1.174,1.25 0.84,2.046 -0.33,0.797 -1.247,1.175 -2.044,0.843l-1.228,-0.508zM17.818,9.422c0,-1.385 -1.128,-2.512 -2.513,-2.512 -1.387,0 -2.512,1.127 -2.512,2.512 0,1.388 1.125,2.513 2.512,2.513 1.386,0 2.512,-1.125 2.512,-2.513zM15.31,7.53c1.04,0 1.888,0.845 1.888,1.888s-0.847,1.888 -1.888,1.888c-1.044,0 -1.888,-0.845 -1.888,-1.888s0.845,-1.888 1.888,-1.888z"/>
|
||||
</vector>
|
||||
33
android/app/src/main/res/drawable/steam_logo.xml
Normal file
33
android/app/src/main/res/drawable/steam_logo.xml
Normal file
@@ -0,0 +1,33 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="295.46dp"
|
||||
android:height="90.47dp"
|
||||
android:viewportWidth="295.46"
|
||||
android:viewportHeight="90.47">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="m45.08,1c-23.24,0 -42.28,17.92 -44.08,40.69l23.71,9.8c2.01,-1.37 4.44,-2.18 7.05,-2.18 0.23,0 0.47,0.01 0.7,0.02l10.54,-15.28c0,-0.07 0,-0.14 0,-0.22 0,-9.2 7.48,-16.68 16.68,-16.68 9.2,0 16.68,7.48 16.68,16.68s-7.48,16.68 -16.68,16.68c-0.13,0 -0.25,0 -0.38,-0.01l-15.04,10.73c0.01,0.19 0.01,0.39 0.01,0.59 0,6.91 -5.62,12.52 -12.52,12.52 -6.06,0 -11.13,-4.33 -12.28,-10.06l-16.96,-7.01c5.25,18.57 22.31,32.18 42.56,32.18 24.43,0 44.24,-19.81 44.24,-44.24 0,-24.43 -19.81,-44.24 -44.24,-44.24"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="m28.72,68.12 l-5.43,-2.24c0.96,2.01 2.63,3.68 4.84,4.61 4.78,1.99 10.3,-0.28 12.29,-5.06 0.96,-2.31 0.97,-4.87 0.01,-7.19 -0.95,-2.32 -2.76,-4.13 -5.07,-5.1 -2.3,-0.96 -4.76,-0.92 -6.93,-0.1l5.61,2.32c3.53,1.47 5.2,5.52 3.72,9.05 -1.47,3.53 -5.52,5.2 -9.05,3.72"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="m70.8,33.83c0,-6.13 -4.99,-11.12 -11.12,-11.12 -6.13,0 -11.12,4.99 -11.12,11.12 0,6.13 4.99,11.11 11.12,11.11 6.13,0 11.12,-4.99 11.12,-11.11m-19.45,-0.02c0,-4.61 3.74,-8.35 8.35,-8.35s8.35,3.74 8.35,8.35 -3.74,8.35 -8.35,8.35 -8.35,-3.74 -8.35,-8.35"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="m136.56,31.27 l-2.96,5.21c-2.28,-1.6 -5.38,-2.56 -8.08,-2.56 -3.09,0 -5,1.28 -5,3.57 0,2.78 3.39,3.43 8.44,5.24 5.42,1.92 8.54,4.17 8.54,9.14 0,6.79 -5.34,10.61 -13.02,10.61 -3.74,0 -8.26,-0.97 -11.73,-3.08l2.16,-5.78c2.82,1.49 6.19,2.37 9.2,2.37 4.05,0 5.98,-1.5 5.98,-3.7 0,-2.53 -2.94,-3.29 -7.68,-4.86 -5.4,-1.8 -9.15,-4.17 -9.15,-9.67 0,-6.2 4.96,-9.76 12.1,-9.76 4.98,0 8.98,1.58 11.2,3.26"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="m152.76,61.9v-27.34h-10.13v-5.99h27.21v5.99h-10.1v27.34z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="m197.9,42.05v5.99h-13.36v7.82h15.5v6.04h-22.47v-33.33h22.47v5.97h-15.5v7.51z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="m215.62,55.43 l-2.21,6.47h-7.32l12.49,-33.33h7.03l12.85,33.32h-7.56l-2.25,-6.47h-13.03zM222.07,36.52 L217.51,49.87h9.2z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="m261.22,60.93 l-8.97,-19.3v20.27h-6.68v-33.33h6.67l11.2,24.06 10.8,-24.06h6.73v33.33h-6.68v-20.44l-9.12,19.47z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="m294.46,32.78c0,2.86 -2.15,4.65 -4.61,4.65 -2.47,0 -4.62,-1.78 -4.62,-4.65 0,-2.86 2.15,-4.64 4.62,-4.64 2.46,0 4.61,1.77 4.61,4.64m-8.46,0c0,2.4 1.73,3.9 3.85,3.9 2.11,0 3.83,-1.5 3.83,-3.9 0,-2.4 -1.72,-3.88 -3.83,-3.88 -2.12,0 -3.85,1.5 -3.85,3.88m3.91,-2.37c1.2,0 1.6,0.63 1.6,1.32 0,0.63 -0.37,1.05 -0.82,1.26l1.07,2.01h-0.88l-0.9,-1.78h-0.93v1.78h-0.73v-4.58zM289.05,32.54h0.81c0.53,0 0.84,-0.33 0.84,-0.75 0,-0.42 -0.22,-0.69 -0.84,-0.69h-0.81v1.44z"/>
|
||||
</vector>
|
||||
35
android/app/src/main/res/layout/activity_main.xml
Normal file
35
android/app/src/main/res/layout/activity_main.xml
Normal file
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appBarLayout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/fragmentContainerView"
|
||||
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:defaultNavHost="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/appBarLayout"
|
||||
app:navGraph="@navigation/nav_graph"
|
||||
tools:layout="@layout/fragment_library"/>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
111
android/app/src/main/res/layout/card_game.xml
Normal file
111
android/app/src/main/res/layout/card_game.xml
Normal file
@@ -0,0 +1,111 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
style="@style/Widget.Material3.CardView.Filled"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="128dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/gameCover"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:adjustViewBounds="true"
|
||||
android:scaleType="centerCrop"
|
||||
tools:src="@android:mipmap/sym_def_app_icon" />
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
style="@style/Widget.Material3.CardView.Filled"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="48dp"
|
||||
android:layout_margin="8dp"
|
||||
app:contentPadding="10dp"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/buttonsContainer"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintStart_toStartOf="parent">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/gameIcon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginRight="8dp"
|
||||
android:adjustViewBounds="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/gameTitle"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@android:mipmap/sym_def_app_icon" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/gameTitle"
|
||||
style="@style/TextAppearance.Material3.TitleMedium"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:singleLine="true"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/gameIcon"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="Game Title" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/buttonsContainer"
|
||||
style="@style/Widget.Material3.CardView.Filled"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.progressindicator.CircularProgressIndicator
|
||||
android:id="@+id/progressIndicator"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:indeterminate="true"
|
||||
android:padding="10dp"
|
||||
android:visibility="gone"
|
||||
app:indicatorSize="20dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/launchButton"
|
||||
style="@style/Widget.Material3.Button.IconButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
app:icon="@drawable/ic_baseline_play_arrow_24"
|
||||
app:layout_constraintEnd_toStartOf="@+id/settingsButton" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/settingsButton"
|
||||
style="@style/Widget.Material3.Button.IconButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
app:icon="@drawable/ic_baseline_settings_24"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
30
android/app/src/main/res/layout/edit_text_preference.xml
Normal file
30
android/app/src/main/res/layout/edit_text_preference.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<!-- <com.google.android.material.textfield.TextInputLayout-->
|
||||
<!-- android:layout_width="match_parent"-->
|
||||
<!-- android:layout_height="wrap_content"-->
|
||||
<!-- app:layout_constraintTop_toTopOf="parent"-->
|
||||
<!-- app:layout_constraintBottom_toBottomOf="parent">-->
|
||||
|
||||
<!-- <com.google.android.material.textfield.TextInputEditText-->
|
||||
<!-- android:layout_width="match_parent"-->
|
||||
<!-- android:layout_height="wrap_content" />-->
|
||||
<!-- </com.google.android.material.textfield.TextInputLayout>-->
|
||||
<TextView
|
||||
android:id="@android:id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:textAppearance="@style/TextAppearance.Material3.TitleMedium"/>
|
||||
|
||||
<TextView
|
||||
android:id="@android:id/summary"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toBottomOf="@android:id/title"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"/>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
14
android/app/src/main/res/layout/fragment_app_settings.xml
Normal file
14
android/app/src/main/res/layout/fragment_app_settings.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingHorizontal="8dp">
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/settingsFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:layout="@android:layout/list_content" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
39
android/app/src/main/res/layout/fragment_game_settings.xml
Normal file
39
android/app/src/main/res/layout/fragment_game_settings.xml
Normal file
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<include
|
||||
android:id="@+id/gameCard"
|
||||
layout="@layout/card_game"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
style="@style/Widget.Material3.CardView.Filled"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
app:contentPadding="16dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/gameCard">
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/settingsFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:layout="@android:layout/list_content" />
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<com.google.android.material.bottomnavigation.BottomNavigationView
|
||||
android:id="@+id/bottomNavigation"
|
||||
style="@style/Widget.MaterialComponents.BottomNavigationView.Colored"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:menu="@menu/menu_game_settings" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
23
android/app/src/main/res/layout/fragment_library.xml
Normal file
23
android/app/src/main/res/layout/fragment_library.xml
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/swipeRefresh">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/gamesList"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layoutManager="LinearLayoutManager"
|
||||
android:clipToPadding="false"
|
||||
android:paddingHorizontal="8dp"
|
||||
tools:listitem="@layout/card_game" />
|
||||
</RelativeLayout>
|
||||
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
11
android/app/src/main/res/layout/fragment_setup.xml
Normal file
11
android/app/src/main/res/layout/fragment_setup.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
android:id="@+id/viewPager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
58
android/app/src/main/res/layout/fragment_steam_login.xml
Normal file
58
android/app/src/main/res/layout/fragment_steam_login.xml
Normal file
@@ -0,0 +1,58 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/steam_background"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="vertical"
|
||||
tools:context=".fragment.SteamLoginFragment">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/steam_logo"
|
||||
app:tint="#C5C3C0" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="320dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="16dp"
|
||||
app:startIconDrawable="@drawable/ic_baseline_person_24">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/steamLogin"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/username"
|
||||
android:inputType="textEmailAddress" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="320dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="16dp"
|
||||
|
||||
app:startIconDrawable="@drawable/ic_baseline_key_24">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/steamPassword"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/password"
|
||||
android:inputType="textPassword" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/loginButton"
|
||||
style="@style/App.Theme.SteamButton"
|
||||
android:layout_width="240dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/login" />
|
||||
</LinearLayout>
|
||||
19
android/app/src/main/res/layout/list_preference.xml
Normal file
19
android/app/src/main/res/layout/list_preference.xml
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<TextView
|
||||
android:id="@android:id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:textAppearance="@style/TextAppearance.Material3.TitleMedium"/>
|
||||
|
||||
<TextView
|
||||
android:id="@android:id/summary"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toBottomOf="@android:id/title"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"/>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user