mirror of
https://github.com/FWGS/xash3d-fwgs.git
synced 2026-08-05 19:45:05 +08:00
Compare commits
946 Commits
continuous
...
yy-twinks
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3366ad06d0 | ||
|
|
57206b3651 | ||
|
|
d9ecd03b4a | ||
|
|
2de4d0d364 | ||
|
|
cc3889146c | ||
|
|
09f5886751 | ||
|
|
fb5b4fed6e | ||
|
|
1e31fbcd51 | ||
|
|
b2a5f0db5a | ||
|
|
abd7b5ccdd | ||
|
|
8c9cd59c71 | ||
|
|
c79b29687c | ||
|
|
1eb922a0f1 | ||
|
|
f6e36994be | ||
|
|
e83ba6509e | ||
|
|
5cfb67940b | ||
|
|
e31c893852 | ||
|
|
85e5f032d8 | ||
|
|
92fd51ad99 | ||
|
|
5480055997 | ||
|
|
d634c6aad5 | ||
|
|
034efb3b31 | ||
|
|
eae45b2e81 | ||
|
|
567d279007 | ||
|
|
27f8385bb1 | ||
|
|
1cfabd5ec9 | ||
|
|
ab6672fb8a | ||
|
|
2d39a7fa24 | ||
|
|
7db12ade3a | ||
|
|
61a84f6953 | ||
|
|
75ffba64c5 | ||
|
|
c134840148 | ||
|
|
51d1bfbe08 | ||
|
|
79ff8332ac | ||
|
|
f155005bb1 | ||
|
|
a8de8cf5e6 | ||
|
|
74fcab9bfb | ||
|
|
efad71bcc9 | ||
|
|
c444ab882a | ||
|
|
8fad3bbad6 | ||
|
|
4cd6d25ee6 | ||
|
|
33e47ef5a9 | ||
|
|
d19e9606e9 | ||
|
|
fe1827f5e4 | ||
|
|
918323fcea | ||
|
|
e75256ec9d | ||
|
|
47582182e6 | ||
|
|
4a2c218cfc | ||
|
|
376817478d | ||
|
|
b96dc61e31 | ||
|
|
b997d5657b | ||
|
|
e9f5bca5c4 | ||
|
|
b36f107a2e | ||
|
|
a305ffb33f | ||
|
|
1cc1b60b10 | ||
|
|
648db4a2b3 | ||
|
|
2c0c20dfe6 | ||
|
|
17e167432a | ||
|
|
97b8ca6b38 | ||
|
|
6dabc6f2c5 | ||
|
|
e808038fee | ||
|
|
a60d0d142d | ||
|
|
bf5d88d9a4 | ||
|
|
adca77672a | ||
|
|
ef5295c5ab | ||
|
|
37da317133 | ||
|
|
c93cb49a9e | ||
|
|
4f342fb19a | ||
|
|
8ee3544507 | ||
|
|
d44e6d5db7 | ||
|
|
21cc9feca7 | ||
|
|
e87b14468d | ||
|
|
e01921dd10 | ||
|
|
e5c4e49782 | ||
|
|
d61983a5d2 | ||
|
|
c3f4eb026b | ||
|
|
fe9b8bcaca | ||
|
|
33cd20b21b | ||
|
|
78a3b958dd | ||
|
|
0e56fa10f9 | ||
|
|
b6a67b56b0 | ||
|
|
eb9de1cac8 | ||
|
|
24abd2deb2 | ||
|
|
996dcc637d | ||
|
|
8b89bdca6f | ||
|
|
3a6e3ba3ca | ||
|
|
42ab099a07 | ||
|
|
09f15042fe | ||
|
|
344712b990 | ||
|
|
2904c3e88b | ||
|
|
25ca2ecb12 | ||
|
|
f6b28e32e2 | ||
|
|
e118376735 | ||
|
|
5ef6123f03 | ||
|
|
e4600b5fe0 | ||
|
|
074bd8b2b3 | ||
|
|
fca347dd00 | ||
|
|
2a284090ef | ||
|
|
ecd74a0a7f | ||
|
|
853a67b0c5 | ||
|
|
80ec1c83f1 | ||
|
|
41681229f9 | ||
|
|
7fce99481f | ||
|
|
142dc7b87a | ||
|
|
3e24d5a347 | ||
|
|
64b1b54f5b | ||
|
|
1fed340330 | ||
|
|
64be4251c8 | ||
|
|
c5a8160c32 | ||
|
|
3c1cf81c2d | ||
|
|
c6c1163aa0 | ||
|
|
e8845fc404 | ||
|
|
d49178d5b6 | ||
|
|
e2cf580818 | ||
|
|
d0a98447c4 | ||
|
|
29a9fcf76c | ||
|
|
37d9791f00 | ||
|
|
c5fbc98d16 | ||
|
|
4c2302ec6c | ||
|
|
de585f80fb | ||
|
|
af58ef4a2a | ||
|
|
dbe3f42485 | ||
|
|
0053edbe7b | ||
|
|
61f3369957 | ||
|
|
334bebb1fb | ||
|
|
463ed05340 | ||
|
|
92bad81d9a | ||
|
|
4c667d9d8c | ||
|
|
cb04e9cbd5 | ||
|
|
c2c781f039 | ||
|
|
dbe1a2742e | ||
|
|
fe2019ee06 | ||
|
|
e5c091c303 | ||
|
|
9d050e7ed3 | ||
|
|
be35b3503e | ||
|
|
5fad4ae6ca | ||
|
|
2290ae0239 | ||
|
|
9082a47348 | ||
|
|
5d7ed9d8a7 | ||
|
|
8120669e5b | ||
|
|
fb6c4a224c | ||
|
|
166763a887 | ||
|
|
e56cf4e1da | ||
|
|
fc4b982ecc | ||
|
|
d305292340 | ||
|
|
9ee6be87d7 | ||
|
|
4e4a54ab68 | ||
|
|
516554b704 | ||
|
|
7be955c493 | ||
|
|
c6fb702fb1 | ||
|
|
cae1d32c66 | ||
|
|
5e70995989 | ||
|
|
50f7f3bb87 | ||
|
|
7c3da8c381 | ||
|
|
e38d3511b5 | ||
|
|
d4fcebb377 | ||
|
|
996f0e906b | ||
|
|
0f490a2f16 | ||
|
|
cef6cb58bb | ||
|
|
47aad1b3a3 | ||
|
|
ff8efebba2 | ||
|
|
be6571a6a2 | ||
|
|
1d67585df4 | ||
|
|
10d441f825 | ||
|
|
2dc132f817 | ||
|
|
e9bdbf2f33 | ||
|
|
b630a84447 | ||
|
|
dccd076b92 | ||
|
|
cb7ea5729a | ||
|
|
9bad17505e | ||
|
|
bf990d1040 | ||
|
|
63e112bf4c | ||
|
|
0aac2e4d32 | ||
|
|
33b3cdb0f5 | ||
|
|
9c4edaed21 | ||
|
|
d4205867dd | ||
|
|
3c07680263 | ||
|
|
e5d6f66f61 | ||
|
|
a106924c7f | ||
|
|
11ee883c84 | ||
|
|
881f0c5ca7 | ||
|
|
ac0c8a2210 | ||
|
|
dba090e0da | ||
|
|
262f32b002 | ||
|
|
843f6297ed | ||
|
|
d1510345f1 | ||
|
|
36307bb386 | ||
|
|
52953a7647 | ||
|
|
ebeff144f9 | ||
|
|
c8cc953d48 | ||
|
|
d6bf06e997 | ||
|
|
568b7abc5f | ||
|
|
1b7f50d3c6 | ||
|
|
c8e1c2673f | ||
|
|
c2df265de7 | ||
|
|
e9e885534a | ||
|
|
95cc6c4445 | ||
|
|
086cb6aab8 | ||
|
|
e6a44b70e0 | ||
|
|
fdb59e990e | ||
|
|
eb79bc6b40 | ||
|
|
e907e9fc95 | ||
|
|
fc356c88b3 | ||
|
|
bc672e5aca | ||
|
|
2635fd9739 | ||
|
|
653fe51fd4 | ||
|
|
980d9fcaab | ||
|
|
72ef5cffca | ||
|
|
a707f5933d | ||
|
|
b9b0ff5769 | ||
|
|
982a216df4 | ||
|
|
7d87d65a3f | ||
|
|
6e4e048ef8 | ||
|
|
5b6b372cfe | ||
|
|
2f31cf315c | ||
|
|
910cce56ca | ||
|
|
68dba8b4e0 | ||
|
|
dbfeacfecc | ||
|
|
0bdd4648bc | ||
|
|
ec8059173d | ||
|
|
f91e5a7647 | ||
|
|
7942d9b1bc | ||
|
|
7abf0c819f | ||
|
|
85eb82a370 | ||
|
|
90e25cc623 | ||
|
|
fca4152d5a | ||
|
|
2ccef63754 | ||
|
|
87615784b9 | ||
|
|
d3b01d313b | ||
|
|
a3897f402a | ||
|
|
21d84ee692 | ||
|
|
e30ce9c150 | ||
|
|
22391eba1b | ||
|
|
800725453a | ||
|
|
22ecec095e | ||
|
|
8545ed5ccb | ||
|
|
a4fdf19ad8 | ||
|
|
75e4a21d7a | ||
|
|
1099344a7d | ||
|
|
ab1eaed0a5 | ||
|
|
76a01d0855 | ||
|
|
b00679ee30 | ||
|
|
b3bb2fc16f | ||
|
|
86a2ea29e4 | ||
|
|
d4f8543556 | ||
|
|
e850728016 | ||
|
|
11877f4c96 | ||
|
|
2ba5030b62 | ||
|
|
06b14800e1 | ||
|
|
bb7c85fd22 | ||
|
|
37ce62fa6a | ||
|
|
e1b29e2da3 | ||
|
|
b60afd73e9 | ||
|
|
84447a5292 | ||
|
|
fe4ea4aabf | ||
|
|
878822290c | ||
|
|
04ca5d7cb0 | ||
|
|
d46ed40f33 | ||
|
|
82e031205c | ||
|
|
6f5f04a29d | ||
|
|
f9ed578bf1 | ||
|
|
86c84c7de7 | ||
|
|
489c1c9dd5 | ||
|
|
1df6a040ec | ||
|
|
82c2ca53fb | ||
|
|
3ddd1e2448 | ||
|
|
371d619685 | ||
|
|
480f29e96e | ||
|
|
95548053eb | ||
|
|
0daf2c2b4d | ||
|
|
e2d5bea327 | ||
|
|
306a5812a9 | ||
|
|
a3dba64513 | ||
|
|
491ba9bc7d | ||
|
|
1c462df8a2 | ||
|
|
ed975976f8 | ||
|
|
bbf8ab378d | ||
|
|
5383e36af5 | ||
|
|
897fb98b93 | ||
|
|
96e88adfcd | ||
|
|
4dde4047f1 | ||
|
|
905161529f | ||
|
|
044523434d | ||
|
|
da4c4bb76a | ||
|
|
95b3271d68 | ||
|
|
3361a5d760 | ||
|
|
b725d2ece0 | ||
|
|
66bee8b468 | ||
|
|
5e664dc3e9 | ||
|
|
bfdfeb3dc0 | ||
|
|
49248f5e14 | ||
|
|
d03ea4c10a | ||
|
|
780dbd6183 | ||
|
|
50da79f1e0 | ||
|
|
62201900cf | ||
|
|
ae233ac491 | ||
|
|
dd1cb04cb7 | ||
|
|
74869c8f5d | ||
|
|
dd712a0df9 | ||
|
|
bfdb62d31e | ||
|
|
ae7b9eb28d | ||
|
|
c63e13c8f4 | ||
|
|
d3e1407675 | ||
|
|
e2e4728c7c | ||
|
|
dd3583d11a | ||
|
|
e6924844da | ||
|
|
a49db03561 | ||
|
|
84b7b5803b | ||
|
|
c224d39ba7 | ||
|
|
9b24b08df1 | ||
|
|
a37b0e58f1 | ||
|
|
afca422e35 | ||
|
|
3440ddf443 | ||
|
|
6730ed9555 | ||
|
|
fdd3a44fa0 | ||
|
|
241c926076 | ||
|
|
148a9a5a8c | ||
|
|
513984d7fd | ||
|
|
1009795d5c | ||
|
|
e3d2da74db | ||
|
|
ff3a57ae8a | ||
|
|
271a6e2210 | ||
|
|
0d40357b93 | ||
|
|
a9dcdd034c | ||
|
|
7324ff43fc | ||
|
|
d85e7fc2b1 | ||
|
|
d91ecfa3df | ||
|
|
b7a3a02aad | ||
|
|
5aca78951c | ||
|
|
81abae65b5 | ||
|
|
f5f6d9bbe4 | ||
|
|
ef73b55413 | ||
|
|
8334695522 | ||
|
|
d7f416d659 | ||
|
|
5f6577df2e | ||
|
|
e87f307ac1 | ||
|
|
7e015a268b | ||
|
|
a679c53111 | ||
|
|
9e8d248044 | ||
|
|
055d646225 | ||
|
|
a2514b6916 | ||
|
|
0ac5e78040 | ||
|
|
a82221e3df | ||
|
|
bb734456f6 | ||
|
|
a219ca7371 | ||
|
|
c3b5e43a7a | ||
|
|
e7597d2e84 | ||
|
|
386ca1b9bb | ||
|
|
da3960c4f0 | ||
|
|
cd85762b09 | ||
|
|
d92b3a730c | ||
|
|
ffa53f14e5 | ||
|
|
b8bd906832 | ||
|
|
1c73d8d6f4 | ||
|
|
2c3fdb5eee | ||
|
|
4bbe52e3b4 | ||
|
|
2d241ed96b | ||
|
|
ba4bce06b1 | ||
|
|
f7e933aea1 | ||
|
|
dbcf77c9a9 | ||
|
|
8a9aa2da6c | ||
|
|
0e8143aefc | ||
|
|
4345e9e6a6 | ||
|
|
e7fa45d5ca | ||
|
|
abda0a07c2 | ||
|
|
9d22827395 | ||
|
|
7b665b3763 | ||
|
|
58692f397a | ||
|
|
2253fcc955 | ||
|
|
cbbae0c9bf | ||
|
|
ef513f27ac | ||
|
|
e8ae48fd3d | ||
|
|
434e508186 | ||
|
|
4a2b3e89b4 | ||
|
|
6c3f8c7ec5 | ||
|
|
3fffe061d5 | ||
|
|
729c985eaf | ||
|
|
75fb6c5f34 | ||
|
|
8bc9b1865a | ||
|
|
d6993c47d6 | ||
|
|
b8792e4084 | ||
|
|
2234dd48bc | ||
|
|
c8693e1e89 | ||
|
|
3ace4a61d2 | ||
|
|
bd3f865cf7 | ||
|
|
b68e71abfd | ||
|
|
6179bef160 | ||
|
|
983427ebb3 | ||
|
|
1b2ab34745 | ||
|
|
2713f03ec2 | ||
|
|
238083c174 | ||
|
|
78de3ee6d9 | ||
|
|
695081e079 | ||
|
|
2c5e2e1ded | ||
|
|
d151c48d1f | ||
|
|
35998bce81 | ||
|
|
a10520ca9e | ||
|
|
cb4a03ded2 | ||
|
|
31a0d17de5 | ||
|
|
5f765a35b9 | ||
|
|
5606346cc1 | ||
|
|
6faee8fd89 | ||
|
|
255c9bcaed | ||
|
|
25a3fdbcec | ||
|
|
8e450d869b | ||
|
|
e8ef7ad60c | ||
|
|
216eedcdcd | ||
|
|
fd55716633 | ||
|
|
0b920be381 | ||
|
|
e6dfccb6ba | ||
|
|
3129081050 | ||
|
|
00dac928c9 | ||
|
|
3726256514 | ||
|
|
5eb43505cb | ||
|
|
f7e516b775 | ||
|
|
4edd512902 | ||
|
|
acb263368a | ||
|
|
38b3805ad8 | ||
|
|
2ebaa2ad8e | ||
|
|
018c52b998 | ||
|
|
99d01e7967 | ||
|
|
e5670afc4c | ||
|
|
5aba96ce14 | ||
|
|
6b97fc1825 | ||
|
|
42cb5fe547 | ||
|
|
6a1718aa07 | ||
|
|
f980f40f8e | ||
|
|
5506486ed1 | ||
|
|
4a90d2fbe5 | ||
|
|
8767a4d6ba | ||
|
|
34bb2665cf | ||
|
|
1a79a9488d | ||
|
|
c91b707b66 | ||
|
|
a50ea929d6 | ||
|
|
d5b481ac9d | ||
|
|
8e69be0542 | ||
|
|
5296147c6b | ||
|
|
30fa074c8c | ||
|
|
02b1d15e42 | ||
|
|
82475c3b58 | ||
|
|
c421f3136d | ||
|
|
641a3663f5 | ||
|
|
acaede2a0d | ||
|
|
d8d1ebf23c | ||
|
|
926f0ae95d | ||
|
|
c5d5ce5f43 | ||
|
|
2fe950b0af | ||
|
|
a04bb01841 | ||
|
|
2d2de9dc26 | ||
|
|
81fc511f0a | ||
|
|
d85d813d62 | ||
|
|
9f6b571d79 | ||
|
|
292f393f99 | ||
|
|
f523f04ee2 | ||
|
|
88d8115306 | ||
|
|
9a050cb67f | ||
|
|
cf8046ecd9 | ||
|
|
6a292ec68c | ||
|
|
4679359688 | ||
|
|
bca81de210 | ||
|
|
ec1a65c18f | ||
|
|
ed559d9ed7 | ||
|
|
8995a584b9 | ||
|
|
30c81c9f01 | ||
|
|
11c7c1b77d | ||
|
|
93cb146f39 | ||
|
|
59dbe1d01e | ||
|
|
d6dc77d409 | ||
|
|
f8714a57d3 | ||
|
|
9f71255d6f | ||
|
|
d8bab034c2 | ||
|
|
9a25768617 | ||
|
|
e76528d929 | ||
|
|
00ad05c92a | ||
|
|
df95baa282 | ||
|
|
f42e2f59b7 | ||
|
|
d8b784482e | ||
|
|
b136f1e078 | ||
|
|
bd73775a2f | ||
|
|
1f133ea702 | ||
|
|
8acc7e4f76 | ||
|
|
00e2e8e502 | ||
|
|
e5013196ac | ||
|
|
658a3b0c6a | ||
|
|
4e9a2dace0 | ||
|
|
f7b389b7cd | ||
|
|
dac77ec0af | ||
|
|
32d73e62f4 | ||
|
|
b353ec463a | ||
|
|
36be731893 | ||
|
|
0389ef3b04 | ||
|
|
b2886dd2e9 | ||
|
|
af5d190bc4 | ||
|
|
5ca31f0965 | ||
|
|
c19a824a42 | ||
|
|
53c5a33ffd | ||
|
|
711d15958c | ||
|
|
99d7c52b4e | ||
|
|
342a59a408 | ||
|
|
9302b8904c | ||
|
|
bdbf5c0ef2 | ||
|
|
192ed942c4 | ||
|
|
56255f6de0 | ||
|
|
7ce830ea33 | ||
|
|
9a285986c4 | ||
|
|
62c547570a | ||
|
|
88e5013623 | ||
|
|
ea0fed24ec | ||
|
|
f863e346f5 | ||
|
|
7c527d8886 | ||
|
|
e51f93a5b4 | ||
|
|
50147a3d48 | ||
|
|
26acc25018 | ||
|
|
d8e1597726 | ||
|
|
2c004e6cb7 | ||
|
|
9cf9eb91bb | ||
|
|
fc9ab68837 | ||
|
|
be6ad74e1b | ||
|
|
2bdc22fbeb | ||
|
|
382b19e458 | ||
|
|
c63c0ba04b | ||
|
|
109579fa92 | ||
|
|
005544d974 | ||
|
|
07937f5f6c | ||
|
|
8bdd6bfc5e | ||
|
|
8b0d3557f3 | ||
|
|
436fa98f33 | ||
|
|
718fbac9be | ||
|
|
abaf390dfc | ||
|
|
9205ddc5b4 | ||
|
|
6bcb98e779 | ||
|
|
4722ad6be6 | ||
|
|
baa005ff71 | ||
|
|
76363a555c | ||
|
|
de70e12bbe | ||
|
|
eacc35d9e1 | ||
|
|
3040552580 | ||
|
|
24aaeb07cb | ||
|
|
ae15886a14 | ||
|
|
b2911e6d96 | ||
|
|
2735add7d0 | ||
|
|
0abb667c28 | ||
|
|
19dfec5f52 | ||
|
|
e0f01ddae7 | ||
|
|
2f8a0510b8 | ||
|
|
dbbe75fec6 | ||
|
|
c0b15fb186 | ||
|
|
f802b0eed9 | ||
|
|
cf9f9b40e7 | ||
|
|
1b2c00e228 | ||
|
|
587eabd829 | ||
|
|
80e0d45c09 | ||
|
|
f034276354 | ||
|
|
306455280a | ||
|
|
d9221c8491 | ||
|
|
249e1404c1 | ||
|
|
dd55b77543 | ||
|
|
c772fb98d1 | ||
|
|
1c1e5c50a9 | ||
|
|
5cc784f2fa | ||
|
|
983b9611ac | ||
|
|
a7f154d05a | ||
|
|
0e75428d62 | ||
|
|
fa34092ba2 | ||
|
|
f01818a3f8 | ||
|
|
732bc50a3a | ||
|
|
e9ffe12481 | ||
|
|
cca6f8da2d | ||
|
|
d391ef1747 | ||
|
|
4804586728 | ||
|
|
d08fd5c855 | ||
|
|
9cc884dfe6 | ||
|
|
e767770647 | ||
|
|
456ad17d05 | ||
|
|
5ee21ca6ab | ||
|
|
f706b51d14 | ||
|
|
d8895ae497 | ||
|
|
a807168816 | ||
|
|
a26c56629e | ||
|
|
8ee997b1bc | ||
|
|
27c71194ba | ||
|
|
c029def67d | ||
|
|
8bb6968ab8 | ||
|
|
cc9e3a5a81 | ||
|
|
e9cd94eea6 | ||
|
|
572fd063fc | ||
|
|
1052e45928 | ||
|
|
df561147ec | ||
|
|
9ce570fe28 | ||
|
|
20a20c72d4 | ||
|
|
7a8837bf2a | ||
|
|
b83d763a50 | ||
|
|
b2ab748fcb | ||
|
|
56e5a95cb6 | ||
|
|
214b133e5d | ||
|
|
f9bbe2a2c4 | ||
|
|
c5f90aabd6 | ||
|
|
77123f710b | ||
|
|
5c5c0ad94d | ||
|
|
5232ccb6e3 | ||
|
|
bc387477ce | ||
|
|
1c6eb85a12 | ||
|
|
ed950723cd | ||
|
|
e06087cb0b | ||
|
|
8cbc7819a6 | ||
|
|
7c588540d8 | ||
|
|
0e0763c820 | ||
|
|
e5d3f180dc | ||
|
|
5cb5e76872 | ||
|
|
742265c2c4 | ||
|
|
4e49cc49f8 | ||
|
|
09ca41ea36 | ||
|
|
896bbe8fc3 | ||
|
|
ed85d3028f | ||
|
|
8e33f11dc6 | ||
|
|
4bb547fdc7 | ||
|
|
8af5ecdb06 | ||
|
|
5a0517c1b8 | ||
|
|
5ed1bca57f | ||
|
|
d5892f3794 | ||
|
|
32aee7ef2f | ||
|
|
3a5d57603f | ||
|
|
c865999302 | ||
|
|
cb69a52d65 | ||
|
|
d915ed184c | ||
|
|
bdc205b994 | ||
|
|
46ccc37cae | ||
|
|
d7b96ea8c2 | ||
|
|
de9e28700a | ||
|
|
f5b2c4f7af | ||
|
|
79f7fba83c | ||
|
|
cb7f2ad8af | ||
|
|
5cdb58431a | ||
|
|
543d6d67e6 | ||
|
|
72e8a53046 | ||
|
|
deb7b2061a | ||
|
|
8a75104cbc | ||
|
|
e7f758f380 | ||
|
|
a4e5cf8bac | ||
|
|
0976d96419 | ||
|
|
e39f8bf7c4 | ||
|
|
9ed76fbb36 | ||
|
|
4b77170136 | ||
|
|
a168719905 | ||
|
|
c950530e44 | ||
|
|
5584e6376c | ||
|
|
fcd7e7dcea | ||
|
|
548a49ed93 | ||
|
|
68a7689108 | ||
|
|
58996c3d0d | ||
|
|
41925a1aa7 | ||
|
|
c834f7c559 | ||
|
|
a66262a787 | ||
|
|
3b51708160 | ||
|
|
ee6004a88d | ||
|
|
414da98c21 | ||
|
|
30ad3556dd | ||
|
|
67a121ef8e | ||
|
|
e7743587a1 | ||
|
|
1b66367d3d | ||
|
|
bc61516083 | ||
|
|
dae8c6cb9c | ||
|
|
bed1c669aa | ||
|
|
785b2881b6 | ||
|
|
ce08f071b1 | ||
|
|
e102857735 | ||
|
|
e973c42bfa | ||
|
|
4a16ca8bc0 | ||
|
|
f9e82170ac | ||
|
|
ad01be8866 | ||
|
|
40c7c5b348 | ||
|
|
66ea91270b | ||
|
|
44fb4fb594 | ||
|
|
16c849d085 | ||
|
|
e2835a7614 | ||
|
|
837bf652c5 | ||
|
|
ae5793f258 | ||
|
|
146244f1f4 | ||
|
|
fa7a04f535 | ||
|
|
db2c2e685d | ||
|
|
324b152307 | ||
|
|
09a68bbebd | ||
|
|
2b7773d933 | ||
|
|
aaef9fd91c | ||
|
|
15f37a301f | ||
|
|
807b5df17b | ||
|
|
2a3dcecfae | ||
|
|
a858719a4b | ||
|
|
c844cf7ec1 | ||
|
|
a4431b5cfd | ||
|
|
7e730cf171 | ||
|
|
9569900b9c | ||
|
|
5c190567e5 | ||
|
|
2ef374ff4a | ||
|
|
2c20734ad3 | ||
|
|
a4f8fc2205 | ||
|
|
4ed3e539fa | ||
|
|
3bc32d6e1b | ||
|
|
e05d6ec27c | ||
|
|
cfa8e639f1 | ||
|
|
bbe659ebfa | ||
|
|
bf5821e4ea | ||
|
|
a1dc006488 | ||
|
|
31c30aeb28 | ||
|
|
2516ed71d6 | ||
|
|
acbeb8fd30 | ||
|
|
ab5ac3aae8 | ||
|
|
da133d890f | ||
|
|
2736d1fd03 | ||
|
|
c7f18d7abe | ||
|
|
d5347a82cb | ||
|
|
e32470280d | ||
|
|
ceb5cd61e4 | ||
|
|
75339193d7 | ||
|
|
07da038b4b | ||
|
|
a2b597577f | ||
|
|
9cf2d9fa86 | ||
|
|
d45ced5c66 | ||
|
|
6aadb4ceea | ||
|
|
c956c65e33 | ||
|
|
86dfbc3e34 | ||
|
|
3fc59104f3 | ||
|
|
aae59ac281 | ||
|
|
e8a2047d4c | ||
|
|
56972a811c | ||
|
|
27086bfb97 | ||
|
|
93dfcc344b | ||
|
|
c2448c8881 | ||
|
|
26df99ae41 | ||
|
|
45cff25bbc | ||
|
|
5556668fc3 | ||
|
|
1b28d183fc | ||
|
|
15bbe9892e | ||
|
|
7236094421 | ||
|
|
c85c6f80e0 | ||
|
|
dd7811e5bc | ||
|
|
28c118a4e6 | ||
|
|
49b0619c9a | ||
|
|
0c3c6345e9 | ||
|
|
747baa1e16 | ||
|
|
2e5d2c1dcc | ||
|
|
300edb3318 | ||
|
|
0dfeea5213 | ||
|
|
d9150734c9 | ||
|
|
65514b73fd | ||
|
|
d366e04682 | ||
|
|
5151f9559b | ||
|
|
62c56b877f | ||
|
|
420a907776 | ||
|
|
184e976c5d | ||
|
|
6154e54661 | ||
|
|
73950e867f | ||
|
|
822aa00bd6 | ||
|
|
681808ca3d | ||
|
|
a0b40fc2da | ||
|
|
955c5570d8 | ||
|
|
2094c72c95 | ||
|
|
0795fe92fe | ||
|
|
9ce131e6be | ||
|
|
08b718995c | ||
|
|
4900955fc1 | ||
|
|
9d00e52c42 | ||
|
|
6c2bad610f | ||
|
|
27c6004191 | ||
|
|
500d385e6b | ||
|
|
5739946330 | ||
|
|
8a94256558 | ||
|
|
a66774d65f | ||
|
|
a9f159bae5 | ||
|
|
a6108b7930 | ||
|
|
43066656bd | ||
|
|
a9bf700da4 | ||
|
|
ee7dce68b8 | ||
|
|
a14cc82d46 | ||
|
|
b3dc772cd2 | ||
|
|
9bb9575c6d | ||
|
|
359cc9b1e7 | ||
|
|
6a9f61e69f | ||
|
|
21ea83027e | ||
|
|
f22a50b680 | ||
|
|
7217c7f1b2 | ||
|
|
cfb8e0882a | ||
|
|
f1fde32c29 | ||
|
|
c6a5c64448 | ||
|
|
b8f0a89846 | ||
|
|
684f7fb75e | ||
|
|
4dabb5d1f9 | ||
|
|
ea2c864265 | ||
|
|
7315865d34 | ||
|
|
6d987d083e | ||
|
|
6e9edcdd50 | ||
|
|
7a02110a42 | ||
|
|
9330e44817 | ||
|
|
5d7cb90bea | ||
|
|
a7b466e4a9 | ||
|
|
4c7e37d5fd | ||
|
|
d8dcfc0ab3 | ||
|
|
fb56873352 | ||
|
|
8c4ef5200c | ||
|
|
a5f7314ec9 | ||
|
|
4f444ee6ea | ||
|
|
651d6a4529 | ||
|
|
851b3d6d70 | ||
|
|
5c0cc771bc | ||
|
|
acebb5b0cf | ||
|
|
5f707e37b1 | ||
|
|
af585fec08 | ||
|
|
37e9ac453b | ||
|
|
7bb630db2a | ||
|
|
0e22e8aa76 | ||
|
|
492a1cda18 | ||
|
|
01a1c2f496 | ||
|
|
cf23005ff2 | ||
|
|
5ff640b142 | ||
|
|
6c3ae914fa | ||
|
|
654d7013c6 | ||
|
|
4d300e9d30 | ||
|
|
51a9524465 | ||
|
|
6817df534b | ||
|
|
1c8e51c9f9 | ||
|
|
8d7ee70e7d | ||
|
|
0c08f113ea | ||
|
|
8022c4826f | ||
|
|
0efe65a5bc | ||
|
|
ccbb3a2841 | ||
|
|
3d132e0e83 | ||
|
|
0a5a081a9d | ||
|
|
d16ef9bf2a | ||
|
|
748c35d5ef | ||
|
|
a1d3b7d65f | ||
|
|
1834bd6076 | ||
|
|
abde9d30c6 | ||
|
|
64adb3e3d7 | ||
|
|
b4e83ca7fd | ||
|
|
3efbe205ac | ||
|
|
55a86e1df3 | ||
|
|
712c1e2f95 | ||
|
|
6d7bfc37f6 | ||
|
|
8e7a2abec5 | ||
|
|
cc5f95d1ca | ||
|
|
62a49d234f | ||
|
|
8b55d01f78 | ||
|
|
1d7690f280 | ||
|
|
4ef23543ae | ||
|
|
a366f99d7d | ||
|
|
8c2c920076 | ||
|
|
338636092f | ||
|
|
e8d8d90b39 | ||
|
|
cb7094bb2d | ||
|
|
d9567ff3f9 | ||
|
|
3d0533ad84 | ||
|
|
153b04abc3 | ||
|
|
25bfbd3a6b | ||
|
|
5015c90fd0 | ||
|
|
e830472010 | ||
|
|
04f807ef2f | ||
|
|
55d50e2815 | ||
|
|
7266c5469a | ||
|
|
47d181aa92 | ||
|
|
dce3d9d517 | ||
|
|
e5a5804ff5 | ||
|
|
821e5d7bd7 | ||
|
|
52978df139 | ||
|
|
9aab12fc27 | ||
|
|
e83ab95279 | ||
|
|
7c0045a3b7 | ||
|
|
1f88e0465d | ||
|
|
cee1e74872 | ||
|
|
1f124bad9d | ||
|
|
71cec3c069 | ||
|
|
69204aec07 | ||
|
|
8b65cd73fc | ||
|
|
7ba0818b83 | ||
|
|
cb81303ded | ||
|
|
1b1cff1cae | ||
|
|
53bf302868 | ||
|
|
ef70530226 | ||
|
|
d073218df0 | ||
|
|
40ce6c0069 | ||
|
|
3c4c6e5a1c | ||
|
|
2e852e4398 | ||
|
|
04dee4e94d | ||
|
|
96557c2ca6 | ||
|
|
bf32c49a39 | ||
|
|
9becba3bc0 | ||
|
|
c177be0dd8 | ||
|
|
a5ff9b1c84 | ||
|
|
fe9e68ed24 | ||
|
|
2106260238 | ||
|
|
eed1d26191 | ||
|
|
6115ef0821 | ||
|
|
a63db5f31d | ||
|
|
2553bd7f37 | ||
|
|
2da0db4a31 | ||
|
|
5fc104c6a4 | ||
|
|
39066bf82b | ||
|
|
03192be245 | ||
|
|
b59ba5a127 | ||
|
|
c399611fc7 | ||
|
|
ccc44f3c5f | ||
|
|
b2f4d84272 | ||
|
|
97b8fa74a7 | ||
|
|
c679037085 | ||
|
|
793de9ec3e | ||
|
|
ccff8191dd | ||
|
|
d039a08b4e | ||
|
|
c0e8827cd5 | ||
|
|
11f976d9e9 | ||
|
|
7c83aafbf5 | ||
|
|
699e178f62 | ||
|
|
598d756af1 | ||
|
|
c2e2686b6a | ||
|
|
9df2b8f1fb | ||
|
|
1ba45bde13 | ||
|
|
5106ebae58 | ||
|
|
6b6bf7e458 | ||
|
|
2daaa94666 | ||
|
|
b129ed8f76 | ||
|
|
9fda7a15cb | ||
|
|
e38dc29411 | ||
|
|
7e3dc39cdd | ||
|
|
9c6589fa03 | ||
|
|
5e53ccab55 | ||
|
|
42b0a9a13d | ||
|
|
b504876646 | ||
|
|
a0a0d58be5 | ||
|
|
03571f831e | ||
|
|
83fe4d6c59 | ||
|
|
a83c483616 | ||
|
|
4c0931cc89 | ||
|
|
e52c46ec5e | ||
|
|
dd967ffa75 | ||
|
|
6474e07472 | ||
|
|
dc1e39264b | ||
|
|
74b196d2c3 | ||
|
|
4f9eb9f229 | ||
|
|
31c5ed69a7 | ||
|
|
6e00e6c34c | ||
|
|
9df725f1e9 | ||
|
|
f0720026fc | ||
|
|
6b4a16b337 | ||
|
|
f12ef07c02 | ||
|
|
59d771c4e4 | ||
|
|
e716290523 | ||
|
|
3ca168b329 | ||
|
|
8fb8ee7e22 |
21
.cirrus.yml
21
.cirrus.yml
@@ -1,21 +0,0 @@
|
||||
task:
|
||||
name: freebsd-14-amd64
|
||||
freebsd_instance:
|
||||
image_family: freebsd-14-2
|
||||
setup_script:
|
||||
- pkg update
|
||||
- pkg install -y pkgconf git sdl2 python fontconfig libvorbis opusfile bzip2 libbacktrace
|
||||
- git submodule update --init --recursive
|
||||
test_script:
|
||||
- ./scripts/cirrus/build_freebsd.sh
|
||||
|
||||
task:
|
||||
name: freebsd-15-amd64
|
||||
freebsd_instance:
|
||||
image_family: freebsd-15-0-snap
|
||||
setup_script:
|
||||
- pkg update
|
||||
- pkg install -y pkgconf git sdl2 python fontconfig libvorbis opusfile bzip2 libbacktrace
|
||||
- git submodule update --init --recursive
|
||||
test_script:
|
||||
- ./scripts/cirrus/build_freebsd.sh
|
||||
56
.github/workflows/c-cpp.yml
vendored
56
.github/workflows/c-cpp.yml
vendored
@@ -21,14 +21,12 @@ jobs:
|
||||
- os: ubuntu-22.04
|
||||
targetos: linux
|
||||
targetarch: i386
|
||||
- os: ubuntu-22.04
|
||||
- os: ubuntu-22.04-arm
|
||||
targetos: linux
|
||||
targetarch: arm64
|
||||
cross: true
|
||||
- os: ubuntu-22.04
|
||||
- os: ubuntu-22.04-arm
|
||||
targetos: linux
|
||||
targetarch: armhf
|
||||
cross: true
|
||||
- os: ubuntu-24.04 # riscv64 would benefit from having latest compilers
|
||||
targetos: linux
|
||||
targetarch: riscv64
|
||||
@@ -37,9 +35,6 @@ jobs:
|
||||
targetos: linux
|
||||
targetarch: ppc64el
|
||||
cross: true
|
||||
# - os: ubuntu-aarch64-22.04
|
||||
# targetos: linux
|
||||
# targetarch: aarch64
|
||||
# - os: ubuntu-latest
|
||||
# targetos: linux
|
||||
# targetarch: e2k-8c
|
||||
@@ -66,18 +61,22 @@ jobs:
|
||||
- os: macos-14 # arm64 as per github documentation
|
||||
targetos: apple
|
||||
targetarch: arm64
|
||||
- os: macos-13 # x86 as per github documentation (will they fix it before they deprecate this version?..)
|
||||
- os: macos-latest
|
||||
targetos: ios
|
||||
targetarch: arm64
|
||||
- os: macos-15-intel
|
||||
targetos: apple
|
||||
targetarch: amd64
|
||||
env:
|
||||
SDL_VERSION: 2.32.8
|
||||
FFMPEG_VERSION: 7.1
|
||||
SDL_VERSION: 2.32.10
|
||||
FFMPEG_VERSION: 8.1
|
||||
YY_THUNKS_VERSION: v1.2.1
|
||||
GH_CPU_ARCH: ${{ matrix.targetarch }}
|
||||
GH_CPU_OS: ${{ matrix.targetos }}
|
||||
GH_CROSSCOMPILING: ${{ matrix.cross }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install dependencies
|
||||
@@ -87,7 +86,7 @@ jobs:
|
||||
FWGS_PFX_PASSWORD: ${{ secrets.FWGS_PFX_PASSWORD }}
|
||||
run: bash scripts/gha/build_${{ matrix.targetos }}.sh
|
||||
- name: Upload engine (artifacts)
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: artifact-${{ matrix.targetos }}-${{ matrix.targetarch }}
|
||||
path: artifacts/*
|
||||
@@ -99,11 +98,11 @@ jobs:
|
||||
include:
|
||||
- app: su.xash.Engine.Compat.i386
|
||||
container:
|
||||
image: ghcr.io/flathub-infra/flatpak-github-actions:freedesktop-24.08
|
||||
image: ghcr.io/flathub-infra/flatpak-github-actions:freedesktop-25.08
|
||||
options: --privileged
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Build flatpak (Compat.i386)
|
||||
@@ -117,31 +116,10 @@ jobs:
|
||||
needs: [build, flatpak]
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
- name: Remove old release, fetch artifacts, repackage binaries and upload new release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
RELEASE_TAG: ${{ github.ref_name == 'master' && 'continuous' || format('continuous-{0}', github.ref_name) }}
|
||||
run: |
|
||||
gh release delete "$RELEASE_TAG" \
|
||||
--yes \
|
||||
--cleanup-tag \
|
||||
--repo "$GITHUB_REPOSITORY" || true
|
||||
gh run download "$GITHUB_RUN_ID" \
|
||||
--dir artifacts/ \
|
||||
--repo "$GITHUB_REPOSITORY"
|
||||
pushd artifacts/
|
||||
echo "Found artifacts:"
|
||||
ls
|
||||
for i in $(find -mindepth 1 -maxdepth 1 -type d); do
|
||||
mv "$i"/* .
|
||||
rm -rf "$i"
|
||||
done
|
||||
echo "Repackaged artifacts:"
|
||||
ls -R
|
||||
popd
|
||||
sleep 20s
|
||||
gh release create "$RELEASE_TAG" artifacts/* \
|
||||
--title "Xash3D FWGS Continuous ${{ github.ref_name }} Build" \
|
||||
--target $GITHUB_SHA \
|
||||
--repo "$GITHUB_REPOSITORY" \
|
||||
--prerelease
|
||||
BASE_BUILDNUM_DATE: "2015-04-01"
|
||||
run: bash scripts/gha/make_release.sh "Xash3D FWGS" "${{ github.ref_name }}"
|
||||
|
||||
20
.gitignore
vendored
20
.gitignore
vendored
@@ -318,6 +318,12 @@ build-*
|
||||
*.includes
|
||||
*.files
|
||||
|
||||
# ios
|
||||
ios/cl_dlls/*
|
||||
ios/dlls/*
|
||||
ios/*.framework
|
||||
scripts/ios/mod-build
|
||||
|
||||
# Waf
|
||||
build_current
|
||||
*waf-*/
|
||||
@@ -339,6 +345,7 @@ __pycache__
|
||||
.history/*
|
||||
.cache/*
|
||||
enc_temp_folder/
|
||||
Taskfile.yml
|
||||
|
||||
# KDevelop4
|
||||
*.kdev4
|
||||
@@ -354,3 +361,16 @@ cmake-build-*
|
||||
3rdparty/SDL
|
||||
3rdparty/hlsdk-portable
|
||||
!scripts/build-ninja.py
|
||||
|
||||
# slop
|
||||
CLAUDE.md
|
||||
AGENTS.md
|
||||
GEMINI.md
|
||||
.claude/
|
||||
.gemini/
|
||||
.cursor*
|
||||
.windsurf*
|
||||
.aider*
|
||||
.continue/
|
||||
.codeium/
|
||||
.github/copilot-instructions.md
|
||||
|
||||
8
.gitmodules
vendored
8
.gitmodules
vendored
@@ -39,4 +39,10 @@
|
||||
url = https://github.com/ianlancetaylor/libbacktrace
|
||||
[submodule "3rdparty/maintui"]
|
||||
path = 3rdparty/maintui
|
||||
url = https://github.com/numas13/xash3d-maintui.git
|
||||
url = https://github.com/FWGS/xash3d-maintui.git
|
||||
[submodule "3rdparty/library_suffix"]
|
||||
path = 3rdparty/library_suffix
|
||||
url = https://github.com/FWGS/library-suffix.git
|
||||
[submodule "3rdparty/mbedtls/mbedtls"]
|
||||
path = 3rdparty/mbedtls/mbedtls
|
||||
url = https://github.com/FWGS/mbedtls-releases.git
|
||||
|
||||
2
3rdparty/MultiEmulator
vendored
2
3rdparty/MultiEmulator
vendored
Submodule 3rdparty/MultiEmulator updated: 11ff077ca5...e369abc23c
2
3rdparty/extras/wscript
vendored
2
3rdparty/extras/wscript
vendored
@@ -23,6 +23,6 @@ def build(bld):
|
||||
|
||||
bld(features='zip',
|
||||
name = 'extras.pk3',
|
||||
files = srcdir.ant_glob('**/*'),
|
||||
files = srcdir.ant_glob('**/*', excl=['scripts/**']),
|
||||
relative_to = srcdir,
|
||||
install_path = install_path)
|
||||
|
||||
2
3rdparty/extras/xash-extras
vendored
2
3rdparty/extras/xash-extras
vendored
Submodule 3rdparty/extras/xash-extras updated: 0c83a63d00...9f5b017985
2
3rdparty/gl-wes-v2
vendored
2
3rdparty/gl-wes-v2
vendored
Submodule 3rdparty/gl-wes-v2 updated: 5e5a7192e6...9c8ef67f97
2
3rdparty/gl4es/gl4es
vendored
2
3rdparty/gl4es/gl4es
vendored
Submodule 3rdparty/gl4es/gl4es updated: a744af14d4...1543ec5074
2
3rdparty/libbacktrace/libbacktrace
vendored
2
3rdparty/libbacktrace/libbacktrace
vendored
Submodule 3rdparty/libbacktrace/libbacktrace updated: 793921876c...b9e40069c0
27
3rdparty/libbacktrace/wscript
vendored
27
3rdparty/libbacktrace/wscript
vendored
@@ -20,10 +20,10 @@ int main (void) {
|
||||
}'''
|
||||
|
||||
FRAGMENT_GETPAGESIZE='''#include <unistd.h>
|
||||
int main(void) { return getpagesize() }'''
|
||||
int main(void) { return getpagesize(); }'''
|
||||
|
||||
FRAGMENT_GETEXECNAME='''#include <stdlib.h>
|
||||
int main(void) { return getexecname() != 0 }'''
|
||||
int main(void) { return getexecname() != 0; }'''
|
||||
|
||||
FRAGMENT_STRNLEN='''#include <string.h>
|
||||
int main(int argc, char **argv) { return (int)strnlen(argv[0], 10); }'''
|
||||
@@ -62,7 +62,7 @@ def options(opt):
|
||||
|
||||
def configure(conf):
|
||||
# add unsupported platforms here
|
||||
if conf.env.DEST_OS in ['nswitch', 'psvita', 'dos']:
|
||||
if conf.env.DEST_OS in ['nswitch', 'psvita', 'dos', 'irix']:
|
||||
conf.env.DISABLE_LIBBACKTRACE = True
|
||||
return
|
||||
|
||||
@@ -95,7 +95,7 @@ def configure(conf):
|
||||
return {'header_name':hdr, 'msg':'... %s header' % hdr, 'mandatory':False, 'id':hdr, 'compiler': 'c'}
|
||||
|
||||
def check_frag(frag, msg, define, **kw):
|
||||
return dict({'fragment': frag, 'msg':'... %s' % msg, 'mandatory': False, 'compiler': 'c'}, **kw)
|
||||
return dict({'fragment': frag, 'msg':'... %s' % msg, 'mandatory': False, 'define_name': define, 'compiler': 'c'}, **kw)
|
||||
|
||||
conf.multicheck(
|
||||
check_header('dlfcn.h'),
|
||||
@@ -120,8 +120,8 @@ def configure(conf):
|
||||
check_frag(FRAGMENT_SYNC, ' __sync extensions', 'HAVE_SYNC_FUNCTIONS'),
|
||||
check_frag(FRAGMENT_GETPAGESIZE, ' getpagesize function', 'HAVE_DECL_GETPAGESIZE'),
|
||||
check_frag(FRAGMENT_STRNLEN, ' strnlen function', 'HAVE_DECL_STRNLEN'),
|
||||
check_frag(FRAGMENT_DL_ITERATE_PHDR % 'link.h', ' dl_iterate_phdr function in link.h', 'HAVE_DL_ITERATE_PHDR', after_tests=['link.h']),
|
||||
check_frag(FRAGMENT_DL_ITERATE_PHDR % 'sys/link.h', ' dl_iterate_phdr function in sys/link.h', 'HAVE_DL_ITERATE_PHDR', after_tests=['sys/link.h']),
|
||||
check_frag(FRAGMENT_DL_ITERATE_PHDR % 'link.h', ' dl_iterate_phdr function in link.h', 'HAVE_DL_ITERATE_PHDR_LINK_H', after_tests=['link.h']),
|
||||
check_frag(FRAGMENT_DL_ITERATE_PHDR % 'sys/link.h', ' dl_iterate_phdr function in sys/link.h', 'HAVE_DL_ITERATE_PHDR_SYS_LINK_H', after_tests=['sys/link.h']),
|
||||
check_frag(FRAGMENT_FCNTL, 'fnctl function', 'HAVE_FCNTL'),
|
||||
check_frag(FRAGMENT_GETEXECNAME, 'getexecname function','HAVE_GETEXECNAME'),
|
||||
check_frag(FRAGMENT_GETIPINFO, '_Unwind_GetIPInfo function', 'HAVE_GETIPINFO'),
|
||||
@@ -137,6 +137,9 @@ def configure(conf):
|
||||
msg='Checking for in parallel'
|
||||
)
|
||||
|
||||
if conf.is_defined('HAVE_DL_ITERATE_PHDR_LINK_H') or conf.is_defined('HAVE_DL_ITERATE_PHDR_SYS_LINK_H'):
|
||||
conf.define('HAVE_DL_ITERATE_PHDR', 1)
|
||||
|
||||
conf.env[DEFKEYS].sort()
|
||||
|
||||
conf.write_config_header()
|
||||
@@ -162,8 +165,13 @@ def build(bld):
|
||||
if bld.env.DISABLE_LIBBACKTRACE:
|
||||
return
|
||||
|
||||
# we specifically only want mmap-based allocators because calling malloc is not safe from signal handlers
|
||||
sources = ['atomic.c', 'dwarf.c', 'fileline.c', 'posix.c', 'print.c', 'sort.c', 'state.c', 'backtrace.c', 'simple.c', 'mmap.c', 'mmapio.c']
|
||||
sources = ['atomic.c', 'dwarf.c', 'fileline.c', 'posix.c', 'print.c', 'sort.c', 'state.c', 'backtrace.c', 'simple.c']
|
||||
|
||||
if bld.env.DEST_OS == 'win32':
|
||||
sources += ['read.c', 'alloc.c']
|
||||
else:
|
||||
# we specifically only want mmap-based allocators because calling malloc is not safe from signal handlers
|
||||
sources += ['mmap.c', 'mmapio.c']
|
||||
|
||||
if bld.env.DEST_BINFMT == 'pe':
|
||||
sources += ['pecoff.c']
|
||||
@@ -176,7 +184,8 @@ def build(bld):
|
||||
|
||||
task = bld.stlib(
|
||||
source = ['libbacktrace/' + i for i in sources],
|
||||
target = 'backtrace',
|
||||
# while we're using upstream libbacktrace, sometimes compiler picks up it's own version that isn't built with required options
|
||||
target = 'backtrace_custom',
|
||||
features = 'frandomseed' if bld.env.HAVE_FRANDOM_SEED else '',
|
||||
use = 'EXTRAFLAGS lzma z zstd',
|
||||
includes = '. libbacktrace/',
|
||||
|
||||
1
3rdparty/library_suffix
vendored
Submodule
1
3rdparty/library_suffix
vendored
Submodule
Submodule 3rdparty/library_suffix added at a35ac9c12e
2
3rdparty/maintui
vendored
2
3rdparty/maintui
vendored
Submodule 3rdparty/maintui updated: eba8d49880...e5f143ee33
2
3rdparty/mainui
vendored
2
3rdparty/mainui
vendored
Submodule 3rdparty/mainui updated: b015e45062...493b08fb49
107
3rdparty/mbedtls/compat.c
vendored
Normal file
107
3rdparty/mbedtls/compat.c
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
compat.c - mbedTLS platform overrides for targets upstream doesn't cover
|
||||
Copyright (C) 2026 Xash3D FWGS contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
/*
|
||||
WinXP: legacy CryptGenRandom via advapi32
|
||||
Source: ttps://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptgenrandom
|
||||
|
||||
PSVita: sceKernelGetRandomNumber from psp2/kernel/rng.h (64-byte cap).
|
||||
Source: https://github.com/vitasdk/vita-headers/blob/master/include/psp2/kernel/rng.h
|
||||
|
||||
NSwitch: randomGet from libnx.
|
||||
Source: https://github.com/switchbrew/libnx/blob/master/nx/include/switch/kernel/random.h
|
||||
*/
|
||||
|
||||
#include "mbedtls/platform.h"
|
||||
#include "psa/crypto.h"
|
||||
|
||||
#if defined( MBEDTLS_PLATFORM_MS_TIME_ALT )
|
||||
|
||||
mbedtls_ms_time_t mbedtls_ms_time( void )
|
||||
{
|
||||
extern double Platform_DoubleTime( void );
|
||||
|
||||
return (mbedtls_ms_time_t)( Platform_DoubleTime() * 1000.0 );
|
||||
}
|
||||
|
||||
#endif /* MBEDTLS_PLATFORM_MS_TIME_ALT */
|
||||
|
||||
#if defined( MBEDTLS_PSA_DRIVER_GET_ENTROPY )
|
||||
#if defined( _WIN32 ) && !defined( _WIN64 )
|
||||
#include <windows.h>
|
||||
#include <wincrypt.h>
|
||||
int mbedtls_platform_get_entropy( psa_driver_get_entropy_flags_t flags, size_t *estimate_bits, unsigned char *output, size_t output_size )
|
||||
{
|
||||
if( flags != 0 )
|
||||
return PSA_ERROR_NOT_SUPPORTED;
|
||||
|
||||
HCRYPTPROV prov;
|
||||
if( !CryptAcquireContextW( &prov, NULL, NULL, PROV_RSA_FULL,
|
||||
CRYPT_VERIFYCONTEXT | CRYPT_SILENT ))
|
||||
return PSA_ERROR_INSUFFICIENT_ENTROPY;
|
||||
|
||||
if( !CryptGenRandom( prov, (DWORD)output_size, output ))
|
||||
{
|
||||
CryptReleaseContext( prov, 0 );
|
||||
return PSA_ERROR_INSUFFICIENT_ENTROPY;
|
||||
}
|
||||
|
||||
CryptReleaseContext( prov, 0 );
|
||||
*estimate_bits = 8 * output_size;
|
||||
return 0;
|
||||
}
|
||||
#elif defined( __vita__ )
|
||||
#include <psp2/kernel/rng.h>
|
||||
|
||||
int mbedtls_platform_get_entropy( psa_driver_get_entropy_flags_t flags, size_t *estimate_bits, unsigned char *output, size_t output_size )
|
||||
{
|
||||
if( flags != 0 )
|
||||
return PSA_ERROR_NOT_SUPPORTED;
|
||||
|
||||
size_t total = 0;
|
||||
while( total < output_size )
|
||||
{
|
||||
size_t chunk = output_size - total;
|
||||
|
||||
if( chunk > 64 )
|
||||
chunk = 64;
|
||||
|
||||
if( sceKernelGetRandomNumber( output + total, chunk ) != 0 )
|
||||
return PSA_ERROR_INSUFFICIENT_ENTROPY;
|
||||
|
||||
total += chunk;
|
||||
}
|
||||
|
||||
*estimate_bits = 8 * output_size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#elif defined( __SWITCH__ )
|
||||
#include <switch.h>
|
||||
|
||||
int mbedtls_platform_get_entropy( psa_driver_get_entropy_flags_t flags, size_t *estimate_bits, unsigned char *output, size_t output_size )
|
||||
{
|
||||
if( flags != 0 )
|
||||
return PSA_ERROR_NOT_SUPPORTED;
|
||||
|
||||
randomGet( output, output_size );
|
||||
*estimate_bits = 8 * output_size;
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
#error "MBEDTLS_PSA_DRIVER_GET_ENTROPY enabled but no platform impl in compat.c"
|
||||
#endif
|
||||
|
||||
#endif /* MBEDTLS_PSA_DRIVER_GET_ENTROPY */
|
||||
1
3rdparty/mbedtls/mbedtls
vendored
Submodule
1
3rdparty/mbedtls/mbedtls
vendored
Submodule
Submodule 3rdparty/mbedtls/mbedtls added at 701bb7cfde
67
3rdparty/mbedtls/wscript
vendored
Normal file
67
3rdparty/mbedtls/wscript
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
#! /usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
def options(opt):
|
||||
grp = opt.add_option_group('mbedTLS options')
|
||||
grp.add_option('--disable-mbedtls', action='store_false', dest='MBEDTLS', default=True,
|
||||
help='disable bundled mbedTLS and built-in HTTPS support [default: enabled]')
|
||||
|
||||
def configure(conf):
|
||||
conf.env.MBEDTLS = conf.options.MBEDTLS
|
||||
if not conf.env.MBEDTLS:
|
||||
return
|
||||
|
||||
if not conf.path.find_dir('mbedtls') or not conf.path.find_dir('mbedtls/library'):
|
||||
conf.fatal('Can\'t find mbedtls submodule. Run `git submodule update --init --recursive`.')
|
||||
|
||||
if not conf.path.find_dir('mbedtls/tf-psa-crypto/core'):
|
||||
conf.fatal('mbedTLS nested submodule tf-psa-crypto is missing. Run `git submodule update --init --recursive`.')
|
||||
|
||||
def build(bld):
|
||||
if not bld.env.MBEDTLS:
|
||||
return
|
||||
|
||||
sources = bld.path.ant_glob([
|
||||
'mbedtls/library/*.c',
|
||||
'mbedtls/tf-psa-crypto/core/*.c',
|
||||
'mbedtls/tf-psa-crypto/extras/*.c',
|
||||
'mbedtls/tf-psa-crypto/platform/*.c',
|
||||
'mbedtls/tf-psa-crypto/utilities/*.c',
|
||||
'mbedtls/tf-psa-crypto/drivers/builtin/src/*.c',
|
||||
])
|
||||
|
||||
sources += ['compat.c']
|
||||
|
||||
defines = [
|
||||
'MBEDTLS_USER_CONFIG_FILE="xash_mbedtls_config.h"',
|
||||
'TF_PSA_CRYPTO_USER_CONFIG_FILE="xash_psa_config.h"',
|
||||
]
|
||||
|
||||
includes = [
|
||||
'.',
|
||||
'mbedtls/include',
|
||||
'mbedtls/library',
|
||||
'mbedtls/tf-psa-crypto/include',
|
||||
'mbedtls/tf-psa-crypto/drivers/builtin/include',
|
||||
'mbedtls/tf-psa-crypto/core',
|
||||
'mbedtls/tf-psa-crypto/drivers/builtin/src',
|
||||
'mbedtls/tf-psa-crypto/utilities',
|
||||
'mbedtls/tf-psa-crypto/extras',
|
||||
'mbedtls/tf-psa-crypto/platform',
|
||||
'mbedtls/tf-psa-crypto/dispatch',
|
||||
]
|
||||
|
||||
bld.stlib(
|
||||
source = sources,
|
||||
target = 'mbedtls',
|
||||
features = 'c',
|
||||
includes = includes,
|
||||
defines = defines,
|
||||
export_defines = ['XASH_MBEDTLS'],
|
||||
export_includes = [
|
||||
'.',
|
||||
'mbedtls/include',
|
||||
'mbedtls/tf-psa-crypto/include',
|
||||
'mbedtls/tf-psa-crypto/drivers/builtin/include',
|
||||
]
|
||||
)
|
||||
36
3rdparty/mbedtls/xash_mbedtls_config.h
vendored
Normal file
36
3rdparty/mbedtls/xash_mbedtls_config.h
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
#ifndef XASH_MBEDTLS_CONFIG_H
|
||||
#define XASH_MBEDTLS_CONFIG_H
|
||||
|
||||
#undef MBEDTLS_NET_C
|
||||
#undef MBEDTLS_PKCS7_C
|
||||
#undef MBEDTLS_TIMING_C
|
||||
#undef MBEDTLS_SSL_SRV_C
|
||||
#undef MBEDTLS_SSL_PROTO_DTLS
|
||||
#undef MBEDTLS_SSL_DTLS_ANTI_REPLAY
|
||||
#undef MBEDTLS_SSL_DTLS_CONNECTION_ID
|
||||
#undef MBEDTLS_SSL_DTLS_HELLO_VERIFY
|
||||
#undef MBEDTLS_SSL_DTLS_SRTP
|
||||
#undef MBEDTLS_SSL_DTLS_CLIENT_PORT_REUSE
|
||||
#undef MBEDTLS_SSL_COOKIE_C
|
||||
#undef MBEDTLS_SSL_CACHE_C
|
||||
#undef MBEDTLS_SSL_TICKET_C
|
||||
#undef MBEDTLS_SSL_SESSION_TICKETS
|
||||
#undef MBEDTLS_SSL_RENEGOTIATION
|
||||
#undef MBEDTLS_SSL_CONTEXT_SERIALIZATION
|
||||
#undef MBEDTLS_SSL_KEYING_MATERIAL_EXPORT
|
||||
#undef MBEDTLS_SSL_MAX_FRAGMENT_LENGTH
|
||||
#undef MBEDTLS_SSL_ALL_ALERT_MESSAGES
|
||||
#undef MBEDTLS_SSL_ALPN
|
||||
#undef MBEDTLS_SSL_ENCRYPT_THEN_MAC
|
||||
#undef MBEDTLS_KEY_EXCHANGE_PSK_ENABLED
|
||||
#undef MBEDTLS_KEY_EXCHANGE_ECDHE_PSK_ENABLED
|
||||
#undef MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_PSK_ENABLED
|
||||
#undef MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_PSK_EPHEMERAL_ENABLED
|
||||
#undef MBEDTLS_X509_CREATE_C
|
||||
#undef MBEDTLS_X509_CRT_WRITE_C
|
||||
#undef MBEDTLS_X509_CSR_PARSE_C
|
||||
#undef MBEDTLS_X509_CSR_WRITE_C
|
||||
#undef MBEDTLS_X509_CRL_PARSE_C
|
||||
#undef MBEDTLS_VERSION_FEATURES
|
||||
|
||||
#endif /* XASH_MBEDTLS_CONFIG_H */
|
||||
82
3rdparty/mbedtls/xash_psa_config.h
vendored
Normal file
82
3rdparty/mbedtls/xash_psa_config.h
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
#ifndef XASH_PSA_CONFIG_H
|
||||
#define XASH_PSA_CONFIG_H
|
||||
|
||||
#if (defined( _WIN32 ) && !defined( _WIN64 )) || defined( __vita__ ) || defined( __SWITCH__ )
|
||||
/* WinXP, PSVita and NSwitch use entropy paths upstream doesn't cover.
|
||||
compat.c provides mbedtls_platform_get_entropy() for all three. */
|
||||
#undef MBEDTLS_PSA_BUILTIN_GET_ENTROPY
|
||||
#define MBEDTLS_PSA_DRIVER_GET_ENTROPY
|
||||
#endif
|
||||
|
||||
#if defined( __vita__ ) || defined( __SWITCH__ )
|
||||
/* Upstream has no Vita/NSW support; compat.c fills in */
|
||||
#define MBEDTLS_PLATFORM_MS_TIME_ALT
|
||||
#endif
|
||||
|
||||
#undef MBEDTLS_FS_IO
|
||||
#undef MBEDTLS_PSA_ITS_FILE_C
|
||||
#undef MBEDTLS_PSA_CRYPTO_STORAGE_C
|
||||
#undef MBEDTLS_SELF_TEST
|
||||
#undef MBEDTLS_HMAC_DRBG_C
|
||||
#undef MBEDTLS_LMS_C
|
||||
#undef MBEDTLS_NIST_KW_C
|
||||
#undef MBEDTLS_PKCS5_C
|
||||
|
||||
#undef PSA_WANT_ALG_CCM
|
||||
#undef PSA_WANT_ALG_CCM_STAR_NO_TAG
|
||||
#undef PSA_WANT_ALG_CBC_NO_PADDING
|
||||
#undef PSA_WANT_ALG_CBC_PKCS7
|
||||
#undef PSA_WANT_ALG_CFB
|
||||
#undef PSA_WANT_ALG_CMAC
|
||||
#undef PSA_WANT_ALG_CTR
|
||||
#undef PSA_WANT_ALG_ECB_NO_PADDING
|
||||
#undef PSA_WANT_ALG_OFB
|
||||
#undef PSA_WANT_ALG_STREAM_CIPHER
|
||||
#undef PSA_WANT_ALG_JPAKE
|
||||
#undef PSA_WANT_ALG_TLS12_ECJPAKE_TO_PMS
|
||||
#undef PSA_WANT_ALG_TLS12_PSK_TO_MS
|
||||
#undef PSA_WANT_ALG_FFDH
|
||||
#undef PSA_WANT_ALG_PBKDF2_HMAC
|
||||
#undef PSA_WANT_ALG_PBKDF2_AES_CMAC_PRF_128
|
||||
#undef PSA_WANT_KEY_TYPE_PASSWORD
|
||||
#undef PSA_WANT_KEY_TYPE_PASSWORD_HASH
|
||||
#undef PSA_WANT_ALG_SHA3_224
|
||||
#undef PSA_WANT_ALG_SHA3_256
|
||||
#undef PSA_WANT_ALG_SHA3_384
|
||||
#undef PSA_WANT_ALG_SHA3_512
|
||||
#undef PSA_WANT_ALG_SHAKE128
|
||||
#undef PSA_WANT_ALG_SHAKE256
|
||||
#undef PSA_WANT_ALG_SHA_224
|
||||
#undef PSA_WANT_ALG_SHA_1
|
||||
#undef PSA_WANT_ALG_MD5
|
||||
#undef PSA_WANT_ALG_RIPEMD160
|
||||
#undef PSA_WANT_KEY_TYPE_ARIA
|
||||
#undef PSA_WANT_KEY_TYPE_CAMELLIA
|
||||
#undef PSA_WANT_ECC_BRAINPOOL_P_R1_256
|
||||
#undef PSA_WANT_ECC_BRAINPOOL_P_R1_384
|
||||
#undef PSA_WANT_ECC_BRAINPOOL_P_R1_512
|
||||
#undef PSA_WANT_ECC_SECP_K1_256
|
||||
#undef PSA_WANT_ECC_MONTGOMERY_448
|
||||
#undef PSA_WANT_ECC_SECP_R1_521
|
||||
#undef PSA_WANT_DH_RFC7919_2048
|
||||
#undef PSA_WANT_DH_RFC7919_3072
|
||||
#undef PSA_WANT_DH_RFC7919_4096
|
||||
#undef PSA_WANT_DH_RFC7919_6144
|
||||
#undef PSA_WANT_DH_RFC7919_8192
|
||||
#undef PSA_WANT_KEY_TYPE_DH_KEY_PAIR_BASIC
|
||||
#undef PSA_WANT_KEY_TYPE_DH_KEY_PAIR_EXPORT
|
||||
#undef PSA_WANT_KEY_TYPE_DH_KEY_PAIR_GENERATE
|
||||
#undef PSA_WANT_KEY_TYPE_DH_KEY_PAIR_IMPORT
|
||||
#undef PSA_WANT_KEY_TYPE_DH_PUBLIC_KEY
|
||||
#undef PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_EXPORT
|
||||
#undef PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_IMPORT
|
||||
#undef PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_EXPORT
|
||||
#undef PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_GENERATE
|
||||
#undef PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_IMPORT
|
||||
#undef MBEDTLS_PEM_WRITE_C
|
||||
#undef MBEDTLS_PK_WRITE_C
|
||||
#undef MBEDTLS_PK_PARSE_EC_COMPRESSED
|
||||
#undef MBEDTLS_PK_PARSE_EC_EXTENDED
|
||||
#undef MBEDTLS_ASN1_WRITE_C
|
||||
|
||||
#endif /* XASH_PSA_CONFIG_H */
|
||||
2
3rdparty/nanogl
vendored
2
3rdparty/nanogl
vendored
Submodule 3rdparty/nanogl updated: c75f1326f9...6c8aeca5e6
2
3rdparty/vgui_support
vendored
2
3rdparty/vgui_support
vendored
Submodule 3rdparty/vgui_support updated: 9f2b9512cd...d2aaec107d
59
3rdparty/yy-thunks/wscript
vendored
Normal file
59
3rdparty/yy-thunks/wscript
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
#! /usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
import hashlib
|
||||
from waflib import Task, TaskGen, Logs
|
||||
|
||||
OBJ_NAME = 'YY_Thunks_for_WinXP.obj'
|
||||
OBJ_SHA256 = '51a13c4b667c82181d357e274a50308a273be63f5f85ae958465774857721573'
|
||||
DLL_LINKFLAGS = ['-ENTRY:DllMainCRTStartupForYY_Thunks', '-alternatename:_YY_ThunksOriginalDllMainCRTStartup@12=__DllMainCRTStartup@12']
|
||||
|
||||
class copy_yy_thunks(Task.Task):
|
||||
color = 'CYAN'
|
||||
def run(self):
|
||||
return self.outputs[0].write(self.inputs[0].read('rb'), 'wb')
|
||||
|
||||
@TaskGen.feature('yy_thunks')
|
||||
def add_yy_thunks_obj(self):
|
||||
src = self.path.find_node(OBJ_NAME)
|
||||
out = self.path.find_or_declare(OBJ_NAME)
|
||||
self.compiled_tasks = [self.create_task('copy_yy_thunks', src, out)]
|
||||
|
||||
@TaskGen.feature('cshlib', 'cxxshlib')
|
||||
@TaskGen.after_method('process_use')
|
||||
def apply_yy_thunks_dll(self):
|
||||
if 'yy_thunks' not in self.to_list(getattr(self, 'use', [])):
|
||||
return
|
||||
|
||||
try:
|
||||
self.bld.get_tgen_by_name('yy_thunks')
|
||||
except Exception:
|
||||
return
|
||||
self.env.append_value('LINKFLAGS', DLL_LINKFLAGS)
|
||||
|
||||
def options(opt):
|
||||
pass
|
||||
|
||||
def configure(conf):
|
||||
if conf.env.DEST_OS != 'win32' or conf.env.COMPILER_CC != 'msvc':
|
||||
return
|
||||
|
||||
if conf.env.MSVC_TARGETS[0] not in ['amd64_x86', 'x86']:
|
||||
return
|
||||
|
||||
obj = conf.path.find_node(OBJ_NAME)
|
||||
if not obj:
|
||||
Logs.warn('YY-Thunks: %s not found, XP compatibility disabled' % OBJ_NAME)
|
||||
return
|
||||
|
||||
with open(obj.abspath(), 'rb') as f:
|
||||
digest = hashlib.sha256(f.read()).hexdigest()
|
||||
|
||||
if digest != OBJ_SHA256:
|
||||
conf.fatal('%s checksum mismatch: expected %s, got %s' % (OBJ_NAME, OBJ_SHA256, digest))
|
||||
|
||||
conf.env.YY_THUNKS = True
|
||||
|
||||
def build(bld):
|
||||
if bld.env.YY_THUNKS:
|
||||
bld(name='yy_thunks', features='yy_thunks')
|
||||
@@ -29,10 +29,11 @@
|
||||
|
||||
* This project uses mixed Quake's and HLSDK's C/C++ code style convention.
|
||||
* In short:
|
||||
* Use spaces in parenthesis.
|
||||
* Use spaces in parenthesis, but not when two parentheses are consecutive.
|
||||
* Only tabs for indentation.
|
||||
* Any brace must have it's own line.
|
||||
* Short blocks, if statements and loops on single line are allowed.
|
||||
* Prefer generic utilities from libpublic over rolling your own.
|
||||
* Avoid magic numbers.
|
||||
* While macros are powerful, it's better to avoid overusing them.
|
||||
* If you unsure, try to mimic code style from anywhere else of engine source code.
|
||||
|
||||
@@ -22,7 +22,7 @@ The other yet unsupported configuration is the big endian.
|
||||
|
||||
2) Open `public/build.h` file. Add appropriate checks for your operating system and/or CPU architecture. It shouldn't be hard.
|
||||
|
||||
Also, you'll need to build [Half-Life SDK](https://github.com/FWGS/hlsdk-xash3d/). It has same `public/build.h` so reflect changes into it. Note that to be compatible with HLSDK proprietary license, it's relicensed as public domain under [Unlicense](https://unlicense.org).
|
||||
Also, you'll need to build [Half-Life SDK](https://github.com/FWGS/hlsdk-portable/). It has same `public/build.h` so reflect changes into it. Note that to be compatible with HLSDK proprietary license, it's relicensed as public domain under [Unlicense](https://unlicense.org).
|
||||
|
||||
We have a library naming scheme that allows us and game creators to distribute binaries for different platforms in one archive. Read `Documentation/extensions/library-naming.md` for more information.
|
||||
|
||||
103
Documentation/development/setting-up-qt-creator.md
Normal file
103
Documentation/development/setting-up-qt-creator.md
Normal file
@@ -0,0 +1,103 @@
|
||||
## Setting up Xash3D FWGS in Qt Creator
|
||||
|
||||
### Intro
|
||||
|
||||
Most versions of Qt Creator support using `compile_commands.json` as a project
|
||||
file and Waf supports generating this file in a compliant format. In this
|
||||
tutorial we will leverage that features.
|
||||
|
||||
The tutorial is written using Qt Creator 18.0, but I urge you to use whatever
|
||||
latest Qt Creator version is available, as it's often has important bug fixes.
|
||||
|
||||
### Step 1. Configure the build.
|
||||
|
||||
`compile_commands.json` file, as it can be guessed from it's name, contains
|
||||
every command used to invoke compiler during build. As the build process
|
||||
depends on the build configuration, we have to configure it first. I'm not
|
||||
going to get into details here, as basics of compiling Xash3D FWGS already
|
||||
explained in `README.md`, located in repository root.
|
||||
|
||||
Save the configure step command somewhere, as it's going to be used later.
|
||||
|
||||
### Step 2. Generate `compile_commands.json` file.
|
||||
|
||||
Usually, this file is located in `build` directory after a successful build.
|
||||
However, it's also possible to generate it manually by running follow command:
|
||||
|
||||
On *nix systems:
|
||||
|
||||
```
|
||||
$ ./waf clangdb
|
||||
```
|
||||
|
||||
Or on Windows:
|
||||
|
||||
```
|
||||
> waf.bat clangdb
|
||||
```
|
||||
|
||||
### Step 3. Load the project in Qt Creator.
|
||||
|
||||
To do this, you need to be sure that `Compilation Databases` plugin is enabled.
|
||||
As it's considered experimental, it probably needs to be enabled manually. It
|
||||
can be done in `Help` -> `About Plugins...`. Locate it in `Build Systems`
|
||||
category, put the checkbox and press `OK`. The IDE will ask for restart, so do
|
||||
this.
|
||||
|
||||
After IDE has been restarted, you can use `File` -> `Open File or Project...`.
|
||||
Navigate to Xash3D FWGS directory, then to `build` directory and select
|
||||
`compile_commands.json` file. If everything is done correctly, IDE will ask to
|
||||
set up the project. Press `Configure Project` button, as most options there are
|
||||
usually unapplicable to us.
|
||||
|
||||
Next IDE will show project tree, however you might not see any files. This is
|
||||
happens as Qt Creator "thinks" our project root located in `build` directory.
|
||||
Thankfully it provides a way to override this. To fix it, click with right
|
||||
mouse button on the root of project tree (it's usually called something like
|
||||
`build [master]`) and select `Change Root Directory`. In opened window choose
|
||||
Xash3D FWGS repository root and press `OK`.
|
||||
|
||||
### Step 4. Tell IDE how to build this project.
|
||||
|
||||
Open `Projects` on the left menu and choose `Build Settings` tab. You should
|
||||
see an empty configuration for build and clean steps. Press on `Add Build Step`
|
||||
button and choose `Custom Process Step`.
|
||||
|
||||
In created step you should see `Command`, `Arguments` and `Working Directory`.
|
||||
|
||||
For the command, choose the Python executable. For arguments, use configure
|
||||
command for Waf from the first step. Working directory is usually set by IDE
|
||||
to `%{buildDir}`. You need to select the upper directory, where the `waf` is
|
||||
located. To save some time, it can be specified as `%{buildDir}/../`.
|
||||
|
||||
Add another custom step, fill it with the same command and working directory.
|
||||
For arguments use `waf build`.
|
||||
|
||||
Below you should see `Clean steps`. Create a clean step the same way, fill the
|
||||
arguments and working directory as usual, for arguments use `waf clean`.
|
||||
|
||||
### Step 5. Tell IDE how to copy built binaries.
|
||||
|
||||
Now select `Deploy Settings` tab. Press `Add Deploy Step` -> `Custom Process
|
||||
Step`. Again, same command and working directory. In arguments field type:
|
||||
`waf install --destdir="<here put where Xash binaries should be copied to>"`.
|
||||
|
||||
### Step 6. Finally, set up Qt Creator to run the engine.
|
||||
|
||||
Select the `Run Settings` tab. Below you might see familiar text fields:
|
||||
`Executable`, `Command line arguments` and `Working directory`.
|
||||
|
||||
For working directory click on `Browse...` button and select the folder where
|
||||
Xash3D executable will be copied to, from previous step.
|
||||
|
||||
For executable type `./xash3d` if you're on *nix or `xash3d.exe` if you're on
|
||||
Windows. The command line arguments are the same you use to run the engine. Can
|
||||
be empty as well, but I personally use `-dev 2 -log` for extra verbosity.
|
||||
|
||||
### Outro
|
||||
|
||||
At this moment, you should be able to press `Run` button, wait until build
|
||||
finishes and see running Xash3D FWGS. On Windows to use the debugger you might
|
||||
need the `CDB` debugger and appropriate `qtcreatorcdbext` binary for debugging
|
||||
32-bit applications and a separate for 64-bit, but that's left as an exercise
|
||||
for the reader and a potential improvement for this document.
|
||||
@@ -26,3 +26,12 @@ Initial Xash3D SDL2/Linux port author, Xash3D FWGS engine maintainer, creator of
|
||||
|
||||
* [Boosty page](https://boosty.to/snmetamorph)
|
||||
* [Other donation methods](https://snmetamorph.github.io/donate)
|
||||
|
||||
## [$_Vladislav](https://github.com/Vladislav4KZ)
|
||||
|
||||
[YaPB Project](https://github.com/yapb) member, [Xash3D FWGS](https://github.com/FWGS/xash3d-fwgs) tester,
|
||||
editor of the [Official YaPB Documentation](https://github.com/yapb/docs) (in English and Russian), curator of the [YaPB Graph Database](https://github.com/yapb/graph) and author of [YaPB Waypoint/Graph Pack](https://gamebanana.com/mods/40087).
|
||||
|
||||
Also does the Russian localization of text, images (gfx/shell), as well as additional menu buttons in English used in Xash3D FWGS for various mods.
|
||||
|
||||
* [Boosty page](https://boosty.to/rasstaman1337)
|
||||
16
Documentation/extensions/addon-folders.md
Normal file
16
Documentation/extensions/addon-folders.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# Addon folders in Xash3D FWGS
|
||||
|
||||
Xash3D FWGS supports both GoldSource-style addon folders and has few own. Each directory can have it's own archives that will be mounted with lower priority than directory itself.
|
||||
|
||||
Below is the mounts map, in order of precedence from least important to most important.
|
||||
|
||||
|--------------------|------|
|
||||
| Directory | Note |
|
||||
|--------------------|------|
|
||||
| `$game/downloaded` | Always added. Used to store server downloads.|
|
||||
| `$game` | This is the game directory. |
|
||||
| `$game/custom` | Always added. Used for user modifications content. |
|
||||
| `$game_hd` | Added with `fs_mount_hd` set to non-zero value. Used for high definition content, similar to GoldSrc.. |
|
||||
| `$game_addon` | Added with `fs_mount_addon` set to non-zero value. Used for user modifications content, similar to GoldSrc. |
|
||||
| `$game_lv` | Added with `fs_mount_lv` set to non-zero value. Used for low-violence content, similar to GoldSrc. |
|
||||
| `$game_$language` | Added with `fs_mount_l10n` set to non-zero value. Language is controlled with `ui_language` cvar or `-language` command line switch. Used for localization content, similar to GoldSrc. |
|
||||
19
Documentation/extensions/litwater.md
Normal file
19
Documentation/extensions/litwater.md
Normal file
@@ -0,0 +1,19 @@
|
||||
## Lightmapped water
|
||||
|
||||
Xash3D FWGS supports lightmapped water, as an extension. It adds three new cvars and new worldspawn key values.
|
||||
|
||||
### For level designers:
|
||||
|
||||
If you're a level designer and intend to make your level to have lightmapped water, you can put these keyvalues to worldspawn entity description (always first entity in entities list):
|
||||
|
||||
| Key | Value | Description |
|
||||
| ---------------------- | ------- | ----------- |
|
||||
| `_litwater` | integer | Set to any non-zero value to enable lightmapped water. Overrides `gl_litwater_force` cvar value. |
|
||||
| `_litwater_minlight` | integer | Minimal lightmap value water surface will receive. Helps to avoid too dark areas when water isn't properly lit. If not set, defaults to zero. |
|
||||
| `_litwater_scale` | float | Scales up lightmap value for water surfaces. If not set, defaults to 1.0. |
|
||||
|
||||
### For players:
|
||||
|
||||
Some of the maps already have computed lightmap for water surfaces and sometimes water has been properly lit but the support hasn't been declared by the level designer.
|
||||
|
||||
As a player, you can enable it in `Video options` menu or through console with `gl_litwater_force` cvar. There are also `gl_litwater_minlight` and `gl_litwater_scale` cvars that function similar to keys above. The default values has been set to `192` and `1.25` respectively to slightly avoid issues with maps that wasn't intended to have lightmapped water.
|
||||
@@ -16,6 +16,7 @@ Only these objects are guaranteed to be available on all targets.
|
||||
|-------------|-----------|
|
||||
| `VFileSystem009` | Provides C++ interface to filesystem, binary-compatible with Valve's VFileSystem009. |
|
||||
| `XashFileSystemXXX` | Provides C interface to filesystem. This interface is unstable and not recommended for generic use, outside of engine internals. For more info about current version look into `filesystem.h`. |
|
||||
| `MenuFactory` | Returns a `CreateInterface` function pointer (`pfnCreateInterface_t`) for the currently loaded menu library. |
|
||||
|
||||
#### Android-specific objects
|
||||
|
||||
|
||||
139
Documentation/extensions/steam-broker.md
Normal file
139
Documentation/extensions/steam-broker.md
Normal file
@@ -0,0 +1,139 @@
|
||||
# Steam API Broker Protocol Specification
|
||||
|
||||
## Overview
|
||||
|
||||
Due to proprietary nature of Steamworks SDK, it cannot be run on same amount
|
||||
of platforms supported by Xash3D FWGS, neither we can link library directly due to
|
||||
GNU GPLv3 license.
|
||||
|
||||
However, here comes the broker, by running it (in trusted
|
||||
network, preferrably) on a machine that has Steam client installed, the
|
||||
engine can communicate with it, acquiring needed information to log-in into
|
||||
Steam protected multiplayer servers.
|
||||
|
||||
## Key Characteristics
|
||||
|
||||
Broker uses a simple TCP-based binary protocol with the following properties:
|
||||
|
||||
- Single active session per connection: once a session is activated, the broker will reject all other TCP connections until the session is terminated.
|
||||
- All messages follow a consistent structure with a header, length, and payload: this is called a "frame".
|
||||
- Stateful interactions: commands affect the session state, and certain commands are only valid in specific states.
|
||||
- Numeric parameters are encoded in little-endian format for compatibility with majority of platforms.
|
||||
---
|
||||
|
||||
## Frame Format
|
||||
|
||||
All communication uses a fixed frame structure:
|
||||
|
||||
| Field | Size | Type | Description |
|
||||
|-------|------|------|-------------|
|
||||
| Header | 4 bytes | ASCII | Frame start signature |
|
||||
| Length | 2 bytes | uint16_t (LE) | Size of payload in bytes |
|
||||
| Payload | N bytes | Binary | Command string or response data |
|
||||
|
||||
**Notes**:
|
||||
- Header must be exactly `SBRK` (`53 42 52 4B` in hexadecimal form)
|
||||
- Maximum frame size: soft limit is 4096 bytes, hard limit is 65535 bytes (due to uint16_t usage)
|
||||
|
||||
---
|
||||
|
||||
## Session Lifecycle
|
||||
|
||||
```
|
||||
┌──────────────┐
|
||||
│ IDLE │ ← Initial state, accepts new TCP connections
|
||||
└──────────────┘
|
||||
│ sb_gamedir
|
||||
↓
|
||||
┌──────────────┐
|
||||
│ ACTIVE │ ← Session established, rejecting all of other TCP connections
|
||||
└──────────────┘
|
||||
│ sb_connect
|
||||
↓
|
||||
┌──────────────┐
|
||||
│ ACTIVE │ ← Client getting auth ticket to connect to game server
|
||||
└──────────────┘
|
||||
│ sb_disconnect
|
||||
↓
|
||||
┌──────────────┐
|
||||
│ ... │ ← Client announcing disconnect from game server, could connect somewhere again
|
||||
└──────────────┘
|
||||
│
|
||||
↓
|
||||
┌──────────────┐
|
||||
│ ACTIVE │ ← Client shutting down game, terminating broker to reset Steam client state
|
||||
└──────────────┘
|
||||
│ sb_terminate
|
||||
↓
|
||||
┌──────────────┐
|
||||
│ RESTARTING │
|
||||
└──────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Commands
|
||||
|
||||
All commands listed below are meant to be sent from client to broker as payload of a frame. The broker will parse the command string and respond accordingly.
|
||||
|
||||
### Session activation
|
||||
|
||||
Activates the session and signaling game startup.
|
||||
|
||||
**Format**: `sb_gamedir <gamedir>`
|
||||
|
||||
**Response**: None
|
||||
|
||||
---
|
||||
|
||||
### Authentication ticket request
|
||||
|
||||
Requests authentication ticket for a game server.
|
||||
|
||||
**Precondition**: Session must be active
|
||||
|
||||
**Format**: `sb_connect <ip:port> <server_steamid> <secure> <challenge>`
|
||||
|
||||
**Parameters**:
|
||||
|
||||
| Name | Description |
|
||||
|------|------------------|
|
||||
| `<ip:port>` | Game server address (e.g., `127.0.0.1:27015`) |
|
||||
| `<server_steamid>` | Server SteamID |
|
||||
| `<secure>` | 0 = insecure, 1 = secure |
|
||||
| `<challenge>` | Random value for reply prevention |
|
||||
|
||||
|
||||
**Response Format**:
|
||||
|
||||
| Field | Size | Type | Description |
|
||||
|-------|------|------|-------------|
|
||||
| Header | 11 bytes | string | `"sb_connect\n"` |
|
||||
| Challenge | 4 bytes | int32_t (LE) | Echo of challenge from request |
|
||||
| SteamID | 8 bytes | uint64_t (LE) | Client SteamID from configuration |
|
||||
| Size | 4 bytes | uint32_t (LE) | Length of ticket in bytes |
|
||||
| Ticket | N bytes | - | Authentication ticket data |
|
||||
|
||||
---
|
||||
|
||||
### Server disconnection announcement
|
||||
|
||||
Notifies broker of disconnection from game server.
|
||||
|
||||
**Precondition**: Session must be active
|
||||
|
||||
**Format**: `sb_disconnect <ip:port> <challenge>`
|
||||
|
||||
**Response**: None
|
||||
|
||||
---
|
||||
|
||||
### Session termination
|
||||
|
||||
Signaling game shutdown to broker and resetting Steam client state.
|
||||
|
||||
**Format**: `sb_terminate`
|
||||
|
||||
**Response**: None
|
||||
|
||||
---
|
||||
181
Documentation/extensions/xrcon-protocol.md
Normal file
181
Documentation/extensions/xrcon-protocol.md
Normal file
@@ -0,0 +1,181 @@
|
||||
# XRCON Protocol Specification
|
||||
|
||||
## Overview
|
||||
|
||||
XRCON is a TCP-based remote console protocol implemented as a server within the engine. It provides an alternative to the legacy RCON (which operates over UDP as out-of-band packets). XRCON uses a framed binary protocol over a single persistent TCP connection, accepting at most one client at a time.
|
||||
|
||||
Key characteristics:
|
||||
|
||||
- Based on TCP (stream-oriented, reliable)
|
||||
- Default port is 27000
|
||||
- Maximum only one concurrent client
|
||||
- Binary framed protocol with a 12-byte header + variable-length payload
|
||||
|
||||
|
||||
|
||||
## Configuration
|
||||
|
||||
The XRCON server is controlled by two configuration variables, both restricted to privileged users:
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `xrcon_enable` | `0` (disabled) | Master switch; when enabled, the server starts listening for connections |
|
||||
| `xrcon_address` | `127.0.0.1:27000` | Bind address and port; supports both IPv4 and IPv6. Changing this at runtime triggers a XRCON server restart (stop + rebind) |
|
||||
|
||||
By default, XRCON binds only to localhost (`127.0.0.1`), providing a degree of access control. The server will not start listening until explicitly enabled.
|
||||
|
||||
|
||||
|
||||
## Frame Format
|
||||
|
||||
All XRCON messages are encapsulated in a framed binary format. Each frame consists of a fixed 12-byte header followed by a variable-length payload.
|
||||
|
||||
### Header (12 bytes)
|
||||
|
||||
| Offset | Size | Field | Description |
|
||||
|---|---|---|---|
|
||||
| 0 | 4 | Type | 4-character message type string (null-terminated, occupies 5 bytes in the structure but the 5th byte is padding) |
|
||||
| 4 | 4 | Version | Protocol version (32-bit unsigned integer, network byte order) |
|
||||
| 8 | 2 | Length | Total frame length in bytes, including the header (16-bit unsigned integer, network byte order) |
|
||||
| 10 | 2 | Handle | Handle or sequence number (16-bit unsigned integer, network byte order; currently always zero) |
|
||||
|
||||
|
||||
### Protocol Version
|
||||
|
||||
The current protocol version is `0x000000D4` (212 decimal). The server also accepts `0x00D40000` for compatibility with certain third-party clients (e.g., CS2RemoteConsole).
|
||||
|
||||
### Maximum Frame Size
|
||||
|
||||
- Maximum payload per frame: 4096 bytes
|
||||
- Maximum total packet size (header + payload): approximately 4136 bytes
|
||||
- Receive buffer size: 4096 + 64 bytes
|
||||
- Transmit buffer size: 16384 bytes (4 × maximum frame size)
|
||||
|
||||
### Byte Order
|
||||
|
||||
Multi-byte integer fields (version, length, handle) are transmitted in **network byte order** (big-endian).
|
||||
|
||||
|
||||
|
||||
## Message Types
|
||||
|
||||
| Type | Direction | Description |
|
||||
|---|---|---|
|
||||
| `CMND` | Client → Server | Execute a console command |
|
||||
| `PRNT` | Server → Client | Console output / print message |
|
||||
| `CHAN` | Server → Client | Channel list (channel metadata) |
|
||||
| `AINF` | Server → Client | Application / server info |
|
||||
| `ADON` | Server → Client | Additional info / server name |
|
||||
|
||||
### CMND — Client Command
|
||||
|
||||
**Direction**: Client → Server
|
||||
|
||||
Sent by the client to execute a console command on the server.
|
||||
|
||||
| Section | Size | Description |
|
||||
|---|---|---|
|
||||
| Header | 12 bytes | Type = `"CMND"`, version, length, handle |
|
||||
| Payload | Variable | Raw command string (null-terminated) |
|
||||
|
||||
The command string is injected into the engine's command buffer for execution. There is **no authentication** — any connected client may execute arbitrary commands.
|
||||
|
||||
### PRNT — Server Print
|
||||
|
||||
**Direction**: Server → Client
|
||||
|
||||
Sent by the server to stream console output to the connected client.
|
||||
|
||||
| Section | Size | Description |
|
||||
|---|---|---|
|
||||
| Header | 12 bytes | Type = `"PRNT"`, version, length, handle |
|
||||
| Channel ID | 4 bytes | Channel identifier (unsigned 32-bit integer; always 0 for "Console") |
|
||||
| Padding | 20 bytes | Reserved (5 × 32-bit unsigned integers, all zero) |
|
||||
| Red | 1 byte | Red color component (always 255) |
|
||||
| Green | 1 byte | Green color component (always 255) |
|
||||
| Blue | 1 byte | Blue color component (always 255) |
|
||||
| Alpha | 1 byte | Alpha / opacity component (always 255) |
|
||||
| Text | Variable | The console output text (up to 4096 bytes) |
|
||||
|
||||
|
||||
### CHAN — Channel List
|
||||
|
||||
**Direction**: Server → Client
|
||||
|
||||
Sent by the server during the initial handshake (and potentially later) to describe the available console output channels.
|
||||
|
||||
| Section | Size | Description |
|
||||
|---|---|---|
|
||||
| Header | 12 bytes | Type = `"CHAN"`, version, length, handle |
|
||||
| Channel Count | 2 bytes | Number of channel records (unsigned 16-bit integer; always 1) |
|
||||
|
||||
Each channel record (58 bytes):
|
||||
|
||||
| Field | Size | Description |
|
||||
|---|---|---|
|
||||
| Channel ID | 4 bytes | Unique channel identifier (unsigned 32-bit integer; always 0) |
|
||||
| Unknown 1 | 4 bytes | Reserved (unsigned 32-bit integer; always 0) |
|
||||
| Unknown 2 | 4 bytes | Reserved (unsigned 32-bit integer; always 0) |
|
||||
| Default Verbosity | 4 bytes | Default verbosity level (unsigned 32-bit integer; always 5) |
|
||||
| Current Verbosity | 4 bytes | Current verbosity level (unsigned 32-bit integer; always 5) |
|
||||
| Red | 1 byte | Red color component for the channel (always 255) |
|
||||
| Green | 1 byte | Green color component (always 255) |
|
||||
| Blue | 1 byte | Blue color component (always 255) |
|
||||
| Alpha | 1 byte | Alpha component (always 255) |
|
||||
| Name | 34 bytes | Channel name string, null-padded (always `"Console"`) |
|
||||
|
||||
Total payload size for this frame: 2 + 58 = 60 bytes.
|
||||
|
||||
### AINF — Application Info
|
||||
|
||||
**Direction**: Server → Client
|
||||
|
||||
Sent by the server immediately after a client connects, before `ADON` and `CHAN`.
|
||||
|
||||
| Section | Size | Description |
|
||||
|---|---|---|
|
||||
| Header | 12 bytes | Type = `"AINF"`, version, length, handle |
|
||||
| Payload | 77 bytes | All zeros (placeholder / reserved for structured application information) |
|
||||
|
||||
This packet currently serves as a placeholder and contains no meaningful data. It may be extended in future versions to carry structured metadata about the server application.
|
||||
|
||||
### ADON — Additional Info
|
||||
|
||||
**Direction**: Server → Client
|
||||
|
||||
Sent by the server immediately after `AINF` during the connection handshake.
|
||||
|
||||
| Section | Size | Description |
|
||||
|---|---|---|
|
||||
| Header | 12 bytes | Type = `"ADON"`, version, length, handle |
|
||||
| Unknown | 2 bytes | Reserved (unsigned 16-bit integer; always 0) |
|
||||
| Name Length | 2 bytes | Length of the name string (unsigned 16-bit integer) |
|
||||
| Name | Variable | Server name / identifier string (e.g., `"HLDS"`) |
|
||||
|
||||
This packet carries the server application name, which is a short string identifying the server type (e.g., `"HLDS"` for Half-Life Dedicated Server).
|
||||
|
||||
|
||||
|
||||
## Security
|
||||
|
||||
XRCON has **no built-in authentication or encryption**. There is no password validation or transport-layer security. Any client that can establish a TCP connection to the XRCON port has full command execution access to the engine console.
|
||||
|
||||
The only security measures are:
|
||||
|
||||
1. **Default localhost binding**: The server binds to `127.0.0.1` by default, accepting connections only from the local machine.
|
||||
2. **Disabled by default**: The `xrcon_enable` variable defaults to `0`, so the server does not listen unless explicitly enabled.
|
||||
3. **Privileged variables**: Both configuration variables are restricted to privileged users and cannot be modified through unprivileged console commands or external access.
|
||||
|
||||
|
||||
|
||||
## Comparison with Legacy RCON
|
||||
|
||||
| Feature | XRCON | Legacy RCON |
|
||||
|---|---|---|
|
||||
| Transport | TCP (stream) | UDP (datagram, out-of-band) |
|
||||
| Port | Configurable, default 27000 | Game port (default 27015) |
|
||||
| Framing | Binary frame header | Plain text over UDP |
|
||||
| Authentication | None | Password-based |
|
||||
| Max clients | 1 | Multiple |
|
||||
| Interaction format | Full console access | Request-response only for submitted command |
|
||||
| Supported environments | Client and server | Server only |
|
||||
@@ -1,15 +1,18 @@
|
||||
# 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.
|
||||
Without this requirement, you will just get "Steam validation rejected" error on connecting.
|
||||
You need to use [Steam API broker](https://github.com/FWGS/steam-broker) and have purchased Half-Life 1 on your Steam account to be able to join GoldSrc servers.
|
||||
|
||||
That is because proper authorization with Steam API is not implemented in engine yet (but we have plans on it).
|
||||
Broker will run on your PC and will be responsible for communicating with Steam client and obtaining auth tickets.
|
||||
Also, make sure to set that broker as current ticket generator using console or edit `config.cfg` file manually:
|
||||
```
|
||||
cl_ticket_generator steam
|
||||
```
|
||||
|
||||
Also, we encountered that some GoldSrc-based servers are recognizing Xash3D clients as "fake clients" and banning/kicking them. Maybe this problem will be
|
||||
If you want to join GoldSrc servers from other device (Android/iOS or console), make sure to set proper broker IP address with `cl_steam_broker_addr` console variable. Default value assumed that broker is running on the same device as game client.
|
||||
|
||||
By the way, 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.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
ios_lib.h - dynamic library code for iOS
|
||||
Copyright (C) 2017-2018 mittorn
|
||||
filename.ext - do not include this file, only copy the header
|
||||
Copyright (C) 2015-2025 Xash3D FWGS contributors
|
||||
|
||||
This program is free software: you can redistribute it and/sor modify
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
@@ -12,14 +12,4 @@ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
*/
|
||||
#pragma once
|
||||
#if TARGET_OS_IPHONE
|
||||
#ifndef IOS_LIB_H
|
||||
#define IOS_LIB_H
|
||||
|
||||
#define Platform_POSIX_LoadLibrary( x ) IOS_LoadLibrary(( x ))
|
||||
|
||||
void *IOS_LoadLibrary( const char *dllname );
|
||||
|
||||
#endif // IOS_LIB_H
|
||||
#endif // TARGET_OS_IPHONE
|
||||
@@ -4,7 +4,7 @@
|
||||
|---- |------- |--------------- |----------------------
|
||||
|Area 51 |Update 1 |Uses outdated BSP31 map format and custom HLFX SDK libraries. |You can try [this tool](https://hlfx.ru/forum/showthread.php?threadid=5250) to convert maps but there no warranty if it works.
|
||||
|Arrange Mod: Rebirth |v150 |No idea yet. |
|
||||
|Blue Shift |The latest steam release |Uses vgui2 library which xash3d does not support. |Recreated source code here: https://github.com/FWGS/hlsdk-xash3d/tree/bshift.
|
||||
|Blue Shift |The latest steam release |Uses vgui2 library which xash3d does not support. |Recreated source code here: https://github.com/FWGS/hlsdk-portable/tree/bshift.
|
||||
|Counter Strike |Beta 6.5- |Uses an old WON HL 1.0.0.16- interface. |
|
||||
| |1.4 |Has encrypted blob instead of normal client.dll. |You can try [this tool](https://aluigi.altervista.org/papers/hldlldec.zip) to decrypt client.dll but there no warranty if it works.
|
||||
| |1.5 |Has encrypted blob instead of normal client.dll. |Decrypted blob here: https://csm.dev/threads/cs-1-5-client-dll-decrypted-patched-for-usage.38845.
|
||||
@@ -15,6 +15,6 @@
|
||||
|Half-Life: Extended |Day One demo |Uses many hooks to GoldSource engine and version check. |Just wait new version or use more old version.
|
||||
|Icon of Hell |Beta 0.99 |Uses outdated BSP31 map format and paranoia 2 libraries. |You can try [this tool](https://hlfx.ru/forum/showthread.php?threadid=5250) to convert maps and use Paranoia 2: The Savior 1.51 libraries but there no warranty if it works.
|
||||
|Paranoia 2: The Savior |All builds older 1.51 |Uses an old renderer interface and engine features. |
|
||||
|Rebellion |1.0 |Uses an old WON HL 1.0.0.16- interface. |Recreated source code here: https://github.com/FWGS/hlsdk-xash3d/tree/rebellion.
|
||||
|Rebellion |1.0 |Uses an old WON HL 1.0.0.16- interface. |Recreated source code here: https://github.com/FWGS/hlsdk-portable/tree/rebellion.
|
||||
|Sven-Coop |5.0+ |Uses custom GoldSrc engine. |
|
||||
|Time Shadows |Beta 0.1 |Uses Direct3D renderer. |
|
||||
|
||||
@@ -16,6 +16,9 @@ Mirrored on github - https://github.com/JoelTroch/am_src_rebirth
|
||||
## Battle Grounds
|
||||
Mirrored on github - https://github.com/nekonomicon/BattleGrounds
|
||||
|
||||
## Brain Bread
|
||||
Official github repository - https://github.com/IronOak-Studios/BrainBread
|
||||
|
||||
## Bubblemod
|
||||
Download page on official site(WM snapshot) - [http://www.bubblemod.org/dl_default.php](https://web.archive.org/web/20130717133158/http://www.bubblemod.org/dl_default.php)
|
||||
|
||||
@@ -50,6 +53,9 @@ Alpha 2.0 - https://www.gamers-desire.de/details/2830
|
||||
## ESHQ
|
||||
Official github repository - https://github.com/adslbarxatov/xash3d-for-ESHQ
|
||||
|
||||
## Feline
|
||||
Available on ModDB - https://www.moddb.com/mods/feline-10/downloads/feline-sdk
|
||||
|
||||
## Flat-Life
|
||||
Available on GamerLab - http://gamer-lab.com/eng/code_mods_goldsrc/Half-Life_2D_(Flat-Life)
|
||||
|
||||
@@ -64,6 +70,9 @@ Version 3.0, mirrored on github - https://github.com/nekonomicon/Go-mod30
|
||||
## GT mod
|
||||
Available on GamerLab - http://gamer-lab.com/eng/code_mods_goldsrc/GT_mod_(Polnie_ishodniki)
|
||||
|
||||
## Half-Force
|
||||
Available on ModDB - https://www.moddb.com/mods/half-force/downloads/half-force-v20-sdk
|
||||
|
||||
## Half-Life: Advanced Deathmatch
|
||||
Mirrored on github - https://github.com/solidi/hl-mods/tree/master/hla
|
||||
|
||||
@@ -76,6 +85,9 @@ Available on ModDB - https://www.moddb.com/mods/half-life-echoes
|
||||
## Half-Life: Expanded Arsenal
|
||||
Available on ModDB - https://www.moddb.com/mods/half-life-expanded-arsenal
|
||||
|
||||
## Half-Life: Field Intensity
|
||||
Branch **field_intensity_1.7** in halflife-featureful - https://github.com/FreeSlave/halflife-featureful/tree/field_intensity_1.7
|
||||
|
||||
## Half-Life: Gravgun mod
|
||||
Branch **gravgun** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/gravgun
|
||||
|
||||
@@ -97,6 +109,9 @@ Official github repository - https://github.com/Fograin/hl-subsmod-ex
|
||||
## Half-Life: Rally
|
||||
Official gitlab repository - https://gitlab.com/hlrally/src
|
||||
|
||||
## Half-Life: RXT
|
||||
Available on ModDB - https://www.moddb.com/mods/half-life-rxt/downloads/rxt-source-code-v57
|
||||
|
||||
## Half-Life: Weapon Edition
|
||||
Available on ModDB - https://www.moddb.com/mods/half-life-weapon-edition/downloads/half-life-weapon-edition-1508-alpha-open-src
|
||||
|
||||
@@ -118,12 +133,17 @@ Official github repository - https://github.com/HeathGames/half_rats_parasomnia_
|
||||
## Half-Screwed
|
||||
Official github repository - https://github.com/desukuran/half-screwed
|
||||
|
||||
## Halo: GoldSource
|
||||
Available on ModDB - https://www.moddb.com/mods/halo-goldsrc/downloads/halo-gold-source-summer-2025-release-v10-sdk
|
||||
|
||||
## Headcrab Frenzy
|
||||
Version 1.3 on ModDB - https://www.moddb.com/mods/headcrab-frenzy/downloads/headcrab-frenzy-13-beta-source-code
|
||||
|
||||
## Heart of Evil
|
||||
Mirrored on github - https://github.com/nekonomicon/HeartOfEvil
|
||||
|
||||
Mirrored on github (Includes maps and models source files) - https://github.com/malortie/heart-of-evil-source-files
|
||||
|
||||
## Ingram Chillin' Mod
|
||||
Official SourceForge repository - https://sourceforge.net/projects/icm-hl/
|
||||
|
||||
@@ -155,6 +175,9 @@ Prealpha, mirrored on github - https://github.com/a1batross/Paranoia2_ancient
|
||||
|
||||
Version 1.51, mirrored on github - https://github.com/a1batross/Paranoia2_original
|
||||
|
||||
## Public Enemy
|
||||
Official github repository - https://github.com/IronOak-Studios/Public-Enemy
|
||||
|
||||
## Raven City
|
||||
*Unfinished mod*
|
||||
|
||||
@@ -186,6 +209,9 @@ Mirrored on code.idtech.space - https://code.idtech.space/vera/halflife-wastelan
|
||||
## Wizard Wars
|
||||
Download page on official site - http://www.thothie.com/ww/
|
||||
|
||||
## Wrong Door
|
||||
Available on ModDB - https://www.moddb.com/mods/wrong-door/downloads/wrong-door-sdk
|
||||
|
||||
## XashXT
|
||||
Mirrored on github - https://github.com/a1batross/XashXT_original
|
||||
|
||||
@@ -251,6 +277,9 @@ Reverse-engineered code of server part by s1lentq - https://github.com/s1lentq/R
|
||||
## Counter Strike Online
|
||||
CSO-like Xash3D-based mod, CSMoE - https://github.com/MoeMod/CSMoE
|
||||
|
||||
## Crack-Life
|
||||
Reverse-engineered code: branch **cracklife** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/cracklife
|
||||
|
||||
## Crack-Life: Campaign Mode
|
||||
Recreation by lostgamer aka nillerusr - https://github.com/LostGamerHL/crack_life
|
||||
|
||||
@@ -353,6 +382,9 @@ Deathmatch Quaked - https://www.moddb.com/games/deathmatch-classic/downloads/dmq
|
||||
|
||||
Branch **dmc** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/dmc
|
||||
|
||||
## Delta Particles
|
||||
Branch **delta_particles** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/delta_particles
|
||||
|
||||
## Half-Life: Echoes
|
||||
Branch **echoes** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/echoes
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ Table is sorted by status and platform.
|
||||
| PSVita | Supported | @fgsfdsfgs |
|
||||
| Switch | Supported | @fgsfdsfgs |
|
||||
| Windows | Supported | @a1batross, @SNMetamorph |
|
||||
| iOS | Supported | @ksagameng2 |
|
||||
| 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
|
||||
@@ -35,4 +36,3 @@ Table is sorted by status and platform.
|
||||
| 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
|
||||
|
||||
11
Documentation/protocol/01-introduction.md
Normal file
11
Documentation/protocol/01-introduction.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# Xash3D FWGS protocol support
|
||||
|
||||
Xash3D FWGS currently supports four network protocols:
|
||||
* Version 49, introduced in Xash3D in around 2018.
|
||||
* Version 48, used in Xash3D FWGS 0.19 and earlier. Deprecated and to be removed in the future.
|
||||
* GoldSrc version 48, used in current GoldSource version.
|
||||
* Quake version 15, only used for demo playback for Quake Wrapper mod.
|
||||
|
||||
On the server side, we only support Xash3D 49 protocol, but bugcomp `gsmrf` mode can convert GoldSrc 48 messages into Xash3D 49 on the fly, for some mods that directly write engine internal messages.
|
||||
|
||||
In the following documents we will only cover version 49, as everything else is either not Xash3D specific or deprecated.
|
||||
305
Documentation/protocol/02-connectionless.md
Normal file
305
Documentation/protocol/02-connectionless.md
Normal file
@@ -0,0 +1,305 @@
|
||||
# Xash3D 49 connectionless protocol
|
||||
|
||||
Connectionless protocol defines four common message sources and destinations, they can be:
|
||||
* `S` for Server
|
||||
* `C` for Client
|
||||
* `M` for Master
|
||||
* `A` for Anything else
|
||||
|
||||
All connectionless packets have `\xff\xff\xff\xff` (32-bit integer set to all ones) in it's header. Let's go through each of possible direction in this document.
|
||||
|
||||
<!--TOC-->
|
||||
|
||||
- [Any](#any)
|
||||
- [Any to Any](#any-to-any)
|
||||
- [`A2A_NETINFO`](#a2a_netinfo)
|
||||
- [`A2A_INFO`](#a2a_info)
|
||||
- [`A2A_PING` and `A2A_GOLDSRC_PING`](#a2a_ping-and-a2a_goldsrc_ping)
|
||||
- [`A2A_ACK` and `A2A_GOLDSRC_ACK`](#a2a_ack-and-a2a_goldsrc_ack)
|
||||
- [Any to Client](#any-to-client)
|
||||
- [`A2C_PRINT` and `A2C_GOLDSRC_PRINT`](#a2c_print-and-a2c_goldsrc_print)
|
||||
- [Any to Server and Server to Any](#any-to-server-and-server-to-any)
|
||||
- [`A2S_GOLDSRC_INFO`, `A2S_GOLDSRC_RULES` and `A2S_GOLDSRC_PLAYERS` and their responses](#a2s_goldsrc_info-a2s_goldsrc_rules-and-a2s_goldsrc_players-and-their-responses)
|
||||
- [Any to Master](#any-to-master)
|
||||
- [`S2M_SCAN_REQUEST`](#s2m_scan_request)
|
||||
- [Master to Any](#master-to-any)
|
||||
- [`M2A_SERVERSLIST`](#m2a_serverslist)
|
||||
- [Master and game server](#master-and-game-server)
|
||||
- [Server to Master](#server-to-master)
|
||||
- [`S2M_HEARTBEAT`](#s2m_heartbeat)
|
||||
- [`S2M_SHUTDOWN`](#s2m_shutdown)
|
||||
- [`S2M_INFO`](#s2m_info)
|
||||
- [Master to Server](#master-to-server)
|
||||
- [`M2S_CHALLENGE`](#m2s_challenge)
|
||||
- [`M2S_NAT_CONNECT`](#m2s_nat_connect)
|
||||
- [Client and game server](#client-and-game-server)
|
||||
- [Client to Server](#client-to-server)
|
||||
- [`C2S_BANDWIDTHTEST`](#c2s_bandwidthtest)
|
||||
- [`C2S_GETCHALLENGE`](#c2s_getchallenge)
|
||||
- [`C2S_CONNECT`](#c2s_connect)
|
||||
- [Server to Client](#server-to-client)
|
||||
- [`S2C_BANDWIDTHTEST`](#s2c_bandwidthtest)
|
||||
- [`S2C_CHALLENGE`](#s2c_challenge)
|
||||
- [`S2C_CONNECTION`](#s2c_connection)
|
||||
- [`S2C_ERRORMSG`](#s2c_errormsg)
|
||||
- [`S2C_REJECT`](#s2c_reject)
|
||||
|
||||
<!--TOC-->
|
||||
|
||||
## Any
|
||||
|
||||
### Any to Any
|
||||
|
||||
#### `A2A_NETINFO`
|
||||
|
||||
Used to implement Half-Life's NetAPI.
|
||||
* Request message, in ASCII: `netinfo <version> <context> <request_id>`
|
||||
- `version` must be 49, as text.
|
||||
- `context` can be any 32-bit signed integer value, encoded as text.
|
||||
- `request_id` see `common/net_api.h`
|
||||
|
||||
* Response message, in ASCII: `netinfo <context> <request_id> <response>`
|
||||
- `context` same as in request.
|
||||
- `request_id` same as in request.
|
||||
- `response` is server's response, depending on request type. Always encoded as Quake info string.
|
||||
|
||||
Possible requests and response formats:
|
||||
* Request ID `1` will make server respond with empty response string.
|
||||
* Request ID `2` makes server respond with all server game rule cvars where the key is cvar name and the value is cvar value. One additional key `rules` with total amount of cvars is added.
|
||||
* Request ID `3` makes server respond with players list, where:
|
||||
- `p<id>name` is set to player's name
|
||||
- `p<id>frags` is set to player's kill count
|
||||
- `p<id>time` is set to player's total play time
|
||||
- `players` is total player's list.
|
||||
- Alternatively, it can respond with `neterror` set to `forbidden` if server does not wish to expose it's player list.
|
||||
* Request ID `4` makes server to respond with common game details:
|
||||
- `hostname` is server's name
|
||||
- `gamedir` is game directory name
|
||||
- `current` is total player count
|
||||
- `max` is max players limit
|
||||
- `map` is server's current level
|
||||
* On any other server will respond with `neterror` set to `undefined`
|
||||
* If protocol version doesn't match, server responds with `neterror` set to `protocol`
|
||||
|
||||
#### `A2A_INFO`
|
||||
|
||||
Used to request server's details.
|
||||
* Request message, in ASCII: `info <version>`
|
||||
- `version` must be 49, as text
|
||||
* Response message, in ASCII: `info\n<response>`
|
||||
- `response` is Quake info string, otherwise it's an error message.
|
||||
|
||||
* Response info string format:
|
||||
- `p` is set to protocol version (FWGS extension)
|
||||
- `map` is server's current level
|
||||
- `dm` set to 1 if current game mode is deathmatch otherwise 0
|
||||
- `team` set to 1 if current game mode includes teamplay otherwise 0
|
||||
- `coop` set to 1 if current game mode is cooperative otherwise 0
|
||||
- `numcl` is total player count
|
||||
- `maxcl` is max players limit
|
||||
- `gamedir` is game directory name
|
||||
- `password` set to 1 if server is protected with password otherwise 0
|
||||
- `host` is server's name
|
||||
|
||||
#### `A2A_PING` and `A2A_GOLDSRC_PING`
|
||||
|
||||
Simple ping message.
|
||||
* Request, in ASCII: `ping` or `i` in GoldSrc format
|
||||
* Response is always `A2A_ACK` or `A2A_GOLDSRC_ACK`
|
||||
|
||||
#### `A2A_ACK` and `A2A_GOLDSRC_ACK`
|
||||
|
||||
Simple ack message.
|
||||
* Request, in ASCII: `ack` or `j` in GoldSrc format
|
||||
* Response: none
|
||||
|
||||
### Any to Client
|
||||
|
||||
#### `A2C_PRINT` and `A2C_GOLDSRC_PRINT`
|
||||
|
||||
Simple print message.
|
||||
* Header: `\xff\xff\xff\xff`
|
||||
* Request, in ASCII: `print <message>` or `l<message>` in GoldSrc format.
|
||||
* Response: none
|
||||
|
||||
### Any to Server and Server to Any
|
||||
|
||||
#### `A2S_GOLDSRC_INFO`, `A2S_GOLDSRC_RULES` and `A2S_GOLDSRC_PLAYERS` and their responses
|
||||
|
||||
These match Source Engine Query messages used in GoldSrc and Source engine and implemented for compatibility with existing tools. Read documentation for them on VDC: https://developer.valvesoftware.com/wiki/Server_queries
|
||||
|
||||
### Any to Master
|
||||
|
||||
#### `S2M_SCAN_REQUEST`
|
||||
|
||||
* Request: `1<region><IP:Port>\0<info>`
|
||||
- Format of this message is loosely based on https://developer.valvesoftware.com/wiki/Master_Server_Query_Protocol
|
||||
- `info` however adds few additional fields:
|
||||
- `clver` set to engine's version
|
||||
- `nat` set to 1 to filter only servers behind NAT and tell master to notify servers about scan
|
||||
- `commit` set to engine's build commit hash
|
||||
- `branch` set to engine's build branch
|
||||
- `os` set to which operating system engine has been built
|
||||
- `arch` set to which CPU architecture engine has been built
|
||||
- `buildnum` set to engine's build number
|
||||
- `key` set to random 32-bit value formatted as hex, used to validate master responses
|
||||
* Response: `M2A_SERVERSLIST`
|
||||
|
||||
### Master to Any
|
||||
|
||||
#### `M2A_SERVERSLIST`
|
||||
|
||||
* Request: `f\xff<key><reserved><IP:Port>...`
|
||||
- `key` is random 32-bit value set in `S2M_SCAN_REQUEST`
|
||||
- `reserved` is single reserved 8-bit byte
|
||||
- Following is list of IP addresses in binary form: 6-bytes for IPv4 address + port and 18-bytes for IPv6 address + port
|
||||
- If port is 0, it means the end of list, otherwise it's the last IP used for pagination (not implemented in Xash3D)
|
||||
* Response: client doesn't make response to master server but might request `A2S_INFO` from servers
|
||||
|
||||
## Master and game server
|
||||
|
||||
### Server to Master
|
||||
|
||||
#### `S2M_HEARTBEAT`
|
||||
|
||||
Used to update game server information on the master server.
|
||||
|
||||
* Request: `q\xff<heartbeat challenge>`
|
||||
- `heartbeat challenge` is random 32-bit value, used to prevent faked/forged source IP addresses.
|
||||
* Response: `M2S_CHALLENGE`
|
||||
|
||||
#### `S2M_SHUTDOWN`
|
||||
|
||||
Used to notify master server about server shutdown, but due to security reasons must be ignored by any master implementation.
|
||||
|
||||
* Request: `\x62\x0a`
|
||||
* Response: none
|
||||
|
||||
#### `S2M_INFO`
|
||||
|
||||
Game server info response on `M2S_CHALLENGE`.
|
||||
|
||||
* Request: `0\n<info>`
|
||||
- `info` is Quake info string containing server information passed to master server. It contains following fields:
|
||||
- `protocol` is always 49
|
||||
- `challenge` is master challenge
|
||||
- `players` is total player count, without bots
|
||||
- `max` is max players limits
|
||||
- `bots` is total bot count
|
||||
- `gamedir` is set to game directory
|
||||
- `map` is server's current level
|
||||
- `type` set to `d` for dedicated or `l` for listen server
|
||||
- `password` set to `1` if server protected with password set otherwise `0`
|
||||
- `os` always `w`
|
||||
- `secure` always `0`
|
||||
- `lan` always `0`
|
||||
- `version` engine version
|
||||
- `region` always `255`
|
||||
- `product` same as `gamedir`
|
||||
- `nat` set to `1` if server is behind NAT
|
||||
* Response: none
|
||||
|
||||
### Master to Server
|
||||
|
||||
#### `M2S_CHALLENGE`
|
||||
|
||||
Master's respoonse on game server's heartbeat message.
|
||||
|
||||
* Request: `s<master challenge><heartbeat challenge>`
|
||||
- `master challenge` contains 32-bit value, used in server response to prevent faked/forged source IP addresses.
|
||||
- `heartbat challenge` cotnains 32-bit value, used in heartbeat request to prevent faked/forged source IP addresses.
|
||||
* Response: `S2M_INFO`.
|
||||
|
||||
#### `M2S_NAT_CONNECT`
|
||||
|
||||
Master server's message with client IP address and port, used in NAT punching.
|
||||
|
||||
* Request: `c <IP:Port>`
|
||||
* Response: `S2C_INFO` to a specified client address.
|
||||
|
||||
## Client and game server
|
||||
|
||||
### Client to Server
|
||||
|
||||
#### `C2S_BANDWIDTHTEST`
|
||||
|
||||
Used to figure out network MTU. The message is optional and server might choose to not implement it or respond with challenge.
|
||||
|
||||
* Request, in ASCII: `bandwidth <version> <max_size>`
|
||||
- `version` must be 49, as text.
|
||||
- `max_size` is requested maximum packet size.
|
||||
|
||||
* Possible responses:
|
||||
- `S2C_BANDWIDTHTEST`
|
||||
- `S2C_CHALLENGE`
|
||||
- `A2A_PRINT` followed by `S2C_REJECT`
|
||||
- `S2C_ERRORMSG` (as FWGS extension) followed by `A2A_PRINT` and `S2C_REJECT`.
|
||||
|
||||
#### `C2S_GETCHALLENGE`
|
||||
|
||||
Used to validate client's address to prevent faked/forged source IP addresses.
|
||||
|
||||
* Header: `\xff\xff\xff\xff`
|
||||
* Request, in ASCII: `getchallenge steam`
|
||||
- `steam` argument is optional and can be ignored by the server, as it's only kept for compatibility with similar GoldSrc 48 message.
|
||||
|
||||
* Possible responses:
|
||||
- `S2C_CHALLENGE`
|
||||
|
||||
#### `C2S_CONNECT`
|
||||
|
||||
Used to build a connection with the server.
|
||||
|
||||
* Header: `\xff\xff\xff\xff`
|
||||
* Request, in ASCII: `connect <version> <challenge> "<protinfo>" "<userinfo>"`
|
||||
- `version` must be 49, as text.
|
||||
- `challenge` must be challenge value, as text.
|
||||
- `protinfo` is a Quake info string, containing protocol extensions and other connection information.
|
||||
- `uuid` is this client's unique ID (FWGS extension) hasheed with MD5.
|
||||
- `qport` is random integer value from 1 to 65535, unique for this engine run.
|
||||
- `ext` is an integer value OR'ed with requested protocol extensions. Currently only extension is `NET_EXT_SPLITSIZE` with the value of `1`, which tells server to split messages based on `cl_dlmax` value from `userinfo`.
|
||||
- `userinfo` contains initial user info, encoded as Quake info string.
|
||||
* Possible responses:
|
||||
- `A2A_PRINT` followed by `S2C_REJECT`
|
||||
- `S2C_ERRORMSG` (as FWGS extension) followed by `A2A_PRINT` and `S2C_REJECT`.
|
||||
- `S2C_CONNECTION`
|
||||
|
||||
### Server to Client
|
||||
|
||||
#### `S2C_BANDWIDTHTEST`
|
||||
|
||||
* Request: `testpacket<crc><blob>`
|
||||
- `crc` is 32-bit CRC of `blob`
|
||||
- `blob` random data
|
||||
* There is no required response to this message
|
||||
|
||||
Total message size doesn't exceed request max size.
|
||||
|
||||
#### `S2C_CHALLENGE`
|
||||
|
||||
* Request, in ASCII: `challenge <value>`
|
||||
- `value` is challenge value that client must include in it's response
|
||||
* Response: `C2S_CONNECT`
|
||||
|
||||
#### `S2C_CONNECTION`
|
||||
|
||||
* Request, in ASCII: `client_connect <protinfo>`
|
||||
- `protinfo` is an optional Quake info string, contains following optional fields:
|
||||
- `ext` is an integer value OR'ed with allowed protocol extension. Bit fields match requested in `ext` field in `C2S_CONNECT`
|
||||
- `cheats` set to 1 if server allows cheats otherwise 0
|
||||
* Response: there is no out of band from the client but client will proceed to building netchan and switch client-server interaction to it.
|
||||
|
||||
#### `S2C_ERRORMSG`
|
||||
|
||||
Show client error message. Doesn't mean connection reject, only used for UI.
|
||||
|
||||
* Request, in ASCII: `errormsg <message>`
|
||||
- `message` contains error message
|
||||
* Response: none
|
||||
|
||||
#### `S2C_REJECT`
|
||||
|
||||
Client has been rejected in connection.
|
||||
|
||||
* Request, in ASCII: `disconnect`
|
||||
* Response: client can cope, get depressed or mentally tell server to fuck off.
|
||||
3
Documentation/protocol/03-netchan.md
Normal file
3
Documentation/protocol/03-netchan.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Xash3D 49 netchan protocol
|
||||
|
||||
TBD...
|
||||
72
Documentation/protocol/http-server-list.md
Normal file
72
Documentation/protocol/http-server-list.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# Static HTTP server list
|
||||
|
||||
Replacement for the UDP `S2M_SCAN_REQUEST` / `M2A_SERVERSLIST` exchange
|
||||
described in [02-connectionless.md](02-connectionless.md). Read-only HTTP,
|
||||
no NAT punching, no filtering or pagination in the request.
|
||||
|
||||
## URL
|
||||
|
||||
`xashcomm.lst` carries a base URL per source:
|
||||
|
||||
```
|
||||
masterstatic http://master.example.org/server-list
|
||||
```
|
||||
|
||||
The engine appends `/v1/servers/<gamedir>` and `GET`s the result. For
|
||||
`gamedir = valve`:
|
||||
|
||||
```
|
||||
GET http://master.example.org/server-list/v1/servers/valve
|
||||
```
|
||||
|
||||
Trailing slashes on the base URL are stripped. Multiple `masterstatic`
|
||||
lines are allowed; results are merged.
|
||||
|
||||
The request is bare: no body, no auth, no cookies, no compressed encodings.
|
||||
Only the standard `User-Agent` is set. `POST` / `PUT` / `DELETE` are not
|
||||
used; servers register out of band.
|
||||
|
||||
## Response
|
||||
|
||||
UTF-8 text, parsed line by line. Each line is tokenized with
|
||||
`COM_ParseFileSafe` (whitespace separates, `//` and `#` start line
|
||||
comments, `"..."` quotes a token). Blank lines and comment-only lines
|
||||
are ignored. One directive per line:
|
||||
|
||||
* `ip <address>` — Xash3D server (protocol 49).
|
||||
* `gs <address>` — GoldSrc server (protocol 48).
|
||||
|
||||
`<address>` is parsed by `NET_StringToAdr` (`1.2.3.4:27015`,
|
||||
`[2001:db8::1]:27015`, hostnames). Port defaults to `27015`. Lines
|
||||
starting with an unknown directive are skipped entirely, so new keywords
|
||||
with any number of operands can be added without breaking older clients.
|
||||
|
||||
`Content-Type` is not inspected, `text/plain; charset=utf-8` expected.
|
||||
|
||||
### Example
|
||||
|
||||
```
|
||||
# diffusion servers
|
||||
ip 192.0.2.10:27015
|
||||
ip [2001:db8::1]:27015
|
||||
gs 198.51.100.5:27015
|
||||
```
|
||||
|
||||
A file with zero records is valid and represents an empty list.
|
||||
|
||||
### Versioning
|
||||
|
||||
The `/v1/` segment is fixed in this revision. A future protocol revision
|
||||
adds a sibling `/v2/...` resource without breaking older clients.
|
||||
|
||||
## Client behaviour
|
||||
|
||||
Each `ip` / `gs` record from the response triggers a probe to the listed
|
||||
address, identical to a server discovered through the UDP master. DNS
|
||||
errors, non-200 responses, and malformed bodies are reported to the
|
||||
console.
|
||||
|
||||
## Server behaviour
|
||||
|
||||
Any static HTTP server works. A typical setup is a periodic job that
|
||||
probes a set of known addresses and writes `v1/servers/<gamedir>`.
|
||||
@@ -962,53 +962,57 @@ Client: https://github.com/Velaron/cs16-client/
|
||||
|
||||
Server: https://github.com/rehlds/ReGameDLL_CS
|
||||
|
||||
15. **[Crack-Life: Campaign Mode](https://www.moddb.com/mods/crack-life)**
|
||||
15. **[Crack-Life](https://www.moddb.com/mods/crack-life/downloads/crack-life)**
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **cracklife** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
16. **[Crack-Life: Campaign Mode](https://www.moddb.com/mods/crack-life/downloads/crack-life-campaign-mode)**
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **clcampaign** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
16. **[Deathmatch Classic](https://store.steampowered.com/app/40/Deathmatch_Classic/)**
|
||||
17. **[Deathmatch Classic](https://store.steampowered.com/app/40/Deathmatch_Classic/)**
|
||||
|
||||
Steam version already supports x86 Linux.
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **dmc** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
17. **[Delta Particles](https://www.moddb.com/mods/half-life-delta)**
|
||||
18. **[Delta Particles](https://www.moddb.com/mods/half-life-delta)**
|
||||
|
||||
This mod already supports x86 Linux.
|
||||
|
||||
Support for other platforms may be available later.
|
||||
|
||||
18. **[Escape from the Darkness](https://www.moddb.com/mods/escape-from-the-darkness)**
|
||||
19. **[Escape from the Darkness](https://www.moddb.com/mods/escape-from-the-darkness)**
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **eftd** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
19. **[Gang Wars](https://www.moddb.com/mods/gangwars)**
|
||||
20. **[Gang Wars](https://www.moddb.com/mods/gangwars)**
|
||||
|
||||
This mod already supports x86 Linux.
|
||||
|
||||
Support for other platforms may be available later.
|
||||
|
||||
20. **[Half-Life: Blue Shift](https://store.steampowered.com/app/130/HalfLife_Blue_Shift/)**
|
||||
21. **[Half-Life: Blue Shift](https://store.steampowered.com/app/130/HalfLife_Blue_Shift/)**
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **bshift** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
21. **[Half-Life: Decay](https://www.moddb.com/mods/half-life-decay)**
|
||||
22. **[Half-Life: Decay](https://www.moddb.com/mods/half-life-decay)**
|
||||
|
||||
Currently, you can compile mod libraries for x86 Linux from **decay-pc** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
Support for other platforms may be available later.
|
||||
|
||||
22. **[Half-Life: Echoes](https://www.moddb.com/mods/half-life-echoes)**
|
||||
23. **[Half-Life: Echoes](https://www.moddb.com/mods/half-life-echoes)**
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **echoes** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
23. **[Half-Life: Field Intensity](https://www.moddb.com/mods/field-intensity)**
|
||||
24. **[Half-Life: Field Intensity](https://www.moddb.com/mods/field-intensity)**
|
||||
|
||||
This mod already supports x86 Linux.
|
||||
|
||||
Support for other platforms may be available later.
|
||||
|
||||
24. **Half-Life: Gravgun** (unfinished) by *mittorn*, *a1batross* and *Solexid*
|
||||
25. **Half-Life: Gravgun** (unfinished) by *mittorn*, *a1batross* and *Solexid*
|
||||
|
||||
*Support temporary abandoned*
|
||||
|
||||
@@ -1018,163 +1022,163 @@ Currently, MSVC is not supported, use MinGW if you need build for Windows.
|
||||
|
||||
Mod files: [1](http://mittorn.fwgs.ru/coop-entpatches/), [2](http://mittorn.fwgs.ru/ggm/), [3](https://github.com/nillerusr/gravgun-extras).
|
||||
|
||||
25. **[Half-Life: Induction 1.2](https://www.moddb.com/mods/half-life-induction)**
|
||||
26. **[Half-Life: Induction 1.2](https://www.moddb.com/mods/half-life-induction)**
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **induction_1.2** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
26. **[Half-Life: Invasion](https://www.moddb.com/mods/half-life-invasion)**
|
||||
27. **[Half-Life: Invasion](https://www.moddb.com/mods/half-life-invasion)**
|
||||
|
||||
This mod supports x86 Linux.
|
||||
|
||||
Support for other platforms may be available later.
|
||||
|
||||
27. **[Half-Life: Intense Force](https://www.moddb.com/downloads/intense-force)**
|
||||
28. **[Half-Life: Intense Force](https://www.moddb.com/downloads/intense-force)**
|
||||
|
||||
This mod already supports x86 Linux.
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **intense_force** branch of [halflife-featureful](https://github.com/FreeSlave/halflife-featureful).
|
||||
|
||||
28. **[Half-Life: Opposing Force](https://store.steampowered.com/app/50/HalfLife_Opposing_Force/)**
|
||||
29. **[Half-Life: Opposing Force](https://store.steampowered.com/app/50/HalfLife_Opposing_Force/)**
|
||||
|
||||
Steam version already supports x86 Linux.
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **opfor** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
29. **[Half-Life: Quest mod](https://csm.dev/threads/half-life-the-quest-mod-isxodniki.38030/)**
|
||||
30. **[Half-Life: Quest mod](https://csm.dev/threads/half-life-the-quest-mod-isxodniki.38030/)**
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **sci** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
30. **[Half-Life: TopDown](https://www.moddb.com/mods/half-life-top-downs)**
|
||||
31. **[Half-Life: TopDown](https://www.moddb.com/mods/half-life-top-downs)**
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **topdown** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
31. **[Half-Life: Urbicide](https://www.moddb.com/mods/half-life-urbicide)**
|
||||
32. **[Half-Life: Urbicide](https://www.moddb.com/mods/half-life-urbicide)**
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **hl_urbicide** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
32. **[Half-Life: Visitors](https://www.moddb.com/mods/half-life-visitors)**
|
||||
33. **[Half-Life: Visitors](https://www.moddb.com/mods/half-life-visitors)**
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **visitors** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
33. **[Half-Rats: Parasomnia](https://www.moddb.com/mods/half-rats-parasomnia/downloads/half-rats-parasomnia-v10b-steamlinux)**
|
||||
34. **[Half-Rats: Parasomnia](https://www.moddb.com/mods/half-rats-parasomnia/downloads/half-rats-parasomnia-v10b-steamlinux)**
|
||||
|
||||
This mod already supports x86 Linux.
|
||||
|
||||
Support for other platforms may be available later.
|
||||
|
||||
34. **[Half-Screwed: Death and Rebirth](https://www.moddb.com/mods/half-screwed)**
|
||||
35. **[Half-Screwed: Death and Rebirth](https://www.moddb.com/mods/half-screwed)**
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **half-screwed** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
35. **[Half-Secret](https://www.moddb.com/mods/half-secret)**
|
||||
36. **[Half-Secret](https://www.moddb.com/mods/half-secret)**
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **half-secret** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
36. **[Natural Selection](https://www.moddb.com/mods/natural-selection)**
|
||||
37. **[Natural Selection](https://www.moddb.com/mods/natural-selection)**
|
||||
|
||||
This mod already supports x86 Linux.
|
||||
|
||||
Support for other platforms may be available later.
|
||||
|
||||
37. **[Night at the Office](https://www.moddb.com/mods/night-at-the-office)**
|
||||
38. **[Night at the Office](https://www.moddb.com/mods/night-at-the-office)**
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **noffice** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
38. **[Paranoia](https://www.moddb.com/mods/paranoia)**
|
||||
39. **[Paranoia](https://www.moddb.com/mods/paranoia)**
|
||||
|
||||
*Support temporary abandoned*
|
||||
|
||||
[Source code](https://github.com/FWGS/paranoia_toolkit).
|
||||
|
||||
39. **[Poke646](https://poke646.com/)**
|
||||
40. **[Poke646](https://poke646.com/)**
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **poke646** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
40. **[Poke646: Vendetta](https://poke646.com/)**
|
||||
41. **[Poke646: Vendetta](https://poke646.com/)**
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **poke646_vendetta** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
41. [PrimeXT](https://snmetamorph.github.io/PrimeXT/)
|
||||
42. [PrimeXT](https://snmetamorph.github.io/PrimeXT/)
|
||||
|
||||
[Source code](https://github.com/SNMetamorph/PrimeXT)
|
||||
|
||||
42. **[Quake Remake](https://www.moddb.com/games/quake-remake)**
|
||||
43. **[Quake Remake](https://www.moddb.com/games/quake-remake)**
|
||||
|
||||
*Support was fully abandoned due Quake Wrapper release.*
|
||||
|
||||
[Actual source code](https://github.com/FWGS/quakeremake)
|
||||
|
||||
43. **[Rebellion](https://www.moddb.com/mods/rebellion1)**
|
||||
44. **[Rebellion](https://www.moddb.com/mods/rebellion1)**
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **rebellion** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
44. **[Residual Life](https://www.moddb.com/mods/hl-residual-life)**
|
||||
45. **[Residual Life](https://www.moddb.com/mods/hl-residual-life)**
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **residual_point** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
45. **[Residual Point](https://www.moddb.com/mods/hl-residual-point)**
|
||||
46. **[Residual Point](https://www.moddb.com/mods/hl-residual-point)**
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **residual_point** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
46. **[Ricochet](https://store.steampowered.com/app/60/Ricochet/)**
|
||||
47. **[Ricochet](https://store.steampowered.com/app/60/Ricochet/)**
|
||||
|
||||
This mod already supports x86 Linux.
|
||||
|
||||
Support for other platforms may be available later.
|
||||
|
||||
47. [Sewer](https://www.moddb.com/games/half-life/addons/sewer-beta)
|
||||
48. [Sewer](https://www.moddb.com/games/half-life/addons/sewer-beta)
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **sewer_beta** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
48. [Spirit of Half-Life 1.2](https://www.moddb.com/mods/spirit-of-half-life)
|
||||
49. [Spirit of Half-Life 1.2](https://www.moddb.com/mods/spirit-of-half-life)
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **sohl1.2** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
49. **[Swiss Cheese Halloween](https://www.moddb.com/mods/half-life-halloween-mod)**
|
||||
50. **[Swiss Cheese Halloween](https://www.moddb.com/mods/half-life-halloween-mod)**
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **halloween** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
50. **[Team Fortress Classic](https://store.steampowered.com/app/20/Team_Fortress_Classic/)**
|
||||
51. **[Team Fortress Classic](https://store.steampowered.com/app/20/Team_Fortress_Classic/)**
|
||||
|
||||
This mod already supports x86 Linux.
|
||||
|
||||
Currently, only source code of [client part](https://github.com/Velaron/tf15-client) is available.
|
||||
|
||||
51. **[The Gate](https://www.moddb.com/mods/the-gate)**
|
||||
52. **[The Gate](https://www.moddb.com/mods/the-gate)**
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **thegate** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
52. **[They Hunger: Trilogy](https://www.moddb.com/mods/they-hunger)**
|
||||
53. **[They Hunger: Trilogy](https://www.moddb.com/mods/they-hunger)**
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **theyhunger** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
53. **[ThreeWave CTF](https://www.moddb.com/games/deathmatch-classic/downloads/threewave-ctf)**
|
||||
54. **[ThreeWave CTF](https://www.moddb.com/games/deathmatch-classic/downloads/threewave-ctf)**
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **dmc** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
54. **[Times of Troubles](https://www.moddb.com/mods/times-of-troubles)**
|
||||
55. **[Times of Troubles](https://www.moddb.com/mods/times-of-troubles)**
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **tot** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
55. [XashXT](https://csm.dev/threads/xashxt-0-65-rev-4-release-stable.38319/)
|
||||
56. [XashXT](https://csm.dev/threads/xashxt-0-65-rev-4-release-stable.38319/)
|
||||
|
||||
*Support was fully abandoned due PrimeXT development*
|
||||
|
||||
[Source code](https://github.com/FWGS/XashXT-FWGS/)
|
||||
|
||||
56. **[X-Half-Life DeathMatch](https://www.moddb.com/mods/xdm)**
|
||||
57. **[X-Half-Life DeathMatch](https://www.moddb.com/mods/xdm)**
|
||||
|
||||
This mod already supports x86 Linux.
|
||||
|
||||
Support for other platforms may be available later.
|
||||
|
||||
57. **[Xen-Warrior](https://www.moddb.com/mods/xen-warrior)**
|
||||
58. **[Xen-Warrior](https://www.moddb.com/mods/xen-warrior)**
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **sohl1.2** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
Don't forget to enable XENWARRIOR build option!!!
|
||||
|
||||
58. [Zombie-X](https://www.moddb.com/mods/zombie-x-10-final)
|
||||
59. [Zombie-X](https://www.moddb.com/mods/zombie-x-10-final)
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **zombie-x** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
|
||||
42
README.md
42
README.md
@@ -1,6 +1,9 @@
|
||||
> [!CAUTION]
|
||||
> **Only download Xash3D FWGS from official sources.** Third-party builds, "modded launchers", "optimized" repacks and random mirrors are frequently bundled with malware, miners, spyware and credential stealers. We cannot vouch for anything we did not build. Get official binaries only from the [releases page](https://github.com/FWGS/xash3d-fwgs/releases/tag/continuous).
|
||||
|
||||
# Xash3D FWGS Engine <img align="right" width="128" height="128" src="https://github.com/FWGS/xash3d-fwgs/raw/master/game_launch/icon-xash-material.png" alt="Xash3D FWGS icon" />
|
||||
[](https://github.com/FWGS/xash3d-fwgs/actions/workflows/c-cpp.yml) [](https://cirrus-ci.com/github/FWGS/xash3d-fwgs) \
|
||||
[](http://fwgsdiscord.mentality.rip/) [](https://t.me/flyingwithgauss) \
|
||||
[](http://xash.su/discord/) [](https://t.me/flyingwithgauss) \
|
||||
[](https://github.com/FWGS/xash3d-fwgs/releases/tag/continuous)
|
||||
|
||||
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.
|
||||
@@ -41,10 +44,12 @@ For additional info, run Xash3D with `-help` command line key.
|
||||
1) Copy `valve` directory to a folder named `xash` in the Internal storage.
|
||||
2) Run games from within the app.
|
||||
|
||||
## 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.
|
||||
## Reporting issues
|
||||
* Issues are accepted in both English and Russian.
|
||||
* Before sending a PR, check if you followed our contribution guide in CONTRIBUTING.md file.
|
||||
* They are only accepted if you run legally acquired product (e.g. Half-Life in Steam).
|
||||
|
||||
## Contributing code
|
||||
* Check the CONTRIBUTING.md file.
|
||||
|
||||
## Build instructions
|
||||
We are using Waf build system. If you have some Waf-related questions, I recommend you to read [Waf Book](https://waf.io/book/).
|
||||
@@ -81,11 +86,11 @@ This repository contains our fork of HLSDK and restored source code for Half-Lif
|
||||
|
||||
##### 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 freetype-devel.i686 bzip2-devel.i686 libvorbis-devel.i686 opusfile-devel.i686 libogg-devel.i686`.
|
||||
* Install development tools: `$ sudo dnf install git gcc gcc-c++ glibc-devel.i686 SDL3-devel.i686 sdl2-compat-devel.i686 opus-devel.i686 freetype-devel.i686 bzip2-devel.i686 libvorbis-devel.i686 opusfile-devel.i686 libogg-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 freetype-devel bzip2-devel libvorbis-devel opusfile-devel libogg-devel`.
|
||||
* Install development tools: `$ sudo dnf install git gcc gcc-c++ SDL3-devel sdl2-compat-devel opus-devel freetype-devel bzip2-devel libvorbis-devel opusfile-devel libogg-devel`.
|
||||
|
||||
* Clone this repostory: `$ git clone --recursive https://github.com/FWGS/xash3d-fwgs`.
|
||||
|
||||
@@ -98,6 +103,16 @@ This repository contains our fork of HLSDK and restored source code for Half-Lif
|
||||
|
||||
* Clone this repostory: `$ git clone --recursive https://github.com/FWGS/xash3d-fwgs`.
|
||||
|
||||
#### iOS/iPadOS
|
||||
* Install Xcode from the appstore.
|
||||
* Install [Homebrew package manager](https://brew.sh).
|
||||
|
||||
* Install build dependencies by running: `brew install python`.
|
||||
|
||||
* Clone the SDL2 repo `$ git clone --recursive https://github.com/libsdl-org/SDL.git -b SDL2` and compile the iOS framework by navigating to SDL/Xcode/SDL and opening the Xcode project.
|
||||
|
||||
* Clone this repository: `$ git clone --recursive https://github.com/FWGS/xash3d-fwgs`.
|
||||
|
||||
### Building
|
||||
#### Windows (Visual Studio)
|
||||
0) Open command line.
|
||||
@@ -116,4 +131,17 @@ If compiling 32-bit on amd64, make sure `PKG_CONFIG_PATH` from the previous step
|
||||
3) Install: `./waf install --destdir=/path/to/any/output/directory`.
|
||||
|
||||
#### Android (Windows/Linux/macOS)
|
||||
You can just open the `android` folder in Android Studio and build from here, or use `gradlew` to build from command line.
|
||||
To build you should clone [SDL](https://github.com/libsdl-org/SDL) from `SDL2` branch and [HLSDK-portable](https://github.com/FWGS/hlsdk-portable) `mobile-hacks` branch repositories to 3rdparty folder, after that you should be able to open the project in Android Studio from `android` directory or manually call Gradle to build the APK.
|
||||
|
||||
#### iOS/iPadOS (MacOS only)
|
||||
0) (optional) Examine which build options are available: `./waf --help`.
|
||||
1) Configure build: `./waf configure --ios --enable-bundled-deps --sdl2 (path/to/SDL2.framework)`, set `--ios-simulator` instead of `--ios` if you want to build for simulator.
|
||||
2) Compile `./waf build`.
|
||||
3) Navigate to `build` and copy your compiled SDL2.framework there, then add your game dylibs to `build/ios/libs/(gamedir)/(dlls/cl_dlls)`(You can also run `scripts/ios/buildhlsdk.sh` instead to automatically create an ipa with hlsdk dylibs)
|
||||
4) Run `scripts/ios/createipa.sh` to create an installable ipa
|
||||
|
||||
### Running tests
|
||||
|
||||
Tests are enabled with `--enable-tests` passed to `./waf configure` and can be run with `./waf --alltests`.
|
||||
|
||||
This builds both standalone unit tests and a separate engine test binary (`xash3d_tests`) that embeds engine-level tests. The engine test binary requires no game assets.
|
||||
|
||||
18
SECURITY.md
Normal file
18
SECURITY.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# Xash3D FWGS Security Policy
|
||||
|
||||
## Supported versions
|
||||
|
||||
Because we don't have clear release cycle yet, support is only provided to the
|
||||
latest engine versions from `master` branch. You can use `host_ver` command in
|
||||
console to fetch detailed info about the engine build.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you discover a security vulnerability in this project, it can be reported
|
||||
privately though `Security` tab on GitHub:
|
||||
https://github.com/FWGS/xash3d-fwgs/security
|
||||
|
||||
Alternatively, you can report them publicly on the `Issues` tab:
|
||||
https://github.com/FWGS/xash3d-fwgs/issues
|
||||
|
||||
In both cases, expect a reply within approximately a week.
|
||||
3
android/.gitignore
vendored
3
android/.gitignore
vendored
@@ -11,4 +11,5 @@ local.properties
|
||||
release/
|
||||
*.hprof
|
||||
.vscode/
|
||||
*.bak
|
||||
*.bak
|
||||
.kotlin/
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import com.android.build.api.dsl.ApplicationExtension
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
import java.time.LocalDateTime
|
||||
import java.time.Month
|
||||
@@ -5,12 +6,11 @@ import java.time.temporal.ChronoUnit
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
}
|
||||
|
||||
android {
|
||||
extensions.configure<ApplicationExtension> {
|
||||
namespace = "su.xash.engine"
|
||||
ndkVersion = "28.2.13676358"
|
||||
ndkVersion = "29.0.14206865"
|
||||
compileSdk = 35
|
||||
|
||||
defaultConfig {
|
||||
@@ -20,10 +20,12 @@ android {
|
||||
minSdk = 21
|
||||
targetSdk = 35
|
||||
|
||||
buildConfigField("String", "GIT_HASH", "\"${getGitHash()}\"")
|
||||
|
||||
externalNativeBuild {
|
||||
val engineRoot = projectDir.parentFile.parent
|
||||
|
||||
experimentalProperties["ninja.abiFilters"] = setOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
|
||||
experimentalProperties["ninja.abiFilters"] = setOf("armeabi-v7a", "arm64-v8a", "x86")
|
||||
experimentalProperties["ninja.path"] = File(engineRoot, "wscript").path
|
||||
experimentalProperties["ninja.configure"] = "run-python"
|
||||
experimentalProperties["ninja.arguments"] = setOf(
|
||||
@@ -34,7 +36,7 @@ android {
|
||||
"--configuration-dir=\${ndk.buildRoot}",
|
||||
"--ndk-version=\${ndk.moduleNdkVersion}",
|
||||
"--min-sdk-version=\${ndk.minPlatform}",
|
||||
"--ndk-root=${android.ndkDirectory}",
|
||||
"--ndk-root=${androidComponents.sdkComponents.ndkDirectory.get()}",
|
||||
// shut up, fake options
|
||||
"-p:Configuration=\${ndk.variantName}",
|
||||
"-p:Platform=\${ndk.abi}"
|
||||
@@ -58,6 +60,15 @@ android {
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
create("androidDebugKey") {
|
||||
storeFile = File(projectDir.parentFile, "debug.keystore")
|
||||
storePassword = "android"
|
||||
keyAlias = "androiddebugkey"
|
||||
keyPassword = "android"
|
||||
}
|
||||
}
|
||||
|
||||
lint {
|
||||
abortOnError = false
|
||||
}
|
||||
@@ -77,8 +88,8 @@ android {
|
||||
|
||||
sourceSets {
|
||||
getByName("main") {
|
||||
assets.srcDirs("../../3rdparty/extras/xash-extras")
|
||||
java.srcDir("../../3rdparty/SDL/android-project/app/src/main/java")
|
||||
assets.directories.add("../../3rdparty/extras/xash-extras")
|
||||
java.directories.add("../../3rdparty/SDL/android-project/app/src/main/java")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,6 +102,7 @@ android {
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
|
||||
)
|
||||
buildConfigField("boolean", "ENABLE_AUTO_UPDATE", "false")
|
||||
}
|
||||
|
||||
release {
|
||||
@@ -99,6 +111,7 @@ android {
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
|
||||
)
|
||||
buildConfigField("boolean", "ENABLE_AUTO_UPDATE", "false")
|
||||
}
|
||||
|
||||
register("asan") {
|
||||
@@ -108,6 +121,8 @@ android {
|
||||
register("continuous") {
|
||||
initWith(getByName("release"))
|
||||
applicationIdSuffix = ".test"
|
||||
buildConfigField("boolean", "ENABLE_AUTO_UPDATE", "true")
|
||||
signingConfig = signingConfigs.getByName("androidDebugKey")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +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>
|
||||
|
||||
@@ -29,10 +29,10 @@
|
||||
<!-- Allow downloading to the external storage on Android 5.1 and older -->
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="22" />
|
||||
android:maxSdkVersion="29" />
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32" />
|
||||
android:maxSdkVersion="29" />
|
||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
|
||||
<!-- Allow access to Bluetooth devices -->
|
||||
<!-- Currently this is just for Steam Controller support and requires setting SDL_HINT_JOYSTICK_HIDAPI_STEAM -->
|
||||
@@ -48,6 +48,8 @@
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<!-- Dedicated server -->
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<!-- Self-update: install downloaded APK -->
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
|
||||
<application
|
||||
android:name=".MainApplication"
|
||||
@@ -57,7 +59,9 @@
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher"
|
||||
android:theme="@style/Theme.App"
|
||||
android:usesCleartextTraffic="true">
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:usesCleartextTraffic="true"
|
||||
android:requestLegacyExternalStorage="true">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true">
|
||||
@@ -67,6 +71,23 @@
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.fileprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/paths" />
|
||||
</provider>
|
||||
<receiver
|
||||
android:name=".model.InstallStatusReceiver"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="su.xash.engine.INSTALL_RESULT" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<activity
|
||||
android:name=".XashActivity"
|
||||
android:alwaysRetainTaskState="true"
|
||||
@@ -84,5 +105,9 @@
|
||||
<intent>
|
||||
<action android:name="su.xash.engine.MOD" />
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.SENDTO" />
|
||||
<data android:scheme="mailto" />
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
|
||||
@@ -1,13 +1,30 @@
|
||||
package su.xash.engine
|
||||
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.net.toUri
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
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 com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlinx.coroutines.launch
|
||||
import su.xash.engine.databinding.ActivityMainBinding
|
||||
import su.xash.engine.model.AppUpdater
|
||||
import su.xash.engine.util.CrashReports
|
||||
import su.xash.engine.util.monospaceTextView
|
||||
import su.xash.engine.util.showDownloadProgressDialog
|
||||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
private lateinit var binding: ActivityMainBinding
|
||||
@@ -27,9 +44,143 @@ class MainActivity : AppCompatActivity() {
|
||||
navController = navHostFragment.navController
|
||||
appBarConfiguration = AppBarConfiguration(navController.graph)
|
||||
setupActionBarWithNavController(navController, appBarConfiguration)
|
||||
|
||||
CrashReports.prune(this)
|
||||
showPendingCrashReport()
|
||||
|
||||
checkForEngineUpdate()
|
||||
}
|
||||
|
||||
private fun checkForEngineUpdate() {
|
||||
val prefs = getSharedPreferences(UPDATE_PREFS, Context.MODE_PRIVATE)
|
||||
val now = System.currentTimeMillis()
|
||||
if (now - prefs.getLong(KEY_LAST_CHECK, 0L) < CHECK_INTERVAL_MS)
|
||||
return
|
||||
|
||||
val updater = AppUpdater(this)
|
||||
lifecycleScope.launch {
|
||||
val info = updater.checkForUpdate()
|
||||
prefs.edit().putLong(KEY_LAST_CHECK, now).apply()
|
||||
if (info == null)
|
||||
return@launch
|
||||
if (prefs.getInt(KEY_DISMISSED_BUILDNUM, -1) >= info.buildNum)
|
||||
return@launch
|
||||
val changelog = updater.fetchChangelog(BuildConfig.GIT_HASH, info.tagName)
|
||||
showEngineUpdateDialog(updater, info.buildNum, changelog, prefs)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showEngineUpdateDialog(
|
||||
updater: AppUpdater,
|
||||
remoteBuildNum: Int,
|
||||
changelog: List<AppUpdater.CommitInfo>?,
|
||||
prefs: android.content.SharedPreferences,
|
||||
) {
|
||||
val builder = MaterialAlertDialogBuilder(this)
|
||||
.setTitle(R.string.engine_update_available)
|
||||
.setMessage(getString(R.string.engine_update_message, remoteBuildNum))
|
||||
.setPositiveButton(R.string.engine_update_download) { _, _ ->
|
||||
showEngineDownloadDialog(updater)
|
||||
}
|
||||
.setNegativeButton(R.string.engine_update_later) { _, _ ->
|
||||
prefs.edit().putInt(KEY_DISMISSED_BUILDNUM, remoteBuildNum).apply()
|
||||
}
|
||||
|
||||
if (!changelog.isNullOrEmpty()) {
|
||||
val text = buildString {
|
||||
append(getString(R.string.engine_update_changelog_header))
|
||||
val shown = changelog.take(CHANGELOG_MAX_LINES)
|
||||
for (c in shown)
|
||||
append("\n• ").append(c.subject)
|
||||
val extra = changelog.size - shown.size
|
||||
if (extra > 0)
|
||||
append("\n").append(getString(R.string.engine_update_changelog_more, extra))
|
||||
}
|
||||
builder.setView(monospaceTextView(this, text))
|
||||
}
|
||||
|
||||
builder.show()
|
||||
}
|
||||
|
||||
private fun showEngineDownloadDialog(updater: AppUpdater) {
|
||||
if (!updater.canInstall()) {
|
||||
promptForInstallPermission()
|
||||
return
|
||||
}
|
||||
showDownloadProgressDialog(
|
||||
ctx = this,
|
||||
titleRes = R.string.engine_update_downloading,
|
||||
cancelable = true,
|
||||
scope = lifecycleScope,
|
||||
download = { onProgress -> updater.downloadAndInstall(onProgress) },
|
||||
)
|
||||
}
|
||||
|
||||
private fun promptForInstallPermission() {
|
||||
MaterialAlertDialogBuilder(this)
|
||||
.setTitle(R.string.engine_update_permission_needed)
|
||||
.setMessage(R.string.engine_update_permission_message)
|
||||
.setPositiveButton(R.string.engine_update_open_settings) { _, _ ->
|
||||
val packageIntent = Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES,
|
||||
"package:$packageName".toUri())
|
||||
try {
|
||||
startActivity(packageIntent)
|
||||
} catch (_: ActivityNotFoundException) {
|
||||
try {
|
||||
startActivity(Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES))
|
||||
} catch (_: ActivityNotFoundException) {
|
||||
// no settings screen — nothing more we can do
|
||||
}
|
||||
}
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
override fun onSupportNavigateUp(): Boolean {
|
||||
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
|
||||
}
|
||||
|
||||
private fun showPendingCrashReport() {
|
||||
val pending = CrashReports.pendingStacktrace(this)
|
||||
if (!pending.exists() || pending.length() == 0L)
|
||||
return
|
||||
|
||||
val historyDir = CrashReports.historyDir(this).apply { mkdirs() }
|
||||
val ts = SimpleDateFormat("yyyyMMdd-HHmmss", Locale.US).format(Date())
|
||||
val entryDir = File(historyDir, "crash-$ts").apply { mkdirs() }
|
||||
|
||||
moveOrCopy(pending, File(entryDir, CrashReports.STACKTRACE_NAME))
|
||||
moveOrCopy(CrashReports.pendingSysinfo(this), File(entryDir, CrashReports.SYSINFO_NAME))
|
||||
moveOrCopy(CrashReports.pendingIntent(this), File(entryDir, CrashReports.INTENT_NAME))
|
||||
moveOrCopy(CrashReports.pendingEngineLog(this), File(entryDir, CrashReports.ENGINELOG_NAME))
|
||||
|
||||
val entry = CrashReports.Entry(entryDir)
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle(R.string.crash_dialog_title)
|
||||
.setView(monospaceTextView(this, entry.summary()))
|
||||
.setPositiveButton(R.string.crash_send_to_developers) { _, _ -> CrashReports.sendByEmail(this, entry) }
|
||||
.setNeutralButton(R.string.crash_share) { _, _ -> CrashReports.share(this, entry) }
|
||||
.setNegativeButton(R.string.crash_dismiss, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun moveOrCopy(src: File, dst: File) {
|
||||
if (!src.exists())
|
||||
return
|
||||
|
||||
if (src.renameTo(dst))
|
||||
return
|
||||
|
||||
src.copyTo(dst, overwrite = true)
|
||||
src.delete()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val CHANGELOG_MAX_LINES = 15
|
||||
private const val UPDATE_PREFS = "app_updater"
|
||||
private const val KEY_LAST_CHECK = "last_check_ms"
|
||||
private const val KEY_DISMISSED_BUILDNUM = "dismissed_buildnum"
|
||||
private const val CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000L
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package su.xash.engine;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.res.AssetManager;
|
||||
import android.os.Build;
|
||||
@@ -14,6 +15,11 @@ import android.view.WindowManager;
|
||||
import org.libsdl.app.SDLActivity;
|
||||
|
||||
import su.xash.engine.util.AndroidBug5497Workaround;
|
||||
import su.xash.engine.util.CrashReports;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class XashActivity extends SDLActivity {
|
||||
private boolean mUseVolumeKeys;
|
||||
@@ -117,9 +123,54 @@ public class XashActivity extends SDLActivity {
|
||||
return getWindow().superDispatchKeyEvent(event);
|
||||
}
|
||||
|
||||
private static void appendStringExtra(StringBuilder sb, Intent intent, String key) {
|
||||
String value = intent.getStringExtra(key);
|
||||
if (value != null)
|
||||
sb.append(" ").append(key).append(" = ").append(value).append('\n');
|
||||
}
|
||||
|
||||
// record intent info, so that it could be consumed later for crash reporting
|
||||
private void recordLaunchInfo() {
|
||||
// do not overwrite current launch info with pending crash log, shouldn't happen but might
|
||||
File pendingCrash = new File(getFilesDir(), "crashes/" + CrashReports.STACKTRACE_NAME);
|
||||
if (pendingCrash.exists() && pendingCrash.length() > 0)
|
||||
return;
|
||||
|
||||
// write Android version, fingerprint, supported abis, etc
|
||||
CrashReports.writeSystemInfo(this);
|
||||
|
||||
// now create intent info and pass it to crash reporting
|
||||
Intent intent = getIntent();
|
||||
if (intent == null)
|
||||
return;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Action: ").append(intent.getAction()).append('\n');
|
||||
sb.append("Data: ").append(intent.getDataString()).append('\n');
|
||||
sb.append("Calling package: ").append(getCallingPackage()).append('\n');
|
||||
sb.append("Extras:\n");
|
||||
// only write intent extras that we care about
|
||||
appendStringExtra(sb, intent, "gamedir");
|
||||
appendStringExtra(sb, intent, "gamelibdir");
|
||||
appendStringExtra(sb, intent, "pakfile");
|
||||
appendStringExtra(sb, intent, "basedir");
|
||||
appendStringExtra(sb, intent, "package");
|
||||
appendStringExtra(sb, intent, "argv");
|
||||
sb.append(" usevolume = ").append(intent.getBooleanExtra("usevolume", false)).append('\n');
|
||||
String[] env = intent.getStringArrayExtra("env");
|
||||
if (env != null)
|
||||
sb.append(" env = ").append(Arrays.toString(env)).append('\n');
|
||||
CrashReports.writeIntentInfo(this, sb.toString());
|
||||
}
|
||||
|
||||
// TODO: REMOVE LATER, temporary launchers support?
|
||||
@Override
|
||||
protected String[] getArguments() {
|
||||
File crashDir = new File(getFilesDir(), "crashes");
|
||||
crashDir.mkdirs();
|
||||
nativeSetenv("XASH3D_CRASH_DIR", crashDir.getAbsolutePath());
|
||||
|
||||
recordLaunchInfo();
|
||||
|
||||
String gamedir = getIntent().getStringExtra("gamedir");
|
||||
if (gamedir == null) gamedir = "valve";
|
||||
nativeSetenv("XASH3D_GAME", gamedir);
|
||||
@@ -127,6 +178,14 @@ public class XashActivity extends SDLActivity {
|
||||
String gamelibdir = getIntent().getStringExtra("gamelibdir");
|
||||
if (gamelibdir != null) nativeSetenv("XASH3D_GAMELIBDIR", gamelibdir);
|
||||
|
||||
String rodir = System.getenv("XASH3D_RODIR");
|
||||
if (rodir == null) {
|
||||
// FIXME: we are using rodir as a supplier for downloaded game libraries
|
||||
rodir = getFilesDir().getAbsolutePath() + "/gamelibs";
|
||||
nativeSetenv("XASH3D_RODIR", rodir);
|
||||
}
|
||||
Log.i(TAG, "XASH3D_RODIR = " + rodir);
|
||||
|
||||
String pakfile = getIntent().getStringExtra("pakfile");
|
||||
if (pakfile != null) nativeSetenv("XASH3D_EXTRAS_PAK2", pakfile);
|
||||
|
||||
@@ -149,6 +208,7 @@ public class XashActivity extends SDLActivity {
|
||||
|
||||
String argv = getIntent().getStringExtra("argv");
|
||||
if (argv == null) argv = "-console -log";
|
||||
|
||||
return argv.split(" ");
|
||||
}
|
||||
}
|
||||
|
||||
205
android/app/src/main/java/su/xash/engine/model/AppUpdater.kt
Normal file
205
android/app/src/main/java/su/xash/engine/model/AppUpdater.kt
Normal file
@@ -0,0 +1,205 @@
|
||||
package su.xash.engine.model
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageInstaller
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ensureActive
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
import su.xash.engine.BuildConfig
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
import kotlin.coroutines.coroutineContext
|
||||
|
||||
class AppUpdater(private val context: Context) {
|
||||
|
||||
data class UpdateInfo(val buildNum: Int, val tagName: String)
|
||||
data class CommitInfo(val sha: String, val subject: String)
|
||||
|
||||
fun canInstall(): Boolean =
|
||||
Build.VERSION.SDK_INT < Build.VERSION_CODES.O ||
|
||||
context.packageManager.canRequestPackageInstalls()
|
||||
|
||||
suspend fun checkForUpdate(): UpdateInfo? {
|
||||
if (!BuildConfig.ENABLE_AUTO_UPDATE) return null
|
||||
return withContext(Dispatchers.IO) {
|
||||
var connection: HttpURLConnection? = null
|
||||
try {
|
||||
connection = URL(RELEASE_API_URL).openConnection() as HttpURLConnection
|
||||
connection.connectTimeout = 5000
|
||||
connection.readTimeout = 5000
|
||||
connection.setRequestProperty("Accept", "application/vnd.github+json")
|
||||
connection.connect()
|
||||
|
||||
if (connection.responseCode != HttpURLConnection.HTTP_OK) {
|
||||
Log.w(TAG, "Release API check failed: HTTP ${connection.responseCode}")
|
||||
return@withContext null
|
||||
}
|
||||
|
||||
val release = JSONObject(connection.inputStream.bufferedReader().readText())
|
||||
val body = release.optString("body", "")
|
||||
val tagName = release.optString("tag_name").ifEmpty { TAG_CONTINUOUS }
|
||||
|
||||
// buildnum is days-since-2015-04-01, same metric as VERSION_CODE / 10000
|
||||
val remote = BUILDNUM_REGEX.find(body)?.groupValues?.get(1)?.toIntOrNull()
|
||||
val localDays = BuildConfig.VERSION_CODE / 10000
|
||||
Log.i(TAG, "Remote buildnum: $remote (tag=$tagName), local: $localDays")
|
||||
|
||||
if (remote != null && remote - localDays >= STALENESS_DAYS)
|
||||
UpdateInfo(remote, tagName)
|
||||
else
|
||||
null
|
||||
} catch (e: CancellationException) {
|
||||
throw e
|
||||
} catch (e: IOException) {
|
||||
Log.w(TAG, "Update check failed: ${e.message}")
|
||||
null
|
||||
} catch (e: JSONException) {
|
||||
Log.w(TAG, "Update check parse failed: ${e.message}")
|
||||
null
|
||||
} finally {
|
||||
connection?.disconnect()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun fetchChangelog(fromRef: String, toRef: String): List<CommitInfo>? {
|
||||
if (fromRef.isEmpty() || toRef.isEmpty())
|
||||
return null
|
||||
return withContext(Dispatchers.IO) {
|
||||
var connection: HttpURLConnection? = null
|
||||
try {
|
||||
connection = URL("$COMPARE_API_BASE/$fromRef...$toRef").openConnection() as HttpURLConnection
|
||||
connection.connectTimeout = 5000
|
||||
connection.readTimeout = 5000
|
||||
connection.setRequestProperty("Accept", "application/vnd.github+json")
|
||||
connection.connect()
|
||||
|
||||
if (connection.responseCode != HttpURLConnection.HTTP_OK) {
|
||||
Log.w(TAG, "Compare API failed: HTTP ${connection.responseCode}")
|
||||
return@withContext null
|
||||
}
|
||||
|
||||
val json = JSONObject(connection.inputStream.bufferedReader().readText())
|
||||
val commits = json.optJSONArray("commits") ?: return@withContext null
|
||||
val result = ArrayList<CommitInfo>(commits.length())
|
||||
for (i in 0 until commits.length()) {
|
||||
val c = commits.getJSONObject(i)
|
||||
val sha = c.optString("sha").ifEmpty { continue }
|
||||
val msg = c.optJSONObject("commit")?.optString("message") ?: continue
|
||||
val subject = msg.substringBefore('\n').trim()
|
||||
if (subject.isNotEmpty())
|
||||
result.add(CommitInfo(sha, subject))
|
||||
}
|
||||
// GitHub returns oldest first
|
||||
result.reverse()
|
||||
result
|
||||
} catch (e: CancellationException) {
|
||||
throw e
|
||||
} catch (e: IOException) {
|
||||
Log.w(TAG, "Changelog fetch failed: ${e.message}")
|
||||
null
|
||||
} catch (e: JSONException) {
|
||||
Log.w(TAG, "Changelog parse failed: ${e.message}")
|
||||
null
|
||||
} finally {
|
||||
connection?.disconnect()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun downloadAndInstall(onProgress: (Long, Long) -> Unit): Result<Unit> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val tempFile = File(context.cacheDir, "xash3d-fwgs-update.apk")
|
||||
var connection: HttpURLConnection? = null
|
||||
try {
|
||||
connection = URL(APK_URL).openConnection() as HttpURLConnection
|
||||
connection.connectTimeout = 10000
|
||||
connection.readTimeout = 30000
|
||||
connection.instanceFollowRedirects = true
|
||||
connection.connect()
|
||||
|
||||
if (connection.responseCode != HttpURLConnection.HTTP_OK)
|
||||
return@withContext Result.failure(IOException("HTTP ${connection.responseCode}"))
|
||||
|
||||
val total = connection.contentLengthLong
|
||||
var downloaded = 0L
|
||||
var lastEmit = 0L
|
||||
|
||||
connection.inputStream.use { input ->
|
||||
FileOutputStream(tempFile).use { output ->
|
||||
val buffer = ByteArray(65536)
|
||||
while (true) {
|
||||
coroutineContext.ensureActive()
|
||||
val read = input.read(buffer)
|
||||
if (read < 0)
|
||||
break
|
||||
output.write(buffer, 0, read)
|
||||
downloaded += read
|
||||
val now = System.currentTimeMillis()
|
||||
if (now - lastEmit >= PROGRESS_INTERVAL_MS) {
|
||||
lastEmit = now
|
||||
withContext(Dispatchers.Main) { onProgress(downloaded, total) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
withContext(Dispatchers.Main) { onProgress(downloaded, total) }
|
||||
|
||||
Log.i(TAG, "Downloaded APK: ${tempFile.length()} bytes -> ${tempFile.absolutePath}")
|
||||
|
||||
triggerInstall(tempFile)
|
||||
Result.success(Unit)
|
||||
} catch (e: CancellationException) {
|
||||
tempFile.delete()
|
||||
throw e
|
||||
} catch (e: IOException) {
|
||||
tempFile.delete()
|
||||
Result.failure(e)
|
||||
} finally {
|
||||
connection?.disconnect()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun triggerInstall(apk: File) {
|
||||
val installer = context.packageManager.packageInstaller
|
||||
val params = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
|
||||
val sessionId = installer.createSession(params)
|
||||
installer.openSession(sessionId).use { session ->
|
||||
session.openWrite("base.apk", 0, apk.length()).use { out ->
|
||||
apk.inputStream().use { it.copyTo(out) }
|
||||
session.fsync(out)
|
||||
}
|
||||
val statusIntent = Intent(INSTALL_ACTION).setPackage(context.packageName)
|
||||
val piFlags = PendingIntent.FLAG_UPDATE_CURRENT or
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) PendingIntent.FLAG_MUTABLE else 0
|
||||
val pi = PendingIntent.getBroadcast(context, sessionId, statusIntent, piFlags)
|
||||
session.commit(pi.intentSender)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "AppUpdater"
|
||||
private const val STALENESS_DAYS = 3
|
||||
private const val PROGRESS_INTERVAL_MS = 100L
|
||||
private const val TAG_CONTINUOUS = "continuous"
|
||||
private const val INSTALL_ACTION = "su.xash.engine.INSTALL_RESULT"
|
||||
private const val APK_URL =
|
||||
"https://github.com/FWGS/xash3d-fwgs/releases/download/continuous/xash3d-fwgs-android.apk"
|
||||
private const val RELEASE_API_URL =
|
||||
"https://api.github.com/repos/FWGS/xash3d-fwgs/releases/tags/continuous"
|
||||
private const val COMPARE_API_BASE =
|
||||
"https://api.github.com/repos/FWGS/xash3d-fwgs/compare"
|
||||
private val BUILDNUM_REGEX = Regex("""buildnum\s+(\d+)""")
|
||||
}
|
||||
}
|
||||
@@ -6,28 +6,39 @@ import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.net.Uri
|
||||
import androidx.core.net.toUri
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import su.xash.engine.R
|
||||
import su.xash.engine.XashActivity
|
||||
import su.xash.engine.util.showDownloadProgressDialog
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
|
||||
|
||||
class Game(val ctx: Context, val basedir: File) {
|
||||
class Game(val ctx: Context, val basedir: File, val gameInfoFile: File) {
|
||||
private var iconName = "game.ico"
|
||||
var title = "Unknown Game"
|
||||
var icon: Bitmap? = null
|
||||
var cover: Bitmap? = null
|
||||
|
||||
val mobileHacksGames = arrayOf("aom", "bdlands", "biglolly", "bshift", "caseclosed",
|
||||
"hl_urbicide", "induction", "redempt", "secret",
|
||||
"sewer_beta", "tot", "valve", "vendetta")
|
||||
|
||||
// a1ba: follow the behavior of Xash3D's game_launch.
|
||||
// for hl mods we put `valve` as game directory
|
||||
// for any other game that's not hl this string must be replaced with your
|
||||
// main game directory
|
||||
// mods always use -game command line parameter
|
||||
var defaultGameDir = "valve"
|
||||
|
||||
private val pref = ctx.getSharedPreferences(basedir.name, Context.MODE_PRIVATE)
|
||||
|
||||
init {
|
||||
val gameInfo = File(basedir, "gameinfo.txt")
|
||||
if (gameInfo.exists()) {
|
||||
parseGameInfo(gameInfo)
|
||||
} else {
|
||||
val libListGam = File(basedir, "liblist.gam")
|
||||
if (libListGam.exists()) parseGameInfo(libListGam)
|
||||
}
|
||||
parseGameInfo(gameInfoFile)
|
||||
|
||||
val iconFile = File(basedir, iconName)
|
||||
if (iconFile.exists()) {
|
||||
@@ -42,14 +53,125 @@ class Game(val ctx: Context, val basedir: File) {
|
||||
}
|
||||
|
||||
fun startEngine(ctx: Context) {
|
||||
val packageNames = getPackageNamesForGameDir(basedir.name)
|
||||
var externalGame = false
|
||||
var commandLineArgs = ""
|
||||
|
||||
if (basedir.name != defaultGameDir)
|
||||
commandLineArgs += "-game ${basedir.name} "
|
||||
|
||||
if (packageNames != null) {
|
||||
if (packageNames.contains("su.xash.engine")) {
|
||||
commandLineArgs += "-dll @hl "
|
||||
} else if (packageNames.contains("su.xash.cs16client")) {
|
||||
if (pref.getBoolean("enable_yapb_bots", true)) {
|
||||
commandLineArgs += "-dll @yapb "
|
||||
}
|
||||
externalGame = true
|
||||
}
|
||||
}
|
||||
|
||||
commandLineArgs += pref.getString("arguments", "-console -log") ?: ""
|
||||
|
||||
if (externalGame && packageNames != null) {
|
||||
var packageName: String? = null
|
||||
var gameLibDir: String? = null
|
||||
|
||||
for (pn in packageNames) {
|
||||
gameLibDir = try {
|
||||
getGameLibDir(ctx, pn)
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
null
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
null
|
||||
}
|
||||
|
||||
if (gameLibDir != null) {
|
||||
packageName = pn
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (gameLibDir == null) {
|
||||
MaterialAlertDialogBuilder(ctx).apply {
|
||||
setTitle(R.string.game_apk_required)
|
||||
setMessage(R.string.game_apk_message)
|
||||
setPositiveButton(R.string.game_apk_install) { _, _ ->
|
||||
val intent = Intent(Intent.ACTION_VIEW,
|
||||
getDownloadPageForGameDir(basedir.name).toUri())
|
||||
ctx.startActivity(intent)
|
||||
}
|
||||
show()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
launchEngine(ctx, commandLineArgs, packageName = packageName, gameLibDir = gameLibDir)
|
||||
return
|
||||
}
|
||||
|
||||
if (packageNames == null) {
|
||||
// Unknown game — try to use downloaded libraries from hlsdk-mega-build
|
||||
val downloader = GameLibDownloader(ctx)
|
||||
val args = commandLineArgs
|
||||
|
||||
if (downloader.isDownloaded(basedir.name)) {
|
||||
downloader.logExistingLibs(basedir.name)
|
||||
launchEngine(ctx, args)
|
||||
return
|
||||
}
|
||||
|
||||
val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
||||
scope.launch {
|
||||
when (val r = downloader.lookupBuild(basedir.name)) {
|
||||
is GameLibDownloader.Lookup.Available -> showDownloadDialog(ctx, downloader, args)
|
||||
is GameLibDownloader.Lookup.NotInManifest -> launchEngine(ctx, args)
|
||||
is GameLibDownloader.Lookup.Error -> showManifestErrorDialog(ctx, args, r.cause)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
launchEngine(ctx, commandLineArgs)
|
||||
}
|
||||
|
||||
private fun showDownloadDialog(ctx: Context, downloader: GameLibDownloader, commandLineArgs: String) {
|
||||
showDownloadProgressDialog(
|
||||
ctx = ctx,
|
||||
titleRes = R.string.downloading_game_libs,
|
||||
cancelable = true,
|
||||
scope = CoroutineScope(Dispatchers.Main + SupervisorJob()),
|
||||
download = { onProgress -> downloader.download(basedir.name, onProgress) },
|
||||
onSuccess = { launchEngine(ctx, commandLineArgs) },
|
||||
)
|
||||
}
|
||||
|
||||
private fun showManifestErrorDialog(ctx: Context, commandLineArgs: String, cause: Throwable) {
|
||||
MaterialAlertDialogBuilder(ctx)
|
||||
.setTitle(R.string.manifest_error_title)
|
||||
.setMessage(ctx.getString(R.string.manifest_error_message, cause.message ?: cause.javaClass.simpleName))
|
||||
.setPositiveButton(R.string.launch_anyway) { _, _ -> launchEngine(ctx, commandLineArgs) }
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun launchEngine(
|
||||
ctx: Context,
|
||||
commandLineArgs: String,
|
||||
packageName: String? = null,
|
||||
gameLibDir: String? = null
|
||||
) {
|
||||
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("gamedir", defaultGameDir)
|
||||
putExtra("argv", commandLineArgs)
|
||||
putExtra("usevolume", pref.getBoolean("use_volume_buttons", false))
|
||||
putExtra("basedir", basedir.parent)
|
||||
//.putExtra("gamelibdir", getGameLibDir(context))
|
||||
//.putExtra("package", getPackageName()) }
|
||||
|
||||
if (gameLibDir != null) putExtra("gamelibdir", gameLibDir)
|
||||
if (packageName != null) putExtra("package", packageName)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -70,44 +192,47 @@ class Game(val ctx: Context, val basedir: File) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun getPackageName(): String? {
|
||||
// return if (mDbEntry != null) {
|
||||
// mDbEntry.getPackageName()
|
||||
// } else null
|
||||
private fun getPackageNamesForGameDir(gamedir: String): Array<String>? {
|
||||
if (gamedir.equals("cstrike", ignoreCase = true)
|
||||
|| gamedir.equals("czero", ignoreCase = true))
|
||||
return arrayOf("su.xash.cs16client.test", "su.xash.cs16client")
|
||||
|
||||
if (gamedir.equals("tfc", ignoreCase = true))
|
||||
return arrayOf("su.xash.tf15client.test", "su.xash.tf15client")
|
||||
|
||||
// mobile_hacks hlsdk-portable branch allows us to have few more mods out of the box
|
||||
if (mobileHacksGames.any { it.equals(gamedir, ignoreCase = true) })
|
||||
return arrayOf("su.xash.engine")
|
||||
|
||||
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
|
||||
private fun getDownloadPageForGameDir(gamedir: String): String {
|
||||
if (gamedir.equals("cstrike", ignoreCase = true)
|
||||
|| gamedir.equals("czero", ignoreCase = true))
|
||||
return "https://github.com/Velaron/cs16-client/releases/download/continuous/CS16Client-Android.apk"
|
||||
|
||||
if (gamedir.equals("tfc", ignoreCase = true))
|
||||
return "https://github.com/Velaron/tf15-client/releases/download/continuous/TF15Client-Android.apk"
|
||||
|
||||
// just so we don't return null
|
||||
return "https://github.com/FWGS/xash3d-fwgs/releases/download/continuous/xash3d-fwgs-android.apk"
|
||||
}
|
||||
|
||||
private fun getGameLibDir(ctx: Context, packageName: String): String? {
|
||||
val packageInfo: PackageInfo = ctx.packageManager.getPackageInfo(packageName, 0)
|
||||
return packageInfo.applicationInfo?.nativeLibraryDir
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun getGames(ctx: Context, file: File): List<Game> {
|
||||
fun getGames(ctx: Context, root: File): 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))
|
||||
}
|
||||
root.listFiles()?.forEach {
|
||||
if (it.isDirectory) {
|
||||
val subDirGameInfoFile = checkIfGamedir(it)
|
||||
if (subDirGameInfoFile != null) {
|
||||
games.add(Game(ctx, it, subDirGameInfoFile))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -115,24 +240,18 @@ class Game(val ctx: Context, val basedir: File) {
|
||||
return games
|
||||
}
|
||||
|
||||
fun checkIfGamedir(file: File): Boolean {
|
||||
if (File(file, "liblist.gam").exists()) return true
|
||||
if (File(file, "gameinfo.txt").exists()) return true
|
||||
fun checkIfGamedir(gamedir: File): File? {
|
||||
gamedir.listFiles()?.forEach {
|
||||
if (it.isFile) {
|
||||
if (it.name.equals("liblist.gam", ignoreCase = true))
|
||||
return it
|
||||
|
||||
return false
|
||||
if (it.name.equals("gameinfo.txt", ignoreCase = true))
|
||||
return it
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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,377 @@
|
||||
package su.xash.engine.model
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ensureActive
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.json.JSONObject
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
import java.security.MessageDigest
|
||||
import java.util.zip.ZipInputStream
|
||||
import kotlin.coroutines.coroutineContext
|
||||
|
||||
class GameLibDownloader(private val context: Context) {
|
||||
|
||||
private val prefs get() = context.getSharedPreferences("gamelib_metadata", Context.MODE_PRIVATE)
|
||||
|
||||
// converts Android's ABI to library-suffix's arch names
|
||||
fun getArch(): String? {
|
||||
for (abi in Build.SUPPORTED_ABIS) {
|
||||
when (abi) {
|
||||
"arm64-v8a" -> return "arm64"
|
||||
"armeabi-v7a" -> return "armv7l"
|
||||
"x86_64" -> return "amd64"
|
||||
"x86" -> return "i386"
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun getLibsBaseDir(): File = File(context.filesDir, "gamelibs")
|
||||
|
||||
fun isDownloaded(gamedir: String): Boolean {
|
||||
val dir = File(getLibsBaseDir(), gamedir)
|
||||
if (dir.isDirectory && dir.list()?.isNotEmpty() == true)
|
||||
return true
|
||||
|
||||
// zips extract to the case used in the manifest (e.g. "CAd"); the user's
|
||||
// folder name might differ in case, so do a case-insensitive lookup too.
|
||||
val base = getLibsBaseDir()
|
||||
val match = base.listFiles()?.firstOrNull {
|
||||
it.isDirectory && it.name.equals(gamedir, ignoreCase = true)
|
||||
}
|
||||
return match != null && (match.list()?.isNotEmpty() == true)
|
||||
}
|
||||
|
||||
data class ManifestEntry(
|
||||
val modKey: String,
|
||||
val platformArch: String,
|
||||
val filename: String,
|
||||
val sha256: String,
|
||||
val sourceJson: String?
|
||||
)
|
||||
|
||||
data class SourceInfo(
|
||||
val url: String?,
|
||||
val branch: String?,
|
||||
val commit: String?
|
||||
)
|
||||
|
||||
private fun manifestCacheFile(): File = File(context.cacheDir, "hlsdk-manifest.json")
|
||||
|
||||
private suspend fun fetchManifest(): JSONObject? = fetchManifestOrError().first
|
||||
|
||||
private suspend fun fetchManifestOrError(): Pair<JSONObject?, Throwable?> {
|
||||
val cacheFile = manifestCacheFile()
|
||||
var connection: HttpURLConnection? = null
|
||||
try {
|
||||
connection = URL(MANIFEST_URL).openConnection() as HttpURLConnection
|
||||
connection.connectTimeout = 10000
|
||||
connection.readTimeout = 15000
|
||||
connection.instanceFollowRedirects = true
|
||||
connection.connect()
|
||||
|
||||
if (connection.responseCode != HttpURLConnection.HTTP_OK) {
|
||||
val err = IOException("HTTP ${connection.responseCode} fetching manifest")
|
||||
Log.w(TAG, "Manifest fetch failed: ${err.message}")
|
||||
val cached = readCachedManifest(cacheFile)
|
||||
return if (cached != null) cached to null else null to err
|
||||
}
|
||||
|
||||
val text = connection.inputStream.bufferedReader().use { it.readText() }
|
||||
val json = JSONObject(text)
|
||||
val versionError = checkManifestVersion(json)
|
||||
if (versionError != null) {
|
||||
Log.w(TAG, "Manifest rejected: ${versionError.message}")
|
||||
return null to versionError
|
||||
}
|
||||
cacheFile.writeText(text)
|
||||
return json to null
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Manifest fetch failed: ${e.message}")
|
||||
val cached = readCachedManifest(cacheFile)
|
||||
return if (cached != null) cached to null else null to e
|
||||
} finally {
|
||||
connection?.disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
private fun readCachedManifest(cacheFile: File): JSONObject? {
|
||||
if (!cacheFile.exists())
|
||||
return null
|
||||
val json = try {
|
||||
JSONObject(cacheFile.readText())
|
||||
} catch (_: Exception) {
|
||||
return null
|
||||
}
|
||||
return if (checkManifestVersion(json) == null) json else null
|
||||
}
|
||||
|
||||
private fun checkManifestVersion(json: JSONObject): IOException? {
|
||||
if (!json.has("version"))
|
||||
return IOException("Manifest missing 'version' field")
|
||||
val v = json.opt("version")
|
||||
if (v !is Int || v != MANIFEST_VERSION)
|
||||
return IOException("Unsupported manifest version: $v (expected $MANIFEST_VERSION)")
|
||||
return null
|
||||
}
|
||||
|
||||
private fun lookupEntry(manifest: JSONObject, gamedir: String): ManifestEntry? {
|
||||
val arch = getArch() ?: return null
|
||||
val platformArch = "android-$arch"
|
||||
val mods = manifest.optJSONObject("mods") ?: return null
|
||||
|
||||
// Find mod key case-insensitively
|
||||
var modKey: String? = null
|
||||
val keys = mods.keys()
|
||||
while (keys.hasNext()) {
|
||||
val k = keys.next()
|
||||
if (k.equals(gamedir, ignoreCase = true)) {
|
||||
modKey = k
|
||||
break
|
||||
}
|
||||
}
|
||||
if (modKey == null)
|
||||
return null
|
||||
|
||||
val builds = mods.optJSONObject(modKey)?.optJSONObject("builds") ?: return null
|
||||
val build = builds.optJSONObject(platformArch) ?: return null
|
||||
val filename = build.optString("filename", "")
|
||||
val sha256 = build.optString("sha256", "")
|
||||
if (filename.isEmpty() || sha256.isEmpty())
|
||||
return null
|
||||
|
||||
val sourceJson = build.optJSONObject("source")?.toString()
|
||||
return ManifestEntry(modKey, platformArch, filename, sha256.lowercase(), sourceJson)
|
||||
}
|
||||
|
||||
fun getSourceInfo(gamedir: String): SourceInfo? {
|
||||
val raw = prefs.getString("${gamedir}_source", null) ?: return null
|
||||
return try {
|
||||
val o = JSONObject(raw)
|
||||
SourceInfo(
|
||||
url = o.optString("url").ifEmpty { null },
|
||||
branch = o.optString("branch").ifEmpty { null },
|
||||
commit = o.optString("commit").ifEmpty { null },
|
||||
)
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun getDownloadTime(gamedir: String): Long =
|
||||
prefs.getLong("${gamedir}_download_time", 0L)
|
||||
|
||||
sealed class Lookup {
|
||||
data class Available(val entry: ManifestEntry) : Lookup()
|
||||
object NotInManifest : Lookup()
|
||||
data class Error(val cause: Throwable) : Lookup()
|
||||
}
|
||||
|
||||
suspend fun lookupBuild(gamedir: String): Lookup {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val (manifest, error) = fetchManifestOrError()
|
||||
if (manifest == null)
|
||||
return@withContext Lookup.Error(error ?: IOException("Failed to fetch manifest"))
|
||||
val entry = lookupEntry(manifest, gamedir)
|
||||
?: return@withContext Lookup.NotInManifest
|
||||
Lookup.Available(entry)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun isUpdateAvailable(gamedir: String): Boolean {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val manifest = fetchManifest() ?: return@withContext false
|
||||
val entry = lookupEntry(manifest, gamedir) ?: return@withContext false
|
||||
val storedSha = prefs.getString("${gamedir}_sha256", null)
|
||||
storedSha == null || !storedSha.equals(entry.sha256, ignoreCase = true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun urlForFilename(filename: String): String = "$RELEASE_BASE_URL/$filename"
|
||||
|
||||
private suspend fun tryDownload(
|
||||
url: String,
|
||||
dest: File,
|
||||
onProgress: (Long, Long) -> Unit
|
||||
): Exception? {
|
||||
var connection: HttpURLConnection? = null
|
||||
try {
|
||||
connection = URL(url).openConnection() as HttpURLConnection
|
||||
connection.connectTimeout = 10000
|
||||
connection.readTimeout = 30000
|
||||
connection.instanceFollowRedirects = true
|
||||
connection.connect()
|
||||
|
||||
val code = connection.responseCode
|
||||
if (code !in 200..299)
|
||||
return IOException("HTTP $code: $url")
|
||||
|
||||
val total = connection.contentLengthLong
|
||||
var downloaded = 0L
|
||||
var lastEmit = 0L
|
||||
|
||||
connection.inputStream.use { input ->
|
||||
FileOutputStream(dest).use { output ->
|
||||
val buffer = ByteArray(65536)
|
||||
while (true) {
|
||||
coroutineContext.ensureActive()
|
||||
val read = input.read(buffer)
|
||||
if (read < 0)
|
||||
break
|
||||
output.write(buffer, 0, read)
|
||||
downloaded += read
|
||||
val now = System.currentTimeMillis()
|
||||
if (now - lastEmit >= PROGRESS_INTERVAL_MS) {
|
||||
lastEmit = now
|
||||
withContext(Dispatchers.Main) { onProgress(downloaded, total) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
withContext(Dispatchers.Main) { onProgress(downloaded, total) }
|
||||
|
||||
return null
|
||||
} catch (e: Exception) {
|
||||
return e
|
||||
} finally {
|
||||
connection?.disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun download(gamedir: String, onProgress: (Long, Long) -> Unit): Result<Unit> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val manifest = fetchManifest()
|
||||
?: return@withContext Result.failure(IOException("Failed to fetch manifest"))
|
||||
|
||||
val entry = lookupEntry(manifest, gamedir)
|
||||
?: return@withContext Result.failure(IOException("No build for '$gamedir' on ${getArch() ?: "this arch"}"))
|
||||
|
||||
val url = urlForFilename(entry.filename)
|
||||
val tempFile = File(context.cacheDir, entry.filename)
|
||||
|
||||
try {
|
||||
coroutineContext.ensureActive()
|
||||
Log.i(TAG, "Downloading $url -> ${tempFile.absolutePath}")
|
||||
val error = tryDownload(url, tempFile, onProgress)
|
||||
if (error != null)
|
||||
return@withContext Result.failure(error)
|
||||
|
||||
val actualSha = computeSHA256(tempFile)
|
||||
Log.i(TAG, "Downloaded ${tempFile.length()} bytes, SHA-256: $actualSha (expected ${entry.sha256})")
|
||||
|
||||
if (!actualSha.equals(entry.sha256, ignoreCase = true))
|
||||
return@withContext Result.failure(IOException("SHA-256 mismatch: expected ${entry.sha256}, got $actualSha"))
|
||||
|
||||
val destDir = getLibsBaseDir()
|
||||
destDir.mkdirs()
|
||||
val destCanon = destDir.canonicalPath
|
||||
|
||||
// Remove any prior install (possibly under a differently-cased dir) so we
|
||||
// don't leave behind .so files from an older build.
|
||||
destDir.listFiles()?.forEach {
|
||||
if (it.isDirectory && it.name.equals(entry.modKey, ignoreCase = true))
|
||||
it.deleteRecursively()
|
||||
}
|
||||
|
||||
Log.i(TAG, "Extracting to ${destDir.absolutePath}")
|
||||
|
||||
val extractedFiles = mutableListOf<String>()
|
||||
ZipInputStream(tempFile.inputStream()).use { zip ->
|
||||
var zipEntry = zip.nextEntry
|
||||
while (zipEntry != null) {
|
||||
coroutineContext.ensureActive()
|
||||
if (!zipEntry.isDirectory) {
|
||||
val outFile = File(destDir, zipEntry.name)
|
||||
if (!outFile.canonicalPath.startsWith(destCanon + File.separator))
|
||||
throw SecurityException("Zip path traversal: ${zipEntry.name}")
|
||||
outFile.parentFile?.mkdirs()
|
||||
FileOutputStream(outFile).use { zip.copyTo(it) }
|
||||
// ZipInputStream doesn't restore Unix permissions; shared libraries
|
||||
// need the execute bit so dlopen can mmap them with PROT_EXEC
|
||||
if (outFile.name.endsWith(".so"))
|
||||
outFile.setExecutable(true, false)
|
||||
extractedFiles.add(" ${outFile.absolutePath} (${outFile.length()} bytes, executable: ${outFile.canExecute()})")
|
||||
}
|
||||
zip.closeEntry()
|
||||
zipEntry = zip.nextEntry
|
||||
}
|
||||
}
|
||||
|
||||
Log.i(TAG, "Extracted ${extractedFiles.size} files:")
|
||||
extractedFiles.forEach { Log.i(TAG, it) }
|
||||
|
||||
prefs.edit()
|
||||
.putString("${gamedir}_sha256", entry.sha256)
|
||||
.putString("${gamedir}_filename", entry.filename)
|
||||
.putString("${gamedir}_source", entry.sourceJson)
|
||||
.putLong("${gamedir}_download_time", System.currentTimeMillis())
|
||||
.apply()
|
||||
|
||||
Result.success(Unit)
|
||||
} catch (e: Exception) {
|
||||
// Best-effort: rewinding to a clean state on failure
|
||||
File(getLibsBaseDir(), entry.modKey).deleteRecursively()
|
||||
Result.failure(e)
|
||||
} finally {
|
||||
tempFile.delete()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun logExistingLibs(gamedir: String) {
|
||||
val gameDir = File(getLibsBaseDir(), gamedir)
|
||||
val resolved = if (gameDir.exists()) {
|
||||
gameDir
|
||||
} else {
|
||||
getLibsBaseDir().listFiles()?.firstOrNull {
|
||||
it.isDirectory && it.name.equals(gamedir, ignoreCase = true)
|
||||
}
|
||||
}
|
||||
|
||||
if (resolved == null) {
|
||||
Log.i(TAG, "Game libs dir does not exist for $gamedir")
|
||||
return
|
||||
}
|
||||
|
||||
Log.i(TAG, "Existing game libs under ${resolved.absolutePath}:")
|
||||
resolved.walkTopDown().filter { it.isFile }.forEach {
|
||||
Log.i(TAG, " ${it.absolutePath} (${it.length()} bytes)")
|
||||
}
|
||||
val storedSha = prefs.getString("${gamedir}_sha256", null)
|
||||
val downloadTime = prefs.getLong("${gamedir}_download_time", 0L)
|
||||
Log.i(TAG, "Archive SHA-256: $storedSha")
|
||||
if (downloadTime > 0L)
|
||||
Log.i(TAG, "Downloaded at: ${java.util.Date(downloadTime)}")
|
||||
}
|
||||
|
||||
private fun computeSHA256(file: File): String {
|
||||
val digest = MessageDigest.getInstance("SHA-256")
|
||||
file.inputStream().use { input ->
|
||||
val buffer = ByteArray(65536)
|
||||
while (true) {
|
||||
val read = input.read(buffer)
|
||||
if (read < 0)
|
||||
break
|
||||
digest.update(buffer, 0, read)
|
||||
}
|
||||
}
|
||||
return digest.digest().joinToString("") { "%02x".format(it) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "GameLibDownloader"
|
||||
private const val PROGRESS_INTERVAL_MS = 100L
|
||||
private const val RELEASE_BASE_URL =
|
||||
"https://github.com/FWGS/hlsdk-mega-build/releases/download/continuous"
|
||||
private const val MANIFEST_URL =
|
||||
"$RELEASE_BASE_URL/manifest.json"
|
||||
private const val MANIFEST_VERSION = 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package su.xash.engine.model
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageInstaller
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import su.xash.engine.R
|
||||
|
||||
class InstallStatusReceiver : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -1)
|
||||
when (status) {
|
||||
PackageInstaller.STATUS_PENDING_USER_ACTION -> {
|
||||
val confirm: Intent? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
intent.getParcelableExtra(Intent.EXTRA_INTENT)
|
||||
}
|
||||
confirm?.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
confirm?.let { context.startActivity(it) }
|
||||
}
|
||||
PackageInstaller.STATUS_SUCCESS -> Log.i(TAG, "Install succeeded")
|
||||
else -> {
|
||||
val msg = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE).orEmpty()
|
||||
Log.w(TAG, "Install status $status: $msg")
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.engine_install_failed, msg),
|
||||
Toast.LENGTH_LONG,
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "InstallStatus"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package su.xash.engine.ui.library
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
@@ -13,7 +15,8 @@ import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.MenuProvider
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
@@ -25,6 +28,7 @@ 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!!
|
||||
@@ -38,23 +42,85 @@ class LibraryFragment : Fragment(), MenuProvider {
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkStoragePermissions(): Boolean {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !Environment.isExternalStorageManager()) {
|
||||
MaterialAlertDialogBuilder(requireContext()).apply {
|
||||
setTitle(R.string.file_access_required)
|
||||
setMessage(R.string.file_access_message)
|
||||
setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
startActivityForResult.launch(
|
||||
Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION).setData(
|
||||
Uri.fromParts("package", BuildConfig.APPLICATION_ID, null)
|
||||
)
|
||||
)
|
||||
}
|
||||
setCancelable(false)
|
||||
show()
|
||||
}
|
||||
private val requiredPermissions = arrayOf(
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
)
|
||||
|
||||
return false
|
||||
private val requestPermissionLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.RequestMultiplePermissions()
|
||||
) { permissions ->
|
||||
val granted = permissions.entries.all { it.value }
|
||||
if (granted) {
|
||||
libraryViewModel.reloadGames(requireContext())
|
||||
} else {
|
||||
checkStoragePermissions()
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkStoragePermissions(): Boolean {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
if (!Environment.isExternalStorageManager()) {
|
||||
MaterialAlertDialogBuilder(requireContext()).apply {
|
||||
setTitle(R.string.file_access_required)
|
||||
setMessage(R.string.file_access_message)
|
||||
setPositiveButton(R.string.open_settings) { _, _ ->
|
||||
startActivityForResult.launch(
|
||||
Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION).setData(
|
||||
Uri.fromParts("package", BuildConfig.APPLICATION_ID, null)
|
||||
)
|
||||
)
|
||||
}
|
||||
setNeutralButton(R.string.done_check_permissions) { dialog, _ ->
|
||||
checkStoragePermissions()
|
||||
}
|
||||
setCancelable(false)
|
||||
show()
|
||||
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
val permissionsNeeded = requiredPermissions.filter {
|
||||
ContextCompat.checkSelfPermission(
|
||||
requireContext(),
|
||||
it
|
||||
) != PackageManager.PERMISSION_GRANTED
|
||||
}.toTypedArray()
|
||||
|
||||
if (!permissionsNeeded.isEmpty()) {
|
||||
val showRationale = permissionsNeeded.any {
|
||||
ActivityCompat.shouldShowRequestPermissionRationale(requireActivity(), it)
|
||||
}
|
||||
|
||||
MaterialAlertDialogBuilder(requireContext()).apply {
|
||||
setTitle(R.string.external_storage_required)
|
||||
setMessage(R.string.external_storage_message)
|
||||
setPositiveButton(R.string.open_settings) { _, _ ->
|
||||
if (showRationale) {
|
||||
requestPermissionLauncher.launch(permissionsNeeded)
|
||||
} else {
|
||||
val intent =
|
||||
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
|
||||
data =
|
||||
Uri.fromParts("package", requireContext().packageName, null)
|
||||
}
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
setNeutralButton(R.string.done_check_permissions) { _, _ ->
|
||||
checkStoragePermissions()
|
||||
}
|
||||
setCancelable(false)
|
||||
show()
|
||||
}
|
||||
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
@@ -107,4 +173,12 @@ class LibraryFragment : Fragment(), MenuProvider {
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
if (checkStoragePermissions()) {
|
||||
libraryViewModel.reloadGames(requireContext())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import su.xash.engine.model.Game
|
||||
import su.xash.engine.util.Nomedia
|
||||
import java.io.File
|
||||
|
||||
class LibraryViewModel(application: Application) : AndroidViewModel(application) {
|
||||
@@ -39,6 +40,8 @@ class LibraryViewModel(application: Application) : AndroidViewModel(application)
|
||||
?: (Environment.getExternalStorageDirectory().absolutePath + "/xash")
|
||||
val root = File(rootPath)
|
||||
|
||||
Nomedia.ensureNomedia(root)
|
||||
|
||||
_installedGames.postValue(Game.getGames(ctx, root))
|
||||
_isReloading.postValue(false)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package su.xash.engine.ui.settings
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import su.xash.engine.R
|
||||
|
||||
@@ -8,5 +10,10 @@ class AppSettingsPreferenceFragment() : PreferenceFragmentCompat() {
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
preferenceManager.sharedPreferencesName = "app_preferences";
|
||||
setPreferencesFromResource(R.xml.app_preferences, rootKey);
|
||||
|
||||
findPreference<Preference>("crash_logs")?.setOnPreferenceClickListener {
|
||||
findNavController().navigate(R.id.action_appSettingsFragment_to_crashLogsFragment)
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
package su.xash.engine.ui.settings
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import su.xash.engine.R
|
||||
import su.xash.engine.util.CrashReports
|
||||
import su.xash.engine.util.monospaceTextView
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
class CrashLogsFragment : PreferenceFragmentCompat() {
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
preferenceScreen = preferenceManager.createPreferenceScreen(requireContext())
|
||||
populate()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
populate()
|
||||
}
|
||||
|
||||
private fun populate() {
|
||||
val ctx = requireContext()
|
||||
preferenceScreen.removeAll()
|
||||
|
||||
val dirs = CrashReports.historyDir(ctx).listFiles()
|
||||
?.filter { it.isDirectory }
|
||||
?.sortedByDescending { it.lastModified() }
|
||||
?: emptyList()
|
||||
|
||||
if (dirs.isEmpty()) {
|
||||
preferenceScreen.addPreference(Preference(ctx).apply {
|
||||
setTitle(R.string.crash_logs_empty)
|
||||
isSelectable = false
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
val fmt = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US)
|
||||
dirs.forEach { dir ->
|
||||
preferenceScreen.addPreference(Preference(ctx).apply {
|
||||
title = fmt.format(Date(dir.lastModified()))
|
||||
summary = dir.name
|
||||
setOnPreferenceClickListener {
|
||||
showCrashLog(CrashReports.Entry(dir))
|
||||
true
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private fun showCrashLog(entry: CrashReports.Entry) {
|
||||
val ctx = requireContext()
|
||||
AlertDialog.Builder(ctx)
|
||||
.setTitle(entry.name)
|
||||
.setView(monospaceTextView(ctx, entry.summary()))
|
||||
.setPositiveButton(R.string.crash_send_to_developers) { _, _ -> CrashReports.sendByEmail(ctx, entry) }
|
||||
.setNeutralButton(R.string.crash_share) { _, _ -> CrashReports.share(ctx, entry) }
|
||||
.setNegativeButton(R.string.crash_log_delete) { _, _ ->
|
||||
entry.dir.deleteRecursively()
|
||||
populate()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,17 @@
|
||||
package su.xash.engine.ui.settings
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.core.net.toUri
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import su.xash.engine.R
|
||||
import su.xash.engine.model.Game
|
||||
import su.xash.engine.model.GameLibDownloader
|
||||
import java.text.DateFormat
|
||||
import java.util.Date
|
||||
|
||||
class GameSettingsPreferenceFragment(val game: Game) : PreferenceFragmentCompat() {
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
@@ -20,6 +26,14 @@ class GameSettingsPreferenceFragment(val game: Game) : PreferenceFragmentCompat(
|
||||
packageList.setValueIndex(0);
|
||||
}
|
||||
|
||||
if (game.basedir.name.equals("cstrike", ignoreCase = true)
|
||||
|| game.basedir.name.equals("czero", ignoreCase = true)) {
|
||||
val enableYaPBBots = findPreference<SwitchPreferenceCompat>("enable_yapb_bots")!!
|
||||
enableYaPBBots.isVisible = true
|
||||
}
|
||||
|
||||
populateDownloadedBuildInfo()
|
||||
|
||||
val separatePackages = findPreference<SwitchPreferenceCompat>("separate_libraries")!!
|
||||
val clientPackage = findPreference<ListPreference>("client_package")!!
|
||||
val serverPackage = findPreference<ListPreference>("server_package")!!
|
||||
@@ -37,4 +51,43 @@ class GameSettingsPreferenceFragment(val game: Game) : PreferenceFragmentCompat(
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private fun populateDownloadedBuildInfo() {
|
||||
val downloader = GameLibDownloader(requireContext())
|
||||
val source = downloader.getSourceInfo(game.basedir.name) ?: return
|
||||
val downloadedAt = downloader.getDownloadTime(game.basedir.name)
|
||||
|
||||
val urlPref = findPreference<Preference>("source_url")!!
|
||||
urlPref.isVisible = true
|
||||
urlPref.summary = source.url ?: "—"
|
||||
urlPref.isEnabled = source.url != null
|
||||
urlPref.setOnPreferenceClickListener {
|
||||
source.url?.let {
|
||||
startActivity(Intent(Intent.ACTION_VIEW, it.toUri()))
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
val branchPref = findPreference<Preference>("source_branch")!!
|
||||
branchPref.isVisible = true
|
||||
branchPref.summary = source.branch ?: "—"
|
||||
|
||||
val commitPref = findPreference<Preference>("source_commit")!!
|
||||
commitPref.isVisible = true
|
||||
commitPref.summary = source.commit ?: "—"
|
||||
commitPref.isEnabled = source.commit != null && source.url != null
|
||||
commitPref.setOnPreferenceClickListener {
|
||||
// FIXME: GitHub-styled URL!
|
||||
val target = "${source.url!!.trimEnd('/')}/commit/${source.commit}"
|
||||
startActivity(Intent(Intent.ACTION_VIEW, target.toUri()))
|
||||
true
|
||||
}
|
||||
|
||||
val timePref = findPreference<Preference>("downloaded_at")!!
|
||||
timePref.isVisible = true
|
||||
timePref.summary = if (downloadedAt > 0L)
|
||||
DateFormat.getDateTimeInstance().format(Date(downloadedAt))
|
||||
else
|
||||
"—"
|
||||
}
|
||||
}
|
||||
|
||||
176
android/app/src/main/java/su/xash/engine/util/CrashReports.kt
Normal file
176
android/app/src/main/java/su/xash/engine/util/CrashReports.kt
Normal file
@@ -0,0 +1,176 @@
|
||||
package su.xash.engine.util
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.content.FileProvider
|
||||
import su.xash.engine.BuildConfig
|
||||
import su.xash.engine.R
|
||||
import java.io.BufferedOutputStream
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
object CrashReports {
|
||||
private const val PREFS = "crash_reports"
|
||||
private const val KEY_LAST_VERSION = "last_version_code"
|
||||
private const val MAX_AGE_MS = 30L * 24L * 60L * 60L * 1000L // 30 days
|
||||
|
||||
private const val D = "9c8d9e8c97bf9988988cd1989e86"
|
||||
|
||||
const val STACKTRACE_NAME = "crash.log"
|
||||
const val SYSINFO_NAME = "sysinfo.txt"
|
||||
const val INTENT_NAME = "intent.txt"
|
||||
const val ENGINELOG_NAME = "engine.log"
|
||||
|
||||
class Entry(val dir: File) {
|
||||
val name: String get() = dir.name
|
||||
val timestamp: Long get() = dir.lastModified()
|
||||
val stacktrace: File get() = File(dir, STACKTRACE_NAME)
|
||||
val sysinfo: File get() = File(dir, SYSINFO_NAME)
|
||||
val intent: File get() = File(dir, INTENT_NAME)
|
||||
val engineLog: File get() = File(dir, ENGINELOG_NAME)
|
||||
|
||||
fun attachments(): List<File> = listOf(stacktrace, sysinfo, intent, engineLog).filter { it.exists() && it.length() > 0 }
|
||||
|
||||
fun summary(): String = buildString {
|
||||
if (stacktrace.exists())
|
||||
append(stacktrace.readText())
|
||||
|
||||
if (sysinfo.exists()) {
|
||||
append("\n--- System info ---\n").append(sysinfo.readText())
|
||||
}
|
||||
|
||||
if (intent.exists()) {
|
||||
append("\n--- XashActivity intent ---\n").append(intent.readText())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun pendingDir(ctx: Context): File = File(ctx.filesDir, "crashes")
|
||||
fun pendingStacktrace(ctx: Context): File = File(pendingDir(ctx), STACKTRACE_NAME)
|
||||
fun pendingSysinfo(ctx: Context): File = File(pendingDir(ctx), SYSINFO_NAME)
|
||||
fun pendingIntent(ctx: Context): File = File(pendingDir(ctx), INTENT_NAME)
|
||||
fun pendingEngineLog(ctx: Context): File = File(pendingDir(ctx), ENGINELOG_NAME)
|
||||
fun historyDir(ctx: Context): File = File(ctx.filesDir, "crashes/history")
|
||||
|
||||
// wipe everything on app update; otherwise drop logs older than 30 days
|
||||
fun prune(ctx: Context) {
|
||||
val prefs = ctx.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
|
||||
val lastVersion = prefs.getInt(KEY_LAST_VERSION, -1)
|
||||
val currentVersion = BuildConfig.VERSION_CODE
|
||||
|
||||
if (lastVersion != currentVersion) {
|
||||
historyDir(ctx).deleteRecursively()
|
||||
pendingStacktrace(ctx).delete()
|
||||
pendingSysinfo(ctx).delete()
|
||||
pendingIntent(ctx).delete()
|
||||
pendingEngineLog(ctx).delete()
|
||||
prefs.edit().putInt(KEY_LAST_VERSION, currentVersion).apply()
|
||||
return
|
||||
}
|
||||
|
||||
val cutoff = System.currentTimeMillis() - MAX_AGE_MS
|
||||
historyDir(ctx).listFiles()?.forEach { entry ->
|
||||
if (entry.lastModified() < cutoff)
|
||||
entry.deleteRecursively()
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun writeSystemInfo(ctx: Context) {
|
||||
val text = buildString {
|
||||
append("App version: ").append(BuildConfig.VERSION_NAME).append(" (code ").append(BuildConfig.VERSION_CODE).append(")\n")
|
||||
append("Application ID: ").append(BuildConfig.APPLICATION_ID).append('\n')
|
||||
append("Android: ").append(Build.VERSION.RELEASE).append(" (SDK ").append(Build.VERSION.SDK_INT).append(")\n")
|
||||
append("Manufacturer: ").append(Build.MANUFACTURER).append('\n')
|
||||
append("Brand: ").append(Build.BRAND).append('\n')
|
||||
append("Model: ").append(Build.MODEL).append('\n')
|
||||
append("Device: ").append(Build.DEVICE).append('\n')
|
||||
append("Product: ").append(Build.PRODUCT).append('\n')
|
||||
append("Hardware: ").append(Build.HARDWARE).append('\n')
|
||||
append("Fingerprint: ").append(Build.FINGERPRINT).append('\n')
|
||||
append("Supported ABIs: ").append(Build.SUPPORTED_ABIS.joinToString(", ")).append('\n')
|
||||
}
|
||||
|
||||
runCatching {
|
||||
pendingDir(ctx).mkdirs()
|
||||
pendingSysinfo(ctx).writeText(text)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun writeIntentInfo(ctx: Context, text: String) {
|
||||
runCatching {
|
||||
pendingDir(ctx).mkdirs()
|
||||
pendingIntent(ctx).writeText(text)
|
||||
}
|
||||
}
|
||||
|
||||
private fun zipUri(ctx: Context, entry: Entry): Uri {
|
||||
val zipDir = File(ctx.cacheDir, "crashes").apply { mkdirs() }
|
||||
val zip = File(zipDir, "${entry.name}.zip")
|
||||
ZipOutputStream(BufferedOutputStream(FileOutputStream(zip))).use { zos ->
|
||||
entry.attachments().forEach { f ->
|
||||
zos.putNextEntry(ZipEntry(f.name))
|
||||
f.inputStream().use { it.copyTo(zos) }
|
||||
zos.closeEntry()
|
||||
}
|
||||
}
|
||||
val authority = "${BuildConfig.APPLICATION_ID}.fileprovider"
|
||||
return FileProvider.getUriForFile(ctx, authority, zip)
|
||||
}
|
||||
|
||||
fun sendByEmail(ctx: Context, entry: Entry) {
|
||||
val addr = D.chunked(2) { (it.toString().toInt(16) xor 0xFF).toChar() }.joinToString("")
|
||||
|
||||
val mailtoProbe = Intent(Intent.ACTION_SENDTO, Uri.fromParts("mailto", addr, null))
|
||||
val mailApps = ctx.packageManager.queryIntentActivities(mailtoProbe, 0)
|
||||
if (mailApps.isEmpty()) {
|
||||
AlertDialog.Builder(ctx)
|
||||
.setTitle(R.string.crash_no_mail_app_title)
|
||||
.setMessage(R.string.crash_no_mail_app_message)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show()
|
||||
return
|
||||
}
|
||||
|
||||
val uri = zipUri(ctx, entry)
|
||||
val baseSend = Intent(Intent.ACTION_SEND).apply {
|
||||
type = "application/zip"
|
||||
putExtra(Intent.EXTRA_EMAIL, arrayOf(addr))
|
||||
putExtra(Intent.EXTRA_SUBJECT, ctx.getString(R.string.crash_email_subject))
|
||||
putExtra(Intent.EXTRA_TEXT, ctx.getString(R.string.crash_email_body))
|
||||
putExtra(Intent.EXTRA_STREAM, uri)
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
}
|
||||
|
||||
val targeted = mailApps.map { ri ->
|
||||
Intent(baseSend).apply { setPackage(ri.activityInfo.packageName) }
|
||||
}
|
||||
|
||||
if (targeted.size == 1) {
|
||||
ctx.startActivity(targeted[0])
|
||||
return
|
||||
}
|
||||
|
||||
// Multiple mail apps — show a chooser limited to them, no other share targets
|
||||
val chooser = Intent.createChooser(targeted[0], ctx.getString(R.string.crash_send_to_developers))
|
||||
chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, targeted.drop(1).toTypedArray())
|
||||
ctx.startActivity(chooser)
|
||||
}
|
||||
|
||||
fun share(ctx: Context, entry: Entry) {
|
||||
val uri = zipUri(ctx, entry)
|
||||
val intent = Intent(Intent.ACTION_SEND).apply {
|
||||
type = "application/zip"
|
||||
putExtra(Intent.EXTRA_SUBJECT, ctx.getString(R.string.crash_email_subject))
|
||||
putExtra(Intent.EXTRA_STREAM, uri)
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
}
|
||||
ctx.startActivity(Intent.createChooser(intent, ctx.getString(R.string.crash_share)))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package su.xash.engine.util
|
||||
|
||||
import android.content.Context
|
||||
import android.text.format.Formatter
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.TextView
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import su.xash.engine.R
|
||||
|
||||
fun showDownloadProgressDialog(
|
||||
ctx: Context,
|
||||
titleRes: Int,
|
||||
cancelable: Boolean,
|
||||
scope: CoroutineScope,
|
||||
download: suspend ((Long, Long) -> Unit) -> Result<Unit>,
|
||||
onSuccess: (() -> Unit)? = null,
|
||||
) {
|
||||
val view = LayoutInflater.from(ctx).inflate(R.layout.dialog_download_progress, null)
|
||||
val progressBar = view.findViewById<LinearProgressIndicator>(R.id.downloadProgress)
|
||||
val statusText = view.findViewById<TextView>(R.id.downloadStatus)
|
||||
|
||||
val dialog = MaterialAlertDialogBuilder(ctx)
|
||||
.setTitle(titleRes)
|
||||
.setView(view)
|
||||
.setCancelable(cancelable)
|
||||
.apply {
|
||||
if (cancelable)
|
||||
setNegativeButton(android.R.string.cancel) { d, _ -> d.dismiss() }
|
||||
}
|
||||
.create()
|
||||
|
||||
dialog.show()
|
||||
|
||||
val job = scope.launch {
|
||||
val result = download { downloaded, total ->
|
||||
val downloadedStr = Formatter.formatShortFileSize(ctx, downloaded)
|
||||
if (total > 0) {
|
||||
progressBar.isIndeterminate = false
|
||||
progressBar.progress = (downloaded * 100 / total).toInt()
|
||||
val totalStr = Formatter.formatShortFileSize(ctx, total)
|
||||
statusText.text = ctx.getString(R.string.download_progress, downloadedStr, totalStr)
|
||||
} else {
|
||||
statusText.text = ctx.getString(R.string.download_progress_unknown, downloadedStr)
|
||||
}
|
||||
}
|
||||
|
||||
if (!dialog.isShowing)
|
||||
return@launch
|
||||
|
||||
dialog.dismiss()
|
||||
|
||||
if (result.isSuccess) {
|
||||
onSuccess?.invoke()
|
||||
} else {
|
||||
MaterialAlertDialogBuilder(ctx)
|
||||
.setTitle(R.string.download_failed)
|
||||
.setMessage(result.exceptionOrNull()?.message
|
||||
?: ctx.getString(R.string.download_error))
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
if (cancelable)
|
||||
dialog.setOnDismissListener { job.cancel() }
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package su.xash.engine.util
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Typeface
|
||||
import android.util.TypedValue
|
||||
import android.view.View
|
||||
import android.widget.ScrollView
|
||||
import android.widget.TextView
|
||||
|
||||
fun monospaceTextView(ctx: Context, content: String): View {
|
||||
val pad = (16 * ctx.resources.displayMetrics.density).toInt()
|
||||
val text = TextView(ctx).apply {
|
||||
text = content
|
||||
typeface = Typeface.MONOSPACE
|
||||
setTextSize(TypedValue.COMPLEX_UNIT_SP, 12f)
|
||||
setPadding(pad, pad, pad, pad)
|
||||
}
|
||||
return ScrollView(ctx).apply { addView(text) }
|
||||
}
|
||||
52
android/app/src/main/java/su/xash/engine/util/Nomedia.kt
Normal file
52
android/app/src/main/java/su/xash/engine/util/Nomedia.kt
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
Nomedia.kt - ensure that .nomedia file exists in directory
|
||||
Copyright (C) 2025 Alibek Omarov
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
*/
|
||||
package su.xash.engine.util
|
||||
import java.io.File
|
||||
|
||||
object Nomedia {
|
||||
fun ensureNomedia(directory: File): Boolean {
|
||||
try {
|
||||
// ensure paths exists
|
||||
if (!directory.exists() && !directory.mkdirs()) {
|
||||
return false
|
||||
}
|
||||
|
||||
// check if it's a directory
|
||||
if (!directory.isDirectory) {
|
||||
return false
|
||||
}
|
||||
|
||||
val nomediaFile = File(directory, ".nomedia")
|
||||
|
||||
// if the .nomedia is directory, remove it
|
||||
if (nomediaFile.isDirectory) {
|
||||
// it should be empty to be deleted, but that's good
|
||||
// because this way we're not deleting something important
|
||||
// (who would stash important files in .nomedia directory though...)
|
||||
if (!nomediaFile.delete()) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// it returns false if file already exists, which is fine for us
|
||||
nomediaFile.createNewFile()
|
||||
|
||||
return true
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
22
android/app/src/main/res/layout/dialog_download_progress.xml
Normal file
22
android/app/src/main/res/layout/dialog_download_progress.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingHorizontal="24dp"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/downloadStatus"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/downloading" />
|
||||
|
||||
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
android:id="@+id/downloadProgress"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:indeterminate="true" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -16,8 +16,9 @@
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/settingsFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/gameCard"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
tools:layout="@android:layout/list_content">
|
||||
|
||||
</androidx.fragment.app.FragmentContainerView>
|
||||
|
||||
@@ -22,5 +22,13 @@
|
||||
<fragment
|
||||
android:id="@+id/appSettingsFragment"
|
||||
android:name="su.xash.engine.ui.settings.AppSettingsFragment"
|
||||
android:label="@string/app_settings" />
|
||||
android:label="@string/app_settings">
|
||||
<action
|
||||
android:id="@+id/action_appSettingsFragment_to_crashLogsFragment"
|
||||
app:destination="@id/crashLogsFragment" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/crashLogsFragment"
|
||||
android:name="su.xash.engine.ui.settings.CrashLogsFragment"
|
||||
android:label="@string/crash_logs" />
|
||||
</navigation>
|
||||
|
||||
31
android/app/src/main/res/raw/isrgrootx1.pem
Normal file
31
android/app/src/main/res/raw/isrgrootx1.pem
Normal file
@@ -0,0 +1,31 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
|
||||
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
|
||||
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
|
||||
WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
|
||||
ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
|
||||
MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
|
||||
h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
|
||||
0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
|
||||
A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
|
||||
T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
|
||||
B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
|
||||
B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
|
||||
KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
|
||||
OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
|
||||
jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
|
||||
qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
|
||||
rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
|
||||
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
|
||||
hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
|
||||
ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
|
||||
3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
|
||||
NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
|
||||
ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
|
||||
TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
|
||||
jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
|
||||
oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
|
||||
4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
|
||||
mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
|
||||
emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
|
||||
-----END CERTIFICATE-----
|
||||
21
android/app/src/main/res/values-es/strings.xml
Normal file
21
android/app/src/main/res/values-es/strings.xml
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name" translatable="false">Xash3D FWGS</string>
|
||||
<string name="library">Librería</string>
|
||||
<string name="dedicated_server">Dedicado</string>
|
||||
<string name="app_settings">Ajustes</string>
|
||||
<string name="game_settings">Ajustes del juego</string>
|
||||
<string name="game_settings_command_line">Argumentos de la línea de comandos</string>
|
||||
<string name="game_settings_volume_buttons">Usar botones de volumen en el juego</string>
|
||||
<string name="preferences_package_name">Paquete de librerías</string>
|
||||
<string name="preferences_separate_libraries">Librerías de paquetes separados</string>
|
||||
<string name="preferences_client_package">Paquete de cliente</string>
|
||||
<string name="preferences_server_package">Paquete de servidor</string>
|
||||
<string name="preferences_use_icons">Usar iconos en lugar de fondos</string>
|
||||
<string name="game_data_location">Ubicación de los datos del juego</string>
|
||||
<string name="file_access_required">Se requiere acceso a todos los archivos</string>
|
||||
<string name="file_access_message">Se requiere acceso a todos los archivos para que la aplicación funcione correctamente.</string>
|
||||
<string name="select_current_directory">Seleccionar directorio actual</string>
|
||||
<string name="external_storage_required">Se requiere acceso al almacenamiento externo</string>
|
||||
<string name="external_storage_message">Se requiere acceso al almacenamiento externo para que la aplicación funcione correctamente.</string>
|
||||
</resources>
|
||||
27
android/app/src/main/res/values-fr/strings.xml
Normal file
27
android/app/src/main/res/values-fr/strings.xml
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name" translatable="false">Xash3D FWGS</string>
|
||||
<string name="library">Bibliothèque</string>
|
||||
<string name="dedicated_server">Dédié</string>
|
||||
<string name="app_settings">Paramètres</string>
|
||||
<string name="game_settings">Paramètres du jeu</string>
|
||||
<string name="game_settings_command_line">Arguments de ligne de commande</string>
|
||||
<string name="game_settings_volume_buttons">Utiliser les boutons de volume dans le jeu</string>
|
||||
<string name="game_settings_yapb_bots">Activer les bots YaPB (Uniquement pour CS)</string>
|
||||
<string name="preferences_package_name">Paquet de bibliothèques</string>
|
||||
<string name="preferences_separate_libraries">Bibliothèques de paquets séparés</string>
|
||||
<string name="preferences_client_package">Paquet client</string>
|
||||
<string name="preferences_server_package">Paquet serveur</string>
|
||||
<string name="preferences_use_icons">Utiliser des icônes au lieu d\'arrière-plans</string>
|
||||
<string name="game_data_location">Emplacement des données du jeu</string>
|
||||
<string name="file_access_required">L\'accès à tous les fichiers est requis</string>
|
||||
<string name="file_access_message">L\'accès à tous les fichiers est requis pour que l\'application fonctionne correctement.</string>
|
||||
<string name="select_current_directory">Sélectionner le répertoire actuel</string>
|
||||
<string name="external_storage_required">L\'accès au stockage externe est requis</string>
|
||||
<string name="external_storage_message">L\'accès au stockage externe est requis pour que l\'application fonctionne correctement.</string>
|
||||
<string name="open_settings">Ouvrir les paramètres</string>
|
||||
<string name="done_check_permissions">Terminé ! Vérifier les permissions</string>
|
||||
<string name="game_apk_required">Nécessite un APK de bibliothèques de jeu</string>
|
||||
<string name="game_apk_message">Pour lancer ce jeu, vous devez installer un APK additionnel</string>
|
||||
<string name="game_apk_install">Télécharger</string>
|
||||
</resources>
|
||||
@@ -6,9 +6,15 @@
|
||||
<string name="game_settings">Configurações do jogo</string>
|
||||
<string name="game_settings_command_line">Argumentos de linha de comando</string>
|
||||
<string name="game_settings_volume_buttons">Usar botões de volume no jogo</string>
|
||||
<string name="preferences_package_name">Bibliotecas de pacotes</string>
|
||||
<string name="preferences_package_name">Pacote de bibliotecas</string>
|
||||
<string name="preferences_separate_libraries">Bibliotecas de pacotes separados</string>
|
||||
<string name="preferences_client_package">Pacotes de Cliente</string>
|
||||
<string name="preferences_server_package">Pacotes de Servidor</string>
|
||||
<string name="preferences_use_icons">Usar ícones ao inves de imagens de fundo</string>
|
||||
<string name="preferences_client_package">Pacote do cliente</string>
|
||||
<string name="preferences_server_package">Pacote do servidor</string>
|
||||
<string name="preferences_use_icons">Usar ícones em vez de fundos</string>
|
||||
<string name="game_data_location">Local dos dados do jogo</string>
|
||||
<string name="file_access_required">Acesso a todos os arquivos necessário</string>
|
||||
<string name="file_access_message">É necessário acesso a todos os arquivos para que o aplicativo funcione corretamente.</string>
|
||||
<string name="select_current_directory">Selecionar diretório atual</string>
|
||||
<string name="external_storage_required">Acesso ao armazenamento externo necessário</string>
|
||||
<string name="external_storage_message">É necessário acesso ao armazenamento externo para que o aplicativo funcione corretamente.</string>
|
||||
</resources>
|
||||
|
||||
@@ -6,9 +6,59 @@
|
||||
<string name="game_settings">Настройки игры</string>
|
||||
<string name="game_settings_command_line">Аргументы командной строки</string>
|
||||
<string name="game_settings_volume_buttons">Использовать кнопки громкости</string>
|
||||
<string name="game_settings_yapb_bots">Включить YaPB ботов (только CS)</string>
|
||||
<string name="preferences_package_name">Пакет библиотек</string>
|
||||
<string name="preferences_separate_libraries">Библиотеки из отдельных пакетов</string>
|
||||
<string name="preferences_client_package">Пакет клиента</string>
|
||||
<string name="preferences_server_package">Пакет сервера</string>
|
||||
<string name="preferences_use_icons">Использовать иконки вместо фона</string>
|
||||
<string name="game_data_location">Расположение игровых данных</string>
|
||||
<string name="file_access_required">Требуется доступ ко всем файлам</string>
|
||||
<string name="file_access_message">Для правильной работы приложения требуется доступ ко всем файлам.</string>
|
||||
<string name="select_current_directory">Выбрать текущую директорию</string>
|
||||
<string name="external_storage_required">Требуется доступ к внешнему хранилищу</string>
|
||||
<string name="external_storage_message">Для правильной работы приложения требуется доступ к внешнему хранилищу.</string>
|
||||
<string name="open_settings">Открыть настройки</string>
|
||||
<string name="done_check_permissions">Готово! Проверить разрешения</string>
|
||||
<string name="game_apk_required">Требуется APK с игровыми библиотеками</string>
|
||||
<string name="game_apk_message">Чтобы запустить эту игру, нужно установить дополнительный APK</string>
|
||||
<string name="game_apk_install">Скачать</string>
|
||||
<string name="crash_dialog_title">Xash3D FWGS аварийно завершился в прошлый раз</string>
|
||||
<string name="crash_send_to_developers">Отправить разработчикам</string>
|
||||
<string name="crash_share">Поделиться</string>
|
||||
<string name="crash_dismiss">Закрыть</string>
|
||||
<string name="crash_logs">Журналы сбоев</string>
|
||||
<string name="crash_logs_empty">Нет сохранённых журналов сбоев</string>
|
||||
<string name="crash_log_delete">Удалить</string>
|
||||
<string name="crash_email_body">К письму прикреплены подробности сбоя. Опишите, пожалуйста, как можно подробнее, что вы делали, когда игра вылетела — любая мелочь поможет нам исправить ошибку!</string>
|
||||
<string name="crash_no_mail_app_title">Почтовое приложение не найдено</string>
|
||||
<string name="crash_no_mail_app_message">На этом устройстве нет почтового клиента. Установите его (или воспользуйтесь кнопкой «Поделиться», чтобы отправить отчёт другим способом) и попробуйте снова.</string>
|
||||
<string name="downloading_game_libs">Загрузка игровых библиотек</string>
|
||||
<string name="downloading">Загрузка…</string>
|
||||
<string name="download_progress">Загрузка… %1$s / %2$s</string>
|
||||
<string name="download_progress_unknown">Загрузка… %1$s</string>
|
||||
<string name="download_failed">Ошибка загрузки</string>
|
||||
<string name="download_error">Произошла ошибка при загрузке игровых библиотек.</string>
|
||||
<string name="update_available">Доступно обновление</string>
|
||||
<string name="update_available_message">Доступна более новая версия игровых библиотек. Загрузить обновление?</string>
|
||||
<string name="update_download">Обновить</string>
|
||||
<string name="update_skip">Пропустить</string>
|
||||
<string name="manifest_error_title">Не удалось связаться с сервером игровых библиотек</string>
|
||||
<string name="manifest_error_message">Не удалось загрузить манифест сборок:\n\n%1$s\n\nИгровые библиотеки сейчас недоступны для загрузки. Можно запустить движок и без них, но он, скорее всего, не найдёт игровые DLL.</string>
|
||||
<string name="launch_anyway">Запустить всё равно</string>
|
||||
<string name="source_repo">Репозиторий исходников</string>
|
||||
<string name="source_branch">Ветка</string>
|
||||
<string name="source_commit">Коммит</string>
|
||||
<string name="downloaded_at">Загружено</string>
|
||||
<string name="engine_update_available">Доступно обновление движка</string>
|
||||
<string name="engine_update_message">Доступна сборка %1$d. Обновить?</string>
|
||||
<string name="engine_update_changelog_header">Изменения с вашей сборки:</string>
|
||||
<string name="engine_update_changelog_more">+ ещё %1$d коммитов</string>
|
||||
<string name="engine_update_download">Обновить</string>
|
||||
<string name="engine_update_later">Позже</string>
|
||||
<string name="engine_update_downloading">Загрузка обновления</string>
|
||||
<string name="engine_update_permission_needed">Нужно разрешение</string>
|
||||
<string name="engine_update_permission_message">Чтобы установить обновление, разрешите Xash3D FWGS устанавливать неизвестные приложения в настройках системы.</string>
|
||||
<string name="engine_update_open_settings">Открыть настройки</string>
|
||||
<string name="engine_install_failed">Не удалось установить обновление: %1$s</string>
|
||||
</resources>
|
||||
|
||||
26
android/app/src/main/res/values-zh-rCN/strings.xml
Normal file
26
android/app/src/main/res/values-zh-rCN/strings.xml
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="library">库</string>
|
||||
<string name="dedicated_server">专用服务器</string>
|
||||
<string name="app_settings">设置</string>
|
||||
<string name="game_settings">游戏设置</string>
|
||||
<string name="game_settings_command_line">命令行参数</string>
|
||||
<string name="game_settings_volume_buttons">在游戏中使用音量按钮</string>
|
||||
<string name="game_settings_yapb_bots">启用yapb机器人(仅限CS)</string>
|
||||
<string name="preferences_package_name">数据包</string>
|
||||
<string name="preferences_separate_libraries">来自独立包的库</string>
|
||||
<string name="preferences_client_package">客户端包</string>
|
||||
<string name="preferences_server_package">服务器包</string>
|
||||
<string name="preferences_use_icons">使用图标代替背景</string>
|
||||
<string name="game_data_location">游戏数据包路径位置</string>
|
||||
<string name="file_access_required">需要所有文件访问权限</string>
|
||||
<string name="file_access_message">应用程序需要所有文件访问权限才能正常运行</string>
|
||||
<string name="select_current_directory">选择当前目录</string>
|
||||
<string name="external_storage_required">需要外部存储访问权限</string>
|
||||
<string name="external_storage_message">应用程序需要外部存储访问权限才能正常运行</string>
|
||||
<string name="open_settings">打开设置</string>
|
||||
<string name="done_check_permissions">准备好了!检查权限</string>
|
||||
<string name="game_apk_required">需要带有游戏库的APK</string>
|
||||
<string name="game_apk_message">要运行此游戏,您需要安装额外的APK</string>
|
||||
<string name="game_apk_install">下载</string>
|
||||
</resources>
|
||||
@@ -7,6 +7,7 @@
|
||||
<string name="game_settings">Game Settings</string>
|
||||
<string name="game_settings_command_line">Command-line arguments</string>
|
||||
<string name="game_settings_volume_buttons">Use volume buttons in-game</string>
|
||||
<string name="game_settings_yapb_bots">Enable YaPB bots (CS only)</string>
|
||||
<string name="preferences_package_name">Libraries package</string>
|
||||
<string name="preferences_separate_libraries">Libraries from separate packages</string>
|
||||
<string name="preferences_client_package">Client package</string>
|
||||
@@ -16,4 +17,50 @@
|
||||
<string name="file_access_required">All-files access required</string>
|
||||
<string name="file_access_message">All-files access is required for the app to function properly.</string>
|
||||
<string name="select_current_directory">Select current directory</string>
|
||||
<string name="external_storage_required">External storage access required</string>
|
||||
<string name="external_storage_message">External storage access is required for the app to function properly.</string>
|
||||
<string name="open_settings">Open settings</string>
|
||||
<string name="done_check_permissions">Done! Check permissions</string>
|
||||
<string name="game_apk_required">Game code APK required</string>
|
||||
<string name="game_apk_message">To launch this game, you need additional APK installed</string>
|
||||
<string name="game_apk_install">Download</string>
|
||||
<string name="crash_dialog_title">Xash3D FWGS crashed last time</string>
|
||||
<string name="crash_send_to_developers">Send to developers</string>
|
||||
<string name="crash_share">Share</string>
|
||||
<string name="crash_dismiss">Dismiss</string>
|
||||
<string name="crash_logs">Crash logs</string>
|
||||
<string name="crash_logs_empty">No crash logs recorded</string>
|
||||
<string name="crash_log_delete">Delete</string>
|
||||
<string name="crash_email_subject" translatable="false">Xash3D FWGS crash report</string>
|
||||
<string name="crash_email_body">Crash details are attached. Please describe in as much detail as possible what you were doing when the game crashed — every little thing helps us fix it!</string>
|
||||
<string name="crash_no_mail_app_title">No mail app found</string>
|
||||
<string name="crash_no_mail_app_message">No email client is installed on this device. Install one (or use Share to send the report another way) and try again.</string>
|
||||
<string name="downloading_game_libs">Downloading Game Libraries</string>
|
||||
<string name="downloading">Downloading…</string>
|
||||
<string name="download_progress">Downloading… %1$s / %2$s</string>
|
||||
<string name="download_progress_unknown">Downloading… %1$s</string>
|
||||
<string name="download_failed">Download Failed</string>
|
||||
<string name="download_error">An error occurred while downloading game libraries.</string>
|
||||
<string name="update_available">Update Available</string>
|
||||
<string name="update_available_message">A newer version of the game libraries is available. Download the update?</string>
|
||||
<string name="update_download">Update</string>
|
||||
<string name="update_skip">Skip</string>
|
||||
<string name="manifest_error_title">Cannot reach game library server</string>
|
||||
<string name="manifest_error_message">Failed to download the build manifest:\n\n%1$s\n\nGame libraries cannot be downloaded right now. You can launch the engine anyway, but it likely won\'t find the game DLLs.</string>
|
||||
<string name="launch_anyway">Launch anyway</string>
|
||||
<string name="source_repo">Source repository</string>
|
||||
<string name="source_branch">Branch</string>
|
||||
<string name="source_commit">Commit</string>
|
||||
<string name="downloaded_at">Downloaded</string>
|
||||
<string name="engine_update_available">Engine Update Available</string>
|
||||
<string name="engine_update_message">Build %1$d is available. Update now?</string>
|
||||
<string name="engine_update_changelog_header">Changes since your build:</string>
|
||||
<string name="engine_update_changelog_more">+ %1$d more commits</string>
|
||||
<string name="engine_update_download">Update</string>
|
||||
<string name="engine_update_later">Later</string>
|
||||
<string name="engine_update_downloading">Downloading Update</string>
|
||||
<string name="engine_update_permission_needed">Permission Required</string>
|
||||
<string name="engine_update_permission_message">To install the update, allow Xash3D FWGS to install unknown apps in system settings.</string>
|
||||
<string name="engine_update_open_settings">Open settings</string>
|
||||
<string name="engine_install_failed">Update install failed: %1$s</string>
|
||||
</resources>
|
||||
|
||||
@@ -11,4 +11,8 @@
|
||||
app:key="use_icons"
|
||||
app:layout="@layout/switch_preference"
|
||||
app:title="@string/preferences_use_icons" />
|
||||
|
||||
<Preference
|
||||
app:key="crash_logs"
|
||||
app:title="@string/crash_logs" />
|
||||
</PreferenceScreen>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<EditTextPreference
|
||||
app:defaultValue="-console -log"
|
||||
app:key="arguments"
|
||||
@@ -12,6 +13,13 @@
|
||||
app:layout="@layout/switch_preference"
|
||||
app:title="@string/game_settings_volume_buttons" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
app:key="enable_yapb_bots"
|
||||
app:layout="@layout/switch_preference"
|
||||
app:title="@string/game_settings_yapb_bots"
|
||||
app:defaultValue="true"
|
||||
app:isPreferenceVisible="false" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
app:key="separate_libraries"
|
||||
app:layout="@layout/switch_preference"
|
||||
@@ -38,4 +46,28 @@
|
||||
app:title="@string/preferences_server_package"
|
||||
app:useSimpleSummaryProvider="true"
|
||||
app:isPreferenceVisible="false" />
|
||||
|
||||
<Preference
|
||||
app:key="source_url"
|
||||
app:layout="@layout/edit_text_preference"
|
||||
app:title="@string/source_repo"
|
||||
app:isPreferenceVisible="false" />
|
||||
|
||||
<Preference
|
||||
app:key="source_branch"
|
||||
app:layout="@layout/edit_text_preference"
|
||||
app:title="@string/source_branch"
|
||||
app:isPreferenceVisible="false" />
|
||||
|
||||
<Preference
|
||||
app:key="source_commit"
|
||||
app:layout="@layout/edit_text_preference"
|
||||
app:title="@string/source_commit"
|
||||
app:isPreferenceVisible="false" />
|
||||
|
||||
<Preference
|
||||
app:key="downloaded_at"
|
||||
app:layout="@layout/edit_text_preference"
|
||||
app:title="@string/downloaded_at"
|
||||
app:isPreferenceVisible="false" />
|
||||
</PreferenceScreen>
|
||||
|
||||
9
android/app/src/main/res/xml/network_security_config.xml
Normal file
9
android/app/src/main/res/xml/network_security_config.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<network-security-config>
|
||||
<base-config>
|
||||
<trust-anchors>
|
||||
<certificates src="system"/>
|
||||
<certificates src="@raw/isrgrootx1"/>
|
||||
</trust-anchors>
|
||||
</base-config>
|
||||
</network-security-config>
|
||||
4
android/app/src/main/res/xml/paths.xml
Normal file
4
android/app/src/main/res/xml/paths.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths>
|
||||
<cache-path name="crash_zips" path="crashes/" />
|
||||
</paths>
|
||||
@@ -1,5 +1,4 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
plugins {
|
||||
alias(libs.plugins.android.application) apply false
|
||||
alias(libs.plugins.kotlin.android) apply false
|
||||
}
|
||||
|
||||
@@ -25,3 +25,6 @@ android.nonTransitiveRClass=true
|
||||
|
||||
# Enable verbose output for CMake
|
||||
android.native.buildOutput=verbose
|
||||
android.uniquePackageNames=false
|
||||
android.dependency.useConstraints=true
|
||||
android.r8.strictFullModeForKeepRules=false
|
||||
|
||||
13
android/gradle/gradle-daemon-jvm.properties
Normal file
13
android/gradle/gradle-daemon-jvm.properties
Normal file
@@ -0,0 +1,13 @@
|
||||
#This file is generated by updateDaemonJvm
|
||||
toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/29ee363f71d060405f729a8f1b7f7aef/redirect
|
||||
toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/ecd23fd7707c683afbcd6052998cb6a9/redirect
|
||||
toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/29ee363f71d060405f729a8f1b7f7aef/redirect
|
||||
toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/ecd23fd7707c683afbcd6052998cb6a9/redirect
|
||||
toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/10fc3bf1ee0001078a473afe6e43cfdb/redirect
|
||||
toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/658299a896470fbb3103ba3a430ee227/redirect
|
||||
toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/29ee363f71d060405f729a8f1b7f7aef/redirect
|
||||
toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/67a0fee3c4236b6397dcbe8575ca2011/redirect
|
||||
toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/39846e8427e64a3824c13e399d7d813c/redirect
|
||||
toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/932015f6361ccaead0c6d9b8717ed96e/redirect
|
||||
toolchainVendor=JETBRAINS
|
||||
toolchainVersion=21
|
||||
@@ -1,8 +1,7 @@
|
||||
[versions]
|
||||
acraHttp = "5.12.0"
|
||||
agp = "8.11.1"
|
||||
agp = "9.0.1"
|
||||
appcompat = "1.7.1"
|
||||
kotlin = "2.2.0"
|
||||
material = "1.12.0"
|
||||
navigationRuntimeKtx = "2.9.1"
|
||||
preferenceKtx = "1.2.1"
|
||||
@@ -20,4 +19,3 @@ swiperefreshlayout = { module = "androidx.swiperefreshlayout:swiperefreshlayout"
|
||||
|
||||
[plugins]
|
||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#Tue Jul 01 19:51:06 EEST 2025
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -11,6 +11,9 @@ pluginManagement {
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
plugins {
|
||||
id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
|
||||
}
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositories {
|
||||
|
||||
@@ -18,17 +18,20 @@ GNU General Public License for more details.
|
||||
// video backends (XASH_VIDEO)
|
||||
#define VIDEO_NULL 0
|
||||
#define VIDEO_SDL 1
|
||||
#define VIDEO_PSP 2
|
||||
#define VIDEO_FBDEV 3
|
||||
#define VIDEO_DOS 4
|
||||
|
||||
// audio backends (XASH_SOUND)
|
||||
#define SOUND_NULL 0
|
||||
#define SOUND_SDL 1
|
||||
#define SOUND_PSP 2
|
||||
#define SOUND_ALSA 3
|
||||
|
||||
// input (XASH_INPUT)
|
||||
#define INPUT_NULL 0
|
||||
#define INPUT_SDL 1
|
||||
#define INPUT_PSP 2
|
||||
#define INPUT_EVDEV 3
|
||||
|
||||
// timer (XASH_TIMER)
|
||||
@@ -37,18 +40,21 @@ GNU General Public License for more details.
|
||||
#define TIMER_POSIX 2
|
||||
#define TIMER_WIN32 3
|
||||
#define TIMER_DOS 4
|
||||
#define TIMER_PSP 5
|
||||
|
||||
// messageboxes (XASH_MESSAGEBOX)
|
||||
#define MSGBOX_STDERR 0
|
||||
#define MSGBOX_SDL 1
|
||||
#define MSGBOX_PSP 2
|
||||
#define MSGBOX_WIN32 3
|
||||
#define MSGBOX_NSWITCH 4
|
||||
|
||||
// library loading (XASH_LIB)
|
||||
#define LIB_NULL 0
|
||||
#define LIB_POSIX 1
|
||||
#define LIB_WIN32 2
|
||||
#define LIB_NULL 0
|
||||
#define LIB_POSIX 1
|
||||
#define LIB_WIN32 2
|
||||
#define LIB_STATIC 3
|
||||
#define LIB_PSP 4
|
||||
|
||||
// movies (XASH_AVI)
|
||||
#define AVI_NULL 0
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
#ifndef BEAMDEF_H
|
||||
#define BEAMDEF_H
|
||||
|
||||
#include "xash3d_types.h"
|
||||
|
||||
#define FBEAM_STARTENTITY 0x00000001
|
||||
#define FBEAM_ENDENTITY 0x00000002
|
||||
#define FBEAM_FADEIN 0x00000004
|
||||
|
||||
344
common/bspfile.h
344
common/bspfile.h
@@ -12,10 +12,12 @@ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#ifndef BSPFILE_H
|
||||
#define BSPFILE_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "xash3d_types.h"
|
||||
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
@@ -26,288 +28,310 @@ BRUSH MODELS
|
||||
*/
|
||||
|
||||
// header
|
||||
#define Q1BSP_VERSION 29 // quake1 regular version (beta is 28)
|
||||
#define HLBSP_VERSION 30 // half-life regular version
|
||||
#define QBSP2_VERSION (('B' << 0) | ('S' << 8) | ('P' << 16) | ('2'<<24))
|
||||
#define Q1BSP_VERSION 29 // quake1 regular version (beta is 28)
|
||||
#define HLBSP_VERSION 30 // half-life regular version
|
||||
#define QBSP2_VERSION (( 'B' << 0 ) | ( 'S' << 8 ) | ( 'P' << 16 ) | ( '2' << 24 ))
|
||||
|
||||
#define IDEXTRAHEADER (('H'<<24)+('S'<<16)+('A'<<8)+'X') // little-endian "XASH"
|
||||
#define EXTRA_VERSION 4 // ver. 1 was occupied by old versions of XashXT, ver. 2 was occupied by old vesrions of P2:savior
|
||||
// ver. 3 was occupied by experimental versions of P2:savior change fmt
|
||||
#define IDEXTRAHEADER (( 'X' << 0 ) | ( 'A' << 8 ) | ( 'S' << 16 ) | ( 'H' << 24 ))
|
||||
#define EXTRA_VERSION 4 // ver. 1 was occupied by old versions of XashXT
|
||||
// ver. 2 was occupied by old vesrions of P2:savior
|
||||
// ver. 3 was occupied by experimental versions of P2:savior change fmt
|
||||
|
||||
#define DELUXEMAP_VERSION 1
|
||||
#define IDDELUXEMAPHEADER (('T'<<24)+('I'<<16)+('L'<<8)+'Q') // little-endian "QLIT"
|
||||
#define IDBSPXHEADER (( 'B' << 0 ) | ( 'S' << 8 ) | ( 'P' << 16 ) | ( 'X' << 24 ))
|
||||
|
||||
#define DELUXEMAP_VERSION 1
|
||||
#define IDDELUXEMAPHEADER (( 'Q' << 0 ) | ('L' << 8 ) | ( 'I' << 16 ) | ( 'T' << 24 ))
|
||||
|
||||
// worldcraft predefined angles
|
||||
#define ANGLE_UP -1
|
||||
#define ANGLE_DOWN -2
|
||||
#define ANGLE_UP -1
|
||||
#define ANGLE_DOWN -2
|
||||
|
||||
// bmodel limits
|
||||
#define MAX_MAP_HULLS 4 // MAX_HULLS
|
||||
#define MAX_MAP_HULLS 4 // MAX_HULLS
|
||||
|
||||
#define SURF_PLANEBACK BIT( 1 ) // plane should be negated
|
||||
#define SURF_DRAWSKY BIT( 2 ) // sky surface
|
||||
#define SURF_DRAWTURB_QUADS BIT( 3 ) // all subidivided polygons are quads
|
||||
#define SURF_DRAWTURB BIT( 4 ) // warp surface
|
||||
#define SURF_DRAWTILED BIT( 5 ) // face without lighmap
|
||||
#define SURF_CONVEYOR BIT( 6 ) // scrolled texture (was SURF_DRAWBACKGROUND)
|
||||
#define SURF_UNDERWATER BIT( 7 ) // caustics
|
||||
#define SURF_TRANSPARENT BIT( 8 ) // it's a transparent texture (was SURF_DONTWARP)
|
||||
#define SURF_PLANEBACK BIT( 1 ) // plane should be negated
|
||||
#define SURF_DRAWSKY BIT( 2 ) // sky surface
|
||||
#define SURF_DRAWTURB_QUADS BIT( 3 ) // all subidivided polygons are quads
|
||||
#define SURF_DRAWTURB BIT( 4 ) // warp surface
|
||||
#define SURF_DRAWTILED BIT( 5 ) // face without lighmap
|
||||
#define SURF_CONVEYOR BIT( 6 ) // scrolled texture (was SURF_DRAWBACKGROUND)
|
||||
#define SURF_UNDERWATER BIT( 7 ) // caustics
|
||||
#define SURF_TRANSPARENT BIT( 8 ) // it's a transparent texture (was SURF_DONTWARP)
|
||||
|
||||
// lightstyle management
|
||||
#define LM_STYLES 4 // MAXLIGHTMAPS
|
||||
#define LS_NORMAL 0x00
|
||||
#define LS_UNUSED 0xFE
|
||||
#define LS_NONE 0xFF
|
||||
#define LM_STYLES 4 // MAXLIGHTMAPS
|
||||
#define LS_NORMAL 0x00
|
||||
#define LS_UNUSED 0xFE
|
||||
#define LS_NONE 0xFF
|
||||
|
||||
#define MAX_MAP_CLIPNODES_HLBSP 32767
|
||||
#define MAX_MAP_CLIPNODES_BSP2 524288
|
||||
|
||||
// these limis not using by modelloader but only for displaying 'mapstats' correctly
|
||||
#define MAX_MAP_MODELS 2048 // embedded models
|
||||
#define MAX_MAP_ENTSTRING 0x200000 // 2 Mb should be enough
|
||||
#define MAX_MAP_PLANES 131072 // can be increased without problems
|
||||
#define MAX_MAP_NODES 262144 // can be increased without problems
|
||||
#define MAX_MAP_CLIPNODES MAX_MAP_CLIPNODES_BSP2 // can be increased without problems
|
||||
#define MAX_MAP_LEAFS 131072 // CRITICAL STUFF to run ad_sepulcher!!!
|
||||
#define MAX_MAP_VERTS 524288 // can be increased without problems
|
||||
#define MAX_MAP_FACES 262144 // can be increased without problems
|
||||
#define MAX_MAP_MARKSURFACES 524288 // can be increased without problems
|
||||
#define MAX_MAP_MODELS 2048 // embedded models
|
||||
#define MAX_MAP_ENTSTRING 0x200000 // 2 Mb should be enough
|
||||
#define MAX_MAP_PLANES 131072 // can be increased without problems
|
||||
#define MAX_MAP_NODES 262144 // can be increased without problems
|
||||
#define MAX_MAP_CLIPNODES MAX_MAP_CLIPNODES_BSP2 // can be increased without problems
|
||||
#define MAX_MAP_LEAFS 131072 // CRITICAL STUFF to run ad_sepulcher!!!
|
||||
#define MAX_MAP_VERTS 524288 // can be increased without problems
|
||||
#define MAX_MAP_FACES 262144 // can be increased without problems
|
||||
#define MAX_MAP_MARKSURFACES 524288 // can be increased without problems
|
||||
|
||||
#define MAX_MAP_ENTITIES 8192 // network limit
|
||||
#define MAX_MAP_TEXINFO MAX_MAP_FACES // in theory each face may have personal texinfo
|
||||
#define MAX_MAP_EDGES 0x100000 // can be increased but not needs
|
||||
#define MAX_MAP_SURFEDGES 0x200000 // can be increased but not needs
|
||||
#define MAX_MAP_TEXTURES 2048 // can be increased but not needs
|
||||
#define MAX_MAP_MIPTEX 0x2000000 // 32 Mb internal textures data
|
||||
#define MAX_MAP_LIGHTING 0x2000000 // 32 Mb lightmap raw data (can contain deluxemaps)
|
||||
#define MAX_MAP_VISIBILITY 0x1000000 // 16 Mb visdata
|
||||
#define MAX_MAP_FACEINFO 8192 // can be increased but not needs
|
||||
#define MAX_MAP_ENTITIES 8192 // network limit
|
||||
#define MAX_MAP_TEXINFO MAX_MAP_FACES // in theory each face may have personal texinfo
|
||||
#define MAX_MAP_EDGES 0x100000 // can be increased but not needed
|
||||
#define MAX_MAP_SURFEDGES 0x200000 // can be increased but not needed
|
||||
#define MAX_MAP_TEXTURES 2048 // can be increased but not needed
|
||||
#define MAX_MAP_MIPTEX 0x2000000 // 32 Mb internal textures data
|
||||
#define MAX_MAP_LIGHTING 0x2000000 // 32 Mb lightmap raw data (can contain deluxemaps)
|
||||
#define MAX_MAP_VISIBILITY 0x1000000 // 16 Mb visdata
|
||||
#define MAX_MAP_FACEINFO 8192 // can be increased but not needed
|
||||
|
||||
// quake lump ordering
|
||||
#define LUMP_ENTITIES 0
|
||||
#define LUMP_PLANES 1
|
||||
#define LUMP_TEXTURES 2 // internal textures
|
||||
#define LUMP_VERTEXES 3
|
||||
#define LUMP_VISIBILITY 4
|
||||
#define LUMP_NODES 5
|
||||
#define LUMP_TEXINFO 6
|
||||
#define LUMP_FACES 7
|
||||
#define LUMP_LIGHTING 8
|
||||
#define LUMP_CLIPNODES 9
|
||||
#define LUMP_LEAFS 10
|
||||
#define LUMP_MARKSURFACES 11
|
||||
#define LUMP_EDGES 12
|
||||
#define LUMP_SURFEDGES 13
|
||||
#define LUMP_MODELS 14 // internal submodels
|
||||
#define HEADER_LUMPS 15
|
||||
enum
|
||||
{
|
||||
LUMP_ENTITIES = 0,
|
||||
LUMP_PLANES = 1,
|
||||
LUMP_TEXTURES = 2, // internal textures
|
||||
LUMP_VERTEXES = 3,
|
||||
LUMP_VISIBILITY = 4,
|
||||
LUMP_NODES = 5,
|
||||
LUMP_TEXINFO = 6,
|
||||
LUMP_FACES = 7,
|
||||
LUMP_LIGHTING = 8,
|
||||
LUMP_CLIPNODES = 9,
|
||||
LUMP_LEAFS = 10,
|
||||
LUMP_MARKSURFACES = 11,
|
||||
LUMP_EDGES = 12,
|
||||
LUMP_SURFEDGES = 13,
|
||||
LUMP_MODELS = 14, // internal submodels
|
||||
HEADER_LUMPS = 15,
|
||||
};
|
||||
|
||||
// extra lump ordering
|
||||
#define LUMP_LIGHTVECS 0 // deluxemap data
|
||||
#define LUMP_FACEINFO 1 // landscape and lightmap resolution info
|
||||
#define LUMP_CUBEMAPS 2 // cubemap description
|
||||
#define LUMP_VERTNORMALS 3 // phong shaded vertex normals
|
||||
#define LUMP_LEAF_LIGHTING 4 // store vertex lighting for statics
|
||||
#define LUMP_WORLDLIGHTS 5 // list of all the virtual and real lights (used to relight models in-game)
|
||||
#define LUMP_COLLISION 6 // physics engine collision hull dump (userdata)
|
||||
#define LUMP_AINODEGRAPH 7 // node graph that stored into the bsp (userdata)
|
||||
#define LUMP_SHADOWMAP 8 // contains shadow map for direct light
|
||||
#define LUMP_VERTEX_LIGHT 9 // store vertex lighting for statics
|
||||
#define LUMP_UNUSED0 10 // one lump reserved for me
|
||||
#define LUMP_UNUSED1 11 // one lump reserved for me
|
||||
#define EXTRA_LUMPS 12 // count of the extra lumps
|
||||
enum
|
||||
{
|
||||
LUMP_LIGHTVECS = 0, // deluxemap data
|
||||
LUMP_FACEINFO = 1, // landscape and lightmap resolution info
|
||||
LUMP_CUBEMAPS = 2, // cubemap description
|
||||
LUMP_VERTNORMALS = 3, // phong shaded vertex normals
|
||||
LUMP_LEAF_LIGHTING = 4, // store vertex lighting for statics
|
||||
LUMP_WORLDLIGHTS = 5, // list of all the virtual and real lights (used to relight models in-game)
|
||||
LUMP_COLLISION = 6, // physics engine collision hull dump (userdata)
|
||||
LUMP_AINODEGRAPH = 7, // node graph that stored into the bsp (userdata)
|
||||
LUMP_SHADOWMAP = 8, // contains shadow map for direct light
|
||||
LUMP_VERTEX_LIGHT = 9, // store vertex lighting for statics
|
||||
LUMP_UNUSED0 = 10, // one lump reserved for me
|
||||
LUMP_UNUSED1 = 11, // one lump reserved for me
|
||||
EXTRA_LUMPS = 12, // count of the extra lumps
|
||||
};
|
||||
|
||||
// texture flags
|
||||
#define TEX_SPECIAL BIT( 0 ) // sky or slime, no lightmap or 256 subdivision
|
||||
#define TEX_WORLD_LUXELS BIT( 1 ) // alternative lightmap matrix will be used (luxels per world units instead of luxels per texels)
|
||||
#define TEX_AXIAL_LUXELS BIT( 2 ) // force world luxels to axial positive scales
|
||||
#define TEX_EXTRA_LIGHTMAP BIT( 3 ) // bsp31 legacy - using 8 texels per luxel instead of 16 texels per luxel
|
||||
#define TEX_SCROLL BIT( 6 ) // Doom special FX
|
||||
#define TEX_SPECIAL BIT( 0 ) // sky or slime, no lightmap or 256 subdivision
|
||||
#define TEX_WORLD_LUXELS BIT( 1 ) // alternative lightmap matrix will be used (luxels per world units instead of luxels per texels)
|
||||
#define TEX_AXIAL_LUXELS BIT( 2 ) // force world luxels to axial positive scales
|
||||
#define TEX_EXTRA_LIGHTMAP BIT( 3 ) // bsp31 legacy - using 8 texels per luxel instead of 16 texels per luxel
|
||||
#define TEX_SCROLL BIT( 6 ) // Doom special FX
|
||||
|
||||
#define IsLiquidContents( cnt ) ( cnt == CONTENTS_WATER || cnt == CONTENTS_SLIME || cnt == CONTENTS_LAVA )
|
||||
|
||||
// ambient sound types
|
||||
enum
|
||||
{
|
||||
AMBIENT_WATER = 0, // waterfall
|
||||
AMBIENT_SKY, // wind
|
||||
AMBIENT_SLIME, // never used in quake
|
||||
AMBIENT_LAVA, // never used in quake
|
||||
NUM_AMBIENTS, // automatic ambient sounds
|
||||
AMBIENT_WATER = 0, // waterfall
|
||||
AMBIENT_SKY, // wind
|
||||
AMBIENT_SLIME, // never used in quake
|
||||
AMBIENT_LAVA, // never used in quake
|
||||
NUM_AMBIENTS, // automatic ambient sounds
|
||||
};
|
||||
|
||||
//
|
||||
// BSP File Structures
|
||||
//
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int fileofs;
|
||||
int filelen;
|
||||
int32_t fileofs;
|
||||
int32_t filelen;
|
||||
} dlump_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int version;
|
||||
dlump_t lumps[HEADER_LUMPS];
|
||||
int32_t version;
|
||||
dlump_t lumps[HEADER_LUMPS];
|
||||
} dheader_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int id; // must be little endian XASH
|
||||
int version;
|
||||
dlump_t lumps[EXTRA_LUMPS];
|
||||
int32_t id; // must be little endian XASH
|
||||
int32_t version;
|
||||
dlump_t lumps[EXTRA_LUMPS];
|
||||
} dextrahdr_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
vec3_t mins;
|
||||
vec3_t maxs;
|
||||
vec3_t origin; // for sounds or lights
|
||||
int headnode[MAX_MAP_HULLS];
|
||||
int visleafs; // not including the solid leaf 0
|
||||
int firstface;
|
||||
int numfaces;
|
||||
char lumpname[24]; // up to 23 chars, zero-padded
|
||||
int32_t fileofs;
|
||||
int32_t filelen;
|
||||
} dbspx_lump_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int32_t id; // must be little endian BSPX
|
||||
int32_t numlumps;
|
||||
dbspx_lump_t lumps[];
|
||||
} dbspx_hdr_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
vec3_t mins;
|
||||
vec3_t maxs;
|
||||
vec3_t origin; // for sounds or lights
|
||||
int32_t headnode[MAX_MAP_HULLS];
|
||||
int32_t visleafs; // not including the solid leaf 0
|
||||
int32_t firstface;
|
||||
int32_t numfaces;
|
||||
} dmodel_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int nummiptex;
|
||||
int dataofs[4]; // [nummiptex]
|
||||
int32_t nummiptex;
|
||||
int32_t dataofs[4]; // [nummiptex]
|
||||
} dmiptexlump_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
vec3_t point;
|
||||
vec3_t point;
|
||||
} dvertex_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
vec3_t normal;
|
||||
float dist;
|
||||
int type; // PLANE_X - PLANE_ANYZ ?
|
||||
vec3_t normal;
|
||||
float dist;
|
||||
int32_t type; // PLANE_X - PLANE_ANYZ ?
|
||||
} dplane_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int planenum;
|
||||
short children[2]; // negative numbers are -(leafs + 1), not nodes
|
||||
short mins[3]; // for sphere culling
|
||||
short maxs[3];
|
||||
word firstface;
|
||||
word numfaces; // counting both sides
|
||||
int32_t planenum;
|
||||
int16_t children[2]; // negative numbers are -(leafs + 1), not nodes
|
||||
int16_t mins[3]; // for sphere culling
|
||||
int16_t maxs[3];
|
||||
uint16_t firstface;
|
||||
uint16_t numfaces; // counting both sides
|
||||
} dnode_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int planenum;
|
||||
int children[2]; // negative numbers are -(leafs+1), not nodes
|
||||
float mins[3]; // for sphere culling
|
||||
float maxs[3];
|
||||
int firstface;
|
||||
int numfaces; // counting both sides
|
||||
int32_t planenum;
|
||||
int32_t children[2]; // negative numbers are -(leafs+1), not nodes
|
||||
float mins[3]; // for sphere culling
|
||||
float maxs[3];
|
||||
int32_t firstface;
|
||||
int32_t numfaces; // counting both sides
|
||||
} dnode32_t;
|
||||
|
||||
// leaf 0 is the generic CONTENTS_SOLID leaf, used for all solid areas
|
||||
// all other leafs need visibility info
|
||||
typedef struct
|
||||
{
|
||||
int contents;
|
||||
int visofs; // -1 = no visibility info
|
||||
int32_t contents;
|
||||
int32_t visofs; // -1 = no visibility info
|
||||
|
||||
short mins[3]; // for frustum culling
|
||||
short maxs[3];
|
||||
word firstmarksurface;
|
||||
word nummarksurfaces;
|
||||
int16_t mins[3]; // for frustum culling
|
||||
int16_t maxs[3];
|
||||
uint16_t firstmarksurface;
|
||||
uint16_t nummarksurfaces;
|
||||
|
||||
// automatic ambient sounds
|
||||
byte ambient_level[NUM_AMBIENTS]; // ambient sound level (0 - 255)
|
||||
uint8_t ambient_level[NUM_AMBIENTS]; // ambient sound level (0 - 255)
|
||||
} dleaf_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int contents;
|
||||
int visofs; // -1 = no visibility info
|
||||
int32_t contents;
|
||||
int32_t visofs; // -1 = no visibility info
|
||||
|
||||
float mins[3]; // for frustum culling
|
||||
float maxs[3];
|
||||
float mins[3]; // for frustum culling
|
||||
float maxs[3];
|
||||
|
||||
int firstmarksurface;
|
||||
int nummarksurfaces;
|
||||
int32_t firstmarksurface;
|
||||
int32_t nummarksurfaces;
|
||||
|
||||
byte ambient_level[NUM_AMBIENTS];
|
||||
uint8_t ambient_level[NUM_AMBIENTS];
|
||||
} dleaf32_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int planenum;
|
||||
short children[2]; // negative numbers are contents
|
||||
int32_t planenum;
|
||||
int16_t children[2]; // negative numbers are contents
|
||||
} dclipnode_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int planenum;
|
||||
int children[2]; // negative numbers are contents
|
||||
int32_t planenum;
|
||||
int32_t children[2]; // negative numbers are contents
|
||||
} dclipnode32_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
float vecs[2][4]; // texmatrix [s/t][xyz offset]
|
||||
int miptex;
|
||||
short flags;
|
||||
short faceinfo; // -1 no face info otherwise dfaceinfo_t
|
||||
float vecs[2][4]; // texmatrix [s/t][xyz offset]
|
||||
int32_t miptex;
|
||||
int16_t flags;
|
||||
int16_t faceinfo; // -1 no face info otherwise dfaceinfo_t
|
||||
} dtexinfo_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char landname[16]; // name of decsription in mapname_land.txt
|
||||
unsigned short texture_step; // default is 16, pixels\luxels ratio
|
||||
unsigned short max_extent; // default is 16, subdivision step ((texture_step * max_extent) - texture_step)
|
||||
short groupid; // to determine equal landscapes from various groups, -1 - no group
|
||||
char landname[16]; // name of decsription in mapname_land.txt
|
||||
uint16_t texture_step; // default is 16, pixels\luxels ratio
|
||||
uint16_t max_extent; // default is 16, subdivision step ((texture_step * max_extent) - texture_step)
|
||||
int16_t groupid; // to determine equal landscapes from various groups, -1 - no group
|
||||
} dfaceinfo_t;
|
||||
|
||||
typedef word dmarkface_t; // leaf marksurfaces indexes
|
||||
typedef int dmarkface32_t; // leaf marksurfaces indexes
|
||||
typedef uint16_t dmarkface_t; // leaf marksurfaces indexes
|
||||
typedef int32_t dmarkface32_t; // leaf marksurfaces indexes
|
||||
|
||||
typedef int dsurfedge_t; // map surfedges
|
||||
typedef int32_t dsurfedge_t; // map surfedges
|
||||
|
||||
// NOTE: that edge 0 is never used, because negative edge nums
|
||||
// are used for counterclockwise use of the edge in a face
|
||||
typedef struct
|
||||
{
|
||||
word v[2]; // vertex numbers
|
||||
uint16_t v[2]; // vertex numbers
|
||||
} dedge_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int v[2]; // vertex numbers
|
||||
int32_t v[2]; // vertex numbers
|
||||
} dedge32_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
word planenum;
|
||||
short side;
|
||||
uint16_t planenum;
|
||||
int16_t side;
|
||||
|
||||
int firstedge; // we must support > 64k edges
|
||||
short numedges;
|
||||
short texinfo;
|
||||
int32_t firstedge; // we must support > 64k edges
|
||||
int16_t numedges;
|
||||
int16_t texinfo;
|
||||
|
||||
// lighting info
|
||||
byte styles[LM_STYLES];
|
||||
int lightofs; // start of [numstyles*surfsize] samples
|
||||
uint8_t styles[LM_STYLES];
|
||||
int32_t lightofs; // start of [numstyles*surfsize] samples
|
||||
} dface_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int planenum;
|
||||
int side;
|
||||
int32_t planenum;
|
||||
int32_t side;
|
||||
|
||||
int firstedge; // we must support > 64k edges
|
||||
int numedges;
|
||||
int texinfo;
|
||||
int32_t firstedge; // we must support > 64k edges
|
||||
int32_t numedges;
|
||||
int32_t texinfo;
|
||||
|
||||
// lighting info
|
||||
byte styles[LM_STYLES];
|
||||
int lightofs; // start of [numstyles*surfsize] samples
|
||||
uint8_t styles[LM_STYLES];
|
||||
int32_t lightofs; // start of [numstyles*surfsize] samples
|
||||
} dface32_t;
|
||||
|
||||
#endif//BSPFILE_H
|
||||
#endif // BSPFILE_H
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
#ifndef CL_ENTITY_H
|
||||
#define CL_ENTITY_H
|
||||
|
||||
#include "const.h"
|
||||
|
||||
typedef struct efrag_s
|
||||
{
|
||||
struct mleaf_s *leaf;
|
||||
|
||||
@@ -1,4 +1,23 @@
|
||||
#pragma once
|
||||
/*
|
||||
com_image.h - image definitions common between client and server
|
||||
Copyright (C) 2007 Uncle Mike
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#ifndef COM_IMAGE_H
|
||||
#define COM_IMAGE_H
|
||||
|
||||
#include "xash3d_types.h"
|
||||
|
||||
/*
|
||||
========================================================================
|
||||
|
||||
@@ -8,21 +27,9 @@ typically expanded to rgba buffer
|
||||
NOTE: number at end of pixelformat name it's a total bitscount e.g. PF_RGB_24 == PF_RGB_888
|
||||
========================================================================
|
||||
*/
|
||||
#define ImageRAW( type ) (type == PF_RGBA_32 || type == PF_BGRA_32 || type == PF_RGB_24 || type == PF_BGR_24 || type == PF_LUMINANCE)
|
||||
#define ImageCompressed( type ) \
|
||||
( type == PF_DXT1 \
|
||||
|| type == PF_DXT3 \
|
||||
|| type == PF_DXT5 \
|
||||
|| type == PF_ATI2 \
|
||||
|| type == PF_BC4_SIGNED \
|
||||
|| type == PF_BC4_UNSIGNED \
|
||||
|| type == PF_BC5_SIGNED \
|
||||
|| type == PF_BC5_UNSIGNED \
|
||||
|| type == PF_BC6H_SIGNED \
|
||||
|| type == PF_BC6H_UNSIGNED \
|
||||
|| type == PF_BC7_UNORM \
|
||||
|| type == PF_BC7_SRGB \
|
||||
|| type == PF_KTX2_RAW )
|
||||
#define ImageRAW( type ) ( type >= PF_RGBA_32 && type <= PF_LUMINANCE )
|
||||
#define ImageCompressed( type ) ( type >= PF_DXT1 && type <= PF_KTX2_RAW )
|
||||
#define ImageBigEndian( type ) ( type == PF_BGRA_32 || type == PF_BGR_24 )
|
||||
|
||||
typedef enum
|
||||
{
|
||||
@@ -50,11 +57,20 @@ typedef enum
|
||||
PF_TOTALCOUNT, // must be last
|
||||
} pixformat_t;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
RF_COMPRESSED,
|
||||
RF_RGBA,
|
||||
RF_BGRA,
|
||||
RF_BGR,
|
||||
RF_LUMINANCE
|
||||
} rawformat_t;
|
||||
|
||||
typedef struct bpc_desc_s
|
||||
{
|
||||
int format; // pixelformat
|
||||
char name[16]; // used for debug
|
||||
uint glFormat; // RGBA format
|
||||
rawformat_t rawformat;
|
||||
int bpp; // channels (e.g. rgb = 3, rgba = 4)
|
||||
} bpc_desc_t;
|
||||
|
||||
@@ -70,6 +86,8 @@ typedef enum
|
||||
IL_OVERVIEW = BIT(6), // overview required some unque operations
|
||||
IL_LOAD_PLAYER_DECAL = BIT(7), // special mode for player decals
|
||||
IL_KTX2_RAW = BIT(8), // renderer can consume raw KTX2 files (e.g. ref_vk)
|
||||
IL_ALLOW_WAD3_LUMA = BIT(9), // allow usage of luma textures in wad3 (tilde textures)
|
||||
IL_HOST_ENDIAN = BIT(10), // buffer is already in host endian, skip byte-swapping
|
||||
} ilFlags_t;
|
||||
|
||||
// goes into rgbdata_t->encode
|
||||
@@ -99,6 +117,8 @@ typedef enum
|
||||
IMAGE_MULTILAYER = BIT(8), // to differentiate from 3D texture
|
||||
IMAGE_ONEBIT_ALPHA = BIT(9), // binary alpha
|
||||
IMAGE_QUAKEPAL = BIT(10), // image has quake1 palette
|
||||
IMAGE_PREMULTIPLIED = BIT(11),
|
||||
IMAGE_PLAYERDECAL = BIT(12), // loaded with IL_LOAD_PLAYER_DECAL (custom player spray)
|
||||
|
||||
// Image_Process manipulation flags
|
||||
IMAGE_FLIP_X = BIT(16), // flip the image by width
|
||||
@@ -132,3 +152,4 @@ typedef struct rgbdata_s
|
||||
size_t size; // for bounds checking
|
||||
} rgbdata_t;
|
||||
|
||||
#endif//COM_IMAGE_H
|
||||
|
||||
@@ -16,9 +16,11 @@ GNU General Public License for more details.
|
||||
#ifndef COM_MODEL_H
|
||||
#define COM_MODEL_H
|
||||
|
||||
#include "xash3d_types.h"
|
||||
#include "const.h"
|
||||
#include "bspfile.h" // we need some declarations from it
|
||||
|
||||
#include "xash3d_mathlib.h" // mplane_t
|
||||
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
@@ -46,15 +48,6 @@ typedef enum
|
||||
mod_studio
|
||||
} modtype_t;
|
||||
|
||||
typedef struct mplane_s
|
||||
{
|
||||
vec3_t normal;
|
||||
float dist;
|
||||
byte type; // for fast side tests
|
||||
byte signbits; // signx + (signy<<1) + (signz<<1)
|
||||
byte pad[2];
|
||||
} mplane_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
vec3_t position;
|
||||
@@ -619,9 +612,9 @@ static inline mnode_t *node_child( const mnode_t *n, int side, const model_t *mo
|
||||
}
|
||||
}
|
||||
|
||||
return n->children_[side];
|
||||
return n->children_[side ? 1 : 0];
|
||||
#else
|
||||
return n->children_[side];
|
||||
return n->children_[side ? 1 : 0];
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -723,8 +723,6 @@ enum
|
||||
typedef int func_t;
|
||||
typedef int string_t;
|
||||
|
||||
typedef unsigned short word;
|
||||
|
||||
#include "xash3d_types.h"
|
||||
|
||||
typedef struct
|
||||
@@ -737,11 +735,6 @@ typedef struct
|
||||
unsigned r, g, b, a;
|
||||
} colorVec;
|
||||
|
||||
typedef struct link_s
|
||||
{
|
||||
struct link_s *prev, *next;
|
||||
} link_t;
|
||||
|
||||
typedef struct edict_s edict_t;
|
||||
|
||||
typedef struct
|
||||
|
||||
@@ -21,7 +21,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
#ifndef CVAR
|
||||
#define CVAR
|
||||
|
||||
#include STDINT_H
|
||||
#include <stdint.h>
|
||||
#include "xash3d_types.h"
|
||||
|
||||
/*
|
||||
|
||||
@@ -26,68 +26,44 @@ SETUP BACKENDS DEFINITIONS
|
||||
|
||||
===================================================================
|
||||
*/
|
||||
#if !XASH_DEDICATED
|
||||
#if XASH_SDL
|
||||
// we are building using libSDL
|
||||
#ifndef XASH_VIDEO
|
||||
#define XASH_VIDEO VIDEO_SDL
|
||||
#endif // XASH_VIDEO
|
||||
|
||||
#ifndef XASH_INPUT
|
||||
#define XASH_INPUT INPUT_SDL
|
||||
#endif // XASH_INPUT
|
||||
|
||||
#ifndef XASH_SOUND
|
||||
#define XASH_SOUND SOUND_SDL
|
||||
#endif // XASH_SOUND
|
||||
|
||||
#if XASH_SDL == 2
|
||||
#ifndef XASH_TIMER
|
||||
#define XASH_TIMER TIMER_SDL
|
||||
#endif // XASH_TIMER
|
||||
|
||||
#ifndef XASH_MESSAGEBOX
|
||||
#if !XASH_NSWITCH // SDL2 messageboxes not available
|
||||
#define XASH_MESSAGEBOX MSGBOX_SDL
|
||||
#endif
|
||||
#endif // XASH_MESSAGEBOX
|
||||
#endif
|
||||
#elif XASH_LINUX
|
||||
// we are building for Linux without SDL2, can draw only to framebuffer yet
|
||||
#ifndef XASH_VIDEO
|
||||
#define XASH_VIDEO VIDEO_FBDEV
|
||||
#endif // XASH_VIDEO
|
||||
|
||||
#ifndef XASH_INPUT
|
||||
#define XASH_INPUT INPUT_EVDEV
|
||||
#endif // XASH_INPUT
|
||||
|
||||
#ifndef XASH_SOUND
|
||||
#define XASH_SOUND SOUND_ALSA
|
||||
#endif // XASH_SOUND
|
||||
|
||||
//
|
||||
// when compiling client, we need to pick video, audio and input implementations
|
||||
//
|
||||
#if !XASH_DEDICATED // when compiling client, we need to pick video, audio and input implementations
|
||||
#if XASH_SDL // we are building with SDL
|
||||
#define XASH_VIDEO VIDEO_SDL
|
||||
#define XASH_INPUT INPUT_SDL
|
||||
#define XASH_SOUND SOUND_SDL
|
||||
#elif XASH_LINUX // we are building for Linux without SDL, only framebuffer is supported for now
|
||||
#define XASH_VIDEO VIDEO_FBDEV
|
||||
#define XASH_INPUT INPUT_EVDEV
|
||||
#define XASH_SOUND SOUND_ALSA
|
||||
#define XASH_USE_EVDEV 1
|
||||
#elif XASH_DOS4GW
|
||||
#ifndef XASH_VIDEO
|
||||
#define XASH_VIDEO VIDEO_DOS
|
||||
#endif
|
||||
#ifndef XASH_TIMER
|
||||
#define XASH_TIMER TIMER_DOS
|
||||
#endif
|
||||
|
||||
// usually only 10-20 fds availiable
|
||||
#define XASH_REDUCE_FD
|
||||
#define XASH_VIDEO VIDEO_DOS
|
||||
#define XASH_REDUCE_FD 1 // usually only 10-20 fds available
|
||||
#elif XASH_PSP
|
||||
#define XASH_VIDEO VIDEO_PSP
|
||||
#define XASH_INPUT INPUT_PSP
|
||||
#define XASH_SOUND SOUND_PSP
|
||||
#define XASH_REDUCE_FD 1
|
||||
#define XASH_NO_TOUCH 1
|
||||
#define XASH_NO_ZIP 1
|
||||
#endif
|
||||
#endif // XASH_DEDICATED
|
||||
#endif // !XASH_DEDICATED
|
||||
|
||||
//
|
||||
// select messagebox implementation
|
||||
//
|
||||
#ifndef XASH_MESSAGEBOX
|
||||
#if XASH_WIN32
|
||||
#if XASH_SDL >= 2 && !XASH_NSWITCH // SDL2 messageboxes are not available on NSW
|
||||
#define XASH_MESSAGEBOX MSGBOX_SDL
|
||||
#elif XASH_WIN32
|
||||
#define XASH_MESSAGEBOX MSGBOX_WIN32
|
||||
#elif XASH_NSWITCH
|
||||
#define XASH_MESSAGEBOX MSGBOX_NSWITCH
|
||||
#elif XASH_PSP
|
||||
#define XASH_MESSAGEBOX MSGBOX_PSP
|
||||
#else // !XASH_WIN32
|
||||
#define XASH_MESSAGEBOX MSGBOX_STDERR
|
||||
#endif // !XASH_WIN32
|
||||
@@ -97,8 +73,14 @@ SETUP BACKENDS DEFINITIONS
|
||||
// no timer - no xash
|
||||
//
|
||||
#ifndef XASH_TIMER
|
||||
#if XASH_WIN32
|
||||
#if XASH_SDL >= 2
|
||||
#define XASH_TIMER TIMER_SDL
|
||||
#elif XASH_WIN32
|
||||
#define XASH_TIMER TIMER_WIN32
|
||||
#elif XASH_DOS4GW
|
||||
#define XASH_TIMER TIMER_DOS
|
||||
#elif XASH_PSP
|
||||
#define XASH_TIMER TIMER_PSP
|
||||
#else // !XASH_WIN32
|
||||
#define XASH_TIMER TIMER_POSIX
|
||||
#endif // !XASH_WIN32
|
||||
@@ -115,14 +97,16 @@ SETUP BACKENDS DEFINITIONS
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef XASH_STATIC_LIBS
|
||||
#define XASH_LIB LIB_STATIC
|
||||
#define XASH_INTERNAL_GAMELIBS
|
||||
#define XASH_ALLOW_SAVERESTORE_OFFSETS
|
||||
#if XASH_PSP
|
||||
#define XASH_PSP LIB_PSP
|
||||
#elif defined( XASH_STATIC_LIBS )
|
||||
#define XASH_LIB LIB_STATIC
|
||||
#define XASH_INTERNAL_GAMELIBS
|
||||
#define XASH_ALLOW_SAVERESTORE_OFFSETS
|
||||
#elif XASH_WIN32
|
||||
#define XASH_LIB LIB_WIN32
|
||||
#define XASH_LIB LIB_WIN32
|
||||
#elif XASH_POSIX
|
||||
#define XASH_LIB LIB_POSIX
|
||||
#define XASH_LIB LIB_POSIX
|
||||
#endif
|
||||
|
||||
//
|
||||
@@ -149,7 +133,11 @@ Default build-depended cvar and constant values
|
||||
*/
|
||||
|
||||
// Platform overrides
|
||||
#if XASH_NSWITCH
|
||||
#if XASH_WIN32
|
||||
// set up windowed by default on Windows to avoid problems with
|
||||
// Xbox Game Bar
|
||||
#define DEFAULT_FULLSCREEN "0"
|
||||
#elif XASH_NSWITCH
|
||||
#define DEFAULT_TOUCH_ENABLE "1"
|
||||
#define DEFAULT_M_IGNORE "1"
|
||||
#define DEFAULT_MODE_WIDTH 1280
|
||||
@@ -168,12 +156,6 @@ Default build-depended cvar and constant values
|
||||
#define DEFAULT_M_IGNORE "1"
|
||||
#endif // !XASH_MOBILE_PLATFORM && !XASH_NSWITCH
|
||||
|
||||
#if XASH_IOS
|
||||
// this means that libraries are provided with engine, but not in game data
|
||||
// You need add library loading code to library.c when adding new platform
|
||||
#define XASH_INTERNAL_GAMELIBS
|
||||
#endif // XASH_IOS
|
||||
|
||||
// Defaults
|
||||
#ifndef DEFAULT_TOUCH_ENABLE
|
||||
#define DEFAULT_TOUCH_ENABLE "0"
|
||||
@@ -196,11 +178,28 @@ Default build-depended cvar and constant values
|
||||
#endif // DEFAULT_ALLOWCONSOLE
|
||||
|
||||
#ifndef DEFAULT_FULLSCREEN
|
||||
#define DEFAULT_FULLSCREEN "1" // must be a string
|
||||
#define DEFAULT_FULLSCREEN "2" // must be a string
|
||||
#endif // DEFAULT_FULLSCREEN
|
||||
|
||||
#ifndef DEFAULT_MAX_EDICTS
|
||||
#define DEFAULT_MAX_EDICTS 1200 // was 900 before HL25
|
||||
#endif // DEFAULT_MAX_EDICTS
|
||||
|
||||
|
||||
#ifndef DEFAULT_ACCELERATED_RENDERER
|
||||
#if XASH_PSP
|
||||
#define DEFAULT_ACCELERATED_RENDERER "gu"
|
||||
#elif XASH_MOBILE_PLATFORM
|
||||
#define DEFAULT_ACCELERATED_RENDERER "gles1"
|
||||
#elif XASH_IOS
|
||||
#define DEFAULT_ACCELERATED_RENDERER "gles2"
|
||||
#else // !XASH_MOBILE_PLATFORM
|
||||
#define DEFAULT_ACCELERATED_RENDERER "gl"
|
||||
#endif // !XASH_MOBILE_PLATFORM
|
||||
#endif // DEFAULT_ACCELERATED_RENDERER
|
||||
|
||||
#ifndef DEFAULT_SOFTWARE_RENDERER
|
||||
#define DEFAULT_SOFTWARE_RENDERER "soft" // mittorn's ref_soft
|
||||
#endif // DEFAULT_SOFTWARE_RENDERER
|
||||
|
||||
#endif // DEFAULTS_H
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
/***
|
||||
*
|
||||
* Copyright (c) 1996-2002, Valve LLC. All rights reserved.
|
||||
*
|
||||
* This product contains software technology licensed from Id
|
||||
* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc.
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Use, distribution, and modification of this source code and/or resulting
|
||||
* object code is restricted to non-commercial enhancements to products from
|
||||
* Valve LLC. All other use, distribution, or modification is prohibited
|
||||
* without written permission from Valve LLC.
|
||||
*
|
||||
****/
|
||||
|
||||
#ifndef DLIGHT_H
|
||||
#define DLIGHT_H
|
||||
|
||||
typedef struct dlight_s
|
||||
{
|
||||
vec3_t origin;
|
||||
float radius;
|
||||
color24 color;
|
||||
float die; // stop lighting after this time
|
||||
float decay; // drop this each second
|
||||
float minlight; // don't add when contributing less
|
||||
int key;
|
||||
qboolean dark; // subtracts light instead of adding
|
||||
} dlight_t;
|
||||
|
||||
#endif//DLIGHT_H
|
||||
@@ -1,59 +0,0 @@
|
||||
/***
|
||||
*
|
||||
* Copyright (c) 1996-2002, Valve LLC. All rights reserved.
|
||||
*
|
||||
* This product contains software technology licensed from Id
|
||||
* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc.
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Use, distribution, and modification of this source code and/or resulting
|
||||
* object code is restricted to non-commercial enhancements to products from
|
||||
* Valve LLC. All other use, distribution, or modification is prohibited
|
||||
* without written permission from Valve LLC.
|
||||
*
|
||||
****/
|
||||
|
||||
#ifndef HLTV_H
|
||||
#define HLTV_H
|
||||
|
||||
#define TYPE_CLIENT 0 // client is a normal HL client (default)
|
||||
#define TYPE_PROXY 1 // client is another proxy
|
||||
#define TYPE_COMMENTATOR 3 // client is a commentator
|
||||
#define TYPE_DEMO 4 // client is a demo file
|
||||
|
||||
// sub commands of svc_hltv:
|
||||
#define HLTV_ACTIVE 0 // tells client that he's an spectator and will get director commands
|
||||
#define HLTV_STATUS 1 // send status infos about proxy
|
||||
#define HLTV_LISTEN 2 // tell client to listen to a multicast stream
|
||||
|
||||
// sub commands of svc_director:
|
||||
#define DRC_CMD_NONE 0 // NULL director command
|
||||
#define DRC_CMD_START 1 // start director mode
|
||||
#define DRC_CMD_EVENT 2 // informs about director command
|
||||
#define DRC_CMD_MODE 3 // switches camera modes
|
||||
#define DRC_CMD_CAMERA 4 // sets camera registers
|
||||
#define DRC_CMD_TIMESCALE 5 // sets time scale
|
||||
#define DRC_CMD_MESSAGE 6 // send HUD centerprint
|
||||
#define DRC_CMD_SOUND 7 // plays a particular sound
|
||||
#define DRC_CMD_STATUS 8 // status info about broadcast
|
||||
#define DRC_CMD_BANNER 9 // banner file name for HLTV gui
|
||||
#define DRC_CMD_FADE 10 // send screen fade command
|
||||
#define DRC_CMD_SHAKE 11 // send screen shake command
|
||||
#define DRC_CMD_STUFFTEXT 12 // like the normal svc_stufftext but as director command
|
||||
|
||||
#define DRC_CMD_LAST 12
|
||||
|
||||
// HLTV_EVENT event flags
|
||||
#define DRC_FLAG_PRIO_MASK 0x0F // priorities between 0 and 15 (15 most important)
|
||||
#define DRC_FLAG_SIDE (1<<4) //
|
||||
#define DRC_FLAG_DRAMATIC (1<<5) // is a dramatic scene
|
||||
#define DRC_FLAG_SLOWMOTION (1<<6) // would look good in SloMo
|
||||
#define DRC_FLAG_FACEPLAYER (1<<7) // player is doning something (reload/defuse bomb etc)
|
||||
#define DRC_FLAG_INTRO (1<<8) // is a introduction scene
|
||||
#define DRC_FLAG_FINAL (1<<9) // is a final scene
|
||||
#define DRC_FLAG_NO_RANDOM (1<<10) // don't randomize event data
|
||||
|
||||
#define MAX_DIRECTOR_CMD_PARAMETERS 4
|
||||
#define MAX_DIRECTOR_CMD_STRING 128
|
||||
|
||||
#endif//HLTV_H
|
||||
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
Copyright (C) 1996-1997 Id Software, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
*/
|
||||
#ifndef KBUTTON_H
|
||||
#define KBUTTON_H
|
||||
|
||||
// a1ba: copied from WinQuake/client.h
|
||||
//
|
||||
// cl_input
|
||||
//
|
||||
typedef struct
|
||||
{
|
||||
int down[2]; // key nums holding it down
|
||||
int state; // low bit is down state
|
||||
} kbutton_t;
|
||||
|
||||
STATIC_CHECK_SIZEOF( kbutton_t, 12, 12 );
|
||||
|
||||
#endif // KBUTTON_H
|
||||
@@ -16,6 +16,8 @@ GNU General Public License for more details.
|
||||
#ifndef LIGHTSTYLE_H
|
||||
#define LIGHTSTYLE_H
|
||||
|
||||
#include "xash3d_types.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char pattern[256];
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
/***
|
||||
*
|
||||
* Copyright (c) 1996-2002, Valve LLC. All rights reserved.
|
||||
*
|
||||
* This product contains software technology licensed from Id
|
||||
* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc.
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Use, distribution, and modification of this source code and/or resulting
|
||||
* object code is restricted to non-commercial enhancements to products from
|
||||
* Valve LLC. All other use, distribution, or modification is prohibited
|
||||
* without written permission from Valve LLC.
|
||||
*
|
||||
****/
|
||||
// mathlib.h
|
||||
|
||||
#include <math.h>
|
||||
|
||||
typedef float vec_t;
|
||||
typedef vec_t vec2_t[2];
|
||||
typedef vec_t vec3_t[3];
|
||||
typedef vec_t vec4_t[4]; // x,y,z,w
|
||||
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h
|
||||
#endif
|
||||
|
||||
struct mplane_s;
|
||||
|
||||
extern vec3_t vec3_origin;
|
||||
extern int nanmask;
|
||||
|
||||
#define IS_NAN(x) (((*(int *)&x)&nanmask)==nanmask)
|
||||
|
||||
#ifndef VECTOR_H
|
||||
#define DotProduct(x,y) ((x)[0]*(y)[0]+(x)[1]*(y)[1]+(x)[2]*(y)[2])
|
||||
#endif
|
||||
|
||||
#define VectorSubtract(a,b,c) {(c)[0]=(a)[0]-(b)[0];(c)[1]=(a)[1]-(b)[1];(c)[2]=(a)[2]-(b)[2];}
|
||||
#define VectorAdd(a,b,c) {(c)[0]=(a)[0]+(b)[0];(c)[1]=(a)[1]+(b)[1];(c)[2]=(a)[2]+(b)[2];}
|
||||
#define VectorCopy(a,b) {(b)[0]=(a)[0];(b)[1]=(a)[1];(b)[2]=(a)[2];}
|
||||
#define VectorClear(a) {(a)[0]=0.0;(a)[1]=0.0;(a)[2]=0.0;}
|
||||
|
||||
void VectorMA (const vec3_t veca, float scale, const vec3_t vecb, vec3_t vecc);
|
||||
|
||||
vec_t _DotProduct (vec3_t v1, vec3_t v2);
|
||||
void _VectorSubtract (vec3_t veca, vec3_t vecb, vec3_t out);
|
||||
void _VectorAdd (vec3_t veca, vec3_t vecb, vec3_t out);
|
||||
void _VectorCopy (vec3_t in, vec3_t out);
|
||||
|
||||
int VectorCompare (const vec3_t v1, const vec3_t v2);
|
||||
float Length (const vec3_t v);
|
||||
void CrossProduct (const vec3_t v1, const vec3_t v2, vec3_t cross);
|
||||
float VectorNormalize (vec3_t v); // returns vector length
|
||||
void VectorInverse (vec3_t v);
|
||||
void VectorScale (const vec3_t in, vec_t scale, vec3_t out);
|
||||
|
||||
void R_ConcatRotations (float in1[3][3], float in2[3][3], float out[3][3]);
|
||||
void R_ConcatTransforms (float in1[3][4], float in2[3][4], float out[3][4]);
|
||||
|
||||
void AngleVectors (const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up);
|
||||
void AngleVectorsTranspose (const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up);
|
||||
#define AngleIVectors AngleVectorsTranspose
|
||||
|
||||
void AngleMatrix (const vec3_t angles, float (*matrix)[4] );
|
||||
void AngleIMatrix (const vec3_t angles, float (*matrix)[4] );
|
||||
void VectorTransform (const vec3_t in1, float in2[3][4], vec3_t out);
|
||||
|
||||
void NormalizeAngles( vec3_t angles );
|
||||
void InterpolateAngles( vec3_t start, vec3_t end, vec3_t output, float frac );
|
||||
float AngleBetweenVectors( const vec3_t v1, const vec3_t v2 );
|
||||
|
||||
void VectorMatrix( vec3_t forward, vec3_t right, vec3_t up);
|
||||
void VectorAngles( const vec3_t forward, vec3_t angles );
|
||||
|
||||
int InvertMatrix( const float * m, float *out );
|
||||
|
||||
int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, struct mplane_s *plane);
|
||||
float anglemod(float a);
|
||||
|
||||
#define BOX_ON_PLANE_SIDE(emins, emaxs, p) \
|
||||
(((p)->type < 3)? \
|
||||
( \
|
||||
((p)->dist <= (emins)[(p)->type])? \
|
||||
1 \
|
||||
: \
|
||||
( \
|
||||
((p)->dist >= (emaxs)[(p)->type])?\
|
||||
2 \
|
||||
: \
|
||||
3 \
|
||||
) \
|
||||
) \
|
||||
: \
|
||||
BoxOnPlaneSide( (emins), (emaxs), (p)))
|
||||
@@ -27,7 +27,7 @@
|
||||
// Set this flag for things like broadcast requests, etc. where the engine should not
|
||||
// kill the request hook after receiving the first response
|
||||
#define FNETAPI_MULTIPLE_RESPONSE ( 1<<0 )
|
||||
#define FNETAPI_LEGACY_PROTOCOL ( 1<<1 ) // xash3d-fwgs extension
|
||||
// 1<<1 is reserved for deprecated xash3d-fwgs extension
|
||||
|
||||
struct net_response_s;
|
||||
typedef void (*net_api_response_func_t) ( struct net_response_s *response );
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user