Compare commits

..

1024 Commits

Author SHA1 Message Date
Alibek Omarov
7e06417db6 ref: use vector initializer macros 2026-05-20 13:13:07 -04:00
Alibek Omarov
1901c8a91d engine: use vector initializer macros 2026-05-20 13:11:07 -04:00
Alibek Omarov
2511c07a66 ref: use designated initializers where possible 2026-05-20 12:44:47 -04:00
Alibek Omarov
20b2332ca1 filesystem: use designated initializers where possible 2026-05-20 12:44:36 -04:00
Alibek Omarov
a244c4d453 engine: use designated initializers where possible 2026-05-20 12:44:26 -04:00
Alibek Omarov
0b14349cdb public: add Vec2, Vec3 and Vec4 macros used for initializers 2026-05-20 12:43:40 -04:00
Alibek Omarov
70272ca903 engine: platform: refactor variable declarations 2026-05-20 20:15:01 +05:00
Alibek Omarov
4fbdf5e8ee engine: common: refactor variable declarations 2026-05-20 20:15:01 +05:00
Alibek Omarov
7e939b5cd7 engine: client: refactor variable declarations 2026-05-20 20:15:01 +05:00
Alibek Omarov
264f69cd91 engine: server: refactor variable declarations 2026-05-20 20:15:01 +05:00
Alibek Omarov
40af9f992e ref: gl: refactor variable declarations 2026-05-20 20:15:01 +05:00
Alibek Omarov
d8e3a78aba ref: soft: refactor variable declarations 2026-05-20 20:15:01 +05:00
Alibek Omarov
28ac39fd84 ref: common: refactor variable declarations 2026-05-19 21:14:25 +05:00
Alibek Omarov
a742fa385d filesystem: refactor variable declarations 2026-05-19 21:10:29 +05:00
Alibek Omarov
e47c675386 public: refactor variable declarations 2026-05-19 21:05:09 +05:00
Владислав Сухов
de585f80fb 3rdparty: mainui: update 2026-05-19 19:38:12 +05:00
Владислав Сухов
af58ef4a2a engine: menu_int: add flags for mipmaps and alpha channel support in image handling 2026-05-19 19:38:12 +05:00
Владислав Сухов
dbe3f42485 engine: update player logo color to RGB format 2026-05-19 19:38:12 +05:00
Alibek Omarov
0053edbe7b scripts: gha: drop signing step from build_android.sh, as it's done on gradle level now 2026-05-18 13:01:22 +05:00
Alibek Omarov
61f3369957 filesystem: refactor large filesystem.c into smaller files 2026-05-18 12:50:08 +05:00
Alibek Omarov
334bebb1fb engine: use memcpy when reading sprite model to avoid unaligned access 2026-05-17 12:34:21 +05:00
Alibek Omarov
463ed05340 android: add auto-updater to the launcher 2026-05-17 12:12:40 +05:00
Alibek Omarov
92bad81d9a engine: client: fix off-by-one 2026-05-17 11:48:20 +05:00
Alibek Omarov
4c667d9d8c engine: client: bound current_position with HISTORY_MASK, in case of something passes invalid values 2026-05-17 11:45:22 +05:00
Alibek Omarov
cb04e9cbd5 engine: server: also use constant time string comparison for sv_password 2026-05-17 11:26:53 +05:00
Alibek Omarov
c2c781f039 engine: server: harden rcon, use constant time comparison, do not output untrusted data to log 2026-05-17 11:23:55 +05:00
Alibek Omarov
dbe1a2742e public: add Q_strcmp_constant_time 2026-05-17 11:23:08 +05:00
Alibek Omarov
fe2019ee06 engine: common: add more cases to COM_IsSafeToDownload 2026-05-17 11:20:00 +05:00
Alibek Omarov
e5c091c303 engine: client: fix off-by-one error in CL_ParseResource 2026-05-17 11:17:42 +05:00
Alibek Omarov
9d050e7ed3 engine: limit decompressed buffer alloc size to 64MiB 2026-05-17 11:17:23 +05:00
Alibek Omarov
be35b3503e engine: limit maximum amount of pending fragbufs 2026-05-17 10:54:51 +05:00
Alibek Omarov
5fad4ae6ca engine: client: mark rcon and connect commands privileged 2026-05-17 10:51:44 +05:00
Alibek Omarov
2290ae0239 public: disable Amiga's double-slash check in CheckNastyPath 2026-05-17 10:42:22 +05:00
Alibek Omarov
9082a47348 engine: validate paths before downloading 2026-05-17 10:41:59 +05:00
Alibek Omarov
5d7ed9d8a7 public: add CheckNastyPath from Darkplaces's VFS so it can be used in engine for path traversal checks 2026-05-16 21:28:51 +05:00
Alibek Omarov
8120669e5b engine: restore bounds check in Delta_FindStructByIndex, as it gets passed arbitrary numbers too 2026-05-16 21:27:51 +05:00
Alibek Omarov
fb6c4a224c engine: check for overflow in split packet reassembly 2026-05-16 21:13:47 +05:00
Alibek Omarov
166763a887 engine: common: limit amount of delta encoded fields by 64 in GoldSrc protocol (because this is the maximum it allows anyway) 2026-05-16 21:12:49 +05:00
Alibek Omarov
e56cf4e1da engine: client: validate player index and max players 2026-05-16 21:00:50 +05:00
Alibek Omarov
fc4b982ecc engine: client: correctly skip message that would overflow temp entity buffer 2026-05-16 20:59:57 +05:00
Alibek Omarov
d305292340 engine: zone: in CheckAlloc walk through over both allocation chains, so that we don't read out of bounds if passed pointer is not allocated by zone allocator 2026-05-16 12:56:49 +05:00
Alibek Omarov
9ee6be87d7 engine: host: try to avoid recursive Host_Error 2026-05-16 12:37:08 +05:00
Alibek Omarov
4e4a54ab68 engine: despite byte-swapping code is no-op on little endian, it still fires and can read out of bounds on some models, leading to crash 2026-05-16 12:21:46 +05:00
Alibek Omarov
516554b704 public: update miniz to latest version 2026-05-15 12:36:56 +05:00
Alibek Omarov
7be955c493 gitignore: do not even try adding slop generators garbage 2026-05-15 11:39:45 +05:00
Владислав Сухов
c6fb702fb1 engine: add MenuFactory native object 2026-05-15 08:48:58 +05:00
Alibek Omarov
cae1d32c66 3rdparty: gl-wes-v2: update 2026-05-13 11:14:32 +05:00
Alibek Omarov
5e70995989 ref: gl: more maybe unused stuff 2026-05-13 11:13:46 +05:00
Alibek Omarov
50f7f3bb87 engine: server: mark str64 as maybe unused 2026-05-13 11:12:19 +05:00
Alibek Omarov
7c3da8c381 3rdparty: gl-wes-v2: update 2026-05-13 11:11:41 +05:00
Alibek Omarov
e38d3511b5 3rdparty: nanogl: update 2026-05-13 11:11:32 +05:00
Alibek Omarov
d4fcebb377 3rdparty: MultiEmulator: update 2026-05-13 11:11:22 +05:00
Alibek Omarov
996f0e906b engine: client: drop double free check from dbghulls 2026-05-13 10:53:21 +05:00
Alibek Omarov
0f490a2f16 engine: client: fix warnings, cleanup unused or maybe-uninitialized variables 2026-05-13 10:48:37 +05:00
Alibek Omarov
cef6cb58bb ref: gl: fix maybe-uninitialized 2026-05-13 10:47:15 +05:00
Alibek Omarov
47aad1b3a3 ref: soft: cleanup unused variables 2026-05-13 10:46:59 +05:00
Alibek Omarov
ff8efebba2 engine: fix more warnings 2026-05-13 10:46:44 +05:00
Alibek Omarov
be6571a6a2 3rdparty: library_suffix: update 2026-05-13 10:45:50 +05:00
Alibek Omarov
1d67585df4 3rdparty: mainui: update 2026-05-13 10:45:39 +05:00
Alibek Omarov
10d441f825 public: fix warnings, add guards around some of the macros, remove unused variable 2026-05-13 10:30:35 +05:00
Alibek Omarov
2dc132f817 engine: common: fix up warnings, clean up unused variables 2026-05-13 10:13:52 +05:00
Alibek Omarov
e9bdbf2f33 ref: gl: mark maybe unused globals 2026-05-13 10:11:47 +05:00
Alibek Omarov
b630a84447 common: xash3d_types: add macro MAYBE_UNUSED 2026-05-13 10:11:21 +05:00
Alibek Omarov
dccd076b92 engine: platform: sdl2: cleanup unused variables 2026-05-13 10:09:49 +05:00
Alibek Omarov
cb7ea5729a engine: server: cleanup unused variables 2026-05-13 10:09:37 +05:00
Alibek Omarov
9bad17505e filesystem: cleanup unused variables 2026-05-13 10:09:19 +05:00
Alibek Omarov
bf990d1040 ref: gl: cleanup unused variables 2026-05-13 10:09:09 +05:00
Alibek Omarov
63e112bf4c engine: client: soundlib: add more broken null files 2026-05-13 09:32:50 +05:00
Alibek Omarov
0aac2e4d32 engine: zone: add small allocation optimization, reducing the size of <255 bytes allocations at the cost of less debug info
Keep it opt-in through a flag at pool alloc time.
Opt-in console related allocations, they are usually small enough strings.
2026-05-13 09:32:50 +05:00
Alibek Omarov
33b3cdb0f5 engine: client: soundlib: catch broken null.wav and replace it with silence 2026-05-10 03:02:24 +05:00
Alibek Omarov
9c4edaed21 engine: client: sound: switch to single-pass mixing, now channels gets upscaled directly to DMA speed, fixing the issue with s_lerping 2026-05-10 02:48:23 +05:00
Alibek Omarov
d4205867dd public: simplify COM_Strip and COM_DefaultExtension by reusing extension extraction function. Add moar tests for them 2026-05-10 00:56:44 +05:00
Alibek Omarov
3c07680263 android: pack crash log to ZIP file, put system and intent info alongside it. Properly handle if multple email clients are installed on device (tested with gmail, outlook and k-9). 2026-05-10 00:32:29 +05:00
Alibek Omarov
e5d6f66f61 filesystem: strip exec flag and add nowrite flag to archives
pk3dir now disallows executables, which shouldn't be a problem for anybody.
Revised FS_Path command output.
2026-05-09 19:22:10 +05:00
Alibek Omarov
a106924c7f filesystem: avoid scanning game directory as executable in FS_FindLibrary on Android, generic refactoring 2026-05-09 16:57:21 +05:00
Alibek Omarov
11ee883c84 android: show build info for mod's downloaded libraries 2026-05-09 16:57:21 +05:00
Alibek Omarov
881f0c5ca7 engine: show the missing library warning message on Android, but only if APK doesn't try to override it, skip client for now 2026-05-09 16:57:21 +05:00
Alibek Omarov
ac0c8a2210 android: implement game library downloading 2026-05-09 16:57:21 +05:00
Alibek Omarov
dba090e0da android: bundle Let's Encrypt's ISRG Root X1 certificate for EOL devices 2026-05-09 16:57:21 +05:00
Alibek Omarov
262f32b002 engine: platform: android: implement library loading from VFS paths on Android 2026-05-09 16:57:21 +05:00
Zane van Iperen
843f6297ed engine: net_ws: fix NET_QueuePacket indentation after previous change 2026-05-09 13:15:17 +05:00
Zane van Iperen
d1510345f1 engine: net_ws: round-robin IPv4/IPv6 reads
Previously, it would always return on the first one that had activity.
In practice, this meant that if IPv4 starts getting slammed, IPv6 reads
wouldn't happen.
2026-05-09 13:15:17 +05:00
Alibek Omarov
36307bb386 android: handle crash logs created by the engine, keep them for 30 days for current version 2026-05-07 17:58:25 +05:00
Alibek Omarov
52953a7647 engine: platform: posix: drop crash info into crash.log on Android 2026-05-07 15:48:34 +05:00
Alibek Omarov
ebeff144f9 Merge remote-tracking branch 'origin/master' 2026-05-07 13:50:55 +05:00
Alibek Omarov
c8cc953d48 engine: platform: posix: make crash message a global because the stack could be limited when we hit crash handler 2026-05-07 13:45:39 +05:00
Alibek Omarov
d6bf06e997 engine: common: check XASH3D_BASEDIR when opening log file, fixes engine.log creation on Android 2026-05-07 12:23:56 +05:00
Alibek Omarov
568b7abc5f scripts: waifulib: xcompile: set max-page-size to 64k on PSVita to workaround latest toolchain issue 2026-05-07 12:07:25 +05:00
Alibek Omarov
1b7f50d3c6 scripts: waifulib: xcompile: fix build error with newer NDKs caused by incompatibility of legacy STL and Clang 2026-05-07 12:05:26 +05:00
Alibek Omarov
c8e1c2673f engine: client: sound: revert singleplayer check for local sounds 2026-05-07 11:57:09 +05:00
Alibek Omarov
c2df265de7 engine: operate on a local copy of dspriteframe_t to avoid unaligned access on ARMv7 2026-05-07 11:56:08 +05:00
Alibek Omarov
e9e885534a filesystem: dir: hand off file existence check to the OS if path starts with .. or ../ 2026-05-07 10:32:00 +05:00
Alibek Omarov
95cc6c4445 filesystem: small refactoring, remove double nullptr checks 2026-05-07 10:32:00 +05:00
Alibek Omarov
086cb6aab8 engine: move {gamedir}/downloaded directory to {gamedir}_downloads for compatibility
Some mods like Sven-Coop 4.8 stupidly open game files directly and need this to
load downloaded soundcache.
2026-05-07 10:32:00 +05:00
Alibek Omarov
e6a44b70e0 engine: client: always load client.dll last to crash on nullptr in mods that fetch cvar pointers early, add comment for anyone who would modify this file 2026-05-05 01:15:16 +05:00
Alibek Omarov
fdb59e990e engine: rename Host_IsLocalGame to Host_IsSinglePlayerGame to avoid confusion 2026-05-05 01:04:57 +05:00
Alibek Omarov
eb79bc6b40 engine: use Host_IsLocalClient where it should be, as it checks for local games, not specifically singleplayer games 2026-05-05 01:03:03 +05:00
Alibek Omarov
e907e9fc95 engine: client: fix check for local game when deciding to enable netchan splitsize extension 2026-05-05 00:38:33 +05:00
SNMetamorph
fc356c88b3 engine: client: minor fixes in steam broker client side code 2026-05-02 10:50:21 +05:00
SNMetamorph
bc672e5aca Documentation: extensions: updated description of steam broker protocol 2026-05-02 10:50:21 +05:00
SNMetamorph
2635fd9739 engine: common: make use NET_MakeSocketNonBlocking instead of direct system API functions 2026-05-02 10:50:21 +05:00
SNMetamorph
653fe51fd4 engine: client: refactor data serializing in steam broker client side logic code 2026-05-02 10:50:21 +05:00
SNMetamorph
980d9fcaab engine: client: improve logging in steam broker client side logic code 2026-05-02 10:50:21 +05:00
SNMetamorph
72ef5cffca engine: client: total refactoring of steam broker client side logic code 2026-05-02 10:50:21 +05:00
SNMetamorph
a707f5933d engine: common: expose network utilities functions needed for broker 2026-05-02 10:50:21 +05:00
Alibek Omarov
b9b0ff5769 3rdparty: mainui: update 2026-05-01 20:10:06 +05:00
Marc-Antoine Lortie
982a216df4 Fix hlsdk-xash3d references where applicable. 2026-05-01 19:02:13 +05:00
Alibek Omarov
7d87d65a3f engine: server: lift down Host_Error on NULL name in PrecacheModel 2026-04-28 20:05:09 +05:00
Alibek Omarov
6e4e048ef8 ci: thanks for your service, cirrus. F. 2026-04-28 11:25:25 +05:00
Alibek Omarov
5b6b372cfe ref: gl: drop broken minlight/scale cvars for litwater. Keep them for mods. Fix post-merge issues 2026-04-28 11:21:56 +05:00
Alibek Omarov
2f31cf315c ref: gl: add litwater support 2026-04-28 11:12:44 +05:00
Alibek Omarov
910cce56ca Documentation: extensions: document lightmapped water extension 2026-04-28 11:12:11 +05:00
Alibek Omarov
68dba8b4e0 engine: add litwater support 2026-04-28 11:12:11 +05:00
Alibek Omarov
dbfeacfecc 3rdparty: vgui_support: update 2026-04-28 09:50:50 +05:00
Alibek Omarov
0bdd4648bc utils: mdldec: explicitly cast chars to bytes in ctype functions 2026-04-28 09:50:33 +05:00
Alibek Omarov
ec8059173d public: explicitly cast chars to bytes in ctype functions 2026-04-28 09:50:33 +05:00
Alibek Omarov
f91e5a7647 engine: explicitly cast chars to bytes in ctype functions 2026-04-28 09:50:33 +05:00
Alibek Omarov
7942d9b1bc engine: mod_bmodel: refactor unified bmodel loader
* Use offsets into dbspmodel_t struct, get rid of srcmodel global variable.
* Allocate dbspmodel_t when it's required
* Make srclumps const, move Blue-Shift hack into lump loader's flag.
2026-04-26 06:29:04 +05:00
Alibek Omarov
7abf0c819f 3rdparty: update xash-extras and mainui 2026-04-26 05:52:30 +05:00
Alibek Omarov
85eb82a370 Revert "engine: client: show difference between demo time and real time in timedemo result"
This reverts commit 90e25cc623.
2026-04-26 04:26:50 +05:00
Alibek Omarov
90e25cc623 engine: client: show difference between demo time and real time in timedemo result 2026-04-26 04:15:15 +05:00
Alibek Omarov
fca4152d5a ref: gl: fix typo in gl_drawrangeelements 2026-04-26 04:13:50 +05:00
Alibek Omarov
2ccef63754 engine: ref_api: change RefGetParm return type to intptr_t, bump RefAPI version 2026-04-26 04:12:52 +05:00
Alibek Omarov
87615784b9 engine: client: fix server list response parsing on BE 2026-04-26 04:12:52 +05:00
Alibek Omarov
d3b01d313b ref: gl: do not limit texture units by broken report of shaders extension 2026-04-26 04:12:52 +05:00
Alibek Omarov
a3897f402a 3rdparty: mainui: update 2026-04-26 04:12:52 +05:00
Alibek Omarov
21d84ee692 ref: gl: add -glnoerr flag that enables no error context, for possible performance gains 2026-04-26 04:12:52 +05:00
Alibek Omarov
e30ce9c150 engine: fix palette and builtin textures on BE 2026-04-26 04:12:52 +05:00
Alibek Omarov
22391eba1b engine: handle swapping sequence files in LoadCacheFile for games with external StudioModelRenderer 2026-04-26 04:12:52 +05:00
Alibek Omarov
800725453a engine: soundlib: pass XASH_BIG_ENDIAN to ov_read in vorbis loader 2026-04-26 04:12:52 +05:00
Alibek Omarov
22ecec095e engine: platform: sdl: request AUDIO_S16SYS to make sound output in native endian 2026-04-26 04:12:52 +05:00
Alibek Omarov
8545ed5ccb engine: mod_studio: implement loading sequence files on BE 2026-04-26 04:12:52 +05:00
Alibek Omarov
a4fdf19ad8 engine: client: Fix font loading on BE 2026-04-26 04:12:52 +05:00
Alibek Omarov
75e4a21d7a engine: mod_studio: more studio model swapping 2026-04-26 04:12:52 +05:00
Alibek Omarov
1099344a7d engine: soundlib: swap WAV data 2026-04-26 04:12:52 +05:00
Alibek Omarov
ab1eaed0a5 engine: platform: posix: remove Sys_Crash and Sys_CrashDetailsLibbacktrace from the output by matching function name only once 2026-04-26 04:12:52 +05:00
Alibek Omarov
76a01d0855 engine: platform: posix: remove extra newline from libbacktrace output 2026-04-26 04:12:52 +05:00
Alibek Omarov
b00679ee30 engine: make Sys_LogFileNo return -1 when log file isn't opened 2026-04-26 04:12:52 +05:00
Alibek Omarov
b3bb2fc16f engine: imagelib: add IL_HOST_ENDIAN flag to mark on-disk data that's already in host endianness 2026-04-26 04:12:52 +05:00
Alibek Omarov
86a2ea29e4 wscript: define libbacktrace as libbacktrace_custom, fix DL_ITERATE_PHDR not defined due to failing fallback check
* Changed to libbacktrace_custom to not collide with libbacktrace built as part of compiler's runtime
2026-04-26 04:12:52 +05:00
Alibek Omarov
d4f8543556 engine: platform: posix: better libbacktrace error output 2026-04-26 04:12:52 +05:00
Alibek Omarov
e850728016 engine: soundlib: fixes for BE 2026-04-26 04:12:52 +05:00
Alibek Omarov
11877f4c96 engine: imagelib: fixes for BE 2026-04-26 04:12:52 +05:00
Denis Drakhnia
2ba5030b62 engine: server: fix no response to goldsrc info query 2026-04-24 21:56:22 +05:00
Alibek Omarov
06b14800e1 engine: mod_bmodel: fix bspx swap definition 2026-04-24 08:00:49 +05:00
Alibek Omarov
bb7c85fd22 wscript: turn on progress bar by default 2026-04-24 07:31:36 +05:00
Alibek Omarov
37ce62fa6a waf: update to latest wafsrc + progress-bar-enhacnement 2026-04-24 07:31:11 +05:00
Alibek Omarov
e1b29e2da3 engine: temporarily disable swapping extended header 2026-04-24 06:58:53 +05:00
Alibek Omarov
b60afd73e9 public: swaplib: add comments 2026-04-24 06:58:53 +05:00
Alibek Omarov
84447a5292 public: crclib: add fix for md5 from q3 2026-04-24 06:58:53 +05:00
Alibek Omarov
fe4ea4aabf engine: server: fix pfnMessageEnd on BE 2026-04-24 06:58:53 +05:00
Alibek Omarov
878822290c public: crclib: fix CRC32_BlockSequence on BE 2026-04-24 06:58:53 +05:00
Alibek Omarov
04ca5d7cb0 engine: common: use FloatAsUint and UintAsFloat to write and read floats to/from sizebuf_t 2026-04-24 06:58:53 +05:00
Alibek Omarov
d46ed40f33 engine: pre-swap studio model before loading on BE 2026-04-24 06:58:53 +05:00
Alibek Omarov
82e031205c engine: pre-swap sprite before loading on BE 2026-04-24 06:58:53 +05:00
Alibek Omarov
6f5f04a29d engine: server: fix testpacket on BE server 2026-04-24 06:58:53 +05:00
Alibek Omarov
f9ed578bf1 engine: move CRC32_MapFIle to mod_bmodel, as it's a common function. Use le_struct_swap 2026-04-24 06:58:53 +05:00
Alibek Omarov
86c84c7de7 engine: common: add struct swapping for BSP models
With support for Quake BSP, HL BSP, extended HL BSP and BSPX.
2026-04-24 06:58:53 +05:00
Alibek Omarov
489c1c9dd5 public: add swaplib 2026-04-24 06:58:53 +05:00
Alibek Omarov
1df6a040ec engine: common: fix WriteBits/ReadBits 2026-04-24 06:58:53 +05:00
Alibek Omarov
82c2ca53fb engine: common: fix WriteUBitLong/ReadUBitLong 2026-04-24 06:58:53 +05:00
Alibek Omarov
3ddd1e2448 engine: common: fix LZSS on big endian 2026-04-24 06:58:53 +05:00
Alibek Omarov
371d619685 engine: platform: posix: remove dead ucontext.h include 2026-04-24 06:58:53 +05:00
Владислав Сухов
480f29e96e engine: common: mod_studio: fix model traversal and crash in Mod_StudioComputeBounds
Mod_StudioComputeBounds incorrectly assumed that all models across all body parts were laid out contiguously in memory. This caused out-of-bounds reads or reading garbage data when loading complex models, leading to crashes.
2026-04-24 01:13:46 +05:00
Zane van Iperen
95548053eb engine: common: set cmd_args to "" when tokenising, if we've no arguments
Fixes metamod-fwgs[1] breaking when CMD_ARGS() returns NULL.

[1]: eedede40e8/metamod/src/commands_meta.cpp (L63)
2026-04-22 21:27:27 +05:00
Владислав Сухов
0daf2c2b4d engine: client: add cvar to enable/disable gamepad gyro 2026-04-22 21:26:34 +05:00
Alibek Omarov
e2d5bea327 ref: cleanup unused functions 2026-04-21 23:10:32 +05:00
Alibek Omarov
306a5812a9 engine: server: fix svc_setpause usage in spawn command, fix minor protocol issue when spawnnig particle from pmove 2026-04-20 01:33:54 +05:00
Alibek Omarov
a3dba64513 public: move atlas packing code from renderer, add tests 2026-04-20 01:33:54 +05:00
Alibek Omarov
491ba9bc7d 3rdparty: mainui: update 2026-04-19 23:12:55 +05:00
Alibek Omarov
1c462df8a2 common: q_client: include stdint.h and xash3d_types.h 2026-04-19 23:12:44 +05:00
Alibek Omarov
ed975976f8 engine: client: add feature to reupload logo if any of logo related cvars were changed 2026-04-19 22:46:51 +05:00
Alibek Omarov
bbf8ab378d engine: server: add rate limiting for updating client's resources data too often 2026-04-19 22:30:04 +05:00
Alibek Omarov
5383e36af5 engine: net_chan: fix off-by-one error when trying to decompress packet 2026-04-19 20:50:42 +05:00
Alibek Omarov
897fb98b93 engine: net_chan: fix Netchan_AddBufferToList leaking pointer 2026-04-19 20:43:32 +05:00
Alibek Omarov
96e88adfcd engine: client: try to make R_Blood more accurate 2026-04-19 17:05:26 +05:00
Alibek Omarov
4dde4047f1 engine: make particle texture white, shift 1 pixel down and to the right 2026-04-19 17:04:40 +05:00
Alibek Omarov
905161529f engine: client: ref_common: reorganize R_CreateBuiltinTextures, but also fix a typo without assigned alpha to particle texture 2026-04-19 01:32:40 +05:00
Alibek Omarov
044523434d ref: add R_EntityDynamicLight that calculates lighting for studio and alias models, and also located in libref_common 2026-04-19 01:17:28 +05:00
Alibek Omarov
da4c4bb76a ref: remove unused sprite_radius global variable 2026-04-19 00:32:45 +05:00
Alibek Omarov
95b3271d68 engine: client: move input stuff to input subfolder 2026-04-18 22:18:50 +05:00
Alibek Omarov
3361a5d760 engine: client: split OSK into it's own file 2026-04-18 22:18:50 +05:00
Alibek Omarov
b725d2ece0 engine: client: moving beds 2026-04-18 22:18:50 +05:00
Alibek Omarov
66bee8b468 engine: client: fix trimmed autocomplete output for keys 2026-04-18 22:18:50 +05:00
Alibek Omarov
5e664dc3e9 engine: client: check for upper bound limit in Key_StringToKeynum 2026-04-18 22:18:50 +05:00
Alibek Omarov
bfdfeb3dc0 engine: client: fix Joy_GetHatValueForAxis threshold for forward axis, fix bindings limit 2026-04-18 22:18:50 +05:00
Alibek Omarov
49248f5e14 engine: client: touch fixes, fix division-by-zero in Touch_AspectRatio, GRID_COUNT, fix Touch_RemoveButtonFromList 2026-04-18 22:18:50 +05:00
Владислав Сухов
d03ea4c10a engine: client: in_gyro: fix inverted yaw axis 2026-04-18 00:41:46 +05:00
ksagameng
780dbd6183 scripts: gha: fix ios ci builds not including game libs 2026-04-17 19:41:30 +05:00
GLoOoccK
50da79f1e0 Update cl_parse_gs.c 2026-04-17 19:40:58 +05:00
Alibek Omarov
62201900cf ref: null: remove unused function 2026-04-16 23:25:32 +05:00
Alibek Omarov
ae233ac491 Merge branch 'refapi-15' 2026-04-16 23:19:06 +05:00
Владислав Сухов
dd1cb04cb7 engine: client: sdl: add support for built-in device gyroscope using SDL2 Sensor API (Android/iOS) 2026-04-16 00:15:35 +05:00
Alibek Omarov
74869c8f5d ref: make R_GatherPlayerLight shared between renderers 2026-04-14 22:36:15 +05:00
Alibek Omarov
dd712a0df9 engine: update Mod_PointInLeaf signature for RefAPI 2026-04-14 22:36:15 +05:00
Alibek Omarov
bfdb62d31e engine: ref_api: complete the RefAPI 15 changelog 2026-04-14 22:36:15 +05:00
Alibek Omarov
ae7b9eb28d engine: move creation of default textures to the engnie 2026-04-14 22:36:15 +05:00
Alibek Omarov
c63e13c8f4 engine: move handling of AVI textures into the engine 2026-04-14 22:36:15 +05:00
Alibek Omarov
d3e1407675 engine: remove unused R_Flush and VGUI_UploadTextureBlock from RefAPI 2026-04-14 22:36:15 +05:00
Alibek Omarov
e2e4728c7c engine: remove R_BeamCull from RefAPI 2026-04-14 22:36:15 +05:00
Alibek Omarov
dd3583d11a engine: move details textures parser to the engine 2026-04-14 21:18:57 +05:00
Alibek Omarov
e6924844da engine: move FillRenderAPI and FillTriAPI to renderers to reduce amount of exports 2026-04-14 21:18:56 +05:00
Alibek Omarov
a49db03561 engine: move StudioAPI negotiation to the engine 2026-04-14 21:18:03 +05:00
Alibek Omarov
84b7b5803b ref: soft: remove mistakingly put const on RI.rvp 2026-04-13 23:13:49 +05:00
Alibek Omarov
c224d39ba7 ref: soft: replace RI.onlyClientDraw with RF_ONLY_CLIENTDRAW flag in viewpass 2026-04-13 23:13:49 +05:00
Alibek Omarov
9b24b08df1 ref: gl: remove onlyClientDraw, drawOrtho, RI.params with viewpass flag check. RF_DRAW_OVERVIEW and RF_DRAW_CUBEMAP are cannot be set at the same time, so drawOrtho check replaced by RF_DRAW_CUBEMAP only 2026-04-13 23:13:49 +05:00
Alibek Omarov
a37b0e58f1 ref: soft: get rid of RI.drawOrtho, it's a debugging/development tool, not suited for software renderer use 2026-04-13 23:13:49 +05:00
Alibek Omarov
afca422e35 ref: soft: read ref_viewpass_t flags instead of cached RI.drawWorld 2026-04-13 23:13:49 +05:00
Alibek Omarov
3440ddf443 ref: gl: read ref_viewpass_t flags instead of cached RI.drawWorld 2026-04-13 23:13:49 +05:00
Alibek Omarov
6730ed9555 ref: soft: remove unused isSkyVisible 2026-04-13 23:13:49 +05:00
Alibek Omarov
fdd3a44fa0 ref: gl: remove unused isSkyVisible 2026-04-13 23:13:49 +05:00
Alibek Omarov
241c926076 ref: soft: replace RI.{vieworg,viewangles,pvsorigin} by ref_viewpass_t fields 2026-04-13 23:13:49 +05:00
Alibek Omarov
148a9a5a8c ref: gl: replace RI.{vieworg,viewangles,pvsorigin} by ref_viewpass_t fields 2026-04-13 23:13:49 +05:00
Alibek Omarov
513984d7fd ref: soft: save original viewpass and access viewport and fov through it 2026-04-13 23:13:49 +05:00
Alibek Omarov
1009795d5c ref: gl: save original viewpass and access viewport and fov through it 2026-04-13 23:13:49 +05:00
Alibek Omarov
e3d2da74db engine: move underwater fov effect handling to the engine 2026-04-13 23:13:49 +05:00
Alibek Omarov
ff3a57ae8a ref: gl: drop legacy mirror viewpass code 2026-04-13 23:13:49 +05:00
Alibek Omarov
271a6e2210 ref: soft: drop broken code for all non-viewer viewpasses 2026-04-13 23:13:49 +05:00
Alibek Omarov
0d40357b93 ref: common: wscript: fix psvita build 2026-04-13 13:01:18 +05:00
Alibek Omarov
a9dcdd034c ref: soft: fix build 2026-04-13 13:01:18 +05:00
Alibek Omarov
7324ff43fc ref: add GL_ResampleTexture to libref_common 2026-04-13 13:01:18 +05:00
Alibek Omarov
d85e7fc2b1 ref: move remaining light functions to libref_common 2026-04-13 13:01:18 +05:00
Alibek Omarov
d91ecfa3df ref: gl: refactor R_LightVec 2026-04-13 13:01:18 +05:00
Alibek Omarov
b7a3a02aad ref: common: add R_PushDlightsForBmodel 2026-04-13 13:01:18 +05:00
Alibek Omarov
5aca78951c ref: move R_PushDlights and R_MarkLights to ref_common 2026-04-13 13:01:18 +05:00
Alibek Omarov
81abae65b5 ref: move RunLightstyles to ref_common 2026-04-13 13:01:18 +05:00
Alibek Omarov
f5f6d9bbe4 engine: move sprite texture loading back to engine, only leave rendering for renderer 2026-04-13 13:01:18 +05:00
Alibek Omarov
ef73b55413 ref: common: move malloc and GL_InitRandomTable 2026-04-13 13:01:18 +05:00
Alibek Omarov
8334695522 ref: common: draft new static library, containing shared code for ref_gl and ref_soft 2026-04-13 13:01:18 +05:00
Alibek Omarov
d7f416d659 engine: draft new RefAPI 14: remove reserved functions from render api, remove CL_AddCustomBeam 2026-04-13 13:01:18 +05:00
a1batross
5f6577df2e engine: client: don't allow con_fontscale lower than 1
Prevents possible division by zero
2026-04-12 14:50:58 +05:00
Sergey Degtyar
e87f307ac1 engine: image: fix blue halo around sprays 2026-04-10 15:00:02 +05:00
Владислав Сухов
7e015a268b engine: client: in_joy: implement per-axis gyroscope deadzone support 2026-04-10 14:59:30 +05:00
TheEVolk
a679c53111 engine: client: add cvar sv_voice_singleplayer flag FCVAR_SERVER 2026-04-10 14:58:46 +05:00
TheEVolk
9e8d248044 engine: client: voice api 2026-04-10 14:58:46 +05:00
Alibek Omarov
055d646225 ref: gl: merge refactoring changes from litwater branch 2026-04-09 11:43:47 +05:00
ksagameng
a2514b6916 platform: ios: Remove FTP server as latest apple clang broke it 2026-04-09 10:25:18 +05:00
ksagameng
0ac5e78040 scripts: ios: Modify script to work with dylib loading changes 2026-04-09 10:25:18 +05:00
ksagameng
a82221e3df engine: platform: No more lib_ios, use FS_FindLibaray to load dylibs instead 2026-04-09 10:25:18 +05:00
Alibek Omarov
bb734456f6 engine: client: avi: use typeof() to declare function pointers to ffmpeg API 2026-04-09 09:52:52 +05:00
Alibek Omarov
a219ca7371 ci: upgrade ffmpeg binary to 8.1 2026-04-09 09:20:25 +05:00
Oleg
c3b5e43a7a engine: client: avi: bump FFMPEG symbols 2026-04-09 09:14:04 +05:00
Alibek Omarov
e7597d2e84 Merge branch 'theevolk-feature-soundapi' of github.com:ShadowOfEVolk/xash3d-fwgs 2026-04-08 17:18:03 +05:00
TheEVolk
386ca1b9bb engine: client: make sound api const 2026-04-08 14:27:24 +03:00
TheEVolk
da3960c4f0 engine: client: move bitflag definitions to sound_api 2026-04-08 14:15:16 +03:00
TheEVolk
cd85762b09 engine: client: fix arm builds 2026-04-08 14:11:44 +03:00
TheEVolk
d92b3a730c engine: client: adapt sound api to snd globals 2026-04-08 14:02:10 +03:00
Alibek Omarov
ffa53f14e5 engine: platform: sdl2: implement continuous background recalibration for gyroscope 2026-04-08 11:31:01 +05:00
Alibek Omarov
b8bd906832 engine: menu_int: fix a typo 2026-04-08 11:19:13 +05:00
Alibek Omarov
1c73d8d6f4 engine: client: add joy_debug, tool that visualizes gamepad input 2026-04-08 11:13:07 +05:00
Alibek Omarov
2c3fdb5eee engine: client: in_joy: clear gyro data after each FinalizeMove, only take data when calibration status is set to calibrated 2026-04-08 10:30:32 +05:00
Alibek Omarov
4bbe52e3b4 engine: client: in_joy: fix engine-level axis rebinding 2026-04-08 10:22:12 +05:00
Alibek Omarov
2d241ed96b engine: platform: sdl2: fix calibraion never ending if it was failed, lower sample count requirements to 50%, fix gyro when using PS5 controller over Bluetooth 2026-04-08 10:22:12 +05:00
Alibek Omarov
ba4bce06b1 engine: platform: sdl2: swap pitch and yaw gamepads axis, following the fix in 626a7eb9108376a68c2fd140e1b5942c64ea1e66 2026-04-08 10:22:12 +05:00
Владислав Сухов
f7e933aea1 engine: client: add gyroscope aiming support
refactor: standardize input functions to use (pitch, yaw) parameter order
2026-04-08 10:22:12 +05:00
Alibek Omarov
dbcf77c9a9 engine: cleanup, disallow exec for multiplayer non-TFC games 2026-04-08 08:22:01 +05:00
w_sh1t
8a9aa2da6c engine: common: add wildcard support for exec command, add amount of frames to wait in wait command 2026-04-08 08:05:42 +05:00
Alibek Omarov
0e8143aefc ref: gl: remove duplicate line from if-else branches in R_MarkLeaves 2026-04-08 08:05:00 +05:00
TheEVolk
4345e9e6a6 Merge remote-tracking branch 'upstream/master' into theevolk-feature-soundapi 2026-04-07 18:35:39 +03:00
Alibek Omarov
e7fa45d5ca ref: gl: fix masked textures with VBO 2026-04-06 11:12:09 +05:00
Alibek Omarov
abda0a07c2 ref: gl: cache worldmodel pointer instead of accessing it through gp_cl pointer 2026-04-06 11:07:08 +05:00
Alibek Omarov
9d22827395 ref: gl: reorganize checks in R_CullSurface 2026-04-06 11:04:33 +05:00
Alibek Omarov
7b665b3763 ref: gl: track max height of a dynamic lightmap 2026-04-06 10:04:15 +05:00
Alibek Omarov
58692f397a ref: gl: reorganize R_CheckLightMap to exit early for dlighted surfaces 2026-04-06 09:40:19 +05:00
Alibek Omarov
2253fcc955 ref: gl: skip redundant positions in LM_AllocBlock 2026-04-06 09:05:47 +05:00
Alibek Omarov
cbbae0c9bf ref: gl: simplify backface culling checks in frustum class 2026-04-06 08:25:45 +05:00
Alibek Omarov
ef513f27ac engine: simplify node recursion functions, do not fetch both children at once if only one is used 2026-04-06 08:16:51 +05:00
Alibek Omarov
e8ae48fd3d ref: simplify node recursion functions, do not fetch both children at once if only one is used 2026-04-06 08:16:20 +05:00
Alibek Omarov
434e508186 engine: client: reserve some free space in channel_t and rawchan_t 2026-04-06 07:27:50 +05:00
Alibek Omarov
4a2b3e89b4 engine: client: disable NET_EXT_SPLITSIZE in local game, for slightly better performance 2026-04-06 07:14:06 +05:00
Alibek Omarov
6c3f8c7ec5 engine: mod_bmodel: allow nearest on quake skybox 2026-04-06 06:55:52 +05:00
Alibek Omarov
3fffe061d5 engine: client: switch to dynamically allocating vox words, freeing up 320 Kb of memory 2026-04-06 06:55:27 +05:00
Alibek Omarov
729c985eaf engine: client: consolidate dma, s_listener and bunch of separate sound globals into one snd global struct. Replace bit fields with bit flags in preparation for SoundAPI
Remove s_combine_sounds, it never worked great and was disabled by default.
2026-04-06 06:00:48 +05:00
Alibek Omarov
75fb6c5f34 engine: add Mem_Free2 that takes a pointer to pointer and then sets it to NULL 2026-04-06 05:14:33 +05:00
Alibek Omarov
8bc9b1865a engine: client: allow custom paths in cdaudio.txt 2026-04-05 07:03:46 +05:00
Alibek Omarov
d6993c47d6 engine: mod_bmodel: do not try to load visibility if there is none, like for external bmodels 2026-04-05 07:03:46 +05:00
Alibek Omarov
b8792e4084 filesystem: set qwrap as falldir for Quake games 2026-04-05 07:03:46 +05:00
Alibek Omarov
2234dd48bc engine: client: soundlib: use unsigned int in GetLittleLong to fix ubsan error 2026-04-05 07:03:46 +05:00
Alibek Omarov
c8693e1e89 engine: common: fix char array underflow, on Q_strlen() - 1 2026-04-05 07:03:46 +05:00
Alibek Omarov
3ace4a61d2 ref: gl: fix harmless NULL pointer dereference in alias model loading code 2026-04-05 07:03:46 +05:00
Alibek Omarov
bd3f865cf7 engine: mod_bmodel: only look for BSPX lumps if BSPX header was found 2026-04-05 07:03:46 +05:00
Alibek Omarov
b68e71abfd engine: net_chan: explicitly convert qboolean to uint before right shift to 31 bits 2026-04-05 07:03:46 +05:00
Alibek Omarov
6179bef160 engine: fix possible buffer overflow when looking for BSPX header 2026-04-05 07:03:46 +05:00
Alibek Omarov
983427ebb3 engine: client: allow coloring fixed font text 2026-04-05 07:03:46 +05:00
Alibek Omarov
1b2ab34745 engine: client: keep TF_NEAREST enabled for console font, if HUD font failed to load 2026-04-05 07:03:46 +05:00
Alibek Omarov
2713f03ec2 filesystem: fix Quake gamedir check if pak0.pak is written in uppercase, omitting caseinsensitive FS emulation as is VFS is not enabled at this stage 2026-04-05 07:03:46 +05:00
Alibek Omarov
238083c174 engine: client: move common server message handlers to CL_ParseCommonMessage and only HL specific ones to CL_ParseCommonHLMessage (previously CL_ParseCommonDLLMessage) 2026-04-05 07:03:46 +05:00
Alibek Omarov
78de3ee6d9 engine: client: initialize cl_textmessage at compile-time 2026-04-05 07:03:46 +05:00
Alibek Omarov
695081e079 3rdparty: library_suffix: update 2026-04-05 07:03:46 +05:00
TheEVolk
2c5e2e1ded engine: client: remove sound_t definition from sound.h 2026-04-04 18:30:14 +03:00
TheEVolk
d151c48d1f engine: client: fix api issues 2026-04-04 17:47:54 +03:00
TheEVolk
35998bce81 engine: client: add sound api 2026-04-04 16:08:31 +03:00
Alibek Omarov
a10520ca9e engine: host: add message when native game libraries weren't found but libraries for x86 windows, linux or macos were, telling that the mod probably require custom logic code 2026-04-04 17:37:48 +05:00
Alibek Omarov
cb4a03ded2 engine: platform: add Platform_LibraryExists and implement it for iOS and generally for everything else with FS_FileExists 2026-04-04 17:37:48 +05:00
Alibek Omarov
31a0d17de5 3rdparty: mainui: update 2026-04-04 17:36:21 +05:00
Alibek Omarov
5f765a35b9 ref: gl: show more detailed Host_Error instead of ASSERT in R_GetTexture 2026-04-04 17:36:05 +05:00
Alibek Omarov
5606346cc1 scripts: waifulib: xcompile: add Android NDK 30 2026-04-04 17:34:31 +05:00
Alibek Omarov
6faee8fd89 scripts: ios: call mkdir -p to not error out if the directory already exists (thanks, @ksagaming) 2026-04-04 17:34:07 +05:00
Alibek Omarov
255c9bcaed 3rdparty: mainui: update 2026-04-03 09:27:33 +05:00
Alibek Omarov
25a3fdbcec 3rdparty: mainui: update 2026-04-03 09:20:28 +05:00
Alibek Omarov
8e450d869b engine: common: add host.default_gamedir containing gamedir passed to us from game launcher 2026-04-03 04:09:25 +05:00
Alibek Omarov
e8ef7ad60c engine: server: throw Host_Error when game tries to call precache model with invalid argument, like GoldSrc 2026-04-03 01:43:23 +05:00
Alibek Omarov
216eedcdcd ref: gl: better wording about backface culling goldsrc bug [skip ci] 2026-04-03 01:28:04 +05:00
Alibek Omarov
fd55716633 ref: gl: change backface culling only if it wasn't disabled by mod developer beforehand 2026-04-03 01:23:55 +05:00
Alibek Omarov
0b920be381 engine: client: delay freeing up the sound channel when it becomes inaudible 2026-04-03 00:03:52 +05:00
Alibek Omarov
e6dfccb6ba engine: reorganize common.h header 2026-04-02 20:45:00 +05:00
Alibek Omarov
3129081050 engine: fix a typo USING_ASAN -> USE_ASAN 2026-04-02 02:16:50 +05:00
Alibek Omarov
00dac928c9 ref: replace checking PARM_QUAKE_COMPATIBLE with direct check of feature flag set 2026-04-02 02:13:27 +05:00
Alibek Omarov
3726256514 engine: simplify IsQuakeCompatible by enforcing QUAKE_COMPATIBLE feature flag when Quake demo starts 2026-04-02 02:11:40 +05:00
Alibek Omarov
5eb43505cb engine: platform: try to fix build on 32-bit arm in debug builds (r7 is used by frame pointer, so we push it into stack before using for a syscall) 2026-04-02 02:02:59 +05:00
Alibek Omarov
f7e516b775 ci: run armhf build on arm64 runner too 2026-04-02 02:01:09 +05:00
Alibek Omarov
4edd512902 scripts: gha: enable all renderers on Linux targets 2026-04-01 20:59:48 +05:00
Alibek Omarov
acb263368a ci: try to use github arm64 runner 2026-04-01 20:51:22 +05:00
Alibek Omarov
38b3805ad8 engine: client: gamma: fix gamma when compiled with -ffast-math 2026-04-01 20:35:04 +05:00
Alibek Omarov
2ebaa2ad8e engine: wscript: disable engine tests on macOS temporarily 2026-04-01 09:15:09 +05:00
Alibek Omarov
018c52b998 scripts: gha: apple: add -v flag to build stage 2026-04-01 06:36:40 +05:00
Alibek Omarov
99d01e7967 engine: try to fix running tests on macOS 2 2026-04-01 06:29:22 +05:00
Alibek Omarov
e5670afc4c common: xash3d_types: undefine ARRAYSIZE before defining our ARRAYSIZE (actually we should stop using ARRAYSIZE instead) 2026-04-01 06:19:06 +05:00
Alibek Omarov
5aba96ce14 engine: try to fix tests on macOS 2026-04-01 06:19:06 +05:00
Alibek Omarov
6b97fc1825 engine: fix tests build on Win32 2026-04-01 06:02:08 +05:00
Alibek Omarov
42cb5fe547 engine: wscript: append SDL2 library path to the client tests LD_LIBRARY_PATH (or it's platform-specific alternative) 2026-04-01 05:40:45 +05:00
Alibek Omarov
6a1718aa07 engine: get rid of --enable-engine-tests, now engine tests run as waf targets, allowing their run on CI seamlessly. 2026-04-01 05:15:31 +05:00
Alibek Omarov
f980f40f8e engine: client: add new titles.txt parser, that avoids max messages limit 2026-04-01 04:10:41 +05:00
Alibek Omarov
5506486ed1 README: add note about running unit tests 2026-03-31 23:36:13 +05:00
Alibek Omarov
4a90d2fbe5 contributing: better wording 2026-03-31 23:35:47 +05:00
RevengeRip
8767a4d6ba engine: client: avi: remove XASH_FFMPEG_DLOPEN
Is this a mistake? This define effectively forces `--enable-ffmpeg-dlopen` at all times ignoring configuration (which in turn makes build to fail with ffmpeg 8.1).
2026-03-31 16:24:32 +05:00
Xav101
34bb2665cf wscript: removed redundant irix linkflags 2026-03-30 11:33:46 +05:00
Xav101
1a79a9488d 3rdparty: libbacktrace: wscript: add irix to the unsupported platforms list. This could probably be made to work at some point but we'll disable it just to keep things simple. 2026-03-30 11:33:46 +05:00
Alibek Omarov
c91b707b66 engine: client: ignore blank music tracks in "cd loop" command so music could carry over through changelevels 2026-03-28 19:23:59 +05:00
Alibek Omarov
a50ea929d6 engine: fix a typo in R_Blood, fix a typo in pt_4x_slowgrav 2026-03-28 18:44:50 +05:00
Alibek Omarov
d5b481ac9d ref: gl: fix off-by-one error in GL_PopPolygonOffset 2026-03-28 04:43:14 +05:00
Marc-Antoine Lortie
8e69be0542 Documentation: opensource-mods.md: add Heart of Evil mirror. 2026-03-28 01:36:50 +05:00
Marc-Antoine Lortie
5296147c6b android: Supplement French Translation 2026-03-28 01:36:18 +05:00
Alibek Omarov
30fa074c8c engine: server: implement time window based challenge generation 2026-03-27 22:17:34 +05:00
Alibek Omarov
02b1d15e42 ref: gl: add gl_polyoffset_bmodels to enable poly offset for drawing brush models, avoiding zfighting 2026-03-27 03:27:20 +05:00
Alibek Omarov
82475c3b58 public: remove tgmath.h include in C++ mode 2026-03-26 15:49:33 +05:00
Alibek Omarov
c421f3136d public: reformat BoxOnPlaneSide function 2026-03-26 02:48:13 +05:00
Alibek Omarov
641a3663f5 ref: gl: unlikely but use fast side check for frustum 2026-03-26 02:44:53 +05:00
Alibek Omarov
acaede2a0d public: redefine mplane_t, make PlaneIntersect inline, turn BOX_ON_PLANE_SIDE into an inline function instead of a macro 2026-03-26 02:37:59 +05:00
Alibek Omarov
d8d1ebf23c engine: cleanup unused functions 2026-03-26 02:24:30 +05:00
Alibek Omarov
926f0ae95d engine: server: get rid of {ALLOC_,MAKE_,}STRING macros, call the underlying functions directly 2026-03-26 02:24:30 +05:00
Alibek Omarov
c5d5ce5f43 3rdparty: mainui: update 2026-03-25 23:06:27 +05:00
Alibek Omarov
2fe950b0af engine: server: don't show scary message about missing server library on engine startup in multiplayer capable games 2026-03-25 23:06:27 +05:00
坂柳有栖
a04bb01841 android: Supplement Simplified Chinese Translation
android: Supplement Simplified Chinese Translation
2026-03-25 16:48:04 +05:00
坂柳有栖
2d2de9dc26 android: Supplement Simplified Chinese Translation
Android: values-zh-rCN/strings.xml,Supplement Simplified Chinese Translation
2026-03-25 16:48:04 +05:00
Alibek Omarov
81fc511f0a android: add ability to disable yapb bots from our settings page 2026-03-25 00:51:21 +05:00
Alibek Omarov
d85d813d62 android: add a proper exit from storage permission dialog 2026-03-25 00:50:47 +05:00
Alibek Omarov
9f6b571d79 android: check if user tries to open known mod and redirect 2026-03-25 00:49:44 +05:00
Alibek Omarov
292f393f99 android: fix name with XML file with Simplified Chinese translation 2026-03-24 23:16:38 +05:00
Alibek Omarov
f523f04ee2 android: build mainui_cpp through CMake 2026-03-24 21:05:56 +05:00
Alibek Omarov
88d8115306 ci: update actions to newer versions 2026-03-24 04:20:11 +05:00
Alibek Omarov
9a050cb67f scripts: gha: fix shellcheck warnings in build_* scripts 2026-03-24 04:07:21 +05:00
Alibek Omarov
cf8046ecd9 scripts: gha: fix shellcheck warnings in deps_* scripts 2026-03-24 03:51:54 +05:00
Alibek Omarov
6a292ec68c scripts: gha: android: bump NDK, JDK and commandline tools versions 2026-03-24 03:46:58 +05:00
Alibek Omarov
4679359688 android: update gradle, agp, etc 2026-03-24 03:46:58 +05:00
Andrey Akhmichin
bca81de210 Documentation: opensource-mods.md: update. 2026-03-23 01:39:21 +05:00
Alibek Omarov
ec1a65c18f 3rdparty: mainui: update 2026-03-22 20:13:50 +05:00
Alibek Omarov
ed559d9ed7 engine: client: fix off by one error in KeynumTostring 2026-03-22 20:08:01 +05:00
Alibek Omarov
8995a584b9 engine: do not register autocompletion for commands that are not possible in dedicated build 2026-03-22 14:20:59 +05:00
Alibek Omarov
30c81c9f01 engine: use dynamic allocation for required wad list and optional debug info 2026-03-22 14:20:59 +05:00
ksagameng
11c7c1b77d scripts: ios: enable decay build in buildallmods.sh 2026-03-19 22:36:51 +05:00
ksagameng
93cb146f39 engine: common: Disable changegame menu on iOS 2026-03-19 22:36:51 +05:00
ksagameng
59dbe1d01e ios: Add entitlements.plist and codesign the generated ipa with said plist in createipa.sh 2026-03-19 22:36:51 +05:00
ksagameng
d6dc77d409 engine: common: Change macro for consistency 2026-03-19 22:36:51 +05:00
ksagameng
f8714a57d3 scripts: ios: Build all mods script and postfixing support in buildhlsdk script 2026-03-19 22:36:51 +05:00
ksagameng
9f71255d6f engine: ios: Fixed code for running multiple client libraries bundled in the same ipa 2026-03-19 22:36:51 +05:00
Alibek Omarov
d8bab034c2 engine: client: extend the supported key list for international keyboards, small refactoring of old smelly code 2026-03-19 15:41:12 +05:00
Alibek Omarov
9a25768617 ref: soft: cleanup dead, commented out code and global variables 2026-03-17 00:19:27 +05:00
Alibek Omarov
e76528d929 ref: make sprite and studio global variables static 2026-03-17 00:19:27 +05:00
Alibek Omarov
00ad05c92a ref: gl: move showtextures implementation to gl_image.c 2026-03-17 00:19:27 +05:00
Alibek Omarov
df95baa282 public: minor refactoring, mark some more functions as returns_nonnull 2026-03-17 00:19:27 +05:00
Alibek Omarov
f42e2f59b7 filesystem: mark some fs ops const 2026-03-17 00:19:27 +05:00
SNMetamorph
d8b784482e Documentation: extensions: added description for steam broker protocol 2026-03-16 15:04:02 +05:00
SNMetamorph
b136f1e078 Documentation: updated information about GoldSrc network protocol support 2026-03-16 15:04:02 +05:00
SNMetamorph
bd73775a2f engine: client: refactor steam broker internal logic 2026-03-13 02:54:35 +05:00
SNMetamorph
1f133ea702 engine: client: add steam broker messages for game init & shutdown events 2026-03-13 02:54:35 +05:00
SNMetamorph
8acc7e4f76 engine: client: rename steam broker message sb_terminate to sb_disconnect 2026-03-13 02:54:35 +05:00
Alibek Omarov
00e2e8e502 3rdparty: mainui: update 2026-03-12 03:50:12 +05:00
Alibek Omarov
e5013196ac engine: client: reduce amount of glColor4ub calls when rendering text 2026-03-11 10:13:23 +05:00
Alibek Omarov
658a3b0c6a ref: gl: minor refactoring 2026-03-11 10:11:21 +05:00
Alibek Omarov
4e9a2dace0 ref: gl: only check for GL errors if both gl_check_errors and developer mode are enabled 2026-03-11 08:39:58 +05:00
Alibek Omarov
f7b389b7cd common: remove particledef.h 2026-03-11 08:39:33 +05:00
Alibek Omarov
dac77ec0af engine: client: idenitification: add /etc/machine-id 2026-03-05 06:06:52 +05:00
Alibek Omarov
32d73e62f4 engine: minor refactoring, remove host.apply_game_config as it's unused 2026-03-05 06:06:04 +05:00
Alibek Omarov
b353ec463a engine: host: move exec, userconfigd and memstats to it's respective files 2026-03-05 04:44:19 +05:00
Alibek Omarov
36be731893 engine: cmd: make all command buffers size static 2026-03-05 04:42:56 +05:00
Alibek Omarov
0389ef3b04 game_launch: simplify 2026-03-05 02:58:00 +05:00
Alibek Omarov
b2886dd2e9 engine: platform: remove support for glibc's backtrace(), as it's unused 2026-03-05 02:56:38 +05:00
Alibek Omarov
af5d190bc4 engine: common: mod_bmodel: make texture name array larger so it fits both world name and miptex name 2026-03-04 06:06:00 +05:00
Alibek Omarov
5ca31f0965 engine: client: s_mix: always mix when not in menu, as DSP effects might produce sound even if no channels are active 2026-03-02 15:39:49 +05:00
Alibek Omarov
c19a824a42 public: add Q_memfgets, replacement for COM_MemFgets rewritten using crtlib functions. Add a test 2026-03-01 03:04:33 +05:00
Alibek Omarov
53c5a33ffd engine: only switch to console on Host_Error when developer mode is enabled 2026-02-28 20:39:34 +05:00
Alibek Omarov
711d15958c engine: color Host_Error red to make it more visible in the console 2026-02-28 20:39:13 +05:00
Alibek Omarov
99d7c52b4e 3rdparty: mainui: update 2026-02-28 20:27:44 +05:00
Alibek Omarov
342a59a408 engine: client: idenitification: add server IP address to the MD5 hash, not to the ID bloom filter 2026-02-28 04:20:53 +05:00
Alibek Omarov
9302b8904c engine: client: identification: fix a typo 2026-02-28 04:17:34 +05:00
Alibek Omarov
bdbf5c0ef2 engine: client: titles: add another edge case test for titles.txt parser 2026-02-28 04:17:34 +05:00
Alibek Omarov
192ed942c4 public: add new features to ParseFileSafe, such as disabling interpreting certain characters as separate tokens, or instead forcing them to be separate tokens 2026-02-28 04:17:34 +05:00
Alibek Omarov
56255f6de0 engine: client: add simple test for titles.txt parser 2026-02-28 01:49:25 +05:00
Alibek Omarov
7ce830ea33 engine: client: vox: fix defaulted word parameters 2026-02-28 00:25:19 +05:00
Alibek Omarov
9a285986c4 public: reimplement COM_TrimSpace, add it to crtlib, add tests for it 2026-02-28 00:05:37 +05:00
Alibek Omarov
62c547570a engine: minor refactoring 2026-02-27 23:00:39 +05:00
Alibek Omarov
88e5013623 ref: gl: vgl_shim: try to fix build on PSVita 2026-02-26 17:22:47 +05:00
Alibek Omarov
ea0fed24ec filesystem: remove unneccessary dependency on const.h 2026-02-26 10:29:09 +05:00
Alibek Omarov
f863e346f5 engine: reduce dependency on eiface.h 2026-02-26 10:29:09 +05:00
Alibek Omarov
7c527d8886 public: minor refactoring 2026-02-26 10:29:09 +05:00
Alibek Omarov
e51f93a5b4 engine: remove unused hltv.h 2026-02-26 10:29:09 +05:00
Alibek Omarov
50147a3d48 common: replace HLSDK pmtrace.h and pm_movevars.h by it's QW counterparts 2026-02-26 10:29:09 +05:00
Alibek Omarov
26acc25018 common: use Quake derived definitions for usercmd_t and dlight_t 2026-02-26 10:29:09 +05:00
Alibek Omarov
d8e1597726 common: xash3d_types: add Quake's link_t definition and offsetof macro 2026-02-26 10:29:09 +05:00
Alibek Omarov
2c004e6cb7 3rdparty: mainui: update 2026-02-25 03:20:33 +05:00
Alibek Omarov
9cf9eb91bb common: remove unused mathlib.h 2026-02-25 00:08:17 +05:00
Alibek Omarov
fc9ab68837 public: link tests to build_vcs too 2026-02-23 23:25:24 +05:00
Alibek Omarov
be6ad74e1b utils: mdldec: fix building with plain makefile 2026-02-23 21:50:52 +05:00
Alibek Omarov
2bdc22fbeb public: exclude build_vcs from libpublic dependencies, add it per project 2026-02-23 21:48:23 +05:00
Alibek Omarov
382b19e458 engine: minor refactoring 2026-02-23 21:46:20 +05:00
Alibek Omarov
c63c0ba04b ref: minor refactoring 2026-02-23 21:46:01 +05:00
Alibek Omarov
109579fa92 3rdparty: mainui: update 2026-02-23 18:14:23 +05:00
IntriguingTiles
005544d974 engine: client: always set scaling factors 2026-02-21 17:10:37 +05:00
Alibek Omarov
07937f5f6c engine: client: consolidate two remaining functions in s_utils.c into s_main.c 2026-02-21 03:37:36 +05:00
Alibek Omarov
8bdd6bfc5e ref: gl: add link to web archive where the ripple animation algorithm was originally described [ci skip] 2026-02-21 03:11:02 +05:00
Alibek Omarov
8b0d3557f3 game_launch: change Sys_ChangeGame to a stub 2026-02-21 02:21:09 +05:00
Alibek Omarov
436fa98f33 engine: remove the code that calls pfnChangeGame from game_launch, as we never use it anyway 2026-02-21 02:21:09 +05:00
Alibek Omarov
718fbac9be Merge branch 'steam-broker' 2026-02-21 02:00:53 +05:00
Alibek Omarov
abaf390dfc engine: client: introduce a new mixer 2026-02-21 01:43:14 +05:00
Alibek Omarov
9205ddc5b4 engine: client: voice: rewrite Voice_IsGoldSrcMode to make it easier to understand and also fix build with Clang 2026-02-21 01:20:34 +05:00
Alibek Omarov
6bcb98e779 public: replace COM_CheckString and COM_CheckStringEmpty by COM_StringEmptyOrNULL and COM_StringEmpty. Remove legacy macros 2026-02-21 01:20:34 +05:00
Alibek Omarov
4722ad6be6 ref: replace COM_CheckString and COM_CheckStringEmpty by COM_StringEmptyOrNULL and COM_StringEmpty 2026-02-21 01:20:34 +05:00
Alibek Omarov
baa005ff71 filesystem: replace COM_CheckString and COM_CheckStringEmpty by COM_StringEmptyOrNULL and COM_StringEmpty 2026-02-21 01:20:34 +05:00
Alibek Omarov
76363a555c engine: platform: replace COM_CheckString and COM_CheckStringEmpty by COM_StringEmptyOrNULL and COM_StringEmpty 2026-02-21 01:20:34 +05:00
Alibek Omarov
de70e12bbe engine: server: replace COM_CheckString and COM_CheckStringEmpty by COM_StringEmptyOrNULL and COM_StringEmpty 2026-02-21 01:20:34 +05:00
Alibek Omarov
eacc35d9e1 engine: replace COM_CheckString and COM_CheckStringEmpty by COM_StringEmptyOrNULL and COM_StringEmpty 2026-02-21 01:20:34 +05:00
Alibek Omarov
3040552580 engine: client: replace COM_CheckString with COM_StringEmptyOrNULL 2026-02-21 01:20:34 +05:00
Alibek Omarov
24aaeb07cb engine: client: replace COM_CheckStringEmpty with COM_StringEmpty 2026-02-21 01:20:34 +05:00
Alibek Omarov
ae15886a14 public: add COM_StringEmpty and COM_StringEmptyOrNULL macros (that eventually replace less obvious COM_CheckString and COM_CheckStringEmpty) 2026-02-21 01:20:34 +05:00
Alibek Omarov
b2911e6d96 engine: client: add double quotes around key in bind command in config file. Thanks @TheKingFireS for fix 2026-02-20 23:14:27 +05:00
Alibek Omarov
2735add7d0 engine: drop setgl command 2026-02-20 22:08:26 +05:00
Alibek Omarov
0abb667c28 engine: drop XASH_HASHED_VARS define 2026-02-20 22:08:05 +05:00
Alibek Omarov
19dfec5f52 engine: use BaseCmd_FindAll when looking for cvars and cmds simultaneously 2026-02-20 21:56:59 +05:00
Alibek Omarov
e0f01ddae7 engine: remove unused ignore_group argument from Cvar_FindVarExt 2026-02-20 21:55:15 +05:00
Alibek Omarov
2f8a0510b8 engine: client: return NULL from LookupBinding on unknown bindings, like GoldSource 2026-02-16 23:59:28 +05:00
Alibek Omarov
dbbe75fec6 engine: mod_bmodel: minor refactoring 2026-02-13 12:33:44 +05:00
Alibek Omarov
c0b15fb186 ci: disable freebsd-16-0-snap image as it's unsupported by cirrus 2026-02-13 12:15:46 +05:00
Alibek Omarov
f802b0eed9 engine: mod_bmodel: fix build w/ MSVC 2026-02-13 12:09:01 +05:00
Alibek Omarov
cf9f9b40e7 3rdparty: mainui: update 2026-02-13 11:40:47 +05:00
Alibek Omarov
1b2c00e228 engine: add ability to scan and load BSPX lumps. As an example support RGBLIGHTING and LIGHTINGDIR extensions. 2026-02-13 11:16:12 +05:00
Alibek Omarov
587eabd829 common: bspfile: add basic BSPX definitions 2026-02-13 11:08:35 +05:00
Alibek Omarov
80e0d45c09 3rdparty: mainui: update 2026-02-12 13:30:30 +05:00
Alibek Omarov
f034276354 readme: update discord link 2026-02-10 18:19:03 +05:00
Alibek Omarov
306455280a engine: identification: include build.h early so platform definitions are visible when including system headers 2026-02-10 16:13:56 +05:00
Alibek Omarov
d9221c8491 scripts: waifulib: remove outdated stuff 2026-02-08 10:14:28 +05:00
Alibek Omarov
249e1404c1 engine: client: identification: add client.h so compiler doesn't complain about missing prototypes for public functions 2026-02-08 06:38:05 +05:00
Alibek Omarov
dd55b77543 engine: client: identification: do not salt port as suggested by @snmetamorph 2026-02-08 06:38:05 +05:00
Alibek Omarov
c772fb98d1 engine: client: salt xashid with server address 2026-02-08 06:38:05 +05:00
Alibek Omarov
1c1e5c50a9 3rdparty: mainui: update 2026-02-07 14:38:23 +05:00
Alibek Omarov
5cc784f2fa engine: common: fix inverted check in addmaster 2026-02-07 14:38:12 +05:00
Razcoina
983b9611ac avi: add audio context check before sending packet 2026-02-07 07:50:57 +05:00
Alibek Omarov
a7f154d05a engine: platform: move SDL hints from android code to main SDL code 2026-02-05 08:45:23 +05:00
Alibek Omarov
0e75428d62 engine: avi: do not playback video if codec context is NULL 2026-02-05 08:26:43 +05:00
Alibek Omarov
fa34092ba2 engine: client: make RefAPI's R_GetConfigName optional 2026-02-05 08:17:59 +05:00
Alibek Omarov
f01818a3f8 wscript: allow linking with system-wide opus 1.6.1 which has a fix for custom modes 2026-02-05 07:47:18 +05:00
Alibek Omarov
732bc50a3a filesystem: set custom_loader flag even if loading DLLs from archives is unsupported
After all, it's an engine job to figure out whether it wants to load this binary or not.
2026-02-03 17:48:27 +05:00
Владислав Сухов
e9ffe12481 ref: gl: fix opacity of additive sprites and textures in fog 2026-01-31 20:49:47 +05:00
Alibek Omarov
cca6f8da2d engine: platform: sdl2: more robust error handling when creating a window and opengl context 2026-01-31 14:13:37 +05:00
Владислав Сухов
d391ef1747 public: crtlib: make COM_FixSlashes remove duplicate slashes 2026-01-30 02:25:44 +05:00
Alibek Omarov
4804586728 engine: let movevars.zmax be encoded in 24 bits 2026-01-30 02:04:07 +05:00
Alibek Omarov
d08fd5c855 engine: directly access background state cvar 2026-01-30 00:16:19 +05:00
Alibek Omarov
9cc884dfe6 engine: add function Cvar_DirectFullSet that acts like Cvar_DirectSet and Cvar_FullSet at the same time 2026-01-30 00:16:19 +05:00
Alibek Omarov
e767770647 common: set default window mode to windowed on windows
Window, window, window, window!
2026-01-30 00:16:19 +05:00
Alibek Omarov
456ad17d05 engine: platform: sdl2: make fake fullscreen for windowed mode. Simplify the code by first creating window in windowed mode and then switching to fullscreen if requested 2026-01-30 00:16:19 +05:00
Alibek Omarov
5ee21ca6ab engine: platform: sdl2: remove window_x/ypos cvars 2026-01-30 00:16:19 +05:00
Alibek Omarov
f706b51d14 3rdparty: mainui: update 2026-01-28 15:24:13 +05:00
Alibek Omarov
d8895ae497 engine: do not set actually freed models as to be freed, fixing another models leak. Improve modellist output 2026-01-28 15:23:43 +05:00
Alibek Omarov
a807168816 engine: client: send broadcast for 10 more ports forward when scanning servers in LAN 2026-01-27 14:55:08 +05:00
Alibek Omarov
a26c56629e engine: client: deactivate menu when on escape if we are going back to game 2026-01-26 02:17:03 +05:00
Alibek Omarov
8ee997b1bc Reapply "engine: client: allow console toggle from menu via keybind"
This reverts commit 27c71194ba.
2026-01-26 02:12:07 +05:00
Alibek Omarov
27c71194ba Revert "engine: client: allow console toggle from menu via keybind"
This reverts commit 9ed76fbb36.
2026-01-26 01:37:26 +05:00
Alibek Omarov
c029def67d 3rdparty: MultiEmulator: update 2026-01-24 07:36:35 +05:00
Alibek Omarov
8bb6968ab8 scripts: waifulib: sdl2: add paths support for SDL mingw dev package 2026-01-24 07:32:02 +05:00
Alibek Omarov
cc9e3a5a81 engine: misc build fixes for clang with mingw 2026-01-24 07:21:30 +05:00
Alibek Omarov
e9cd94eea6 3rdparty: mainui: update 2026-01-24 07:20:22 +05:00
Alibek Omarov
572fd063fc common: port: do not redefine NOMINMAX and alloca if already defined 2026-01-24 07:17:53 +05:00
Alibek Omarov
1052e45928 3rdparty: libbacktrace: wscript: add support for building on Windows 2026-01-24 07:17:11 +05:00
Alibek Omarov
df561147ec 3rdparty: libbacktrace: update 2026-01-24 07:16:49 +05:00
Alibek Omarov
9ce570fe28 3rdparty: MultiEmulator: update 2026-01-24 07:16:34 +05:00
Alibek Omarov
20a20c72d4 common: do not override neither _cdecl or _stdcall 2026-01-24 07:09:36 +05:00
Alibek Omarov
7a8837bf2a ref: do not remove permanent decals 2026-01-22 23:40:59 +05:00
Alibek Omarov
b83d763a50 3rdparty: mainui: update 2026-01-21 22:23:19 +05:00
Alibek Omarov
b2ab748fcb engine: do not reset flNextPrimaryMove in pmove struct, as it's used by modders 2026-01-21 05:10:51 +05:00
Alibek Omarov
56e5a95cb6 engine: platform: sdl2: add command vid_info to dump current SDL window state 2026-01-21 01:58:38 +05:00
Alibek Omarov
214b133e5d engine: fix build 2026-01-20 23:43:28 +05:00
Alibek Omarov
f9bbe2a2c4 engine: make host.argv an array of pointers to const data 2026-01-20 22:42:14 +05:00
Alibek Omarov
c5f90aabd6 engine: use Sys_GetIntFromCmdLine where possible 2026-01-20 22:30:39 +05:00
Alibek Omarov
77123f710b 3rdparty: mainui: update 2026-01-20 19:27:18 +05:00
Владислав Сухов
5c5c0ad94d engine: common: prepare support for direct scr writing 2026-01-20 19:26:28 +05:00
Alibek Omarov
5232ccb6e3 engine: server: do not print the port in ban list, it's not used there and causes issues when exported to a config 2026-01-20 00:37:23 +05:00
Alibek Omarov
bc387477ce engine: common: fix world submodels were not being purged correctly, leading to a "memory leak" 2026-01-20 00:34:10 +05:00
Alibek Omarov
1c6eb85a12 public: crtlib: modify format of Q_timestamp TIME_FILENAME to make it sortable 2026-01-19 22:33:49 +05:00
Alibek Omarov
ed950723cd engine: client: add cl_autorecord cvar, to start recording demo as soon as client spawns on server 2026-01-19 22:30:20 +05:00
Alibek Omarov
e06087cb0b engine: client: move svc_restoresound parser into svc_sound parser 2026-01-19 22:07:59 +05:00
Alibek Omarov
8cbc7819a6 engine: common: transform asserts into Host_Error calls in Mod_LoadModel 2026-01-19 22:00:33 +05:00
Alibek Omarov
7c588540d8 engine: common: http: fix incorrect parsing of URLs 2026-01-18 15:33:10 +05:00
Alibek Omarov
0e0763c820 engine: client: steam: drop broker address after terminate 2026-01-17 06:18:07 +05:00
Alibek Omarov
e5d3f180dc engine: client: implement steam broker client side 2026-01-17 06:18:07 +05:00
Alibek Omarov
5cb5e76872 engine: add new needload flag specifically for cleaning up unused models and use unreferenced for free slots. 2026-01-17 06:17:46 +05:00
Alibek Omarov
742265c2c4 ref: remove limit on total amount of textures a studio model can contain 2026-01-17 06:17:46 +05:00
Alibek Omarov
4e49cc49f8 engine: common: http: optimistically treat https as http as a temporary hack 2026-01-17 06:17:46 +05:00
Владислав Сухов
09ca41ea36 engine: server: drop ghost fakeclients before changelevel 2026-01-16 14:27:41 +05:00
Alibek Omarov
896bbe8fc3 engine: server: add Host_Error on unknown pfnWalkMove state
In GoldSrc default branch interpreted as WALKMOVE_NORMAL, and there might be
some mods that expect this.
But Xash never did this, and if Xash-specific games rely on that behavior, we
better know about it.
2026-01-16 14:19:38 +05:00
Alibek Omarov
ed85d3028f engine: client: fix division by zero in cl_showfps on first frame 2026-01-15 04:42:36 +05:00
Alibek Omarov
8e33f11dc6 engine: server: replace MAX_INFO_STRING with appropriate sizeof, remove some set but unused variables 2026-01-13 18:09:37 +05:00
Alibek Omarov
4bb547fdc7 engine: platform: sdl: revert disabled NET_WM_BYPASS_COMPOSITOR 2026-01-13 08:35:46 +05:00
Alibek Omarov
8af5ecdb06 mainui: update 2026-01-13 08:27:03 +05:00
Alibek Omarov
5a0517c1b8 engine: server: refactoring, better validation of cl_updaterate and rate values. Allow sv_min/maxupdaterate to be set to 0 for unlimited powah 2026-01-13 08:27:03 +05:00
Alibek Omarov
5ed1bca57f engine: client: allow enabling sv_cheats during demo playback 2026-01-13 08:27:03 +05:00
Alibek Omarov
d5892f3794 engine: change sleeptime resolution from 1 microsecond to 100 microseconds, as apparently sleeping for a microsecond isn't enough to lower CPU usage 2026-01-13 08:27:03 +05:00
Alibek Omarov
32aee7ef2f engine: add sv_hibernate_when_empty that cuts down CPU usage on empty servers, similar to the feature in Source 2026-01-13 08:27:03 +05:00
Alibek Omarov
3a5d57603f engine: print unknown command message in dedicated mode and when not on server 2026-01-13 08:27:03 +05:00
Steven Linn
c865999302 engine: fix window position drift on X11 with window decorations 2026-01-13 06:31:29 +05:00
Steven Linn
cb69a52d65 engine: fix black fullscreen flash on startup in windowed mode 2026-01-13 06:31:29 +05:00
Steven Linn
d915ed184c Add 'retry' command for quick reconnection to servers 2026-01-13 06:28:15 +05:00
Alibek Omarov
bdc205b994 engine: client: do not grab client updates during intermission, fixes the bug where player was seemingly moving during intermission 2026-01-13 06:26:26 +05:00
Alibek Omarov
46ccc37cae engine: fix undefined pfnTime build error, it has been renamed to Sys_FloatTime 2026-01-13 06:03:34 +05:00
Alibek Omarov
d7b96ea8c2 engine: pass arguments on engine restart on nswitch, add forgotten -changegame parameter, simplify code for other platforms 2026-01-13 05:51:19 +05:00
Alibek Omarov
de9e28700a engine: client: prevent a crash in case of invalid tempentity message (thanks, @Aynekko!) 2026-01-13 05:51:19 +05:00
Alibek Omarov
f5b2c4f7af engine: client: prevent a crash in goldsrc delta entities parser 2026-01-13 05:51:19 +05:00
Alibek Omarov
79f7fba83c engine: client: make both UI and Render API to use same implementation of GetFilesList function 2026-01-13 05:51:19 +05:00
Alibek Omarov
cb7f2ad8af engine: replace Sys_DoubleTime with Platform_DoubleTime in internal code, only use Sys_DoubleTime in APIs
As Sys_DoubleTime is marked with GAME_EXPORT attribute that helps us to
maintain compatible binary interface.
2026-01-13 05:51:19 +05:00
lewa_j
5cdb58431a engine: ref: Remove unused declarations, fix comment after rename 2026-01-13 03:41:19 +03:00
lewa_j
543d6d67e6 engine: platform: Make R_Init_Video parameter an enum 2026-01-13 03:25:29 +03:00
lewa_j
72e8a53046 engine: Fix input_completion buffer comparison
Fixes: 4b771701 ("engine: add con_showcompletion feature from FTEQW")
2026-01-13 03:22:19 +03:00
lewa_j
deb7b2061a engine: platform: WINDOW_MODE_* enums for vid_fullscreen instead of magic numbers 2026-01-13 02:16:05 +03:00
lewa_j
8a75104cbc commoon: engine: Fix not including all dependencies for headers. (Just some used in ref) 2026-01-12 13:52:04 +05:00
lewa_j
e7f758f380 engine: ref: Fix crash on invalid ref lib 2026-01-12 03:20:58 +03:00
Alibek Omarov
a4e5cf8bac engine: client: fix up origin of MOVETYPE_FOLLOW entities immediately after entity update
This fixes an issue when MOVETYPE_FOLLOW entities seem to be "blinking".

Explanation of what's happening is in the comment in the code.
2026-01-09 18:50:29 +05:00
ksagameng
0976d96419 crtlib: Force use xash strchrnul implementation on ios 2026-01-05 09:37:20 +05:00
ncuxonaT
e39f8bf7c4 ref: gl: always use RGTC internal format for ATI2 textures
AMD убрала в новых драйверах поддержку расширения GL_ATI_texture_compression_3dc, из-за чего сломалась загрузка ATI2 текстур. Формат RGTC включен в стандарт OpenGL 3.0, а единственные видеокарты с поддержкой 3DC, но без RGTC - это серия Radeon X1000 (2005г.). Можно было бы только ради Х1000 проверять поддерживаемые расширения и переключаться на 3DC, но без явной необходимости этого делать не хочется.
2026-01-03 08:17:09 +05:00
Steven Linn
9ed76fbb36 engine: client: allow console toggle from menu via keybind 2026-01-02 10:31:44 +05:00
Alibek Omarov
4b77170136 engine: add con_showcompletion feature from FTEQW 2025-12-27 13:13:23 +05:00
Alibek Omarov
a168719905 engine: force generating vfs.cfg if it doesn't exists 2025-12-27 10:40:24 +05:00
Alibek Omarov
c950530e44 engine: common: masterlist: fix reporting wait state 2025-12-24 07:12:43 +05:00
Alibek Omarov
5584e6376c engine: client: voice: shutdown refactoring 2025-12-24 06:51:16 +05:00
Alibek Omarov
fcd7e7dcea engine: client: small sound refactoring, try to round volume values 2025-12-24 06:51:04 +05:00
Alibek Omarov
548a49ed93 engine: client: set a volume for VOX sentences playing from listener 2025-12-24 06:49:38 +05:00
Alibek Omarov
68a7689108 engine: client: fix incorrect usage of Mod_TestBmodelLumps return value in Cmd_ListMaps, causing it to crash 2025-12-23 14:05:36 +05:00
Nikita
58996c3d0d ref: gl: use wireframe overlay for bmodel faces as well 2025-12-21 02:32:12 +05:00
Alibek Omarov
41925a1aa7 engine: platform: sdl3: implement audio output using streams 2025-12-18 12:06:48 +05:00
Alibek Omarov
c834f7c559 engine: platform: sdl3: add events handler 2025-12-18 11:40:52 +05:00
Alibek Omarov
a66262a787 engine: platform: sdl3: add few more vid functions, add REF_WINDOW_TYPE_SDL3 to query specifically for SDL3 window 2025-12-18 10:26:06 +05:00
Alibek Omarov
3b51708160 engine: platform: sdl3: implement window creation, fill out required stubs to make it compile 2025-12-18 10:00:46 +05:00
Alibek Omarov
ee6004a88d engine: platform: sdl2: minor refactorings 2025-12-18 09:59:37 +05:00
Alibek Omarov
414da98c21 Documentation: add example GPL copyright header 2025-12-18 09:58:49 +05:00
Alibek Omarov
30ad3556dd engine: small refactorings in sdl2 support, enable messagebox and timer in sdl3 backend back 2025-12-16 05:18:28 +05:00
Alibek Omarov
67a121ef8e engine: ref_api: actually bump API version 2025-12-16 05:15:23 +05:00
Alibek Omarov
e7743587a1 mainui: update 2025-12-15 11:42:09 +05:00
Alibek Omarov
1b66367d3d engine: server: add a warning if server DLL couldn't be loaded, refactor calling GetEntityAPI exports 2025-12-14 04:59:24 +03:00
Alibek Omarov
bc61516083 engine: client: show an error if user tries to load incompatible or outdated client.dll 2025-12-14 04:43:33 +03:00
Alibek Omarov
dae8c6cb9c filesystem: in case of missing/incomplete liblist.gam make c0a0 default start map 2025-12-14 04:41:52 +03:00
Alibek Omarov
bed1c669aa ci: update freebsd tasks 2025-12-14 03:52:16 +03:00
Alibek Omarov
785b2881b6 engine: client: fix crash when sound pitch is too high 2025-12-14 03:44:17 +03:00
Alibek Omarov
ce08f071b1 engine: platform: sdl2: use permonitor DPI awareness to avoid unneeded window titlebar resize 2025-12-14 02:23:07 +03:00
Alibek Omarov
e102857735 engine: platform: sdl2: prefer tga icons on Windows 2025-12-14 02:17:44 +03:00
Alibek Omarov
e973c42bfa engine: platform: sdl2: fix stupid bug when window of the size of display gets interpreted as fullscreen window 2025-12-14 02:12:44 +03:00
Alibek Omarov
4a16ca8bc0 engine: platform: sdl2: fix fullscreen mode not fullscreening 2025-12-14 01:01:59 +03:00
Alibek Omarov
f9e82170ac engine: platform: sdl2: set window position to undefined when coming back from fullscreen to windowed 2025-12-13 23:36:58 +03:00
Alibek Omarov
ad01be8866 engine: platform: sdl2: get maximized state in VID_SaveWindowMode 2025-12-14 00:44:05 +05:00
Alibek Omarov
40c7c5b348 engine: platform: sdl2: disable high dpi support on Windows temporarily 2025-12-14 00:17:01 +05:00
Alibek Omarov
66ea91270b engine: make Image_ComputeSize shared between renderer and imagelib 2025-12-14 00:00:59 +05:00
Alibek Omarov
44fb4fb594 engine: common: imagelib: remove dependency on OpenGL declarations in texture formats 2025-12-13 23:59:40 +05:00
Alibek Omarov
16c849d085 engine: common: imagelib: refactoring 2025-12-13 23:11:48 +05:00
Alibek Omarov
e2835a7614 engine: common: imagelib: fix incorrect check for big endian pixel formats 2025-12-13 22:30:03 +05:00
Alibek Omarov
837bf652c5 engine: server: check that save file were successfully opened for writing, show the error otherwise 2025-12-13 00:36:33 +05:00
Alibek Omarov
ae5793f258 engine: log the error if file couldn't be opened for writing 2025-12-13 00:36:33 +05:00
ksagameng
146244f1f4 scripts: gha: Set hlsdk minimum ios version to 12.0 2025-12-10 16:56:09 +05:00
ksagameng
fa7a04f535 readme: updated instructions 2025-12-10 16:56:09 +05:00
ksagameng
db2c2e685d scripts: ios: generate .dSYM files and make zip quiet 2025-12-10 16:56:09 +05:00
ksagameng
324b152307 Documentation: change iOS to supported in ports.md 2025-12-10 16:56:09 +05:00
Alibek Omarov
09a68bbebd engine: platform: sdl2: allow DPI scaling on Windows 2025-12-09 19:22:40 +05:00
Alibek Omarov
2b7773d933 engine: platform: sdl2: rework picking suitable resolution when creating or modifying current window 2025-12-09 03:29:16 +05:00
Alibek Omarov
aaef9fd91c engine: platform: sdl2: simplify checking if window fits on any display, always grab updated window size 2025-12-08 21:12:40 +05:00
Alibek Omarov
15f37a301f engine: platform: sdl2: let system pick better window position if it doesn't fit anywhere 2025-12-08 21:01:13 +05:00
Alibek Omarov
807b5df17b engine: platform: sdl2: use generic SDL_GetWindowSizeInPixels to get physical window size, if possible 2025-12-08 19:06:29 +05:00
Alibek Omarov
2a3dcecfae engine: platform: sdl2: add a note about useless and broken WarpMouseInWindow on Wayland 2025-12-08 19:05:38 +05:00
Alibek Omarov
a858719a4b engine: platform: sdl2: restore scaling mouse position for hi-dpi support 2025-12-08 19:05:13 +05:00
Alibek Omarov
c844cf7ec1 engine: platform: sdl2: refactor VID_RestoreScreenResolution to accept window_mode_t instead of reading cvar value 2025-12-08 18:57:31 +05:00
Alibek Omarov
a4431b5cfd engine: store scaling factor between physical and logical window size 2025-12-07 15:23:35 +03:00
Alibek Omarov
7e730cf171 common: by default, use borderless fullscreen mode 2025-12-07 15:23:35 +03:00
Alibek Omarov
9569900b9c engine: redefine cvar flags in functions arguments from plain int to uint32_t 2025-12-07 15:23:35 +03:00
Alibek Omarov
5c190567e5 engine: client: add cvars vid_width and vid_height to query physical window size 2025-12-07 15:23:35 +03:00
Max Parry
2ef374ff4a engine: imagelib: img_bmp: support for big endian RGB format
Allows software rendering save screenshots
2025-12-06 19:46:25 +11:00
Max Parry
2c20734ad3 ref: soft: Fix out of bounds memory access in VID_ScreenShot 2025-12-06 19:41:46 +11:00
TheEVolk
a4f8fc2205 engine: add clc_bad log packet cmd value 2025-12-02 00:21:36 +05:00
Alibek Omarov
4ed3e539fa engine: client: validate server info message 2025-12-01 13:03:53 +05:00
Alibek Omarov
3bc32d6e1b engine: shift netchan flags bit to the right, as one of the flags is removed 2025-12-01 13:03:53 +05:00
Alibek Omarov
e05d6ec27c common: net_api: add a note about reserved bit for deprecated extension 2025-12-01 13:03:53 +05:00
Alibek Omarov
cfa8e639f1 engine: drop legacy protocol support 2025-12-01 13:03:53 +05:00
ncuxonaT
bbe659ebfa ref: gl: fix loading texture arrays 2025-11-30 18:33:57 +05:00
Steven Linn
bf5821e4ea engine: fix vid_maximized always set to true when switching to windowed mode 2025-11-30 18:11:56 +05:00
Alibek Omarov
a1dc006488 scripts: disable software renderer on Android 2025-11-30 02:59:53 +05:00
Alibek Omarov
31c30aeb28 Documentation: move engine/game development guides to development subdirectory 2025-11-30 02:52:00 +05:00
Alibek Omarov
2516ed71d6 Documentation: add tutorial on using Qt Creator 2025-11-30 02:45:46 +05:00
Alibek Omarov
acbeb8fd30 wscript: ignore Qt Creator 18.0 config directory in clean command 2025-11-30 02:42:02 +05:00
Alibek Omarov
ab5ac3aae8 3rdparty: maintui: another build fix, this time for platforms where C chars are unsigned 2025-11-27 23:58:48 +05:00
Alibek Omarov
da133d890f gitmodules: update to our xash3d-maintui fork 2025-11-27 23:42:32 +05:00
ncuxonaT
2736d1fd03 dds volume texture fix - gl_image.c 2025-11-27 23:17:18 +08:00
ncuxonaT
c7f18d7abe dds volume texture fix - img_dds.c 2025-11-27 23:17:18 +08:00
Alibek Omarov
d5347a82cb engine: platform: get rid of the mouse pos scaling hack, this is not how highdpi issues should get resolved 2025-11-27 19:34:51 +05:00
Alibek Omarov
e32470280d engine: add file size argument to brush model loading functions 2025-11-27 19:26:36 +05:00
Alibek Omarov
ceb5cd61e4 ref: gl: enable NPOT textures on GLES3 contexts as per spec 2025-11-27 19:18:27 +05:00
Alibek Omarov
75339193d7 engine: client: remove vid_highdpi cvar, always allow high dpi mode 2025-11-25 15:06:46 +05:00
Alibek Omarov
07da038b4b engine: platform: sdl2: move dpi awareness hint set before SDL_Init, as documentation says it needs to be set before video init 2025-11-23 14:54:15 +05:00
Alibek Omarov
a2b597577f engine: platform: sdl2: do not hide X11 hints under any platform macro 2025-11-23 14:50:51 +05:00
Alibek Omarov
9cf2d9fa86 engine: platform: sdl2: set up DPI awawareness on Windows through SDL hint 2025-11-23 14:50:12 +05:00
Alibek Omarov
d45ced5c66 engine: platform: sdl2: when closest video mode isn't found, revert to desktop video mode 2025-11-23 12:50:54 +05:00
Alibek Omarov
6aadb4ceea engine: initialize default video mode in refState global, as we don't support reloading engine without reinitializing globals anyway 2025-11-23 11:54:33 +05:00
Alibek Omarov
c956c65e33 ref: gl: don't misleadingly tell about enabled vsync on engine startup before configs are parsed 2025-11-23 11:45:47 +05:00
Alibek Omarov
86dfbc3e34 common: make bspfile.h use stdint types, reformat the file 2025-11-22 13:40:11 +05:00
Alibek Omarov
3fc59104f3 engine: make FS_CopyImage argument const 2025-11-22 13:11:41 +05:00
Alibek Omarov
aae59ac281 engine: client: remove unused temppaintbuffer array 2025-11-22 13:11:18 +05:00
Alibek Omarov
e8a2047d4c engine: client: use utility function to copy an image before quantization 2025-11-22 13:11:00 +05:00
Alibek Omarov
56972a811c readme: better wording [skip ci] 2025-11-19 14:47:47 +05:00
Alibek Omarov
27086bfb97 engine: set r_wadtextures and r_allow_wad3_luma as latched cvars, as those require reloading textures and thus the game 2025-11-18 15:38:25 +05:00
Alibek Omarov
93dfcc344b engine: implement loading luma in half-life 2025-11-18 15:35:20 +05:00
Alibek Omarov
c2448c8881 engine: imagelib: allow specifiying index for empty pixels when processing luma texture 2025-11-18 15:35:20 +05:00
Alibek Omarov
26df99ae41 utils: xar: fix build 2025-11-18 15:35:20 +05:00
Marc-Antoine Lortie
45cff25bbc readme: fix hlsdk-portable link 2025-11-18 08:48:27 +08:00
Alibek Omarov
5556668fc3 wscript: add option to disable rpath usage 2025-11-17 13:01:06 +05:00
Xav101
1b28d183fc wscript: rework link flags on IRIX to handle GL and SDL linking correctly 2025-11-17 03:08:38 +08:00
Xav101
15bbe9892e public: mathlib: add parenthesis to fix ambiguous order of operations 2025-11-17 03:08:38 +08:00
Xav101
7236094421 platform: add missing include 2025-11-17 03:08:38 +08:00
Xav101
c85c6f80e0 platform: fix type in formatter string 2025-11-17 03:08:38 +08:00
Alibek Omarov
dd7811e5bc filesystem: wscript: do not link library_suffix on PSVita 2025-11-16 18:46:08 +05:00
veygax
28c118a4e6 engine: zone: free poolchain on every iteration of Memory_Init
free poolchain when calling Memory_Init multiple times.

Fixes: memory leak when running run-fuzzer-Image_LoadBMP
Signed-off-by: veygax <veyga@veygax.dev>
2025-11-16 01:17:05 +08:00
Daniyar Tussupbekov
49b0619c9a readme: update README.md for android build instruction (#2120)
* readme: update README.md for android build instruction

* Update Android build instructions in README

Corrected instructions for running in Android Studio and added a new section for building on Android.

---------

Co-authored-by: a1batross <a1ba.omarov@gmail.com>
2025-11-15 10:06:21 +05:00
veygax
0c3c6345e9 engine: imagelib: fix pixel iteration bounds checking on 1-bit BMPs
move 1-bit BMP bounds checking to the beginning of a loop to prevent a second iteration

Fixes: [GHSA-3p7j-2339-g5mm](https://github.com/FWGS/xash3d-fwgs/security/advisories/GHSA-3p7j-2339-g5mm)
Signed-off-by: veygax <veyga@veygax.dev>
2025-11-15 12:50:33 +08:00
Alibek Omarov
747baa1e16 engine: client: additionally look for loading images with bmp and png extensions 2025-11-13 22:01:55 +05:00
Alibek Omarov
2e5d2c1dcc 3rdparty: library_suffix: update 2025-11-12 20:28:04 +05:00
Alibek Omarov
300edb3318 engine: remember last resolved master server address for about a minute 2025-11-12 20:04:55 +05:00
Alibek Omarov
0dfeea5213 Migrate to library-suffix library 2025-11-11 06:59:41 +05:00
Alibek Omarov
d9150734c9 3rdparty: add library_suffix subproject 2025-11-11 06:59:41 +05:00
Alibek Omarov
65514b73fd common: render_api: explicitly include stdint.h as we use intptr_t in the API 2025-11-11 06:22:43 +05:00
Alibek Omarov
d366e04682 engine: client: voice: fix typo, sequence must be incremented after it's written to the buffer 2025-11-09 02:55:58 +05:00
ksagameng
5151f9559b scripts: ios: Fix built hlsdk not working on older ios 2025-11-08 06:50:07 +08:00
Alibek Omarov
62c56b877f 3rdparty: mainui: update 2025-11-07 04:08:06 +05:00
Alibek Omarov
420a907776 wscript: silently ignore exception if --no-undefined is not in the linkflags list on darwin 2025-11-07 04:05:19 +05:00
Alibek Omarov
184e976c5d engine: call appropriate model loader on big endian 2025-11-06 23:37:27 +05:00
Alibek Omarov
6154e54661 wscript: fix a typo in -Wl,-undefined,error 2025-11-06 23:36:50 +05:00
Alibek Omarov
73950e867f filesystem: initial set of fixes for big endian 2025-11-06 23:26:12 +05:00
Alibek Omarov
822aa00bd6 wscript: replace --no-undefined with -undefined error for Darwin for compatibility with older linkers (tested on 10.4) 2025-11-06 21:51:09 +05:00
Alibek Omarov
681808ca3d 3rdparty: mainui: update 2025-11-06 20:06:52 +05:00
Alibek Omarov
a0b40fc2da engine: common: explicitly cast function offset into size_t, even though it's signed ptrdiff, saverestore offset mistakingly used unsigned numbers 2025-11-06 20:06:51 +05:00
Alibek Omarov
955c5570d8 engine: client: voice: increment sequence before it's passed into LittleShort macro 2025-11-06 20:06:51 +05:00
Alibek Omarov
2094c72c95 engine: client: soundlib: use correct printf specifier for ptrdiff 2025-11-06 20:06:51 +05:00
Alibek Omarov
0795fe92fe public: build: allow older __ppc__ macro defined by GCC 4.0 (even though I probably won't support GCC 4.0) 2025-11-06 20:06:51 +05:00
Alibek Omarov
9ce131e6be engine: client: fix a typo 2025-11-06 23:06:33 +08:00
Alibek Omarov
08b718995c engine: client: restore reduced resend time for bandwidth test 2025-11-06 23:06:33 +08:00
Alibek Omarov
4900955fc1 engine: client: set cl_dlmax to FRAGMENT_DEFAULT_SIZE when connecting to an old server or server that test disabled 2025-11-06 23:06:33 +08:00
Alibek Omarov
9d00e52c42 engine: server: require challenge for bandwidth test 2025-11-06 23:06:33 +08:00
Alibek Omarov
6c2bad610f engine: client: request test packet only after we got challenge from server 2025-11-06 23:06:33 +08:00
Alibek Omarov
27c6004191 ci: disable freebsd-15 build due to bug with missing bzip2.pc 2025-11-05 09:13:45 +05:00
Alibek Omarov
500d385e6b ci: use macos-15-intel for x86 engine builds 2025-11-05 09:10:12 +05:00
Alibek Omarov
5739946330 engine: server: limit bandwidth test to max 1400 bytes 2025-11-03 19:28:59 +05:00
Alibek Omarov
8a94256558 public: fix build with C++ 2025-11-03 19:28:59 +05:00
ksagameng
a66774d65f ci: ios: Minor change 2025-11-03 04:36:59 +08:00
ksagameng
a9f159bae5 ci: ios: Implement suggestions and use hlsdk mobile_hacks 2025-11-03 04:36:59 +08:00
ksagameng
a6108b7930 ci: Add iOS builds 2025-11-03 04:36:59 +08:00
Alibek Omarov
43066656bd 3rdparty: restore submodules hash after last merge 2025-11-01 18:33:42 +05:00
ksagameng2
a9bf700da4 Restore iOS build support (#2352)
* initial commit

* initial commit

* add port files

* ios: Changes to allow creating app bundle without xcode projects

* ios: Added mic perms request

* ios: Very basic app bundle creation script

* ios: Fix -log parameter

* ios: Sdk no longer hardcoded and option to compile for simulator

* general: Update gitignore

* ios: Enable game mode for the app

* ios: Compile instructions and updated ipa creation script

* ios: Code cleanup

* ios: Remove dylibs

* general: Remove extra submodules

* ios: Remove dylibs again

* ios: Implement suggested changes

* ios: Fix building as dylib instead of binary

* ios: Scripts for building mods

* ios: Move 'ios' contents to engine/platform/ios/bundle and modify scripts

* ios: Update createipa.sh to use waf install

* ios: Clean script and updated README

* general: Fix spelling mistake and outdated instructions

* ios: Clean up in xcompile.py

* ios: Fix createipa.sh generating an invalid ipa

* ios: Temporary fix for launch dialog textfield colors in dark mode
2025-11-01 18:32:00 +05:00
Sergey Degtyar
ee7dce68b8 engine: client: streamline samples handling 2025-11-01 21:24:49 +08:00
Alibek Omarov
a14cc82d46 engine: add buffer size argument to RefAPI's Mod_ProcessRenderData, bump RefAPI version 2025-10-31 15:58:05 +05:00
Alibek Omarov
b3dc772cd2 engine: add file size argument to sprite model loader 2025-10-31 15:56:31 +05:00
Alibek Omarov
9bb9575c6d ci: update freebsd dependencies package list 2025-10-31 12:31:07 +05:00
Alibek Omarov
359cc9b1e7 security: add a security policy 2025-10-30 17:43:01 +05:00
Alibek Omarov
6a9f61e69f engine: soundlib: ignore TXXX tags that's size exceeds 255 (sizeof( string ) - 1) bytes to account for null terminator
Thanks @veygax for the report!
2025-10-30 17:31:11 +05:00
Alibek Omarov
21ea83027e engine: imagelib: fix buffer overflow when the amount of colors in palette reported by BMP header are higher than 256
Thanks to @veygax for report!
2025-10-30 17:30:02 +05:00
Alibek Omarov
f22a50b680 3rdparty: update maintui and mainui 2025-10-24 14:30:59 +05:00
Alibek Omarov
7217c7f1b2 engine: client: deprecate and remove all code associated with linear/cubic upsampling 2025-10-23 14:56:50 +05:00
Alibek Omarov
cfb8e0882a Revert "engine: client: fixed blueish outline on sprays converted to WAD3"
This reverts commit a5f7314ec9.
2025-10-20 07:47:58 +05:00
Alibek Omarov
f1fde32c29 Drop legacy AuroraOS code 2025-10-19 07:39:47 +05:00
Alibek Omarov
c6a5c64448 engine: client: touch: implement rotation support 2025-10-19 05:35:28 +03:00
Alibek Omarov
b8f0a89846 engine: client: validate vid_rotate cvar value and save current rotation to ref global 2025-10-19 06:27:10 +05:00
Alibek Omarov
684f7fb75e ref: gl: implement rotation for 3D 2025-10-19 06:26:28 +05:00
Alibek Omarov
4dabb5d1f9 ref: gl: implement rotation for 2D 2025-10-19 06:25:43 +05:00
Alibek Omarov
ea2c864265 engine: server: don't pass uninitialized pointers to the server DLL in SetupVisibility function 2025-10-17 14:54:52 +05:00
Alibek Omarov
7315865d34 engine: client: include null byte in synthetised HudText messages, to match real HudText messages sent from server 2025-10-17 14:07:48 +05:00
Alibek Omarov
6d987d083e android: delete .nomedia if it's a directory 2025-10-17 15:58:59 +08:00
Alibek Omarov
6e9edcdd50 android: try to automatically create .nomedia file in directory where games are located 2025-10-17 15:58:59 +08:00
Alibek Omarov
7a02110a42 ref: gl: fix out of bounds access
On GPUs where GL_ARB_shading_language_100 isn't available (i.e. all mobile GPUs
I believe) this function would return total amount of texture units available.

But ref_gl only tracks and uses MAX_TEXTURE_UNITS texture units, causing memory
corruption in functions that doesn't check the limit.
2025-10-17 09:19:23 +05:00
Alibek Omarov
9330e44817 filesystem: fix missing newline in anonymous file creation error string. Turn it into a warning, because it's not fatal 2025-10-17 09:08:50 +05:00
Alibek Omarov
5d7cb90bea ref: gl: use our own matrix functions to create 2D projection and modelview matrices 2025-10-17 09:08:50 +05:00
Alibek Omarov
a7b466e4a9 scripts: gha: try to update vitaGL 2025-10-16 08:12:13 +08:00
Alibek Omarov
4c7e37d5fd engine: few more cases of missing newline in Cbuf_AddText that I forgot to include in previous commit 2025-10-16 04:57:04 +05:00
Alibek Omarov
d8dcfc0ab3 engine: add \n to all Cbuf_AddText calls 2025-10-16 04:36:38 +05:00
Alibek Omarov
fb56873352 engine: client: increase lower cl_dlmax bound to 512 for goldsrc protocol 2025-10-16 01:21:30 +05:00
Alibek Omarov
8c4ef5200c engine: client: drop unknown interpolation hack, that might've caused hiccups when prediction fails 2025-10-16 01:21:30 +05:00
SNMetamorph
a5f7314ec9 engine: client: fixed blueish outline on sprays converted to WAD3 2025-10-12 20:58:56 +08:00
Alibek Omarov
4f444ee6ea engine: client: for goldsrc status messages, always mark them of goldsrc protocol 2025-10-12 04:14:37 +05:00
Alibek Omarov
651d6a4529 engine: client: remove useless messages in console about scanned servers 2025-10-12 04:12:43 +05:00
lewa_j
851b3d6d70 utils: mdldec: move XASH_WIN32 check after it is defined in build.h 2025-10-11 20:38:06 +03:00
lewa_j
5c0cc771bc engine: win32: fix msvc warning "'LPSTR' differs in levels of indirection from 'string (*)'" with GetConsoleTitleA 2025-10-11 20:22:37 +03:00
lewa_j
acebb5b0cf engine: client: fix msvc warning "incompatible types - from 'STARTUPINFO *' to 'LPSTARTUPINFOW'" 2025-10-11 20:22:34 +03:00
lewa_j
5f707e37b1 utils: xar: Fix windows build with -Werror=implicit-function-declaration 2025-10-11 20:22:29 +03:00
Alibek Omarov
af585fec08 utils: mdldec: fix build with implicit function declaration on MSVC 2025-10-11 21:23:49 +05:00
lewa_j
37e9ac453b win32: engine: common: client: filesystem: Fix windows build with -Werror=implicit-function-declaration 2025-10-12 00:21:00 +08:00
lewa_j
7bb630db2a wscript: -Werror=implicit-function-declaration analog for msvc 2025-10-12 00:21:00 +08:00
Alibek Omarov
0e22e8aa76 wscript: disable declaration-after-statement, we don't need it anymore 2025-10-11 20:58:48 +05:00
Alibek Omarov
492a1cda18 engine: client: treat A button on gamepad the same as Enter in On Screen Keyboard 2025-10-11 06:04:40 +05:00
Alibek Omarov
01a1c2f496 engine: client: add ability to build engine without touch input support 2025-10-11 05:58:14 +05:00
Alibek Omarov
cf23005ff2 engine: client: remove comments of touch init/shutdown function call that seem to be some indescribable legacy 2025-10-11 05:52:19 +05:00
Alibek Omarov
5ff640b142 engine: client: manually mark entity and model index validation branches are unlikely 2025-10-11 05:48:42 +05:00
Alibek Omarov
6c3ae914fa common: add PSP platform definitions 2025-10-11 05:45:03 +05:00
Alibek Omarov
654d7013c6 public: add PSP definitions 2025-10-09 22:32:30 +05:00
Alibek Omarov
4d300e9d30 scripts: waifulib: xcompile: add PSP compiling tools 2025-10-09 22:32:30 +05:00
SNMetamorph
51a9524465 engine: client: fixed incorrect xashid generation on windows platform 2025-10-10 01:29:57 +08:00
Alibek Omarov
6817df534b engine: console: don't try to process input while printing messages to console, this only was needed in OG Xash where it directly called Win32 APIs 2025-10-09 19:27:29 +05:00
Alibek Omarov
1c8e51c9f9 common: port: always include unistd.h for POSIX systems 2025-10-08 05:28:16 +05:00
Alibek Omarov
8d7ee70e7d engine: platform: android: include dlfcn.h for Android's LoadLibrary implementation 2025-10-08 05:27:53 +05:00
Alibek Omarov
0c08f113ea engine: platform: sdl2: add vrtld.h include for GL_GetProcAddress implementation 2025-10-08 05:24:59 +05:00
Alibek Omarov
8022c4826f engine: platform: sdl2: remove legacy eglGetProcAddress hack for old SDL2 versions 2025-10-08 05:24:16 +05:00
Alibek Omarov
0efe65a5bc utils: mdldec: define local mkdir macro 2025-10-08 05:19:19 +05:00
Alibek Omarov
ccbb3a2841 engine: fix format error when building tests on 64-bit platforms 2025-10-08 05:08:48 +05:00
Alibek Omarov
3d132e0e83 common: port: move filesystem related macros to filesystem module, try to include less globally for faster builds 2025-10-08 05:03:29 +05:00
Alibek Omarov
0a5a081a9d engine: client: touch: forcefully disable edit mode when setclientonly was called 2025-10-08 05:02:08 +05:00
Alibek Omarov
d16ef9bf2a common: xash3d_types: refactoring 2025-10-08 05:02:08 +05:00
Alibek Omarov
748c35d5ef Remove _inline macro, as it's nonstandard way to define inline functions. 2025-10-08 03:53:37 +05:00
Alibek Omarov
a1d3b7d65f Remove STDINT_H macro, use stdint.h directly. Compilers and platforms that do not have this header shall provide them on their own. 2025-10-08 03:53:37 +05:00
Alibek Omarov
1834bd6076 Documentation: protocol: fix groups, regenerate toc 2025-10-05 10:20:30 +05:00
Alibek Omarov
abde9d30c6 Documentation: protocol: add Table of Contents to connectionless protocol documentation 2025-10-05 10:09:30 +05:00
Alibek Omarov
64adb3e3d7 engine: rename MS_SCAN_REQUEST to A2M_SCAN_REQUEST 2025-10-05 10:04:18 +05:00
Alibek Omarov
b4e83ca7fd Documentation: document connectionless messages 2025-10-05 10:03:55 +05:00
Alibek Omarov
3efbe205ac engine: define server-to-master requests in protocol.h 2025-10-05 09:28:11 +05:00
Alibek Omarov
55a86e1df3 engine: server: tell master server if server protected with password 2025-10-05 09:26:41 +05:00
Alibek Omarov
712c1e2f95 engine: fix send state of master servers being cleared twice 2025-10-04 00:14:58 +05:00
Alibek Omarov
6d7bfc37f6 engine: allow loading xashcomm.lst from base directory 2025-10-03 22:45:50 +05:00
Alibek Omarov
8e7a2abec5 scripts: waifulib: compiler_optimizations: always lower case build type 2025-10-03 22:05:47 +05:00
Alibek Omarov
cc5f95d1ca engine: support querying legacy goldsrc master servers
* General refactoring, hide out of band messages under cl_log_outofband cvar, similar to sv_log_outofband.
2025-10-03 21:54:59 +05:00
SNMetamorph
62a49d234f engine: client: improved compatibility with GoldSrc-based clients behavior (thanks @Garey27) 2025-10-03 21:34:03 +08:00
Alibek Omarov
8b55d01f78 filesystem: fix missing const qualifier on FS_WriteGameInfo 2025-10-03 17:41:34 +08:00
Alibek Omarov
1d7690f280 engine: client: remove unused cbtrim and samplefrac fields from voxword_t 2025-10-03 00:04:04 +05:00
Alibek Omarov
4ef23543ae filesystem: make current set GameInfo const 2025-10-02 22:42:51 +05:00
Alibek Omarov
a366f99d7d engine: client: do not modify internal_vgui_support field as game info must be immutable once loaded 2025-10-02 22:42:20 +05:00
Alibek Omarov
8c2c920076 engine: client: fill screen with black before rendering video 2025-10-02 22:41:27 +05:00
Alibek Omarov
338636092f 3rdparty: mainui: update 2025-10-02 19:40:31 +05:00
Alibek Omarov
e8d8d90b39 engine: ref_api: fix typo in the comment (thanks @lewa_j) 2025-10-02 19:21:39 +05:00
Alibek Omarov
cb7094bb2d engine: move setjmp for exit-in-main to the beginning of Host_Main function 2025-10-02 18:42:09 +05:00
Alibek Omarov
d9567ff3f9 engine: cleanup unused glw_state fields, as they only used in GL renderer 2025-10-02 18:07:39 +05:00
Alibek Omarov
3d0533ad84 wscript: fix a typo in -Wuninitialized (thanks @lewa_j) 2025-10-02 18:03:20 +05:00
Alibek Omarov
153b04abc3 engine: move crash handler initialization after console, so error messages could be seen in console, but move log creation before console, so log catches more messages 2025-10-02 05:40:58 +08:00
Alibek Omarov
25bfbd3a6b 3rdparty: libbacktrace: wscript: fix config defines not being set 2025-10-02 05:16:59 +08:00
Alibek Omarov
5015c90fd0 engine: platform: posix: try to be more safe in crash handler, no need to fflush stderr, no need to call Msg if we already writing to log file directly, preserve replaced signal actions 2025-10-02 03:49:31 +08:00
Alibek Omarov
e830472010 engine: move update page checks from platform-specific ShellExecute implementation to gameui call (platform_update_page isn't fully used yet, might get on Android some time later) 2025-10-02 03:16:07 +08:00
Alibek Omarov
04f807ef2f engine: platform: android: tell JVM that we don't need these strings anymore, it wouldn't hurt 2025-10-02 03:15:19 +08:00
Alibek Omarov
55d50e2815 filesystem: explicitly tell JVM that we're done with some objects 2025-10-02 02:42:32 +08:00
Alibek Omarov
7266c5469a ci: upgrade to latest SDL2 release 2025-10-01 17:22:06 +08:00
Alibek Omarov
47d181aa92 scripts: gha: always build tarball with engine even if AppImage port is available 2025-10-01 14:53:27 +08:00
Alibek Omarov
dce3d9d517 3rdparty: mainui: update 2025-09-29 04:38:36 +05:00
Alibek Omarov
e5a5804ff5 3rdparty: mainui: update 2025-09-29 03:19:49 +05:00
Alibek Omarov
821e5d7bd7 engine: client: avi: fix build with ffmpeg with disabled dynamic linking 2025-09-29 03:19:38 +05:00
Alibek Omarov
52978df139 engine: platform: posix: replace Sys_FindExecutable by standard library execvp 2025-09-29 00:35:28 +05:00
Alibek Omarov
9aab12fc27 engine: remove global no async resolve switch, platforms that do not support it shall add stubs to engine/platform 2025-09-29 00:12:37 +05:00
Alibek Omarov
e83ab95279 engine: gethostbyname() is deprecated, remove it and always prefer getaddrinfo 2025-09-29 00:09:56 +05:00
Alibek Omarov
7c0045a3b7 engine: show -noavi and -nointro options help everywhere, as we now have crossplatform video playback 2025-09-29 00:07:21 +05:00
Alibek Omarov
1f88e0465d engine: make Host_ExitInMain used everywhere, as we depend on sjlj functions on all platforms anyway 2025-09-29 00:06:41 +05:00
Alibek Omarov
cee1e74872 engine: drop dllemu glue, as it won't build with current engine anyway and not supported by it's author :( 2025-09-29 00:01:59 +05:00
Alibek Omarov
1f124bad9d engine: client: remove archive flag from cl_trace_* message that was added by mistake 2025-09-28 23:00:35 +05:00
Alibek Omarov
71cec3c069 engine: activate fakelag when both cheats and dev-mode are enabled 2025-09-28 22:50:51 +05:00
Alibek Omarov
69204aec07 engine: client: synchronize sv_cheats state with the server 2025-09-28 22:32:41 +05:00
Alibek Omarov
8b65cd73fc engine: server: advertise sv_cheats state during connect 2025-09-28 22:15:26 +05:00
Alibek Omarov
7ba0818b83 engine: client: change r_pvs_radius default value to 0.1 for more accurate pvs culling and free performance 2025-09-28 18:32:53 +05:00
Alibek Omarov
cb81303ded engine: replace hardcoded pvs radius value by a r_pvs_radius cvar 2025-09-28 18:31:03 +05:00
Alibek Omarov
1b1cff1cae engine: server: allow sending logs to remote server without log on to match GoldSrc behavior 2025-09-25 16:29:32 +05:00
Alibek Omarov
53bf302868 3rdparty: mainui: update 2025-09-25 15:41:47 +05:00
Alibek Omarov
ef70530226 scripts: waifulib: compiler_optimizations: automatically enable xtheadvector extension on C910 (like T-Head TH1520) 2025-09-25 11:07:49 +08:00
lewa_j
d073218df0 3rdparty: mainui: update 2025-09-22 13:31:27 +03:00
Alibek Omarov
40ce6c0069 3rdparty: maintui: update 2025-09-22 09:25:54 +05:00
Alibek Omarov
3c4c6e5a1c 3rdparty: mainui: update 2025-09-22 09:25:33 +05:00
Alibek Omarov
2e852e4398 scripts: flatpak: better message for missing GL32 extension 2025-09-21 01:25:14 +05:00
Alibek Omarov
04dee4e94d scripts: flatpak: add more guardrails just in case if 32-bit runtime missing 2025-09-21 00:18:43 +05:00
Alibek Omarov
96557c2ca6 engine: server: clarify sv_fps cvar usage 2025-09-19 15:56:58 +05:00
Alibek Omarov
bf32c49a39 engine: server: disable sv_fps cvar on dedicated, users shall use sys_ticrate 2025-09-19 15:56:58 +05:00
Alibek Omarov
9becba3bc0 android: automatically load default libraries for known gamedirs, supported by mobile_hacks branch 2025-09-19 09:24:08 +05:00
Alibek Omarov
c177be0dd8 engine: move our HTTP client to http subfolder, prepare for curl integration 2025-09-19 09:15:17 +05:00
Alibek Omarov
a5ff9b1c84 gha: don't forget to checkout before calling release script from the repo 2025-09-19 07:25:02 +05:00
Alibek Omarov
fe9e68ed24 scripts: flatpak: bump freedesktop sdk to 25.08 2025-09-18 10:15:33 +05:00
Alibek Omarov
2106260238 scripts: gha: set make_release.sh to be executable 2025-09-18 10:10:50 +05:00
Alibek Omarov
eed1d26191 engine: client: avi: be more verbose about incompatible ffmpeg libs versions in build-time 2025-09-17 12:47:09 +05:00
Alibek Omarov
6115ef0821 gha: move release commands into it's own script 2025-09-17 12:46:35 +05:00
Marc-Antoine Lortie
a63db5f31d Add backslash before apostrophes. 2025-09-16 04:34:54 +05:00
Marc-Antoine Lortie
2553bd7f37 Android: Add French translation. 2025-09-16 04:34:54 +05:00
Alibek Omarov
2da0db4a31 engine: client: refactor connection to the server, always send bandwidth test and challenge from single function 2025-09-15 13:03:22 +05:00
Drive Kinght
5fc104c6a4 android: add Chinese translation 2025-09-12 16:27:24 +05:00
Alibek Omarov
39066bf82b 3rdparty: mainui: update 2025-09-11 08:06:11 +05:00
Alibek Omarov
03192be245 engine: server: actually register new cvars =/ 2025-09-10 17:52:37 +05:00
Alibek Omarov
b59ba5a127 engine: server: make automatic ban of players stuck in connect loop optional and make ban time configurable 2025-09-09 14:55:22 +05:00
LekKit
c399611fc7 Merge branch 'master' into master 2025-09-09 09:45:06 +03:00
Sergey Degtyar
ccc44f3c5f engine: voice: fix voice loopback bit 2025-09-09 01:19:27 +05:00
Alibek Omarov
b2f4d84272 engine: client: give server a bit more time to reply to bandwidth test 2025-09-08 21:19:53 +05:00
Alibek Omarov
97b8fa74a7 3rdparty: mainui: update 2025-09-07 23:29:16 +05:00
Alibek Omarov
c679037085 ref: gl: fix -Wformat error on 64-bit platforms 2025-09-07 01:56:54 +05:00
Alibek Omarov
793de9ec3e engine: move clc_strings definition to net_buffer, alongside svc_strings 2025-09-06 04:58:34 +05:00
Alibek Omarov
ccff8191dd engine: server: increase sv_connect_timeout to 60 by request 2025-09-06 04:50:34 +05:00
Alibek Omarov
d039a08b4e ref: gl: fix up data types. Engine TMU type is int, rather than GLint, and texture indices are technically unsigned int only (and they aren't GLenum) 2025-09-06 01:57:36 +05:00
Alibek Omarov
c0e8827cd5 ref: gl: refactoring, use bitset macros, use static for global objects 2025-09-06 01:57:36 +05:00
Alibek Omarov
11f976d9e9 ref: gl: simplify r_speeds, get rid of R_Speeds_Printf in favor using Q_snprintf directly 2025-09-06 01:31:53 +05:00
Alibek Omarov
7c83aafbf5 ref: gl: fix incorrect usage of GL_Bind in R_ShowTextures (it accepts engine's texture id, not opengl texture name) 2025-09-06 01:26:43 +05:00
Alibek Omarov
699e178f62 ref: gl: remove excessive ASSERT macro, use Assert everywhere 2025-09-06 00:41:48 +05:00
Alibek Omarov
598d756af1 engine: platform: sdl2: hide SDL log messages under -dev 2, as they're often misleading for everybody except engine developers 2025-09-05 18:20:12 +05:00
Alibek Omarov
c2e2686b6a engine: voice: send disabled voice status when raw chan is exhausted, also send enabled status only when we have something to play
And other minor refactorings.
2025-09-03 04:20:22 +05:00
Alibek Omarov
9df2b8f1fb engine: server: refactor SV_ParseVoiceData 2025-09-03 03:52:44 +05:00
Alibek Omarov
1ba45bde13 engine: server: fix voice block inverted logic, instead of checking the player's we're sending listeners list, check current player 2025-09-03 03:51:15 +05:00
Alibek Omarov
5106ebae58 engine: voice: fix local player server ack'ed message 2025-09-03 03:40:15 +05:00
Alibek Omarov
6b6bf7e458 Revert "engine: voice: unconditionally notify client.dll about talking player if we received the data"
This reverts commit 9fda7a15cb.
2025-09-03 03:30:24 +05:00
Alibek Omarov
2daaa94666 engine: voice: fix voice loopback on GoldSrc proto 2025-09-03 03:07:36 +05:00
Alibek Omarov
b129ed8f76 engine: voice: fix another off-by-one error, to fix server's ack status of local player talking 2025-09-03 03:01:49 +05:00
Alibek Omarov
9fda7a15cb engine: voice: unconditionally notify client.dll about talking player if we received the data 2025-09-03 02:23:54 +05:00
Alibek Omarov
e38dc29411 3rdparty: mainui: update 2025-09-02 22:30:57 +05:00
Alibek Omarov
7e3dc39cdd engine: similar to other config saving functions, print when vfs.cfg is being saved 2025-09-01 01:46:27 +05:00
Alibek Omarov
9c6589fa03 engine: introduce vfs.cfg to properly control user set addon folders
vfs.cfg executed before game directory is mounted, this way we can know which
folders must be mounted before engine initialization.

This commit also removes buggy hot reload of the addon folders.
2025-09-01 01:42:37 +05:00
Alibek Omarov
5e53ccab55 waf: update to latest waifu 2025-08-31 12:30:25 +05:00
Alibek Omarov
42b0a9a13d scripts: gha: copy SDL library with all symlinks to it 2025-08-30 21:46:39 +05:00
Alibek Omarov
b504876646 3rdparty: maintui: update 2025-08-30 21:24:48 +05:00
Alibek Omarov
a0a0d58be5 engine: client: unify quality options between Xash and GoldSrc protocol (lowering overall bitrate for Xash servers, which might help overcome default rate limitations) 2025-08-30 19:32:50 +05:00
Alibek Omarov
03571f831e 3rdparty: vgui_support: update 2025-08-30 19:32:50 +05:00
Alibek Omarov
83fe4d6c59 ref: gl: mark gl_frustum_t pointer as const in frustum culling functions 2025-08-30 19:32:50 +05:00
David
a83c483616 ndroid: add Spanish translation by @MBM711 2025-08-27 21:44:51 +05:00
Alibek Omarov
4c0931cc89 engine: fix loading mods on Linux that have Intel architecture suffix, like ag_i386.so 2025-08-27 16:12:21 +05:00
Alibek Omarov
e52c46ec5e engine: imagelib: rename mips variable to mipmaps, to avoid clash with some MIPS compilers
While here, reduce some variables scope.
2025-08-25 11:37:14 +05:00
Alibek Omarov
dd967ffa75 3rdparty: update mainui_cpp and maintui 2025-08-25 11:32:03 +05:00
Владислав Сухов
6474e07472 android: update russian strings 2025-08-22 21:39:53 +05:00
Владислав Сухов
dc1e39264b Documentation: donate: add new donation info 2025-08-22 13:36:35 +05:00
Alibek Omarov
74b196d2c3 ref: gl: do not apply multisampling when drawing 2D 2025-08-17 23:43:53 +05:00
Andrey Akhmichin
4f9eb9f229 Documentation: opensource-mods.md: add Crack-Life. 2025-08-17 22:25:12 +05:00
Menino David
31c5ed69a7 android: update Brazilian Portuguese translation 2025-08-17 22:25:02 +05:00
Andrey Akhmichin
6e00e6c34c Documentation: supported-mod-list.md: add Crack-Life. 2025-08-17 22:23:16 +05:00
Eoin O'Neill
9df725f1e9 readme: use SDL3 with sdl2-compat on Fedora/RedHat
SDL2 is no longer available directly on Fedora as of Fedora 42.
I believe this should also be true on RedHat EL but I'd like feedback
there.
2025-08-16 14:42:45 +05:00
Bohdan Shulyar
f0720026fc engine: client: touch: don't reset state if clientonly didn't change 2025-08-13 20:51:10 +05:00
Alibek Omarov
6b4a16b337 engine: client: do not cause assertion when using voice chat when sound is disabled or failed to initialize 2025-08-13 19:32:33 +05:00
Alibek Omarov
f12ef07c02 engine: whereami: add support for GNU/Hurd 2025-08-13 17:02:22 +05:00
Alibek Omarov
59d771c4e4 public: add definitions for GNU/Hurd 2025-08-13 16:45:53 +05:00
Bohdan Shulyar
e716290523 android: add runtime permissions for older android versions 2025-08-13 15:43:18 +05:00
Alibek Omarov
3ca168b329 android: set requestLegacyExternalStorage specifically for Android 10 which didn't had MANAGE_EXTERNAL_STORAGE yet 2025-08-12 13:43:02 +05:00
Alibek Omarov
4f45df2293 engine: imagelib: add missing argument name, so it compilers with old C standards compliant compilers 2025-08-11 20:50:03 +05:00
Alibek Omarov
af0d77caf7 filesystem: android: more verbosity when mounting assets on Android fails 2025-08-11 18:49:26 +05:00
Alibek Omarov
3a6862f278 engine: when specifying server dll name with an @ (give that feature a name please), actually look for it in server dlls folder 2025-08-11 18:44:12 +05:00
Alibek Omarov
2ad482ee2f engine: platform: android: add some verbosity when successfully loading libraries on Android (hidden under -dev 2) 2025-08-11 18:36:44 +05:00
Alibek Omarov
be0d31f408 engine: imagelib: try to use FS_Search to bruteforce through supported image formats 2025-08-08 18:14:46 +05:00
Alibek Omarov
7c8e2a598f android: restore su.xash.engine.MOD action intent filter 2025-08-08 17:49:30 +05:00
Alibek Omarov
820a001ced engine: move prepending lib prefix on Android earlier, when the path is constructed 2025-08-08 17:03:56 +05:00
Alibek Omarov
a5b68c5ed9 public: remove unused global 2025-08-08 17:01:35 +05:00
Alibek Omarov
f37738f6f8 android: set useLegacyPackaging to true, disable noCompress 2025-08-08 16:16:35 +05:00
Alibek Omarov
4cc009ac3e engine: always prepend lib on Android, as it's part of library naming scheme now 2025-08-08 16:16:05 +05:00
Alibek Omarov
5100125fef Documentation: extensions: library-naming: add a note about distributing Android game libraries 2025-08-08 16:13:48 +05:00
Alibek Omarov
1c0d145874 filesystem: restore lib prefix on Android, otherwise Android won't unpack the libraries 2025-08-08 16:00:03 +05:00
Alibek Omarov
0e48f57247 3rdparty: mainui: update 2025-08-05 19:04:33 +05:00
Alibek Omarov
f9ec8f0b85 wscript: disable {gcc,msvc}deps with wafcache enabled 2025-08-05 19:03:53 +05:00
Alibek Omarov
a4614a00e9 engine: move android logging category from debug to info 2025-08-05 19:03:53 +05:00
Bohdan Shulyar
0ba45d343e host: common: switch host_writeconfig to direct function call 2025-08-05 01:31:21 +05:00
Bohdan Shulyar
2c26601bee engine: common: add a host_writeconfig console command 2025-08-05 01:31:21 +05:00
Alibek Omarov
10ada3c141 Revert "scripts: gha: temporarily switch hlsdk-portable branch to android_library_naming for testing"
This reverts commit c1c0c2761d.
2025-08-04 10:39:04 +05:00
Alibek Omarov
4912e84d46 android: enable x86 and x86_64 abis back 2025-08-04 10:38:59 +05:00
Alibek Omarov
81c7ef20eb filesystem: android: use commit date as android assets file time 2025-08-04 10:25:42 +05:00
Alibek Omarov
ac22de84bf public: remove Q_buildnum generated from __DATE__ macro, as we rely only on Git now 2025-08-04 10:25:42 +05:00
Alibek Omarov
70f6480a5f engine: add a shortcut for loading custom DLLs that follow library naming scheme (like bots) 2025-08-03 19:01:56 +05:00
Alibek Omarov
95bfb7cdf2 scripts: build-ninja: add support for installing libraries with cmake, compared to yanking them out of build directory
Set up hlsdk-portable to install in APK style directory (omitting game and game library directories from path).
Minor refactoring.
2025-08-03 19:01:56 +05:00
Alibek Omarov
b22186b2f1 engine: platform: android: strip library path when loading libraries from APK 2025-08-03 19:01:56 +05:00
Bohdan Shulyar
70e53f547b android: add some simple run instructions 2025-08-03 19:01:56 +05:00
Bohdan Shulyar
8047f8177b android: add some simple docs 2025-08-03 19:01:56 +05:00
Bohdan Shulyar
95bf2f03ad android: bump deps 2025-08-03 19:01:56 +05:00
Bohdan Shulyar
ca6de641a1 android: experimental ninja build 2025-08-03 19:01:56 +05:00
Bohdan Shulyar
bb811310d2 android: fix weird acra build error 2025-08-03 19:01:56 +05:00
Bohdan Shulyar
cd881c079b android: don't generate x86 libraries for now 2025-08-03 19:01:56 +05:00
Bohdan Shulyar
2319d4caf9 android: bump gradle 2025-08-03 19:01:56 +05:00
Bohdan Shulyar
ac412fc15e android: add a (not working yet) basedir option 2025-08-03 19:01:56 +05:00
Bohdan Shulyar
4f7a216191 android: update codebase to use java File API 2025-08-03 19:01:56 +05:00
Velaron
d87cdd7ff8 android: reformat codebase 2025-08-03 19:01:56 +05:00
Velaron
2c53cc7e70 android: upgrade buildsystem 2025-08-03 19:01:56 +05:00
Velaron
b7bb81fbe7 android: remove DocumentsProvider 2025-08-03 19:01:56 +05:00
Velaron
a767540a2b android: remove currently unused or invalid resources 2025-08-03 19:01:56 +05:00
Alibek Omarov
f1251e8767 engine: platform: android: do not automatically prepend lib prefix 2025-08-03 19:01:56 +05:00
Alibek Omarov
c1c0c2761d scripts: gha: temporarily switch hlsdk-portable branch to android_library_naming for testing 2025-08-03 19:01:56 +05:00
Alibek Omarov
c69a87fa89 filesystem: wscript: strip lib prefix on Android 2025-08-03 19:01:56 +05:00
Alibek Omarov
bf7f952d0e defaults: disable INTERNAL_GAMELIBS for Android 2025-08-03 19:01:56 +05:00
Alibek Omarov
6aecc45ce1 engine: platform: include errno.h globally, it's defined by the C standard, so should be available everywhere 2025-08-03 10:27:36 +05:00
Alibek Omarov
bfc10a732a engine: register forgotten ui_language cvar, so changing language actually works (thanks @Alivebyte on GitHub\!) 2025-08-03 10:23:31 +05:00
Alibek Omarov
67d1536127 public: rename rsqrt to Q_rsqrt due to weird collision with tgmath.h macro
It was reported on Discord with no additional information attached. But I guess
it might be better to prefix our math- and crtlib functions in general.
2025-08-03 10:20:52 +05:00
Alibek Omarov
56db9f73d8 engine: platform: ignore nanosleep EINTR error, otherwise sleeptime will never use nanosleep again 2025-08-03 10:18:29 +05:00
lewa_j
250d425b34 engine: platform: sdl2: use -egl to force EGL on windows, same as on other platforms 2025-08-03 08:21:14 +05:00
lewa_j
99140fc7df engine: platform: sdl2: Allow SDL to use wgl to create gles context, if it can 2025-08-03 08:21:14 +05:00
Alibek Omarov
2557d5acaa 3rdparty: opus: limit assembly to SSE and SSE2
Otherwise whole engine builds with SSE4.1 and AVX support with LTO enabled. =/
2025-08-03 04:34:46 +05:00
lewa_j
b14a1b664f engine: platform: sdl2: Fix uninitialized read introduces in 7fda58561f 2025-08-02 17:59:26 +03:00
Alibek Omarov
56361b52e7 3rdparty: opus: enable platform optimized code (x86 SSE/AVX with runtime detection and NEON on ARM)
Along that enable hardening and check for C99 lrintf (probably can do the same in the engine at some point).
2025-08-02 12:27:26 +05:00
Alibek Omarov
da1b9ad80d scripts: waifulib: xcompile: enable cross compilation automatically if CROSS_COMPILE environment variable is set 2025-07-31 03:17:39 +05:00
Alibek Omarov
aec1213319 scripts: waifulib: xcompile: fix NDK 28 minimal API version 2025-07-31 03:17:39 +05:00
Alibek Omarov
cb4540a877 engine: platform: android: call SDL_OpenURL only if XASH_SDL was defined during build. Remove version check, as Android always builds with latest SDL version. 2025-07-30 23:33:08 +05:00
Alibek Omarov
613768c3f9 wscript: add experimental option to enable wafcache [skip ci] 2025-07-30 21:07:45 +05:00
Alibek Omarov
234a16d611 scripts: waifulib: xcompile: add support for kbuild-style CROSS_COMPILE environment variable [skip ci] 2025-07-30 21:06:32 +05:00
Alibek Omarov
e468371394 3rdparty: libbacktrace: similarly, use C compiler for multicheck tests, as this is a C project 2025-07-29 21:42:40 +05:00
Alibek Omarov
671157ee20 public: wscript: set compiler for tests using compiler key instead of directly setting up features, otherwise it won't add uselib 2025-07-29 21:42:02 +05:00
Alibek Omarov
4398de1dab public: wscript: explicitly set language for libc tests in multicheck, as Waf chooses C++ by default
Compiling tgmath.h test with C++ compiler causes misdetection on PSVita which has broken tgmath.h support.
2025-07-29 20:41:59 +05:00
Alibek Omarov
dc09cf0c39 public: fix incorrect logic where libc extension defines were exported without an actual check 2025-07-29 11:31:45 +05:00
Alibek Omarov
f4854dbcde public: wscript: parallelize libc extension checks for faster reconfigure 2025-07-29 08:57:57 +05:00
Alibek Omarov
25e206f5ae engine: platform: posix: remove old crashhandler as libbacktrace supports much more targets, remove duplicated code around execinfo and libbacktrace-based crashhandlers
Update copyright headers.
2025-07-29 08:14:06 +05:00
Andrey Akhmichin
b3d3a15c3d utils: mdldec: bump version to 1.2.1. 2025-07-27 22:02:59 +05:00
Andrey Akhmichin
92faa25670 utils: mdldec: add support for absolute paths on *nix. 2025-07-27 22:02:59 +05:00
lewa_j
3eed2769d5 ref: wscript: gl4es fix typo 2025-07-27 16:27:55 +05:00
lewa_j
6302553ee4 ref: update nanogl and wes submodules 2025-07-27 16:27:55 +05:00
lewa_j
ae2808dff7 ref: gles3compat: Fix crashes on stubs under windows, probably due to signature missmatch 2025-07-27 16:27:55 +05:00
lewa_j
af69df15e1 ref: Fix gl4es build under windows. Static gl functions declared with APIENTRY even without mangling. 2025-07-27 16:27:55 +05:00
lewa_j
98e4aae6bb ref: wscript: Fix gl4es invalid build flags under msvc 2025-07-27 16:27:55 +05:00
Sergey Degtyar
3e921c829f engine: client: fix CL_DrawCenterPrint aligment for cyrillic text 2025-07-26 19:57:23 +05:00
Sergey Degtyar
e994cc0c8b engine: client: adjust SPRAY_ALPHA_THRESHOLD 2025-07-25 02:32:40 +05:00
Alibek Omarov
cd26b1f279 engine: imagelib: img_quant: reduce used colors from 256 to 255, so last color in palette can be reserved for custom usage (like transparency in player decals) 2025-07-25 01:39:59 +05:00
Alibek Omarov
24f05e3fc5 engine: client: refactor spray logo, get rid of unneeded bmp checks 2025-07-25 01:34:46 +05:00
Alibek Omarov
2bdeb05153 engine: client: oops, fix including non-existent file 2025-07-25 00:24:45 +05:00
Alibek Omarov
de4b635b19 engine: client: move cl_spray.c only external function declaration to client.h, move other definitions to cl_spray.c 2025-07-24 23:59:15 +05:00
SNMetamorph
ab744a8baf engine: client: fixed critical bugs in GoldSrc spray generating code, removed useless hacks 2025-07-24 20:49:05 +05:00
SNMetamorph
0aca03d343 engine: client: refactored GoldSrc sprays code, removed magic numbers 2025-07-24 20:49:05 +05:00
Alibek Omarov
eeca965b5d 3rdparty: update libbacktrace and mainui submodules 2025-07-18 19:00:39 +05:00
Alibek Omarov
eb27dc639e Documentation: remove public server rules and recommendation (it might be back after reworking it) 2025-07-18 19:00:39 +05:00
Bohdan Shulyar
af5c6d1196 donate: update url 2025-07-18 03:47:09 +05:00
Sergey Degtyar
0cce014c01 engine: client: fix spray support for 24-bit BMP (save as miptex, not decal) 2025-07-17 02:20:36 +05:00
Sergey Degtyar
6111df486e engine: client: GoldSrc spray support 2025-07-16 23:54:56 +05:00
Sergey Degtyar
7dc88f9417 filesystem: common: move wad structures to wadfile.h 2025-07-16 23:54:56 +05:00
Sergey Degtyar
e36486b73a engine: imagelib: handling wad extension to save and load image properly 2025-07-16 23:54:56 +05:00
Sergey Degtyar
000304ddde engine: net_chan: added bz2 compression for file fragments 2025-07-16 23:54:56 +05:00
Alibek Omarov
4c9b69af5f engine: mod_bmodel: allow firstclipnode index to be negative, as maps compiled with it rely on this trick by encoding whole hull as empty contents 2025-07-16 23:42:49 +05:00
SNMetamorph
599b1b48ec engine: voice: added wrapper for players voice status array to avoid mess with off-by-one errors 2025-07-15 18:54:01 +05:00
Alibek Omarov
4e23012950 ci: disable build for e2k-8c for now (probably will compile natively on e2k-4c instead) 2025-07-13 04:43:34 +05:00
Alibek Omarov
6e69e1d67b scripts: gha: build engine for Linux and Windows with FFmpeg linked in runtime by default, thus allowing for smaller distribution size for game developers 2025-07-13 04:39:44 +05:00
Alibek Omarov
7564e3c61f engine: client: add support for building with FFmpeg linked in runtime 2025-07-13 04:39:44 +05:00
Alibek Omarov
0543b861ca ci: upgrade win32-i386 build runner to windows-2022 (which probably will break XP support... or it might not) 2025-07-09 19:31:12 +05:00
Alibek Omarov
0d403a7117 engine: client: console: make con history remember modifications to old lines 2025-07-09 19:23:17 +05:00
Alibek Omarov
928ce9d754 engine: client: console: simplify history handling by defining a separate type for console lines saved in history
Thus we don't need to save linewidth for them anymore.
2025-07-09 19:22:29 +05:00
Alibek Omarov
6cc3bed60a engine: client: console: fix uninitialized variable 2025-07-09 19:04:59 +05:00
Alibek Omarov
40dfd2e711 engine: client: voice: fix off by one errors (as suggested by @SNMetamorph) 2025-07-09 18:29:36 +05:00
Alibek Omarov
c554eef3b9 engine: client: add ability to disable prepending [Xash3D] tag to the nickname when connecting to GoldSrc servers 2025-07-09 17:44:13 +05:00
Alibek Omarov
dc0648e812 engine: net_http: validate file size against reported by server, when downloading chunked files 2025-07-09 17:35:25 +05:00
Alibek Omarov
3d762802ab Merge remote-tracking branch 'pwd491/gsvoice' 2025-07-09 16:43:14 +05:00
Sergey Degtyar
21b0516724 engine: client: move voice reset to Voice_Shutdown 2025-07-03 16:25:24 +03:00
Sergey Degtyar
cca1732245 engine: client: refactored voice.c
- reformatted code.
- changed function declarations to static.
- ensured consistent use of defined constants for buffer sizes.
2025-07-03 13:50:57 +03:00
Sergey Degtyar
fddf0200db engine: client: replaced hardcoded buffer sizes with defined constants for voice data 2025-07-03 13:36:25 +03:00
Sergey Degtyar
6bb7bcd378 engine: client: GoldSrc voice chat support
- Added support for GoldSrc voice mode, including initialization and shutdown functions.
- Implemented data size verification for the GoldSrc voice package.
- Creation and processing of voice packets, CRC verification of data integrity.
- Added a Steam ID field to the client_static_t structure
- Automatic gain control has been introduced for voice samples.
- The voice status structure has been updated.
- Refactored voice data handling for better type consistency and maintainability.
- Fixed ISO C90 compliance issues for better portability.
- Fixed decoder index usage.
2025-07-01 16:09:09 +03:00
Sergey Degtyar
f37bbec78e engine: sound: voice buffer isolation and correct MP3 volume handling 2025-06-26 06:57:50 +03:00
Sergey Degtyar
50a8f23b43 engine: sdl: always reset device descriptors on shutdown 2025-06-26 06:57:37 +03:00
Marc-Antoine Lortie
f9fc26239b Update reimplementations and ports links. 2025-06-24 21:41:39 +03:00
Marc-Antoine Lortie
9f797ef705 Fix repository spelling. 2025-06-24 21:40:40 +03:00
Alibek Omarov
a7671a8ac1 3rdparty: update UI submodules 2025-06-19 16:43:08 +05:00
Alibek Omarov
c807faf5b9 engine: client: rename queryserver to ui_queryserver (so q<tab> will always result in quit command, by users request) 2025-06-19 16:43:08 +05:00
Alibek Omarov
45dd15b064 wscript: remove unused --enable-emscripten 2025-06-19 15:48:50 +05:00
Alibek Omarov
7e50026100 engine: server: fix restoring player view entity after save restore (fixes trigger_camera usage) 2025-06-19 15:22:54 +05:00
Alibek Omarov
05a258a5b4 engine: client: respect intro videos aspect ratio 2025-06-19 14:29:34 +05:00
Alibek Omarov
902fa52cbd Revert "scripts: gha: fix ffmpeg build flavor (the one we're using for now don't have minimal flavor)"
This reverts commit 2267b2e4e6.
2025-06-16 06:24:39 +05:00
Alibek Omarov
cc528d407d Revert "scripts: gha: use full ffmpeg builds, as ours are not finalized yet"
This reverts commit 7f5e62d03a.
2025-06-16 06:24:35 +05:00
Alibek Omarov
2267b2e4e6 scripts: gha: fix ffmpeg build flavor (the one we're using for now don't have minimal flavor) 2025-06-16 06:21:17 +05:00
Alibek Omarov
7f5e62d03a scripts: gha: use full ffmpeg builds, as ours are not finalized yet 2025-06-15 16:01:23 +05:00
Alibek Omarov
e3bfdf4127 engine: wscript: remove duplicated --enable-ffmpeg option 2025-06-15 14:25:28 +05:00
Alibek Omarov
bf186b8178 github: set correct flatpak github action version 2025-06-15 13:18:34 +05:00
Alibek Omarov
907e02da8d Merge ffmpeg support 2025-06-15 13:15:17 +05:00
yohimik
2234f91c38 ref: gl: add stubs for WebGL 2025-06-15 13:01:13 +05:00
Alibek Omarov
582fe9a2fc common: defaults: Emscripten doesn't use INTERNAL_GAMELIBS anymore 2025-06-15 13:01:13 +05:00
Alibek Omarov
fa92a5fa0c engine: common: disable colored stdout for Emscripten 2025-06-15 13:01:13 +05:00
Alibek Omarov
342aa56144 engine: wscript: increase default memory size for Emscripten port, include game files from rodir folder in source tree root 2025-06-15 13:01:13 +05:00
Alibek Omarov
e9440a8612 filesystem: fix folder exists check in RoDir 2025-06-15 13:01:13 +05:00
Alibek Omarov
3e396bb55a engine: host: allow console if DEFAULT_DEV is set to larger than 0 value 2025-06-15 13:01:13 +05:00
Alibek Omarov
72b8182af0 public: comment out XASH_JS define to avoid arch misdetection on modern Emscripten versions 2025-06-15 13:01:13 +05:00
Alibek Omarov
6022865cae engine: filesystem_engine: set default root directory on Emscripten 2025-06-15 13:01:13 +05:00
Alibek Omarov
1559b1b331 engine: system: exit from Emscripten with force_exit call 2025-06-15 13:01:13 +05:00
Alibek Omarov
5db150e565 engine: launcher: get rid of Emscripten load library hacks, mount IDBFS into /rwdir 2025-06-15 13:01:13 +05:00
Alibek Omarov
e823ba050a engine: whereami: add stub for Emscripten 2025-06-15 13:01:13 +05:00
Alibek Omarov
6f545f178e engine: host: move main loop contents into separate function 2025-06-15 13:01:13 +05:00
Alibek Omarov
b598daf049 engine: wscript: disable default install for Emscripten, we need to rename main program into index.html and also install additional outputs 2025-06-15 12:51:57 +05:00
Alibek Omarov
3f5ca210a0 common: port: set .wasm as libraries extension on Emscripten 2025-06-15 12:51:57 +05:00
Alibek Omarov
f1e665b5b3 engine: wscript: disable async ns resolve for Emscripten for now 2025-06-15 12:51:57 +05:00
Alibek Omarov
eb84efd260 wscript: set Emscripten defaults 2025-06-15 12:51:57 +05:00
Alibek Omarov
90884a2d09 scripts: waifulib: sdl2: use Emscripten's SDL port 2025-06-15 12:51:57 +05:00
Alibek Omarov
69e38c963d engine: platform: remove Emscripten-specific library loading, it's not needed anymore 2025-06-15 12:51:57 +05:00
Alibek Omarov
d7f4b21db7 3rdparty: mainui: update 2025-06-15 12:51:57 +05:00
Alibek Omarov
6aa281bc59 public: define Emscripten as POSIX-compatible 2025-06-15 12:51:57 +05:00
Alibek Omarov
e22dadc36f scripts: waifulib: add tool for building with Emscripten's Clang wrapper 2025-06-15 12:51:57 +05:00
Alibek Omarov
1bf5909023 wscript: don't forget to recurse into ref/gl with only gles3compat enabled 2025-06-15 09:13:39 +05:00
Andrey Akhmichin
ba71f114d9 Documentation: opensource-mods.md: add "Master Sword". 2025-06-15 07:12:45 +03:00
Alibek Omarov
c90d110096 engine: platform: sdl2: assign K_AUX32 and K_AUX31 to volume buttons to match old engine behavior on Android 2025-06-07 11:21:32 +05:00
Alibek Omarov
05edde050d engine: always remap clipnodes for bsp30ext 2025-05-27 22:54:06 +03:00
Alibek Omarov
1c29490889 Reapply "engine: disable clipnodes remapping for non-extended formats and QBSP2"
This reverts commit 2e748c4aea.
2025-05-27 22:44:53 +03:00
Alibek Omarov
210f51d38c ci: use flatpak github action fork again because I forgot that github has weird limitations 2025-05-27 22:38:13 +03:00
Alibek Omarov
2e748c4aea Revert "engine: disable clipnodes remapping for non-extended formats and QBSP2"
This reverts commit f2ecdad5bb.
2025-05-27 22:23:44 +03:00
Alibek Omarov
375ff6296b ci: try to upgrade flatpak action 2025-05-27 22:21:23 +03:00
Alibek Omarov
026435865a wscript: cleanup defaults, now that we don't need to specify most options from here 2025-05-27 21:54:22 +03:00
Alibek Omarov
216e680965 wscript: detect threading ability by platform
On client, it's handled by SDL{2,3}. On server we rely on pthreads (and some platforms don't have pthreads).
2025-05-27 21:40:07 +03:00
Alibek Omarov
3fe77ec86c wscript: remove msdev.py generator (Visual Studio project still can be generated using msvs.py) 2025-05-27 20:59:47 +03:00
Alibek Omarov
a88fea6ab3 wscript: remove cmake.py generator 2025-05-27 20:57:47 +03:00
Alibek Omarov
78ef00a269 wscript: remove hacky logic determening valid target when crosscompiling to 32-bit x86 2025-05-27 20:50:08 +03:00
Alibek Omarov
f9a9aedea5 engine: set fps_override to 0 by default, it's still experimental and breaks multiplayer
Was probably set to 1 by mistake.
2025-05-26 20:10:45 +05:00
Alibek Omarov
53e59c2e73 ref: load sprites textures with texgamma applied (like GoldSrc) 2025-05-26 19:09:28 +05:00
Alibek Omarov
fc3b8ae45d engine: imagelib: allow installing texgamma processed palette before loading sprites 2025-05-26 19:09:28 +05:00
Alibek Omarov
911b83a9d8 engine: imagelib: load studio model textures with texgamma applied (like GoldSrc) 2025-05-26 19:09:28 +05:00
Alibek Omarov
5276e46dc0 contributing: add a note about usage of LLM-based tools [skip ci] 2025-05-26 03:28:58 +05:00
Alibek Omarov
9fbd47bb4c engine: server: move unexpected master server query packets and failed challenges to more verbose loglevel 2025-05-25 21:05:37 +05:00
Alibek Omarov
b8ead38573 engine: server: prevent flood about unreliable datagram overflow on frozen players 2025-05-25 20:37:06 +05:00
Alibek Omarov
6a318d5000 engine: server: check for master servers addresses in connectionless packet handler, and it it matches, only reply to M2S messages 2025-05-25 20:29:16 +05:00
Alibek Omarov
71040c7ed5 engine: net_http: fix missing newline 2025-05-25 19:21:23 +05:00
Alibek Omarov
c3531b6499 engine: expect that base_cmd is consistent, as for many years no cases of broken basecmd were found 2025-05-25 16:48:31 +05:00
Alibek Omarov
9c6f12f31c engine: make command argc/argv functions inline 2025-05-25 16:47:45 +05:00
Alibek Omarov
913d0eb693 engine: add command to make other commands or variables privileged 2025-05-25 16:33:11 +05:00
Alibek Omarov
7bff848592 engine: base_cmd: simplify FindAll 2025-05-25 16:33:11 +05:00
Alibek Omarov
72485da491 engine: client: make demo recording and demo removing command privileged 2025-05-25 03:22:19 +05:00
Alibek Omarov
694472f4c1 engine: platform: allow querying window type through R_GetWindowHandle 2025-05-24 22:03:43 +05:00
Alibek Omarov
004af02e99 engine: avi: comment out debug messages 2025-01-29 11:13:29 +03:00
Alibek Omarov
8ad56eb085 Merge branch 'master' into avi 2025-01-29 11:12:38 +03:00
Alibek Omarov
32def35408 engine: avi: remove stub file 2025-01-23 18:56:44 +03:00
Alibek Omarov
952ccb1247 engine: avi: very dumb pause 2025-01-23 18:55:48 +03:00
Alibek Omarov
6f0fb594e7 Merge branch 'master' 2025-01-23 18:42:00 +03:00
Alibek Omarov
8b4b0479c8 ci: enable ffmpeg by default for Windows and Linux x86 targets 2024-12-13 03:04:32 +03:00
Alibek Omarov
c8bf7e5630 engine: avi: get rid of stubs file, temporarily put these stubs into avi_ffmpeg.c 2024-12-13 03:04:32 +03:00
Alibek Omarov
4987080e1e engine: client: remove unused S_RawSamples wrapper function 2024-12-13 03:04:32 +03:00
Alibek Omarov
1d78e400ff engine: sound: increase MAX_RAW_SAMPLES to 16384 to fit an audio track coming from the movies 2024-12-13 03:04:32 +03:00
Alibek Omarov
2d1a885954 common: render_api: use movie_state_s in AVI functions for easier type checking 2024-12-13 03:04:32 +03:00
Alibek Omarov
43ba2e747b engine: avi: mention SDL3 and FTEQW developers which ffmpeg integrations in their projects helped me to understand the ffmpeg API 2024-12-13 03:04:32 +03:00
Alibek Omarov
eefb823ee2 engine: client: remove legacy movie audio track streaming functions 2024-12-13 03:04:32 +03:00
Alibek Omarov
a3c33e0fa9 engine: client: sound: make S_RawSampleStereo public to other parts of engine 2024-12-13 03:04:32 +03:00
Alibek Omarov
16fe6b1b1a engine: client: avi: remove VFW-based player 2024-12-13 03:04:32 +03:00
Alibek Omarov
4275829bd5 engine: client: partially rework how AVI playback works in Xash. Add ffmpeg-based player.
This intentionally will not build without --enable-ffmpeg for now.

We add new functions to the RenderAPI called AVI_Think and AVI_SetParm. Game
developer is supposed to call AVI_Think each frame and set video playback
parameters through AVI_SetParm function.

What's tested:
* Intro cinematics.
* Looping videos.
* Playing back an hour long video.
* API extension (PrimeXT's avi branch at https://github.com/a1batross/PrimeXT).

What's broken:
* There is no way to seek a video file, only rewind to the start.
2024-12-13 03:04:32 +03:00
Alibek Omarov
1884f0a3ce engine: client: avi: add new macro XASH_AVI and corresponding backend select macros (AVI_NULL and AVI_FFMPEG) 2024-12-09 00:27:40 +03:00
LekKit
8fb8ee7e22 ref: soft: implement r_fullbright 2022-09-25 08:37:34 +00:00
517 changed files with 44304 additions and 44454 deletions

View File

@@ -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

View File

@@ -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,13 +35,10 @@ jobs:
targetos: linux
targetarch: ppc64el
cross: true
# - os: ubuntu-aarch64-22.04
# - os: ubuntu-latest
# targetos: linux
# targetarch: aarch64
- os: ubuntu-latest
targetos: linux
targetarch: e2k-8c
cross: true
# targetarch: e2k-8c
# cross: true
- os: ubuntu-latest
targetos: android
targetarch: multiarch
@@ -60,22 +55,27 @@ jobs:
- os: windows-latest
targetos: win32
targetarch: amd64
- os: windows-2019 # always use the oldest possible for 32-bit because of older compilers, and better support of certain legacy OSes
- os: windows-2022 # always use the oldest possible for 32-bit because of older compilers, and better support of certain legacy OSes
targetos: win32
targetarch: i386
- os: macos-14 # 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.0
SDL_VERSION: 2.32.10
FFMPEG_VERSION: 8.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
@@ -85,7 +85,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/*
@@ -97,15 +97,15 @@ jobs:
include:
- app: su.xash.Engine.Compat.i386
container:
image: bilelmoussaoui/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)
uses: FWGS/flatpak-github-actions/flatpak-builder@v6.3-fix
uses: FWGS/flatpak-github-actions/flatpak-builder@v6.5
with:
bundle: ${{ matrix.app }}.flatpak
manifest-path: scripts/flatpak/${{ matrix.app }}.yml
@@ -115,31 +115,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 }}"

31
.gitignore vendored
View File

@@ -222,7 +222,7 @@ publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# TODO: Comment the next line if you want to checkin your web deploy settings
# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
@@ -318,6 +318,12 @@ build-*
*.includes
*.files
# ios
ios/cl_dlls/*
ios/dlls/*
ios/*.framework
scripts/ios/mod-build
# Waf
build_current
*waf-*/
@@ -339,9 +345,32 @@ __pycache__
.history/*
.cache/*
enc_temp_folder/
Taskfile.yml
# KDevelop4
*.kdev4
# ccls langauge server
.ccls-*
# JetBrains
.idea/
cmake-build-*
# some Android-specific build stuff
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

5
.gitmodules vendored
View File

@@ -39,4 +39,7 @@
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

View File

@@ -13,10 +13,12 @@ def configure(conf):
def build(bld):
gl4es_srcdir = bld.path.find_node('gl4es/src')
cflags = []
if bld.env.COMPILER_CC != 'msvc':
cflags += ['-w', '-fvisibility=hidden', '-std=gnu99']
bld.stlib(source = gl4es_srcdir.ant_glob(['gl/*.c', 'gl/*/*.c', 'glx/hardext.c']),
target = 'gl4es',
includes = ['gl4es/src', 'gl4es/src/gl', 'gl4es/src/glx', 'gl4es/include'],
defines = ['NOX11', 'NO_GBM', 'NO_INIT_CONSTRUCTOR', 'DEFAULT_ES=2', 'NOEGL', 'NO_LOADER', 'STATICLIB'],
cflags = ['-w', '-fvisibility=hidden', '-std=gnu99'],
cflags = cflags,
export_includes = '.')

View File

@@ -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
@@ -75,13 +75,8 @@ def configure(conf):
conf.fatal('Can\'t find libbacktrace submodule. Run `git submodule update --init --recursive`.')
return
if conf.env.DEST_SIZEOF_VOID_P == 8:
conf.define('BACKTRACE_ELF_SIZE', 64)
conf.define('BACKTRACE_XCOFF_SIZE', 64)
else:
conf.define('BACKTRACE_ELF_SIZE', 32)
conf.define('BACKTRACE_XCOFF_SIZE', 32)
conf.define('BACKTRACE_ELF_SIZE', 64 if conf.env.DEST_SIZEOF_VOID_P == 8 else 32)
conf.define('BACKTRACE_XCOFF_SIZE', 64 if conf.env.DEST_SIZEOF_VOID_P == 8 else 32)
conf.define('_ALL_SOURCE', 1)
conf.define('_GNU_SOURCE', 1)
conf.define('_POSIX_PTHREAD_SEMANTICS', 1)
@@ -97,7 +92,10 @@ def configure(conf):
conf.env.HAVE_FRANDOM_SEED = True
def check_header(hdr):
return {'header_name':hdr, 'msg':'... %s header' % hdr, 'mandatory':False, 'id':hdr}
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, 'define_name': define, 'compiler': 'c'}, **kw)
conf.multicheck(
check_header('dlfcn.h'),
@@ -118,20 +116,20 @@ def configure(conf):
check_header('unistd.h'),
check_header('windows.h'),
{'fragment':FRAGMENT_ATOMIC, 'msg':'... __atomic extensions', 'define_name':'HAVE_ATOMIC_FUNCTIONS', 'mandatory':False},
{'fragment':FRAGMENT_SYNC, 'msg':'... __sync extensions', 'define_name':'HAVE_SYNC_FUNCTIONS', 'mandatory':False},
{'fragment':FRAGMENT_GETPAGESIZE, 'msg':'... getpagesize function', 'define_name':'HAVE_DECL_GETPAGESIZE', 'mandatory':False},
{'fragment':FRAGMENT_STRNLEN, 'msg':'... strnlen function', 'define_name':'HAVE_DECL_STRNLEN', 'mandatory':False},
{'fragment':FRAGMENT_DL_ITERATE_PHDR % 'link.h', 'msg':'... dl_iterate_phdr function in link.h', 'define_name':'HAVE_DL_ITERATE_PHDR', 'after_tests': ['link.h'], 'mandatory':False},
{'fragment':FRAGMENT_DL_ITERATE_PHDR % 'sys/link.h', 'msg':'... dl_iterate_phdr function in sys/link.h', 'define_name':'HAVE_DL_ITERATE_PHDR', 'after_tests': ['sys/link.h'], 'mandatory':False},
{'fragment':FRAGMENT_FCNTL,'msg':'... fnctl function', 'define_name':'HAVE_FCNTL', 'mandatory':False},
{'fragment':FRAGMENT_GETEXECNAME,'msg':'... getexecname function','define_name':'HAVE_GETEXECNAME', 'mandatory':False},
{'fragment':FRAGMENT_GETIPINFO, 'msg':'... _Unwind_GetIPInfo function', 'define_name':'HAVE_GETIPINFO', 'mandatory':False},
{'fragment':FRAGMENT_KERN_PROC % 'KERN_PROC', 'msg': '... KERN_PROC and KERN_PROC_PATHNAME defines', 'define_name':'HAVE_KERN_PROC', 'mandatory':False},
{'fragment':FRAGMENT_KERN_PROC % 'KERN_PROC_ARGS', 'msg': '... KERN_PROC_ARGS and KERN_PROC_PATHNAME defines', 'define_name':'HAVE_KERN_PROC_ARGS', 'mandatory':False},
{'fragment':FRAGMENT_LOADQUERY, 'msg': '... loadquery function', 'define_name':'HAVE_LOADQUERY', 'mandatory':False},
{'fragment':FRAGMENT_LSTAT, 'msg': '... lstat function', 'define_name':'HAVE_LSTAT', 'mandatory':False},
{'fragment':FRAGMENT_READLINK, 'msg': '... readlink function', 'define_name':'HAVE_READLINK', 'mandatory':False},
check_frag(FRAGMENT_ATOMIC, '__atomic extensions', 'HAVE_ATOMIC_FUNCTIONS'),
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_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'),
check_frag(FRAGMENT_KERN_PROC % 'KERN_PROC', 'KERN_PROC and KERN_PROC_PATHNAME defines', 'HAVE_KERN_PROC'),
check_frag(FRAGMENT_KERN_PROC % 'KERN_PROC_ARGS', 'KERN_PROC_ARGS and KERN_PROC_PATHNAME defines', 'HAVE_KERN_PROC_ARGS'),
check_frag(FRAGMENT_LOADQUERY, 'loadquery function', 'HAVE_LOADQUERY'),
check_frag(FRAGMENT_LSTAT, 'lstat function', 'HAVE_LSTAT'),
check_frag(FRAGMENT_READLINK, 'readlink function', 'HAVE_READLINK'),
# {'lib':'lzma', 'define_name':'HAVE_LIBLZMA', 'uselib_store':'lzma', 'msg':'... lzma library', 'mandatory':False},
# {'lib':'z', 'define_name':'HAVE_ZLIB', 'uselib_store':'z', 'msg':'... zlib library', 'mandatory':False},
@@ -139,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()
@@ -164,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']
@@ -178,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

Submodule 3rdparty/library_suffix added at a35ac9c12e

78
3rdparty/opus/wscript vendored
View File

@@ -23,6 +23,17 @@ int main (void) {
int * array = alloca(foo);
}'''
FRAGMENT_LRINT='''#include <math.h>
#include <stdlib.h>
int main (int argc, char **argv) {
return lrint%s((%s)atof(argv[1]));
}'''
# assuming MSVC always enables NEON
FRAGMENT_NEON='''#if !defined __ARM_NEON__ && !defined _MSC_VER
#error
#endif'''
def options(opt):
pass
@@ -31,15 +42,61 @@ def configure(conf):
conf.fatal('Can\'t find opus submodule. Run `git submodule update --init --recursive`.')
return
if conf.check_cc(fragment=FRAGMENT_LRINT % ('', 'double'), msg = 'Checking for C99 lrint', use = 'M', mandatory = False):
conf.define('HAVE_LRINT', 1)
if conf.check_cc(fragment=FRAGMENT_LRINT % ('f', 'float'), msg = 'Checking for C99 lrintf', use = 'M', mandatory = False):
conf.define('HAVE_LRINTF', 1)
# Check for C99 variable-size arrays, or alloca() as fallback
if conf.check_cc(fragment=FRAGMENT_VLA, msg = 'Checking for C99 VLA support', mandatory = False):
conf.define('VAR_ARRAYS', 1)
elif conf.check_cc(fragment=FRAGMENT_ALLOCA_H, msg = 'Checking for alloca in alloca.h header', mandatory = False):
conf.define('USE_ALLOCA', 1)
conf.define('HAVE_ALLOCA_H', 1)
elif conf.check_cc(fragmenmt=FRAGMENT_STDLIB_H, msg = 'Checking for alloca.h in stdlib.h', mandatory = False):
elif conf.check_cc(fragment=FRAGMENT_STDLIB_H, msg = 'Checking for alloca.h in stdlib.h', mandatory = False):
conf.define('USE_ALLOCA', 1)
if conf.env.DEST_CPU in ['thumb', 'arm']:
if conf.check(header_name='arm_neon.h', mandatory = False):
if conf.check(fragment=FRAGMENT_NEON, msg = 'Checking if compiler enabled NEON', mandatory = False):
conf.env.HAVE_NEON = True
conf.define('OPUS_ARM_MAY_HAVE_NEON_INTR', 1)
conf.define('OPUS_ARM_PRESUME_NEON', 1)
elif conf.env.DEST_CPU.startswith('x86'):
if conf.check(header_name='xmmintrin.h', mandatory = False):
if conf.env.COMPILER_CC != 'msvc':
conf.env.CFLAGS += ['-msse']
conf.define('OPUS_X86_MAY_HAVE_SSE', 1)
if conf.env.DEST_SIZEOF_VOID_P > 4:
conf.define('OPUS_X86_PRESUME_SSE', 1)
if conf.check(header_name='emmintrin.h', mandatory = False):
if conf.env.COMPILER_CC != 'msvc':
conf.env.CFLAGS += ['-msse2']
conf.define('OPUS_X86_MAY_HAVE_SSE2', 1)
if conf.env.DEST_SIZEOF_VOID_P > 4:
conf.define('OPUS_X86_PRESUME_SSE2', 1)
# on x86_64 we enable both SSE and SSE2, so RTCD can be disabled (it's actually doesn't even build with RTCD enabled)
if conf.env.DEST_SIZEOF_VOID_P == 4:
if conf.check(header_name='cpuid.h', mandatory=False):
conf.define('CPU_INFO_BY_C', 1)
else:
conf.define('CPU_INFO_BY_ASM', 1)
conf.define('OPUS_HAVE_RTCD', 1)
# if conf.check(header_name='smmintrin.h', mandatory = False):
# if conf.env.COMPILER_CC != 'msvc':
# conf.env.CFLAGS += ['-msse4.1']
# conf.define('OPUS_X86_MAY_HAVE_SSE4_1', 1)
# if conf.check(header_name='immintrin.h', mandatory = False):
# if conf.env.COMPILER_CC != 'msvc':
# conf.env.CFLAGS += ['-mavx']
# conf.define('OPUS_X86_MAY_HAVE_AVX', 1)
# TODO: ARM/x86 intrinsics detection
# TODO: maybe call autotools/cmake/meson instead?
@@ -55,8 +112,23 @@ def build(bld):
'opus/src/opus_compare.c',
'opus/celt/opus_custom_demo.c'
])
includes = ['opus/include/', 'opus/celt/', 'opus/silk/', 'opus/silk/float/']
defines = ['OPUS_BUILD', 'FLOAT_APPROX', 'PACKAGE_VERSION="1.4.0"', 'CUSTOM_MODES']
includes = ['opus', 'opus/include/', 'opus/celt/', 'opus/silk/', 'opus/silk/float/']
if bld.env.DEST_CPU in ['thumb', 'arm']:
if bld.env.HAVE_NEON:
sources += bld.path.ant_glob(['opus/silk/arm/*.c', 'opus/celt/arm/*.c'])
includes += ['opus/silk/arm', 'opus/celt/arm']
elif bld.env.DEST_CPU.startswith('x86'):
sources += ['opus/silk/x86/x86_silk_map.c',
'opus/celt/x86/pitch_sse.c',
'opus/celt/x86/pitch_sse2.c',
'opus/celt/x86/vq_sse2.c',
'opus/celt/x86/x86_celt_map.c',
'opus/celt/x86/x86cpu.c']
includes += ['opus/silk/x86', 'opus/celt/x86']
defines = ['OPUS_BUILD', 'FLOAT_APPROX', 'PACKAGE_VERSION="1.4.0"', 'CUSTOM_MODES', 'ENABLE_HARDENING']
bld.stlib(
source = sources,

View File

@@ -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.
@@ -45,3 +46,8 @@
Tags can be any: subsystem, simple feature name or even just a filename, without extension.
Just keep them always same, it helps keep history clean and commit messages short.
## LLM-based tools usage.
While we wouldn't recommend using any LLM-based (also misleadingly called AI) tools, we understand that they are here to stay.
Whether you're reporting bug or contributing the code, you take complete authorship and responsibility over provided content and the same rules will apply to you as for everybody else, so validate the bug report or the patch before sending it.

View File

@@ -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.

View 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.

View File

@@ -18,7 +18,7 @@ Initial Xash3D SDL2/Linux port author, Xash3D FWGS engine maintainer, creator of
[cs16-client](https://github.com/Velaron/cs16-client) & [tf15-client](https://github.com/Velaron/tf15-client) maintainer and Xash3D FWGS [contributor](https://github.com/FWGS/xash3d-fwgs/commits?author=Velaron) (Android port, voice chat, etc).
* [BuyMeACoffee page](https://www.buymeacoffee.com/velaron)
* [Boosty page](https://boosty.to/velaron)
## [SNMetamorph](https://github.com/SNMetamorph)
@@ -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)

View 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. |

View File

@@ -42,3 +42,6 @@ Issue #3: Some mods (like The Specialists, Tyrian, ...) already apply suffixes _
Resolution: On x86 on **Win/Lin/Mac**, don't change anything. Otherwise, strip the _i?86 part and follow the usual scheme.
See discussion: https://github.com/FWGS/xash3d-fwgs/issues/39
Issue #4: When distributing game libraries on Android inside an APK, they couldn't be loaded.
Resolution: Enable `useLegacyPackaging` option in build.gradle, when distributing games in APK. Always force game libraries to have `lib` prefix on Android, regardless if they are packaged in APK or not..

View 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.

View File

@@ -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

View 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
---

View File

@@ -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.

View File

@@ -0,0 +1,15 @@
/*
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/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.
*/

View File

@@ -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. |

View File

@@ -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,15 +133,25 @@ 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/
## Master Sword
Official github repository of Master Sword Classic - https://github.com/BerntA/MasterSwordClassic
Official github repository of Master Sword Rebirth - https://github.com/MSRevive/MasterSwordRebirth
## MechMod
Official github repository - https://github.com/vermagav/mechmod
@@ -150,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*
@@ -181,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
@@ -205,7 +236,7 @@ Branch **redempt** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tr
OpenAG by YaLTeR - https://github.com/YaLTeR/OpenAG
## Afraid of Monsters
malortie's recreation - https://github.com/malortie/hl-aom
malortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-aom
Branch **aom** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/aom
@@ -213,17 +244,17 @@ Branch **aom** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/a
Reverse-engineered code: branch **aomdc** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/aomdc
## Azure Sheep
malortie's recreation - https://github.com/malortie/hl-asheep
malortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-asheep
Reverse-engineered code: branch **asheep** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/asheep
## Big Lolly
malortie's recreation - https://github.com/malortie/hl-biglolly
malortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-biglolly
Branch **biglolly** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/biglolly
## Black Ops
malortie's recreation - https://github.com/malortie/hl-blackops
malortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-blackops
Branch **blackops** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/blackops
@@ -246,13 +277,16 @@ 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
Reverse-engineered code: branch **clcampaign** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/clcampaign
## Escape from the Darkness
malortie's recreation - https://github.com/malortie/hl-eftd
malortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-eftd
Reverse-engineered code: branch **eftd** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/eftd
@@ -283,7 +317,7 @@ Reverse-engineered code: branch **rebellion** in hlsdk-portable - https://github
Branch **hl_urbicide** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/hl_urbicide
## Half-Life: Visitors
malortie's recreation - https://github.com/malortie/hl-visitors
malortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-visitors
Reverse-engineered code: branch **visitors** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/visitors
@@ -291,17 +325,17 @@ Reverse-engineered code: branch **visitors** in hlsdk-portable - https://github.
Branch **half-secret** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/half-secret
## Night at the Office
malortie's recreation - https://github.com/malortie/hl-nato
malortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-nato
Branch **noffice** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/noffice
## Poke 646
malortie's recreation - https://github.com/malortie/hl-poke646
malortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-poke646
Reverse-engineered code: branch **poke646** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/poke646
## Poke 646: Vendetta
malortie's recreation - https://github.com/malortie/hl-poke646-vendetta
malortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-poke646-vendetta
Reverse-engineered code: branch **poke646_vendetta** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/poke646_vendetta
@@ -318,19 +352,19 @@ Branch **sewer_beta** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable
Reverse-engineered code by Velaron - https://github.com/Velaron/tf15-client
## The Gate
malortie's recreation - https://github.com/malortie/hl-thegate
malortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-thegate
Reverse-engineered code: branch **thegate** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/thegate
## They Hunger
malortie's recreation - https://github.com/malortie/hl-theyhunger
malortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-theyhunger
Reverse-engineered code: branch **theyhunger** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/theyhunger
They Hunger: Tactical - https://www.moddb.com/mods/they-hunger-tactical/downloads/tht-source-code-documentation
## Times of Troubles
malortie's recreation - https://github.com/malortie/hl-tot
malortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-tot
Branch **tot** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/tot
@@ -348,11 +382,14 @@ 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
## Half-Life: Invasion
Port to HLSDK 2.4 by malortie - https://github.com/malortie/hl-invasion
Port to HLSDK 2.4 by malortie - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-invasion
Port to Linux by fmoraw - https://github.com/fmoraw/hlinvasion
@@ -371,7 +408,7 @@ Port to Linux - https://github.com/fmoraw/NS
Version 1.2: branch **sohl1.2** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/sohl1.2
## Swiss Cheese Halloween 2002
Just more playable version by malortie - https://github.com/malortie/hl-shall
Just more playable version by malortie - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-shall
Branch **halloween** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/halloween

View File

@@ -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

View 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.

View 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.

View File

@@ -0,0 +1,3 @@
# Xash3D 49 netchan protocol
TBD...

View File

@@ -1,15 +0,0 @@
As online community is growing, we don't want to keep bad or invalid servers on the Internet Games list. It's a common rules for all Xash3D server owners who are using default master server.
1. Breaking master server or clients is prohibited in any way. If you found a serious bug, contact us at contact 'at' fwgs.ru or post an issue at GitHub.
2. Changing settings, exploiting vulnerabilities of clients is prohibited in any way. If you found a serious bug, contact us at contact 'at' fwgs.ru or post an issue at GitHub.
2. Agressive advertisement and spam on other servers is prohibited in any way.
3. If your server is require downloading custom content, it should be available for everyone. Recommended way to deliver custom content is FastDL.
4. It's recommended to keep your server is up-to-date. You may use our public Debian/Ubuntu/Arch Linux [repository](https://github.com/FWGS/xash3d/wiki/How-to-set-up-a-Xash3D-Dedicated-Server-on-Debian-or-Ubuntu) to simplify updating process.
Just follow these rules to not get banned on our master server. Good luck, have fun!

View File

@@ -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).

View File

@@ -1,6 +1,6 @@
# 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" />
[![GitHub Actions Status](https://github.com/FWGS/xash3d-fwgs/actions/workflows/c-cpp.yml/badge.svg)](https://github.com/FWGS/xash3d-fwgs/actions/workflows/c-cpp.yml) [![FreeBSD Build Status](https://img.shields.io/cirrus/github/FWGS/xash3d-fwgs?label=freebsd%20build)](https://cirrus-ci.com/github/FWGS/xash3d-fwgs) \
[![Discord Server](https://img.shields.io/discord/355697768582610945?logo=Discord&label=International%20Discord%20chat)](http://fwgsdiscord.mentality.rip/) [![Russian speakers Telegram Chat](https://img.shields.io/badge/Russian_speakers_Telegram_chat-gray?logo=Telegram)](https://t.me/flyingwithgauss) \
[![Discord Server](https://img.shields.io/discord/355697768582610945?logo=Discord&label=International%20Discord%20chat)](http://xash.su/discord/) [![Russian speakers Telegram Chat](https://img.shields.io/badge/Russian_speakers_Telegram_chat-gray?logo=Telegram)](https://t.me/flyingwithgauss) \
[![Download Daily Build](https://img.shields.io/badge/downloads-testing-orange)](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.
@@ -36,10 +36,17 @@ You still needed to copy `valve` directory as all game resources located there.
For additional info, run Xash3D with `-help` command line key.
## Contributing
* Before sending an issue, check if someone already reported your issue. Make sure you're following "How To Ask Questions The Smart Way" guide by Eric Steven Raymond. Read more: http://www.catb.org/~esr/faqs/smart-questions.html.
### Android
0) Install the APK file.
1) Copy `valve` directory to a folder named `xash` in the Internal storage.
2) Run games from within the app.
## 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/).
@@ -76,14 +83,33 @@ 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`.
#### Android (Windows/Linux/macOS)
* Install [Android Studio](https://developer.android.com/studio) (or the command line tools).
* Install [Python](https://python.org) (at least 2.7, latest is better).
* Install [Git](https://git-scm.com/download/win).
* Install [Ninja](https://ninja-build.org/).
* Install [CMake](https://cmake.org/) (for some dependencies).
* 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.
@@ -100,3 +126,19 @@ If compiling 32-bit on amd64, make sure `PKG_CONFIG_PATH` from the previous step
1) Configure build: `./waf configure` (you need to pass `-8` to compile 64-bit engine on 64-bit x86 processor).
2) Compile: `./waf build`.
3) Install: `./waf install --destdir=/path/to/any/output/directory`.
#### Android (Windows/Linux/macOS)
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
View 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
View File

@@ -11,4 +11,5 @@ local.properties
release/
*.hprof
.vscode/
*.bak
*.bak
.kotlin/

View File

@@ -1,95 +0,0 @@
cmake_minimum_required(VERSION 3.22)
# Only used to build Android project
project(XASH_ANDROID)
# armeabi-v7a requires cpufeatures library
if(ANDROID)
include_directories(${ANDROID_NDK}/sources/android/cpufeatures)
add_library(cpufeatures ${ANDROID_NDK}/sources/android/cpufeatures/cpu-features.c)
target_link_libraries(cpufeatures dl)
endif()
include(FindPython)
if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
set(BUILD_TYPE "debug")
else()
set(BUILD_TYPE "release")
list(APPEND WAF_EXTRA_ARGS --enable-poly-opt --enable-lto --enable-limited-debuginfo)
endif()
if(ANDROID_ABI STREQUAL "x86")
# HACKHACK: I don't know why but engine gets built as 64-bit binary here
list(APPEND WAF_EXTRA_ARGS -4)
endif()
set(CMAKE_VERBOSE_MAKEFILE ON)
# not cleanest way to get upper directory
set(ENGINE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../")
set(WAF_CC "${CMAKE_C_COMPILER} --target=${CMAKE_C_COMPILER_TARGET}")
set(WAF_CXX "${CMAKE_CXX_COMPILER} --target=${CMAKE_CXX_COMPILER_TARGET}")
set(WAF ${Python_EXECUTABLE} ${ENGINE_SOURCE_DIR}waf -t ${ENGINE_SOURCE_DIR} -o ${CMAKE_CURRENT_BINARY_DIR}/xash3d-fwgs)
# try to build minimal SDL. Enable features as we're gonna use them
set(SDL_RENDER OFF)
set(SDL_POWER OFF)
set(SDL_VULKAN OFF)
set(SDL_DISKAUDIO OFF)
set(SDL_DUMMYAUDIO OFF)
set(SDL_DUMMYVIDEO OFF)
set(SDL_VULKAN OFF)
set(SDL_OFFSCREEN OFF)
set(SDL_STATIC OFF)
add_subdirectory("${ENGINE_SOURCE_DIR}/3rdparty/SDL" SDL)
include(ExternalProject)
# gradle passes backslashes to cmake, how does this even work for everybody else?
string(REPLACE "\\" "/" CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY})
ExternalProject_Add(
Xash3DFWGS
SOURCE_DIR ${ENGINE_SOURCE_DIR}
INSTALL_DIR ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}
BUILD_IN_SOURCE TRUE
DEPENDS SDL2
BUILD_ALWAYS TRUE
LOG_CONFIGURE TRUE
LOG_BUILD TRUE
LOG_INSTALL TRUE
LOG_OUTPUT_ON_FAILURE TRUE
LOG_MERGED_STDOUTERR TRUE
# USES_TERMINAL_CONFIGURE TRUE
# USES_TERMINAL_BUILD TRUE
# USES_TERMINAL_INSTALL TRUE
# NOTE: setting up WAFLOCK is important to avoid possible race conditions
CONFIGURE_COMMAND ${CMAKE_COMMAND} -E env
ANDROID_NDK=${ANDROID_NDK}
BUILD_CMAKE_LIBRARY_OUTPUT_DIRECTORY=${CMAKE_LIBRARY_OUTPUT_DIRECTORY}
WAFLOCK=.lock-waf_android_${ANDROID_ABI}_build
${WAF} configure -T ${BUILD_TYPE} --android=${ANDROID_ABI},,${CMAKE_SYSTEM_VERSION}
-s "${ENGINE_SOURCE_DIR}/3rdparty/SDL" --enable-bundled-deps ${WAF_EXTRA_ARGS}
BUILD_COMMAND ${CMAKE_COMMAND} -E env
WAFLOCK=.lock-waf_android_${ANDROID_ABI}_build
${WAF} build -v
INSTALL_COMMAND ${CMAKE_COMMAND} -E env
WAFLOCK=.lock-waf_android_${ANDROID_ABI}_build
${WAF} install --destdir=${CMAKE_LIBRARY_OUTPUT_DIRECTORY}
)
add_subdirectory("${ENGINE_SOURCE_DIR}/3rdparty/hlsdk-portable" hlsdk-portable)
# a1ba: without this, xash3d target will be ignored as nothing depends on it
add_dependencies(client Xash3DFWGS)
add_dependencies(server Xash3DFWGS)

View File

@@ -1,130 +1,155 @@
import com.android.build.api.dsl.ApplicationExtension
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import java.time.LocalDateTime
import java.time.Month
import java.time.temporal.ChronoUnit
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
alias(libs.plugins.android.application)
}
android {
namespace = "su.xash.engine"
ndkVersion = "28.0.13004108"
extensions.configure<ApplicationExtension> {
namespace = "su.xash.engine"
ndkVersion = "29.0.14206865"
compileSdk = 35
defaultConfig {
applicationId = "su.xash"
applicationIdSuffix = "engine"
versionName = "0.21"
versionCode = getBuildNum()
minSdk = 21
targetSdk = 34
compileSdk = 34
defaultConfig {
applicationId = "su.xash.engine"
versionName = "0.21-" + getGitHash()
versionCode = getBuildNum()
minSdk = 21
targetSdk = 35
externalNativeBuild {
cmake {
abiFilters("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
arguments("-DANDROID_USE_LEGACY_TOOLCHAIN_FILE=OFF")
}
}
}
buildConfigField("String", "GIT_HASH", "\"${getGitHash()}\"")
externalNativeBuild {
cmake {
version = "3.22.1"
path = file("CMakeLists.txt")
}
}
externalNativeBuild {
val engineRoot = projectDir.parentFile.parent
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
experimentalProperties["ninja.abiFilters"] = setOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
experimentalProperties["ninja.path"] = File(engineRoot, "wscript").path
experimentalProperties["ninja.configure"] = "run-python"
experimentalProperties["ninja.arguments"] = setOf(
File(engineRoot, "scripts/configure-ninja.py").path,
engineRoot,
"--variant=\${ndk.variantName}",
"--abi=\${ndk.abi}",
"--configuration-dir=\${ndk.buildRoot}",
"--ndk-version=\${ndk.moduleNdkVersion}",
"--min-sdk-version=\${ndk.minPlatform}",
"--ndk-root=${androidComponents.sdkComponents.ndkDirectory.get()}",
// shut up, fake options
"-p:Configuration=\${ndk.variantName}",
"-p:Platform=\${ndk.abi}"
)
}
}
kotlinOptions {
jvmTarget = "1.8"
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
buildTypes {
debug {
isMinifyEnabled = false
isShrinkResources = false
isDebuggable = true
applicationIdSuffix = ".test"
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
)
}
kotlin {
compilerOptions {
jvmTarget = JvmTarget.JVM_11
}
}
release {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
)
}
buildFeatures {
viewBinding = true
buildConfig = true
}
register("asan") {
initWith(getByName("debug"))
}
signingConfigs {
create("androidDebugKey") {
storeFile = File(projectDir.parentFile, "debug.keystore")
storePassword = "android"
keyAlias = "androiddebugkey"
keyPassword = "android"
}
}
register("continuous") {
initWith(getByName("release"))
applicationIdSuffix = ".test"
}
}
lint {
abortOnError = false
}
sourceSets {
getByName("main") {
assets.srcDirs("../../3rdparty/extras/xash-extras", "../moddb")
java.srcDir("../../3rdparty/SDL/android-project/app/src/main/java")
}
}
/*
androidResources {
noCompress += ""
}
*/
lint {
abortOnError = false
}
packaging {
jniLibs {
keepDebugSymbols.add("**/*.so")
useLegacyPackaging = true
}
}
buildFeatures {
viewBinding = true
buildConfig = true
}
sourceSets {
getByName("main") {
assets.directories.add("../../3rdparty/extras/xash-extras")
java.directories.add("../../3rdparty/SDL/android-project/app/src/main/java")
}
}
androidResources {
noCompress += ""
}
buildTypes {
debug {
isMinifyEnabled = false
isShrinkResources = false
isDebuggable = true
applicationIdSuffix = ".test"
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
)
buildConfigField("boolean", "ENABLE_AUTO_UPDATE", "false")
}
packaging {
jniLibs {
useLegacyPackaging = true
keepDebugSymbols.add("**/*.so")
}
}
release {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
)
buildConfigField("boolean", "ENABLE_AUTO_UPDATE", "false")
}
register("asan") {
initWith(getByName("debug"))
}
register("continuous") {
initWith(getByName("release"))
applicationIdSuffix = ".test"
buildConfigField("boolean", "ENABLE_AUTO_UPDATE", "true")
signingConfig = signingConfigs.getByName("androidDebugKey")
}
}
}
dependencies {
implementation("com.google.android.material:material:1.11.0")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("androidx.navigation:navigation-fragment-ktx:2.7.7")
implementation("androidx.navigation:navigation-ui-ktx:2.7.7")
implementation("androidx.cardview:cardview:1.0.0")
implementation("androidx.annotation:annotation:1.7.1")
implementation("androidx.fragment:fragment-ktx:1.6.2")
implementation("androidx.preference:preference-ktx:1.2.1")
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
implementation("androidx.work:work-runtime-ktx:2.9.0")
// implementation "androidx.legacy:legacy-support-v4:1.0.0"
implementation(libs.material)
implementation("com.madgag.spongycastle:prov:1.58.0.0")
implementation("in.dragonbra:javasteam:1.2.0")
implementation(libs.appcompat)
implementation(libs.navigation.runtime.ktx)
implementation(libs.navigation.fragment.ktx)
implementation(libs.navigation.ui.ktx)
implementation(libs.preference.ktx)
implementation(libs.swiperefreshlayout)
implementation("ch.acra:acra-http:5.11.2")
implementation(libs.acra.http)
}
fun getBuildNum(): Int {
val now = LocalDateTime.now()
val releaseDate = LocalDateTime.of(2015, Month.APRIL, 1, 0, 0, 0)
val qBuildNum = releaseDate.until(now, ChronoUnit.DAYS)
val minuteOfDay = now.hour * 60 + now.minute
return (qBuildNum * 10000 + minuteOfDay).toInt()
val now = LocalDateTime.now()
val releaseDate = LocalDateTime.of(2015, Month.APRIL, 1, 0, 0, 0)
val qBuildNum = releaseDate.until(now, ChronoUnit.DAYS)
val minuteOfDay = now.hour * 60 + now.minute
return (qBuildNum * 10000 + minuteOfDay).toInt()
}
fun getGitHash(): String {
val process = ProcessBuilder("git", "rev-parse", "--short", "HEAD").directory(project.rootDir)
.redirectErrorStream(true).start()
return process.inputStream.bufferedReader().readText().trim()
}

View File

@@ -1,3 +1,25 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
-keep class su.xash.engine.XashActivity {
java.lang.String loadAndroidID();
java.lang.String getAndroidID();
@@ -87,3 +109,8 @@
void hapticRun(int, float, int);
void hapticStop(int);
}
# Unexpected reference to missing service class: META-INF/services/javax.annotation.processing.Processor.
-dontwarn javax.annotation.processing.Processor
-dontwarn javax.annotation.processing.AbstractProcessor
-dontwarn javax.annotation.processing.SupportedOptions

3
android/app/run-python Executable file
View File

@@ -0,0 +1,3 @@
#!/bin/bash
exec python $@

View File

@@ -0,0 +1 @@
python %*

View File

@@ -1,5 +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>
<string name="app_name" translatable="false">Xash3D FWGS (Test)</string>
<string name="authority" translatable="false">su.xash.engine.test.documents</string>
</resources>

View File

@@ -1,101 +1,113 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:allowAudioPlaybackCapture="true"
android:installLocation="preferExternal"
tools:targetApi="q">
<!-- OpenGL ES 1.1 -->
<uses-feature android:glEsVersion="0x00010000" />
<!-- Touchscreen support -->
<uses-feature
android:name="android.hardware.touchscreen"
android:required="false" />
<!-- Game controller support -->
<uses-feature
android:name="android.hardware.bluetooth"
android:required="false" />
<uses-feature
android:name="android.hardware.gamepad"
android:required="false" />
<uses-feature
android:name="android.hardware.usb.host"
android:required="false" />
<!-- External mouse input events -->
<uses-feature
android:name="android.hardware.type.pc"
android:required="false" />
<!-- Audio recording support -->
<uses-feature
android:name="android.hardware.microphone"
android:required="false" />
<!-- Allow downloading to the external storage on Android 5.1 and older -->
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="22" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<!-- Allow access to Bluetooth devices -->
<!-- Currently this is just for Steam Controller support and requires setting SDL_HINT_JOYSTICK_HIDAPI_STEAM -->
<uses-permission
android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<!-- Allow access to the vibrator -->
<uses-permission android:name="android.permission.VIBRATE" />
<!-- Allow recording audio -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!-- Allow internet access -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- Dedicated server -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
android:allowAudioPlaybackCapture="true"
android:installLocation="preferExternal">
<!-- OpenGL ES 1.1 -->
<uses-feature android:glEsVersion="0x00010000" />
<!-- Touchscreen support -->
<uses-feature
android:name="android.hardware.touchscreen"
android:required="false" />
<!-- Game controller support -->
<uses-feature
android:name="android.hardware.bluetooth"
android:required="false" />
<uses-feature
android:name="android.hardware.gamepad"
android:required="false" />
<uses-feature
android:name="android.hardware.usb.host"
android:required="false" />
<!-- External mouse input events -->
<uses-feature
android:name="android.hardware.type.pc"
android:required="false" />
<!-- Audio recording support -->
<uses-feature
android:name="android.hardware.microphone"
android:required="false" />
<!-- Allow downloading to the external storage on Android 5.1 and older -->
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29" />
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"
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 -->
<uses-permission
android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<!-- Allow access to the vibrator -->
<uses-permission android:name="android.permission.VIBRATE" />
<!-- Allow recording audio -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!-- Allow internet access -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- Dedicated server -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- Self-update: install downloaded APK -->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<application
android:name=".MainApplication"
android:allowBackup="true"
android:hardwareAccelerated="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher"
android:theme="@style/Theme.App"
android:usesCleartextTraffic="true">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<application
android:name=".MainApplication"
android:allowBackup="true"
android:hardwareAccelerated="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher"
android:theme="@style/Theme.App"
android:networkSecurityConfig="@xml/network_security_config"
android:usesCleartextTraffic="true"
android:requestLegacyExternalStorage="true">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".XashActivity"
android:alwaysRetainTaskState="true"
android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation|touchscreen"
android:exported="true"
android:launchMode="singleTask"
android:preferMinimalPostProcessing="true"
android:windowSoftInputMode="adjustResize"
tools:targetApi="r">
<intent-filter>
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
</intent-filter>
</activity>
<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>
<provider
android:name=".XashDocumentsProvider"
android:authorities="@string/authority"
android:exported="true"
android:grantUriPermissions="true"
android:permission="android.permission.MANAGE_DOCUMENTS">
<intent-filter>
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
</intent-filter>
</provider>
</application>
<queries>
<intent>
<action android:name="su.xash.engine.MOD" />
</intent>
</queries>
</manifest>
<activity
android:name=".XashActivity"
android:alwaysRetainTaskState="true"
android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation|touchscreen"
android:exported="true"
android:launchMode="singleTask"
android:preferMinimalPostProcessing="true"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
</intent-filter>
</activity>
</application>
<queries>
<intent>
<action android:name="su.xash.engine.MOD" />
</intent>
<intent>
<action android:name="android.intent.action.SENDTO" />
<data android:scheme="mailto" />
</intent>
</queries>
</manifest>

View File

@@ -1,49 +0,0 @@
package su.xash.engine;
import android.app.Activity;
import android.graphics.Rect;
import android.view.View;
import android.widget.FrameLayout;
public class AndroidBug5497Workaround {
// For more information, see https://code.google.com/p/android/issues/detail?id=5497
// To use this class, simply invoke assistActivity() on an Activity that already has its content view set.
public static void assistActivity(Activity activity) {
new AndroidBug5497Workaround(activity);
}
private View mChildOfContent;
private int usableHeightPrevious;
private FrameLayout.LayoutParams frameLayoutParams;
private AndroidBug5497Workaround(Activity activity) {
FrameLayout content = activity.findViewById(android.R.id.content);
mChildOfContent = content.getChildAt(0);
mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(this::possiblyResizeChildOfContent);
frameLayoutParams = (FrameLayout.LayoutParams) mChildOfContent.getLayoutParams();
}
private void possiblyResizeChildOfContent() {
int usableHeightNow = computeUsableHeight();
if (usableHeightNow != usableHeightPrevious) {
int usableHeightSansKeyboard = mChildOfContent.getRootView().getHeight();
int heightDifference = usableHeightSansKeyboard - usableHeightNow;
if (heightDifference > (usableHeightSansKeyboard / 4)) {
// keyboard probably just became visible
frameLayoutParams.height = usableHeightSansKeyboard - heightDifference;
} else {
// keyboard probably just became hidden
frameLayoutParams.height = usableHeightSansKeyboard;
}
mChildOfContent.requestLayout();
usableHeightPrevious = usableHeightNow;
}
}
private int computeUsableHeight() {
Rect r = new Rect();
mChildOfContent.getWindowVisibleDisplayFrame(r);
return (r.bottom - r.top);
}
}

View File

@@ -1,4 +1,3 @@
package su.xash.engine
class DedicatedActivity {
}
class DedicatedActivity {}

View File

@@ -4,8 +4,8 @@ import android.app.Service
import android.content.Intent
import android.os.IBinder
class DedicatedService: Service() {
override fun onBind(intent: Intent?): IBinder? {
TODO("Not yet implemented")
}
}
class DedicatedService : Service() {
override fun onBind(intent: Intent?): IBinder? {
TODO("Not yet implemented")
}
}

View File

@@ -1,35 +1,185 @@
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
private lateinit var appBarConfiguration: AppBarConfiguration
private lateinit var navController: NavController
private lateinit var binding: ActivityMainBinding
private lateinit var appBarConfiguration: AppBarConfiguration
private lateinit var navController: NavController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
setSupportActionBar(binding.toolbar)
setSupportActionBar(binding.toolbar)
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.fragmentContainerView) as NavHostFragment
navController = navHostFragment.navController
appBarConfiguration = AppBarConfiguration(navController.graph)
setupActionBarWithNavController(navController, appBarConfiguration)
}
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.fragmentContainerView) as NavHostFragment
navController = navHostFragment.navController
appBarConfiguration = AppBarConfiguration(navController.graph)
setupActionBarWithNavController(navController, appBarConfiguration)
override fun onSupportNavigateUp(): Boolean {
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
}
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))
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
dst.writeText(src.readText())
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
}
}

View File

@@ -3,26 +3,25 @@ package su.xash.engine
import android.app.Application
import android.content.Context
import android.os.StrictMode
import org.acra.config.httpSender
import org.acra.data.StringFormat
import org.acra.ktx.initAcra
class MainApplication : Application() {
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
if (!BuildConfig.DEBUG) {
if (!BuildConfig.DEBUG) {
initAcra {
buildConfigClass = BuildConfig::class.java
reportFormat = StringFormat.JSON
httpSender {
uri = "http://bodis.pp.ua:5000/report"
}
// httpSender {
// uri = "http://bodis.pp.ua:5000/report"
// }
}
} else {
// enable strict mode to detect memory leaks etc.
StrictMode.enableDefaults();
}
}
}
} else {
// enable strict mode to detect memory leaks etc.
StrictMode.enableDefaults();
}
}
}

View File

@@ -1,10 +1,12 @@
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;
import android.os.Bundle;
import android.os.Environment;
import android.provider.Settings.Secure;
import android.util.Log;
import android.view.KeyEvent;
@@ -12,137 +14,201 @@ 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;
private String mPackageName;
private static final String TAG = "XashActivity";
private boolean mUseVolumeKeys;
private String mPackageName;
private static final String TAG = "XashActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
//getWindow().addFlags(WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES);
getWindow().getAttributes().layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
}
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
//getWindow().addFlags(WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES);
getWindow().getAttributes().layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
}
AndroidBug5497Workaround.assistActivity(this);
}
AndroidBug5497Workaround.assistActivity(this);
}
@Override
public void onDestroy()
{
super.onDestroy();
@Override
public void onDestroy() {
super.onDestroy();
// Now that we don't exit from native code, we need to exit here, resetting
// application state (actually global variables that we don't cleanup on exit)
//
// When the issue with global variables will be resolved, remove that exit() call
System.exit(0);
}
// Now that we don't exit from native code, we need to exit here, resetting
// application state (actually global variables that we don't cleanup on exit)
//
// When the issue with global variables will be resolved, remove that exit() call
System.exit(0);
}
@Override
protected String[] getLibraries() {
return new String[]{"SDL2", "xash"};
}
@Override
protected String[] getLibraries() {
return new String[]{"SDL2", "xash"};
}
@SuppressLint("HardwareIds")
private String getAndroidID() {
return Secure.getString(getContentResolver(), Secure.ANDROID_ID);
}
@SuppressLint("HardwareIds")
private String getAndroidID() {
return Secure.getString(getContentResolver(), Secure.ANDROID_ID);
}
@SuppressLint("ApplySharedPref")
private void saveAndroidID(String id) {
getSharedPreferences("xash_preferences", MODE_PRIVATE).edit().putString("xash_id", id).commit();
}
@SuppressLint("ApplySharedPref")
private void saveAndroidID(String id) {
getSharedPreferences("xash_preferences", MODE_PRIVATE).edit().putString("xash_id", id).commit();
}
private String loadAndroidID() {
return getSharedPreferences("xash_preferences", MODE_PRIVATE).getString("xash_id", "");
}
private String loadAndroidID() {
return getSharedPreferences("xash_preferences", MODE_PRIVATE).getString("xash_id", "");
}
@Override
public String getCallingPackage() {
if (mPackageName != null) {
return mPackageName;
}
@Override
public String getCallingPackage() {
if (mPackageName != null) {
return mPackageName;
}
return super.getCallingPackage();
}
return super.getCallingPackage();
}
private AssetManager getAssets(boolean isEngine) {
AssetManager am = null;
private AssetManager getAssets(boolean isEngine) {
AssetManager am = null;
if (isEngine) {
am = getAssets();
} else {
try {
am = getPackageManager().getResourcesForApplication(getCallingPackage()).getAssets();
} catch (Exception e) {
Log.e(TAG, "Unable to load mod assets!");
e.printStackTrace();
}
}
if (isEngine) {
am = getAssets();
} else {
try {
am = getPackageManager().getResourcesForApplication(getCallingPackage()).getAssets();
} catch (Exception e) {
Log.e(TAG, "Unable to load mod assets!");
e.printStackTrace();
}
}
return am;
}
return am;
}
private String[] getAssetsList(boolean isEngine, String path) {
AssetManager am = getAssets(isEngine);
private String[] getAssetsList(boolean isEngine, String path) {
AssetManager am = getAssets(isEngine);
try {
return am.list(path);
} catch (Exception e) {
e.printStackTrace();
}
try {
return am.list(path);
} catch (Exception e) {
e.printStackTrace();
}
return new String[]{};
}
return new String[]{};
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (SDLActivity.mBrokenLibraries) {
return false;
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (SDLActivity.mBrokenLibraries) {
return false;
}
int keyCode = event.getKeyCode();
if (!mUseVolumeKeys) {
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ||
keyCode == KeyEvent.KEYCODE_VOLUME_UP ||
keyCode == KeyEvent.KEYCODE_CAMERA ||
keyCode == KeyEvent.KEYCODE_ZOOM_IN ||
keyCode == KeyEvent.KEYCODE_ZOOM_OUT) {
return false;
}
}
int keyCode = event.getKeyCode();
if (!mUseVolumeKeys) {
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_CAMERA || keyCode == KeyEvent.KEYCODE_ZOOM_IN || keyCode == KeyEvent.KEYCODE_ZOOM_OUT) {
return false;
}
}
return getWindow().superDispatchKeyEvent(event);
}
return getWindow().superDispatchKeyEvent(event);
}
// TODO: REMOVE LATER, temporary launchers support?
@Override
protected String[] getArguments() {
String gamedir = getIntent().getStringExtra("gamedir");
if (gamedir == null) gamedir = "valve";
nativeSetenv("XASH3D_GAME", gamedir);
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');
}
String gamelibdir = getIntent().getStringExtra("gamelibdir");
if (gamelibdir != null) nativeSetenv("XASH3D_GAMELIBDIR", gamelibdir);
// 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;
String pakfile = getIntent().getStringExtra("pakfile");
if (pakfile != null) nativeSetenv("XASH3D_EXTRAS_PAK2", pakfile);
// write Android version, fingerprint, supported abis, etc
CrashReports.writeSystemInfo(this);
mUseVolumeKeys = getIntent().getBooleanExtra("usevolume", false);
mPackageName = getIntent().getStringExtra("package");
// 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());
}
String[] env = getIntent().getStringArrayExtra("env");
if (env != null) {
for (int i = 0; i < env.length; i += 2)
nativeSetenv(env[i], env[i + 1]);
}
// TODO: REMOVE LATER, temporary launchers support?
@Override
protected String[] getArguments() {
File crashDir = new File(getFilesDir(), "crashes");
crashDir.mkdirs();
nativeSetenv("XASH3D_CRASH_DIR", crashDir.getAbsolutePath());
String argv = getIntent().getStringExtra("argv");
if (argv == null) argv = "-console -log";
return argv.split(" ");
}
recordLaunchInfo();
String gamedir = getIntent().getStringExtra("gamedir");
if (gamedir == null) gamedir = "valve";
nativeSetenv("XASH3D_GAME", gamedir);
String gamelibdir = getIntent().getStringExtra("gamelibdir");
if (gamelibdir != null) nativeSetenv("XASH3D_GAMELIBDIR", gamelibdir);
String 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);
String basedir = getIntent().getStringExtra("basedir");
if (basedir != null) {
nativeSetenv("XASH3D_BASEDIR", basedir);
} else {
String rootPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/xash";
nativeSetenv("XASH3D_BASEDIR", rootPath);
}
mUseVolumeKeys = getIntent().getBooleanExtra("usevolume", false);
mPackageName = getIntent().getStringExtra("package");
String[] env = getIntent().getStringArrayExtra("env");
if (env != null) {
for (int i = 0; i < env.length; i += 2)
nativeSetenv(env[i], env[i + 1]);
}
String argv = getIntent().getStringExtra("argv");
if (argv == null) argv = "-console -log";
return argv.split(" ");
}
}

View File

@@ -1,253 +0,0 @@
package su.xash.engine;
import android.annotation.TargetApi;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.graphics.Point;
import android.os.Build;
import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor;
import android.provider.DocumentsContract.Document;
import android.provider.DocumentsContract.Root;
import android.provider.DocumentsProvider;
import android.webkit.MimeTypeMap;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
@TargetApi(Build.VERSION_CODES.KITKAT)
public class XashDocumentsProvider extends DocumentsProvider {
private static final String ALL_MIME_TYPES = "*/*";
private File mRootDir;
private static final String TAG = "XashDocumentsProvider";
@Override
public boolean onCreate() {
mRootDir = getContext().getExternalFilesDir(null);
return true;
}
private static final String[] DEFAULT_ROOT_PROJECTION = new String[]{Root.COLUMN_ROOT_ID,
Root.COLUMN_MIME_TYPES,
Root.COLUMN_FLAGS,
Root.COLUMN_ICON,
Root.COLUMN_TITLE,
Root.COLUMN_SUMMARY,
Root.COLUMN_DOCUMENT_ID,
Root.COLUMN_AVAILABLE_BYTES};
private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[]{Document.COLUMN_DOCUMENT_ID,
Document.COLUMN_MIME_TYPE,
Document.COLUMN_DISPLAY_NAME,
Document.COLUMN_LAST_MODIFIED,
Document.COLUMN_FLAGS,
Document.COLUMN_SIZE};
@Override
public Cursor queryRoots(String[] projection) {
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_ROOT_PROJECTION);
final String appName = getContext().getString(R.string.app_name);
final String docId = getDocIdForFile(mRootDir);
int flags;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_SEARCH;
} else {
flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_SEARCH | Root.FLAG_SUPPORTS_IS_CHILD;
}
final MatrixCursor.RowBuilder row = result.newRow();
row.add(Root.COLUMN_ROOT_ID, docId);
row.add(Root.COLUMN_DOCUMENT_ID, docId);
row.add(Root.COLUMN_SUMMARY, null);
row.add(Root.COLUMN_FLAGS, flags);
row.add(Root.COLUMN_TITLE, appName);
row.add(Root.COLUMN_MIME_TYPES, ALL_MIME_TYPES);
row.add(Root.COLUMN_AVAILABLE_BYTES, mRootDir.getFreeSpace());
row.add(Root.COLUMN_ICON, R.mipmap.ic_launcher);
return result;
}
@Override
public Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException {
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
includeFile(result, documentId, null);
return result;
}
@Override
public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder) throws FileNotFoundException {
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
final File parent = getFileForDocId(parentDocumentId);
final File[] filesList = parent.listFiles();
if (filesList != null) {
for (File file : filesList) {
includeFile(result, null, file);
}
}
return result;
}
@Override
public ParcelFileDescriptor openDocument(final String documentId, String mode, CancellationSignal signal) throws FileNotFoundException {
final File file = getFileForDocId(documentId);
final int accessMode = ParcelFileDescriptor.parseMode(mode);
return ParcelFileDescriptor.open(file, accessMode);
}
@Override
public AssetFileDescriptor openDocumentThumbnail(String documentId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException {
final File file = getFileForDocId(documentId);
final ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
return new AssetFileDescriptor(pfd, 0, file.length());
}
@Override
public String createDocument(String parentDocumentId, String mimeType, String displayName) throws FileNotFoundException {
File newFile = new File(parentDocumentId, displayName);
int noConflictId = 1;
while (newFile.exists()) {
newFile = new File(parentDocumentId, displayName + " (" + noConflictId++ + ")");
}
try {
boolean succeeded;
if (Document.MIME_TYPE_DIR.equals(mimeType)) {
succeeded = newFile.mkdir();
} else {
succeeded = newFile.createNewFile();
}
if (!succeeded) {
throw new FileNotFoundException("Failed to create document with id " + newFile.getPath());
}
} catch (IOException e) {
throw new FileNotFoundException("Failed to create document with id " + newFile.getPath());
}
return newFile.getPath();
}
@Override
public void deleteDocument(String documentId) throws FileNotFoundException {
File file = getFileForDocId(documentId);
if (file.isDirectory()) {
if (!deleteDirectory(file)) {
throw new FileNotFoundException("Failed to delete document with id " + documentId);
}
} else if (!file.delete()) {
throw new FileNotFoundException("Failed to delete document with id " + documentId);
}
}
@Override
public String getDocumentType(String documentId) throws FileNotFoundException {
File file = getFileForDocId(documentId);
return getMimeType(file);
}
@Override
public boolean isChildDocument(String parentDocumentId, String documentId) {
return documentId.startsWith(parentDocumentId);
}
private static File getFileForDocId(String docId) throws FileNotFoundException {
final File f = new File(docId);
if (!f.exists()) throw new FileNotFoundException(f.getAbsolutePath() + " not found");
return f;
}
private static String getDocIdForFile(File file) {
return file.getAbsolutePath();
}
private static String getMimeType(File file) {
if (file.isDirectory()) {
return Document.MIME_TYPE_DIR;
} else {
final String name = file.getName();
final int lastDot = name.lastIndexOf('.');
if (lastDot >= 0) {
final String extension = name.substring(lastDot + 1).toLowerCase();
final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
if (mime != null) return mime;
}
return "application/octet-stream";
}
}
private static boolean deleteDirectory(File dir) {
final File[] allContents = dir.listFiles();
if (allContents != null) {
for (File file : allContents) {
deleteDirectory(file);
}
}
return dir.delete();
}
private void includeFile(MatrixCursor result, String docId, File file) throws FileNotFoundException {
if (docId == null) {
docId = getDocIdForFile(file);
} else {
file = getFileForDocId(docId);
}
int flags = 0;
if (file.isDirectory()) {
if (file.canWrite()) {
flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
}
} else if (file.canWrite()) {
flags |= Document.FLAG_SUPPORTS_WRITE;
}
File parentFile = file.getParentFile();
if (parentFile != null && parentFile.canWrite()) {
flags |= Document.FLAG_SUPPORTS_DELETE;
}
final String displayName = file.getName();
final String mimeType = getMimeType(file);
if (mimeType.startsWith("image/")) {
flags |= Document.FLAG_SUPPORTS_THUMBNAIL;
}
final MatrixCursor.RowBuilder row = result.newRow();
row.add(Document.COLUMN_DOCUMENT_ID, docId);
row.add(Document.COLUMN_DISPLAY_NAME, displayName);
row.add(Document.COLUMN_SIZE, file.length());
row.add(Document.COLUMN_MIME_TYPE, mimeType);
row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified());
row.add(Document.COLUMN_FLAGS, flags);
row.add(Document.COLUMN_ICON, R.mipmap.ic_launcher);
}
}

View File

@@ -14,64 +14,57 @@ import su.xash.engine.ui.library.LibraryViewModel
class GameAdapter(private val libraryViewModel: LibraryViewModel) :
ListAdapter<Game, GameAdapter.GameViewHolder>(DiffCallback()) {
ListAdapter<Game, GameAdapter.GameViewHolder>(DiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GameAdapter.GameViewHolder {
val binding = CardGameBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return GameViewHolder(binding)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GameAdapter.GameViewHolder {
val binding = CardGameBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return GameViewHolder(binding)
}
override fun onBindViewHolder(holder: GameAdapter.GameViewHolder, position: Int) {
return holder.bind(getItem(position))
}
override fun onBindViewHolder(holder: GameAdapter.GameViewHolder, position: Int) {
return holder.bind(getItem(position))
}
private class DiffCallback : DiffUtil.ItemCallback<Game>() {
override fun areItemsTheSame(oldItem: Game, newItem: Game): Boolean {
return oldItem.basedir.name == newItem.basedir.name
}
private class DiffCallback : DiffUtil.ItemCallback<Game>() {
override fun areItemsTheSame(oldItem: Game, newItem: Game): Boolean {
return oldItem.basedir.name == newItem.basedir.name
}
override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean {
return oldItem.basedir.name == newItem.basedir.name && oldItem.installed == newItem.installed
}
override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean {
return oldItem.basedir.name == newItem.basedir.name
}
}
}
inner class GameViewHolder(val binding: CardGameBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(game: Game) {
binding.apply {
gameTitle.text = game.title
inner class GameViewHolder(val binding: CardGameBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(game: Game) {
binding.apply {
gameTitle.text = game.title
if (game.icon != null) {
gameIcon.setImageBitmap(game.icon)
} else {
gameIcon.visibility = View.GONE
}
if (game.icon != null) {
gameIcon.setImageBitmap(game.icon)
} else {
gameIcon.visibility = View.GONE
}
if (game.cover != null) {
gameCover.setImageBitmap(game.cover)
} else {
gameCover.visibility = View.GONE
}
if (game.cover != null) {
gameCover.setImageBitmap(game.cover)
} else {
gameCover.visibility = View.GONE
}
if (!game.installed) {
launchButton.visibility = View.GONE
settingsButton.visibility = View.GONE
progressIndicator.visibility = View.VISIBLE
return
}
settingsButton.setOnClickListener {
libraryViewModel.setSelectedGame(game)
it.findNavController()
.navigate(R.id.action_libraryFragment_to_gameSettingsFragment)
}
settingsButton.setOnClickListener {
libraryViewModel.setSelectedGame(game)
it.findNavController()
.navigate(R.id.action_libraryFragment_to_gameSettingsFragment)
}
root.setOnClickListener { libraryViewModel.startEngine(it.context, game) }
launchButton.setOnClickListener {
libraryViewModel.startEngine(it.context, game)
}
}
}
}
}
root.setOnClickListener { libraryViewModel.startEngine(it.context, game) }
launchButton.setOnClickListener {
libraryViewModel.startEngine(it.context, game)
}
}
}
}
}

View 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+)""")
}
}

View File

@@ -1,89 +1,90 @@
package su.xash.engine.model
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import androidx.documentfile.provider.DocumentFile
import su.xash.engine.util.TGAReader
import java.io.File
import java.io.FileInputStream
import java.util.Scanner
object BackgroundBitmap {
private const val BACKGROUND_ROWS = 3
private const val BACKGROUND_COLUMNS = 4
private const val BACKGROUND_WIDTH = 800
private const val BACKGROUND_HEIGHT = 600
private const val BACKGROUND_ROWS = 3
private const val BACKGROUND_COLUMNS = 4
private const val BACKGROUND_WIDTH = 800
private const val BACKGROUND_HEIGHT = 600
fun createBackground(ctx: Context, file: DocumentFile): Bitmap {
var bitmap =
Bitmap.createBitmap(BACKGROUND_WIDTH, BACKGROUND_HEIGHT, Bitmap.Config.ARGB_8888)
var canvas = Canvas(bitmap)
var x: Int
var y = 0
var width: Int
var height = 0
fun createBackground(file: File): Bitmap {
var bitmap =
Bitmap.createBitmap(BACKGROUND_WIDTH, BACKGROUND_HEIGHT, Bitmap.Config.ARGB_8888)
var canvas = Canvas(bitmap)
var x: Int
var y = 0
var width: Int
var height = 0
var bgLayout = file.findFile("resource")?.findFile("HD_BackgroundLayout.txt")
if (bgLayout == null) {
bgLayout = file.findFile("resource")?.findFile("BackgroundLayout.txt")
}
val resourceFolder = File(file, "resource")
var bgLayout = File(resourceFolder, "HD_BackgroundLayout.txt")
if (!bgLayout.exists()) {
bgLayout = File(resourceFolder, "BackgroundLayout.txt")
}
if (bgLayout == null) {
val dir = file.findFile("resource")?.findFile("background")
for (i in 0 until BACKGROUND_ROWS) {
x = 0
for (j in 0 until BACKGROUND_COLUMNS) {
val filename = "${BACKGROUND_WIDTH}_${i + 1}_${'a' + j}_loading.tga"
val bmpFile = dir?.findFile(filename)
val bmpImage = loadTga(ctx, bmpFile!!)
if (!bgLayout.exists()) {
val dir = File(resourceFolder, "background")
for (i in 0 until BACKGROUND_ROWS) {
x = 0
for (j in 0 until BACKGROUND_COLUMNS) {
val filename = "${BACKGROUND_WIDTH}_${i + 1}_${'a' + j}_loading.tga"
val bmpFile = File(dir, filename)
val bmpImage = loadTga(bmpFile)
canvas.drawBitmap(bmpImage, x.toFloat(), y.toFloat(), null)
x += bmpImage.width
height = bmpImage.height
canvas.drawBitmap(bmpImage, x.toFloat(), y.toFloat(), null)
x += bmpImage.width
height = bmpImage.height
}
y += height
}
return bitmap
}
}
y += height
}
return bitmap
}
ctx.contentResolver.openInputStream(bgLayout.uri).use { inputStream ->
Scanner(inputStream).use { scanner ->
while (scanner.hasNext()) {
when (val str = scanner.next()) {
"resolution" -> {
width = scanner.nextInt()
height = scanner.nextInt()
bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
canvas = Canvas(bitmap)
}
FileInputStream(bgLayout).use { inputStream ->
Scanner(inputStream).use { scanner ->
while (scanner.hasNext()) {
when (val str = scanner.next()) {
"resolution" -> {
width = scanner.nextInt()
height = scanner.nextInt()
bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
canvas = Canvas(bitmap)
}
else -> {
var bmpFile = file
str.split("/").forEach { bmpFile = bmpFile.findFile(it)!! }
//skip
scanner.next()
x = scanner.nextInt()
y = scanner.nextInt()
val bmp = loadTga(ctx, bmpFile)
canvas.drawBitmap(bmp, x.toFloat(), y.toFloat(), null)
}
}
}
}
}
return bitmap
}
else -> {
var bmpFile = file
str.split("/").forEach { bmpFile = File(bmpFile, it) }
//skip
scanner.next()
x = scanner.nextInt()
y = scanner.nextInt()
val bmp = loadTga(bmpFile)
canvas.drawBitmap(bmp, x.toFloat(), y.toFloat(), null)
}
}
}
}
}
return bitmap
}
private fun loadTga(ctx: Context, file: DocumentFile): Bitmap {
ctx.contentResolver.openInputStream(file.uri).use {
val buffer = it?.readBytes()
val pixels = TGAReader.read(buffer, TGAReader.ARGB)
private fun loadTga(file: File): Bitmap {
FileInputStream(file).use {
val buffer = it.readBytes()
val pixels = TGAReader.read(buffer, TGAReader.ARGB)
val width = TGAReader.getWidth(buffer)
val height = TGAReader.getHeight(buffer)
val width = TGAReader.getWidth(buffer)
val height = TGAReader.getHeight(buffer)
return Bitmap.createBitmap(pixels, 0, width, width, height, Bitmap.Config.ARGB_8888)
}
}
}
return Bitmap.createBitmap(pixels, 0, width, width, height, Bitmap.Config.ARGB_8888)
}
}
}

View File

@@ -5,129 +5,253 @@ import android.content.Intent
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.net.Uri
import android.provider.MediaStore
import androidx.documentfile.provider.DocumentFile
import android.graphics.BitmapFactory
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, val gameInfoFile: File) {
private var iconName = "game.ico"
var title = "Unknown Game"
var icon: Bitmap? = null
var cover: Bitmap? = null
class Game(val ctx: Context, val basedir: DocumentFile, var installed: Boolean = true) {
private var iconName = "game.ico"
var title = "Unknown Game"
var icon: Bitmap? = null
var cover: Bitmap? = null
val mobileHacksGames = arrayOf("aom", "bdlands", "biglolly", "bshift", "caseclosed",
"hl_urbicide", "induction", "redempt", "secret",
"sewer_beta", "tot", "valve", "vendetta")
private val pref = ctx.getSharedPreferences(basedir.name, Context.MODE_PRIVATE)
// 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"
init {
basedir.findFile("gameinfo.txt")?.let {
parseGameInfo(it)
} ?: basedir.findFile("liblist.gam")?.let { parseGameInfo(it) }
private val pref = ctx.getSharedPreferences(basedir.name, Context.MODE_PRIVATE)
basedir.findFile(iconName)
?.let { icon = MediaStore.Images.Media.getBitmap(ctx.contentResolver, it.uri) }
init {
parseGameInfo(gameInfoFile)
try {
cover = BackgroundBitmap.createBackground(ctx, basedir)
} catch (e: Exception) {
e.printStackTrace()
}
}
val iconFile = File(basedir, iconName)
if (iconFile.exists()) {
icon = BitmapFactory.decodeFile(iconFile.path)
}
fun startEngine(ctx: Context) {
ctx.startActivity(Intent(ctx, XashActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
putExtra("gamedir", basedir.name)
putExtra("argv", pref.getString("arguments", "-console -log"))
putExtra("usevolume", pref.getBoolean("use_volume_buttons", false))
//.putExtra("gamelibdir", getGameLibDir(context))
//.putExtra("package", getPackageName()) }
})
}
try {
cover = BackgroundBitmap.createBackground(basedir)
} catch (e: Exception) {
e.printStackTrace()
}
}
private fun parseGameInfo(file: DocumentFile) {
ctx.contentResolver.openInputStream(file.uri).use { inputStream ->
inputStream?.bufferedReader().use { reader ->
reader?.forEachLine {
val tokens = it.split("\\s+".toRegex(), limit = 2)
if (tokens.size >= 2) {
val k = tokens[0]
val v = tokens[1].trim('"')
fun startEngine(ctx: Context) {
val packageNames = getPackageNamesForGameDir(basedir.name)
var externalGame = false
var commandLineArgs = ""
if (k == "title" || k == "game") title = v
if (k == "icon") iconName = v
}
}
}
}
}
if (basedir.name != defaultGameDir)
commandLineArgs += "-game ${basedir.name} "
private fun getPackageName(): String? {
// return if (mDbEntry != null) {
// mDbEntry.getPackageName()
// } else null
return null
}
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
}
}
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
}
commandLineArgs += pref.getString("arguments", "-console -log") ?: ""
companion object {
fun getGames(ctx: Context, file: DocumentFile): List<Game> {
val games = mutableListOf<Game>()
if (externalGame && packageNames != null) {
var packageName: String? = null
var gameLibDir: String? = null
if (checkIfGamedir(file)) {
games.add(Game(ctx, file))
} else {
file.listFiles().forEach {
if (it.isDirectory) {
if (checkIfGamedir(it)) {
games.add(Game(ctx, it))
}
}
}
}
for (pn in packageNames) {
gameLibDir = try {
getGameLibDir(ctx, pn)
} catch (e: PackageManager.NameNotFoundException) {
null
} catch (e: Exception) {
e.printStackTrace()
null
}
return games
}
if (gameLibDir != null) {
packageName = pn
break
}
}
fun checkIfGamedir(file: DocumentFile): Boolean {
// exclude unfinished downloads
if (file.name?.startsWith('.') == true)
return false
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
}
file.findFile("liblist.gam")?.let { return true }
file.findFile("gameinfo.txt")?.let { return true }
return false
}
}
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", defaultGameDir)
putExtra("argv", commandLineArgs)
putExtra("usevolume", pref.getBoolean("use_volume_buttons", false))
putExtra("basedir", basedir.parent)
if (gameLibDir != null) putExtra("gamelibdir", gameLibDir)
if (packageName != null) putExtra("package", packageName)
})
}
private fun parseGameInfo(file: File) {
FileInputStream(file).use { inputStream ->
inputStream.bufferedReader().use { reader ->
reader.forEachLine {
val tokens = it.split("\\s+".toRegex(), limit = 2)
if (tokens.size >= 2) {
val k = tokens[0]
val v = tokens[1].trim('"')
if (k == "title" || k == "game") title = v
if (k == "icon") iconName = v
}
}
}
}
}
private fun 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 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, root: File): List<Game> {
val games = mutableListOf<Game>()
root.listFiles()?.forEach {
if (it.isDirectory) {
val subDirGameInfoFile = checkIfGamedir(it)
if (subDirGameInfoFile != null) {
games.add(Game(ctx, it, subDirGameInfoFile))
}
}
}
return games
}
fun checkIfGamedir(gamedir: File): File? {
gamedir.listFiles()?.forEach {
if (it.isFile) {
if (it.name.equals("liblist.gam", ignoreCase = true))
return it
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()));
//}

View File

@@ -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
}
}

View File

@@ -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"
}
}

View File

@@ -1,59 +0,0 @@
package su.xash.engine.model
import org.json.JSONArray
import org.json.JSONObject
import java.io.InputStream
class ModDatabase(inputStream: InputStream) {
val entries = mutableListOf<Entry>()
companion object {
const val VERSION = 1
fun getFilename(): String {
return "v${VERSION}.json"
}
}
init {
inputStream.bufferedReader().use {
val jsonArray = JSONArray(it.readText())
for (i in 0..<jsonArray.length()) {
entries.add(Entry(jsonArray.getJSONObject(i)))
}
}
}
fun getByGameDir(gamedir: String): Entry? {
return entries.filter { it.gamedir.equals(gamedir) }.firstOrNull()
}
inner class Entry(jsonObject: JSONObject) {
var name: String? = null
var appid: Int? = null
var gamedir: String? = null
// TODO Depots
var pkgname: String? = null
init {
if (jsonObject.has("name")) {
name = jsonObject.getString("name");
}
if (jsonObject.has("app_id")) {
appid = jsonObject.getInt("app_id");
}
if (jsonObject.has("gamedir")) {
gamedir = jsonObject.getString("gamedir");
}
if (jsonObject.has("package_name")) {
pkgname = jsonObject.getString("package_name");
}
}
}
}

View File

@@ -1,101 +1,184 @@
package su.xash.engine.ui.library
import android.annotation.SuppressLint
import android.Manifest
import android.content.Intent
import android.content.ActivityNotFoundException
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.os.Environment
import android.provider.Settings
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
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
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController
import kotlinx.coroutines.launch
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import su.xash.engine.BuildConfig
import su.xash.engine.R
import su.xash.engine.adapters.GameAdapter
import su.xash.engine.databinding.FragmentLibraryBinding
class LibraryFragment : Fragment(), MenuProvider {
private var _binding: FragmentLibraryBinding? = null
private val binding get() = _binding!!
private var _binding: FragmentLibraryBinding? = null
private val binding get() = _binding!!
private val libraryViewModel: LibraryViewModel by activityViewModels()
private val libraryViewModel: LibraryViewModel by activityViewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? {
_binding = FragmentLibraryBinding.inflate(inflater, container, false)
private val startActivityForResult =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (checkStoragePermissions()) {
libraryViewModel.reloadGames(requireContext())
}
}
val adapter = GameAdapter(libraryViewModel)
binding.gamesList.adapter = adapter
private val requiredPermissions = arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
requireActivity().addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED)
private val requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
val granted = permissions.entries.all { it.value }
if (granted) {
libraryViewModel.reloadGames(requireContext())
} else {
checkStoragePermissions()
}
}
return binding.root
}
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()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding.swipeRefresh.setOnRefreshListener { libraryViewModel.reloadGames(requireContext()) }
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()
libraryViewModel.isReloading.observe(viewLifecycleOwner) {
binding.swipeRefresh.isRefreshing = it
}
if (!permissionsNeeded.isEmpty()) {
val showRationale = permissionsNeeded.any {
ActivityCompat.shouldShowRequestPermissionRationale(requireActivity(), it)
}
libraryViewModel.installedGames.observe(viewLifecycleOwner) {
(binding.gamesList.adapter as GameAdapter).submitList(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()
}
libraryViewModel.workInfos.observe(viewLifecycleOwner) {
libraryViewModel.refreshDownloads(requireContext())
}
return false
} else {
return true
}
} else {
return true
}
}
libraryViewModel.downloads.observe(viewLifecycleOwner) {
libraryViewModel.reloadGames(requireContext())
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View {
_binding = FragmentLibraryBinding.inflate(inflater, container, false)
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
val adapter = GameAdapter(libraryViewModel)
binding.gamesList.adapter = adapter
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menuInflater.inflate(R.menu.menu_library, menu)
}
requireActivity().addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED)
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
when (menuItem.itemId) {
R.id.action_browse -> {
try {
startActivity(
Intent(Intent.ACTION_VIEW).setDataAndType(
null, "vnd.android.document/directory"
)
)
}
catch(e: ActivityNotFoundException) {
Toast.makeText(getActivity(), R.string.library_fragment_no_file_manager, Toast.LENGTH_LONG).show()
}
}
return binding.root
}
R.id.action_install -> {
findNavController().navigate(R.id.action_libraryFragment_to_setupFragment)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding.swipeRefresh.setOnRefreshListener { libraryViewModel.reloadGames(requireContext()) }
R.id.action_settings -> {
findNavController().navigate(R.id.action_libraryFragment_to_appSettingsFragment)
}
}
libraryViewModel.isReloading.observe(viewLifecycleOwner) {
binding.swipeRefresh.isRefreshing = it
}
return false
}
libraryViewModel.installedGames.observe(viewLifecycleOwner) {
(binding.gamesList.adapter as GameAdapter).submitList(it)
}
if (checkStoragePermissions()) {
libraryViewModel.reloadGames(requireContext())
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menuInflater.inflate(R.menu.menu_library, menu)
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
when (menuItem.itemId) {
R.id.action_settings -> {
findNavController().navigate(R.id.action_libraryFragment_to_appSettingsFragment)
}
}
return false
}
override fun onResume() {
super.onResume()
if (checkStoragePermissions()) {
libraryViewModel.reloadGames(requireContext())
}
}
}

View File

@@ -3,125 +3,56 @@ package su.xash.engine.ui.library
import android.app.Application
import android.content.Context
import android.content.SharedPreferences
import android.net.Uri
import androidx.documentfile.provider.DocumentFile
import android.os.Environment
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.viewModelScope
import androidx.preference.PreferenceManager
import androidx.work.Data
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.OutOfQuotaPolicy
import androidx.work.WorkInfo
import androidx.work.WorkManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import su.xash.engine.model.Game
import su.xash.engine.model.ModDatabase
import su.xash.engine.workers.FileCopyWorker
import su.xash.engine.workers.KEY_FILE_URI
import java.util.Locale
const val TAG_INSTALL = "TAG_INSTALL"
import su.xash.engine.util.Nomedia
import java.io.File
class LibraryViewModel(application: Application) : AndroidViewModel(application) {
val installedGames: LiveData<List<Game>> get() = _installedGames
private val _installedGames = MutableLiveData(emptyList<Game>())
val installedGames: LiveData<List<Game>> get() = _installedGames
private val _installedGames = MutableLiveData(emptyList<Game>())
val downloads: LiveData<List<Game>> get() = _downloads
private val _downloads = MutableLiveData(emptyList<Game>())
val isReloading: LiveData<Boolean> get() = _isReloading
private val _isReloading = MutableLiveData(false)
val isReloading: LiveData<Boolean> get() = _isReloading
private val _isReloading = MutableLiveData(false)
val selectedItem: LiveData<Game> get() = _selectedItem
private val _selectedItem = MutableLiveData<Game>()
private val workManager = WorkManager.getInstance(application.applicationContext)
val workInfos: LiveData<List<WorkInfo>> = workManager.getWorkInfosByTagLiveData(TAG_INSTALL)
private val appPreferences: SharedPreferences =
application.getSharedPreferences("app_preferences", Context.MODE_PRIVATE)
val selectedItem: LiveData<Game> get() = _selectedItem
private val _selectedItem = MutableLiveData<Game>()
fun reloadGames(ctx: Context) {
if (isReloading.value == true) {
return
}
_isReloading.value = true
val appPreferences = application.getSharedPreferences("app_preferences", Context.MODE_PRIVATE)
val modDb: ModDatabase
viewModelScope.launch {
withContext(Dispatchers.IO) {
val rootPath = appPreferences.getString("game_path", null)
?: (Environment.getExternalStorageDirectory().absolutePath + "/xash")
val root = File(rootPath)
init {
modDb = application.assets.open(ModDatabase.getFilename()).use { ModDatabase(it) }
reloadGames(application.applicationContext)
}
Nomedia.ensureNomedia(root)
fun reloadGames(ctx: Context) {
if (isReloading.value == true) {
return
}
_isReloading.value = true
_installedGames.postValue(Game.getGames(ctx, root))
_isReloading.postValue(false)
}
}
}
viewModelScope.launch {
withContext(Dispatchers.IO) {
val games = mutableListOf<Game>()
val root = DocumentFile.fromFile(ctx.getExternalFilesDir(null)!!)
fun setSelectedGame(game: Game) {
_selectedItem.value = game
}
val installedGames = Game.getGames(ctx, root)
.filter { p -> _downloads.value?.any { p.basedir.name == it.basedir.name } == false }
games.addAll(installedGames)
downloads.value?.let { games.addAll(it) }
_installedGames.postValue(games)
_isReloading.postValue(false)
}
}
}
fun refreshDownloads(ctx: Context) {
viewModelScope.launch {
withContext(Dispatchers.IO) {
val games = mutableListOf<Game>()
workInfos.value?.filter {
it.state == WorkInfo.State.RUNNING && !it.progress.getString(FileCopyWorker.Input)
.isNullOrEmpty()
}?.forEach {
val uri = Uri.parse(it.progress.getString(FileCopyWorker.Input))
val file = DocumentFile.fromTreeUri(ctx, uri)
games.addAll(Game.getGames(ctx, file!!))
games.forEach { g -> g.installed = false }
}
_downloads.postValue(games)
}
}
}
fun installGame(uri: Uri) {
val data = Data.Builder().putString(KEY_FILE_URI, uri.toString()).build()
val request = OneTimeWorkRequestBuilder<FileCopyWorker>().run {
setInputData(data)
addTag(TAG_INSTALL)
build()
}
workManager.enqueue(request)
}
fun setSelectedGame(game: Game) {
_selectedItem.value = game
}
fun uninstallGame(game: Game) {
viewModelScope.launch {
withContext(Dispatchers.IO) {
game.installed = false
game.basedir.delete()
_installedGames.postValue(_installedGames.value)
}
}
}
fun startEngine(ctx: Context, game: Game) {
game.startEngine(ctx)
}
}
fun startEngine(ctx: Context, game: Game) {
game.startEngine(ctx)
}
}

View File

@@ -8,24 +8,24 @@ import androidx.fragment.app.Fragment
import su.xash.engine.databinding.FragmentAppSettingsBinding
class AppSettingsFragment : Fragment() {
private var _binding: FragmentAppSettingsBinding? = null
private val binding get() = _binding!!
private var _binding: FragmentAppSettingsBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? {
_binding = FragmentAppSettingsBinding.inflate(inflater, container, false)
return binding.root
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View {
_binding = FragmentAppSettingsBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
childFragmentManager.beginTransaction()
.add(binding.settingsFragment.id, AppSettingsPreferenceFragment()).commit();
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
childFragmentManager.beginTransaction()
.add(binding.settingsFragment.id, AppSettingsPreferenceFragment()).commit();
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}

View File

@@ -1,12 +1,19 @@
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
class AppSettingsPreferenceFragment() : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
preferenceManager.sharedPreferencesName = "app_preferences";
setPreferencesFromResource(R.xml.app_preferences, rootKey);
}
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
}
}
}

View File

@@ -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()
}
}

View File

@@ -6,59 +6,52 @@ import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import su.xash.engine.R
import su.xash.engine.databinding.FragmentGameSettingsBinding
import su.xash.engine.ui.library.LibraryViewModel
class GameSettingsFragment : Fragment() {
private var _binding: FragmentGameSettingsBinding? = null
private val binding get() = _binding!!
private var _binding: FragmentGameSettingsBinding? = null
private val binding get() = _binding!!
private val libraryViewModel: LibraryViewModel by activityViewModels()
private val libraryViewModel: LibraryViewModel by activityViewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? {
_binding = FragmentGameSettingsBinding.inflate(inflater, container, false)
return binding.root
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View {
_binding = FragmentGameSettingsBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val game = libraryViewModel.selectedItem.value!!
val game = libraryViewModel.selectedItem.value!!
binding.gameCard.apply {
gameTitle.text = game.title
binding.gameCard.apply {
gameTitle.text = game.title
if (game.icon != null) {
gameIcon.setImageBitmap(game.icon)
} else {
gameIcon.visibility = View.GONE
}
if (game.icon != null) {
gameIcon.setImageBitmap(game.icon)
} else {
gameIcon.visibility = View.GONE
}
if (game.cover != null) {
gameCover.setImageBitmap(game.cover)
} else {
gameCover.visibility = View.GONE
}
if (game.cover != null) {
gameCover.setImageBitmap(game.cover)
} else {
gameCover.visibility = View.GONE
}
buttonsContainer.visibility = View.GONE
}
buttonsContainer.visibility = View.GONE
}
childFragmentManager.beginTransaction()
.add(binding.settingsFragment.id, GameSettingsPreferenceFragment(game))
.commit();
childFragmentManager.beginTransaction()
.add(binding.settingsFragment.id, GameSettingsPreferenceFragment(game))
.commit();
}
binding.bottomNavigation.menu.findItem(R.id.action_uninstall).setOnMenuItemClickListener {
libraryViewModel.uninstallGame(game)
findNavController().popBackStack()
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}

View File

@@ -1,40 +1,93 @@
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?) {
preferenceManager.sharedPreferencesName = game.basedir.name;
setPreferencesFromResource(R.xml.game_preferences, rootKey);
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
preferenceManager.sharedPreferencesName = game.basedir.name;
setPreferencesFromResource(R.xml.game_preferences, rootKey);
val packageList = findPreference<ListPreference>("package_name")!!
packageList.entries = arrayOf(getString(R.string.app_name))
packageList.entryValues = arrayOf(requireContext().packageName)
val packageList = findPreference<ListPreference>("package_name")!!
packageList.entries = arrayOf(getString(R.string.app_name))
packageList.entryValues = arrayOf(requireContext().packageName)
if (packageList.value == null) {
packageList.setValueIndex(0);
}
if (packageList.value == null) {
packageList.setValueIndex(0);
}
val separatePackages = findPreference<SwitchPreferenceCompat>("separate_libraries")!!
val clientPackage = findPreference<ListPreference>("client_package")!!
val serverPackage = findPreference<ListPreference>("server_package")!!
separatePackages.setOnPreferenceChangeListener { _, newValue ->
if (newValue == true) {
packageList.isVisible = false
clientPackage.isVisible = true
serverPackage.isVisible = true
} else {
packageList.isVisible = true
clientPackage.isVisible = false
serverPackage.isVisible = false
}
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
}
true
}
}
populateDownloadedBuildInfo()
val separatePackages = findPreference<SwitchPreferenceCompat>("separate_libraries")!!
val clientPackage = findPreference<ListPreference>("client_package")!!
val serverPackage = findPreference<ListPreference>("server_package")!!
separatePackages.setOnPreferenceChangeListener { _, newValue ->
if (newValue == true) {
packageList.isVisible = false
clientPackage.isVisible = true
serverPackage.isVisible = true
} else {
packageList.isVisible = true
clientPackage.isVisible = false
serverPackage.isVisible = false
}
true
}
}
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
""
}
}

View File

@@ -1,48 +0,0 @@
package su.xash.engine.ui.setup
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.viewpager2.adapter.FragmentStateAdapter
import su.xash.engine.databinding.FragmentSetupBinding
import su.xash.engine.ui.setup.pages.LocationPageFragment
import su.xash.engine.ui.setup.pages.WelcomePageFragment
class SetupFragment : Fragment() {
private var _binding: FragmentSetupBinding? = null
private val binding get() = _binding!!
private val setupViewModel: SetupViewModel by activityViewModels()
private lateinit var setupPageAdapter: SetupPageAdapter
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? {
_binding = FragmentSetupBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
setupPageAdapter = SetupPageAdapter(this)
binding.viewPager.isUserInputEnabled = false
binding.viewPager.adapter = setupPageAdapter
setupViewModel.pageNumber.observe(viewLifecycleOwner) {
binding.viewPager.setCurrentItem(it, true)
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
class SetupPageAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) {
val pages = listOf(WelcomePageFragment(), LocationPageFragment())
override fun getItemCount(): Int = 2
override fun createFragment(position: Int): Fragment = pages[position]
}

View File

@@ -1,25 +0,0 @@
package su.xash.engine.ui.setup
import android.app.Application
import android.net.Uri
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import su.xash.engine.MainApplication
import su.xash.engine.model.Game
class SetupViewModel(application: Application) : AndroidViewModel(application) {
val pageNumber: LiveData<Int> get() = _pageNumber
private val _pageNumber = MutableLiveData(0)
fun checkIfGameDir(uri: Uri): Boolean {
val ctx = getApplication<MainApplication>().applicationContext
val file = DocumentFile.fromTreeUri(ctx, uri)!!
return Game.checkIfGamedir(file)
}
fun setPageNumber(pos: Int) {
_pageNumber.value = pos
}
}

View File

@@ -1,59 +0,0 @@
package su.xash.engine.ui.setup.pages
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import su.xash.engine.R
import su.xash.engine.databinding.PageLocationBinding
import su.xash.engine.ui.library.LibraryViewModel
import su.xash.engine.ui.setup.SetupViewModel
class LocationPageFragment : Fragment() {
private var _binding: PageLocationBinding? = null
private val binding get() = _binding!!
private val setupViewModel: SetupViewModel by activityViewModels()
private val libraryViewModel: LibraryViewModel by activityViewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? {
_binding = PageLocationBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding.pageButton.setOnClickListener {
getGamesDirectory.launch(null)
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
private val getGamesDirectory =
registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) {
it?.let {
if (!setupViewModel.checkIfGameDir(it)) {
MaterialAlertDialogBuilder(requireContext()).apply {
setTitle(R.string.error)
setMessage(R.string.setup_location_empty)
setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() }
show()
}
} else {
requireContext().contentResolver.takePersistableUriPermission(it, Intent.FLAG_GRANT_READ_URI_PERMISSION)
libraryViewModel.installGame(it)
findNavController().navigate(R.id.action_setupFragment_to_libraryFragment)
}
}
}
}

View File

@@ -1,35 +0,0 @@
package su.xash.engine.ui.setup.pages
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import su.xash.engine.databinding.PageWelcomeBinding
import su.xash.engine.ui.setup.SetupViewModel
class WelcomePageFragment : Fragment() {
private var _binding: PageWelcomeBinding? = null
private val binding get() = _binding!!
private val setupViewModel: SetupViewModel by activityViewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? {
_binding = PageWelcomeBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding.pageButton.setOnClickListener {
setupViewModel.setPageNumber(1)
}
setupViewModel.setPageNumber(0)
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}

View File

@@ -0,0 +1,49 @@
package su.xash.engine.util;
import android.app.Activity;
import android.graphics.Rect;
import android.view.View;
import android.widget.FrameLayout;
public class AndroidBug5497Workaround {
// For more information, see https://code.google.com/p/android/issues/detail?id=5497
// To use this class, simply invoke assistActivity() on an Activity that already has its content view set.
public static void assistActivity(Activity activity) {
new AndroidBug5497Workaround(activity);
}
private View mChildOfContent;
private int usableHeightPrevious;
private FrameLayout.LayoutParams frameLayoutParams;
private AndroidBug5497Workaround(Activity activity) {
FrameLayout content = activity.findViewById(android.R.id.content);
mChildOfContent = content.getChildAt(0);
mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(this::possiblyResizeChildOfContent);
frameLayoutParams = (FrameLayout.LayoutParams) mChildOfContent.getLayoutParams();
}
private void possiblyResizeChildOfContent() {
int usableHeightNow = computeUsableHeight();
if (usableHeightNow != usableHeightPrevious) {
int usableHeightSansKeyboard = mChildOfContent.getRootView().getHeight();
int heightDifference = usableHeightSansKeyboard - usableHeightNow;
if (heightDifference > (usableHeightSansKeyboard / 4)) {
// keyboard probably just became visible
frameLayoutParams.height = usableHeightSansKeyboard - heightDifference;
} else {
// keyboard probably just became hidden
frameLayoutParams.height = usableHeightSansKeyboard;
}
mChildOfContent.requestLayout();
usableHeightPrevious = usableHeightNow;
}
}
private int computeUsableHeight() {
Rect r = new Rect();
mChildOfContent.getWindowVisibleDisplayFrame(r);
return (r.bottom - r.top);
}
}

View File

@@ -0,0 +1,172 @@
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"
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)
fun attachments(): List<File> = listOf(stacktrace, sysinfo, intent).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 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()
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)))
}
}

View File

@@ -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() }
}

View File

@@ -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) }
}

View 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
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,97 +0,0 @@
package su.xash.engine.workers
import android.content.Context
import android.net.Uri
import androidx.documentfile.provider.DocumentFile
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
const val KEY_FILE_URI = "KEY_FILE_URI"
class FileCopyWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
companion object {
const val Input = "Input"
const val Progress = "Progress"
}
private var fileCount = 0
private var fileCopied = 0
override suspend fun doWork(): Result {
withContext(Dispatchers.IO) {
val fileUri = inputData.getString(KEY_FILE_URI)
setProgress(workDataOf(Input to fileUri))
val uri = Uri.parse(fileUri)
val source = DocumentFile.fromTreeUri(applicationContext, uri)
fileCount = source?.countDirFiles() ?: return@withContext Result.failure()
setProgress(workDataOf(Progress to 0f))
val gamedir = source.name!!
val externalFilesDir = DocumentFile.fromFile(applicationContext.getExternalFilesDir(null)!!)
// create a directory to store staged files
val target = externalFilesDir.createDirectory(".$gamedir")!!
source.copyDirTo(applicationContext, this@FileCopyWorker, target)
target.renameTo(gamedir)
setProgress(workDataOf(Progress to 1f))
}
return Result.success()
}
suspend fun fileCopied(count: Int) {
if(count == 0)
return
fileCopied += count
val percentage: Float = fileCopied.toFloat() / fileCount.toFloat();
setProgress(workDataOf(Progress to percentage))
}
}
fun DocumentFile.countDirFiles(): Int {
var count: Int = 0
listFiles().forEach {
if (it.isDirectory)
count += it.countDirFiles()
else
count++
}
return count
}
fun DocumentFile.copyFileTo(ctx: Context, file: DocumentFile) {
val outFile = file.createFile("application", name!!)!!
ctx.contentResolver.openOutputStream(outFile.uri).use { os ->
ctx.contentResolver.openInputStream(uri).use {
it?.copyTo(os!!)
}
}
}
suspend fun DocumentFile.copyDirTo(ctx: Context, worker: FileCopyWorker, dir: DocumentFile) {
var count: Int = 0
listFiles().forEach {
if (it.isDirectory) {
val outDir = dir.createDirectory(it.name!!)!!
it.copyDirTo(ctx, worker, outDir)
} else {
it.copyFileTo(ctx, dir)
count++
}
}
worker.fileCopied(count)
}

View File

@@ -1,5 +0,0 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M21,10h-8.35C11.83,7.67 9.61,6 7,6c-3.31,0 -6,2.69 -6,6s2.69,6 6,6c2.61,0 4.83,-1.67 5.65,-4H13l2,2l2,-2l2,2l4,-4.04L21,10zM7,15c-1.65,0 -3,-1.35 -3,-3c0,-1.65 1.35,-3 3,-3s3,1.35 3,3C10,13.65 8.65,15 7,15z"/>
</vector>

View File

@@ -1,5 +0,0 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M12,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zM12,14c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z"/>
</vector>

View File

@@ -1,15 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="true">
<shape android:shape="rectangle">
<gradient android:endColor="#2D73FF" android:startColor="#06BFFF" />
<corners android:radius="4dp" />
</shape>
</item>
<item android:state_enabled="false">
<shape android:shape="rectangle">
<gradient android:endColor="#6D6D6D" android:startColor="#8F8F8F" />
<corners android:radius="4dp" />
</shape>
</item>
</selector>

View File

@@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M12.004,2c-5.25,0 -9.556,4.05 -9.964,9.197l5.36,2.216c0.454,-0.31 1.002,-0.492 1.593,-0.492 0.053,0 0.104,0.003 0.157,0.005l2.384,-3.452v-0.049c0,-2.08 1.69,-3.77 3.77,-3.77 2.079,0 3.77,1.692 3.77,3.772s-1.692,3.771 -3.77,3.771h-0.087l-3.397,2.426c0,0.043 0.003,0.088 0.003,0.133 0,1.562 -1.262,2.83 -2.825,2.83 -1.362,0 -2.513,-0.978 -2.775,-2.273l-3.838,-1.589C3.573,18.922 7.427,22 12.005,22c5.522,0 9.998,-4.477 9.998,-10 0,-5.522 -4.477,-10 -9.999,-10zM7.078,16.667c0.218,0.452 0.595,0.832 1.094,1.041 1.081,0.45 2.328,-0.063 2.777,-1.145 0.22,-0.525 0.22,-1.1 0.004,-1.625 -0.215,-0.525 -0.625,-0.934 -1.147,-1.152 -0.52,-0.217 -1.075,-0.208 -1.565,-0.025l1.269,0.525c0.797,0.333 1.174,1.25 0.84,2.046 -0.33,0.797 -1.247,1.175 -2.044,0.843l-1.228,-0.508zM17.818,9.422c0,-1.385 -1.128,-2.512 -2.513,-2.512 -1.387,0 -2.512,1.127 -2.512,2.512 0,1.388 1.125,2.513 2.512,2.513 1.386,0 2.512,-1.125 2.512,-2.513zM15.31,7.53c1.04,0 1.888,0.845 1.888,1.888s-0.847,1.888 -1.888,1.888c-1.044,0 -1.888,-0.845 -1.888,-1.888s0.845,-1.888 1.888,-1.888z"/>
</vector>

View File

@@ -1,33 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="295.46dp"
android:height="90.47dp"
android:viewportWidth="295.46"
android:viewportHeight="90.47">
<path
android:fillColor="#FF000000"
android:pathData="m45.08,1c-23.24,0 -42.28,17.92 -44.08,40.69l23.71,9.8c2.01,-1.37 4.44,-2.18 7.05,-2.18 0.23,0 0.47,0.01 0.7,0.02l10.54,-15.28c0,-0.07 0,-0.14 0,-0.22 0,-9.2 7.48,-16.68 16.68,-16.68 9.2,0 16.68,7.48 16.68,16.68s-7.48,16.68 -16.68,16.68c-0.13,0 -0.25,0 -0.38,-0.01l-15.04,10.73c0.01,0.19 0.01,0.39 0.01,0.59 0,6.91 -5.62,12.52 -12.52,12.52 -6.06,0 -11.13,-4.33 -12.28,-10.06l-16.96,-7.01c5.25,18.57 22.31,32.18 42.56,32.18 24.43,0 44.24,-19.81 44.24,-44.24 0,-24.43 -19.81,-44.24 -44.24,-44.24"/>
<path
android:fillColor="#FF000000"
android:pathData="m28.72,68.12 l-5.43,-2.24c0.96,2.01 2.63,3.68 4.84,4.61 4.78,1.99 10.3,-0.28 12.29,-5.06 0.96,-2.31 0.97,-4.87 0.01,-7.19 -0.95,-2.32 -2.76,-4.13 -5.07,-5.1 -2.3,-0.96 -4.76,-0.92 -6.93,-0.1l5.61,2.32c3.53,1.47 5.2,5.52 3.72,9.05 -1.47,3.53 -5.52,5.2 -9.05,3.72"/>
<path
android:fillColor="#FF000000"
android:pathData="m70.8,33.83c0,-6.13 -4.99,-11.12 -11.12,-11.12 -6.13,0 -11.12,4.99 -11.12,11.12 0,6.13 4.99,11.11 11.12,11.11 6.13,0 11.12,-4.99 11.12,-11.11m-19.45,-0.02c0,-4.61 3.74,-8.35 8.35,-8.35s8.35,3.74 8.35,8.35 -3.74,8.35 -8.35,8.35 -8.35,-3.74 -8.35,-8.35"/>
<path
android:fillColor="#FF000000"
android:pathData="m136.56,31.27 l-2.96,5.21c-2.28,-1.6 -5.38,-2.56 -8.08,-2.56 -3.09,0 -5,1.28 -5,3.57 0,2.78 3.39,3.43 8.44,5.24 5.42,1.92 8.54,4.17 8.54,9.14 0,6.79 -5.34,10.61 -13.02,10.61 -3.74,0 -8.26,-0.97 -11.73,-3.08l2.16,-5.78c2.82,1.49 6.19,2.37 9.2,2.37 4.05,0 5.98,-1.5 5.98,-3.7 0,-2.53 -2.94,-3.29 -7.68,-4.86 -5.4,-1.8 -9.15,-4.17 -9.15,-9.67 0,-6.2 4.96,-9.76 12.1,-9.76 4.98,0 8.98,1.58 11.2,3.26"/>
<path
android:fillColor="#FF000000"
android:pathData="m152.76,61.9v-27.34h-10.13v-5.99h27.21v5.99h-10.1v27.34z"/>
<path
android:fillColor="#FF000000"
android:pathData="m197.9,42.05v5.99h-13.36v7.82h15.5v6.04h-22.47v-33.33h22.47v5.97h-15.5v7.51z"/>
<path
android:fillColor="#FF000000"
android:pathData="m215.62,55.43 l-2.21,6.47h-7.32l12.49,-33.33h7.03l12.85,33.32h-7.56l-2.25,-6.47h-13.03zM222.07,36.52 L217.51,49.87h9.2z"/>
<path
android:fillColor="#FF000000"
android:pathData="m261.22,60.93 l-8.97,-19.3v20.27h-6.68v-33.33h6.67l11.2,24.06 10.8,-24.06h6.73v33.33h-6.68v-20.44l-9.12,19.47z"/>
<path
android:fillColor="#FF000000"
android:pathData="m294.46,32.78c0,2.86 -2.15,4.65 -4.61,4.65 -2.47,0 -4.62,-1.78 -4.62,-4.65 0,-2.86 2.15,-4.64 4.62,-4.64 2.46,0 4.61,1.77 4.61,4.64m-8.46,0c0,2.4 1.73,3.9 3.85,3.9 2.11,0 3.83,-1.5 3.83,-3.9 0,-2.4 -1.72,-3.88 -3.83,-3.88 -2.12,0 -3.85,1.5 -3.85,3.88m3.91,-2.37c1.2,0 1.6,0.63 1.6,1.32 0,0.63 -0.37,1.05 -0.82,1.26l1.07,2.01h-0.88l-0.9,-1.78h-0.93v1.78h-0.73v-4.58zM289.05,32.54h0.81c0.53,0 0.84,-0.33 0.84,-0.75 0,-0.42 -0.22,-0.69 -0.84,-0.69h-0.81v1.44z"/>
</vector>

View File

@@ -1,35 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools">
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</com.google.android.material.appbar.AppBarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragmentContainerView"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/appBarLayout"
app:navGraph="@navigation/nav_graph"
tools:layout="@layout/fragment_library"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragmentContainerView"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/appBarLayout"
app:navGraph="@navigation/nav_graph"
tools:layout="@layout/fragment_library" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,111 +1,98 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
style="@style/Widget.Material3.CardView.Filled"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp">
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
style="@style/Widget.Material3.CardView.Filled"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="128dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="128dp">
<ImageView
android:id="@+id/gameCover"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
tools:src="@android:mipmap/sym_def_app_icon" />
<ImageView
android:id="@+id/gameCover"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
tools:src="@android:mipmap/sym_def_app_icon" />
<com.google.android.material.card.MaterialCardView
style="@style/Widget.Material3.CardView.Filled"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_margin="8dp"
app:contentPadding="10dp"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/buttonsContainer"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="parent">
<com.google.android.material.card.MaterialCardView
style="@style/Widget.Material3.CardView.Filled"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_margin="8dp"
app:contentPadding="10dp"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/buttonsContainer"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="match_parent">
<ImageView
android:id="@+id/gameIcon"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginRight="8dp"
android:adjustViewBounds="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/gameTitle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@android:mipmap/sym_def_app_icon" />
<ImageView
android:id="@+id/gameIcon"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginRight="8dp"
android:adjustViewBounds="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/gameTitle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@android:mipmap/sym_def_app_icon" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/gameTitle"
style="@style/TextAppearance.Material3.TitleMedium"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:singleLine="true"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/gameIcon"
app:layout_constraintTop_toTopOf="parent"
tools:text="Game Title" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.textview.MaterialTextView
android:id="@+id/gameTitle"
style="@style/TextAppearance.Material3.TitleMedium"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:singleLine="true"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/gameIcon"
app:layout_constraintTop_toTopOf="parent"
tools:text="Game Title" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/buttonsContainer"
style="@style/Widget.Material3.CardView.Filled"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<com.google.android.material.card.MaterialCardView
android:id="@+id/buttonsContainer"
style="@style/Widget.Material3.CardView.Filled"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/progressIndicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="true"
android:padding="10dp"
android:visibility="gone"
app:indicatorSize="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/launchButton"
style="@style/Widget.Material3.Button.IconButton"
android:layout_width="wrap_content"
android:layout_height="match_parent"
app:icon="@drawable/ic_baseline_play_arrow_24"
app:layout_constraintEnd_toStartOf="@+id/settingsButton" />
<com.google.android.material.button.MaterialButton
android:id="@+id/launchButton"
style="@style/Widget.Material3.Button.IconButton"
android:layout_width="wrap_content"
android:layout_height="match_parent"
app:icon="@drawable/ic_baseline_play_arrow_24"
app:layout_constraintEnd_toStartOf="@+id/settingsButton" />
<com.google.android.material.button.MaterialButton
android:id="@+id/settingsButton"
style="@style/Widget.Material3.Button.IconButton"
android:layout_width="wrap_content"
android:layout_height="match_parent"
app:icon="@drawable/ic_baseline_settings_24"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.button.MaterialButton
android:id="@+id/settingsButton"
style="@style/Widget.Material3.Button.IconButton"
android:layout_width="wrap_content"
android:layout_height="match_parent"
app:icon="@drawable/ic_baseline_settings_24"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>

View 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>

View File

@@ -1,30 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
style="@style/Widget.Material3.CardView.Filled"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp"
android:layout_marginTop="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent">
<!-- <com.google.android.material.textfield.TextInputLayout-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content"-->
<!-- app:layout_constraintTop_toTopOf="parent"-->
<!-- app:layout_constraintBottom_toBottomOf="parent">-->
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp">
<!-- <com.google.android.material.textfield.TextInputEditText-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content" />-->
<!-- </com.google.android.material.textfield.TextInputLayout>-->
<TextView
android:id="@android:id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
android:textAppearance="@style/TextAppearance.Material3.TitleMedium"/>
<TextView
android:id="@android:id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.Material3.TitleMedium"
app:layout_constraintTop_toTopOf="parent"
tools:text="EditText Preference" />
<TextView
android:id="@android:id/summary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@android:id/title"
tools:text="Summary" />
<!-- <com.google.android.material.button.MaterialButton-->
<!-- style="@style/Widget.Material3.Button.IconButton"-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="wrap_content"-->
<!-- app:icon="@drawable/baseline_edit_24"-->
<!-- app:layout_constraintBottom_toBottomOf="parent"-->
<!-- app:layout_constraintEnd_toEndOf="parent"-->
<!-- app:layout_constraintTop_toTopOf="parent" />-->
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<TextView
android:id="@android:id/summary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@android:id/title"
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,14 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingHorizontal="8dp">
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/settingsFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:layout="@android:layout/list_content" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.fragment.app.FragmentContainerView
android:id="@+id/settingsFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:layout="@android:layout/list_content" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,39 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include
android:id="@+id/gameCard"
layout="@layout/card_game"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
app:layout_constraintTop_toTopOf="parent" />
<include
android:id="@+id/gameCard"
layout="@layout/card_game"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.card.MaterialCardView
style="@style/Widget.Material3.CardView.Filled"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
app:contentPadding="16dp"
app:layout_constraintTop_toBottomOf="@id/gameCard">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/settingsFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="@id/gameCard"
app:layout_constraintBottom_toBottomOf="parent"
tools:layout="@android:layout/list_content">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/settingsFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:layout="@android:layout/list_content" />
</androidx.fragment.app.FragmentContainerView>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottomNavigation"
style="@style/Widget.MaterialComponents.BottomNavigationView.Colored"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:menu="@menu/menu_game_settings" />
</androidx.constraintlayout.widget.ConstraintLayout>
<!-- <com.google.android.material.bottomnavigation.BottomNavigationView-->
<!-- android:id="@+id/bottomNavigation"-->
<!-- style="@style/Widget.MaterialComponents.BottomNavigationView.Colored"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content"-->
<!-- app:layout_constraintBottom_toBottomOf="parent"-->
<!-- app:menu="@menu/menu_game_settings" />-->
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,23 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/swipeRefresh">
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/swipeRefresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/gamesList"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="LinearLayoutManager"
android:clipToPadding="false"
android:paddingHorizontal="8dp"
tools:listitem="@layout/card_game" />
</RelativeLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/gamesList"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingHorizontal="8dp"
app:layoutManager="LinearLayoutManager"
tools:listitem="@layout/card_game" />
</RelativeLayout>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

View File

@@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,58 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/steam_background"
android:gravity="center_vertical"
android:orientation="vertical"
tools:context=".fragment.SteamLoginFragment">
<ImageView
android:id="@+id/imageView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:src="@drawable/steam_logo"
app:tint="#C5C3C0" />
<com.google.android.material.textfield.TextInputLayout
android:layout_width="320dp"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="16dp"
app:startIconDrawable="@drawable/ic_baseline_person_24">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/steamLogin"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/username"
android:inputType="textEmailAddress" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="320dp"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="16dp"
app:startIconDrawable="@drawable/ic_baseline_key_24">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/steamPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/password"
android:inputType="textPassword" />
</com.google.android.material.textfield.TextInputLayout>
<Button
android:id="@+id/loginButton"
style="@style/App.Theme.SteamButton"
android:layout_width="240dp"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="16dp"
android:text="@string/login" />
</LinearLayout>

View File

@@ -1,19 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@android:id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
android:textAppearance="@style/TextAppearance.Material3.TitleMedium"/>
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@android:id/summary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@android:id/title"
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<TextView
android:id="@android:id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.Material3.TitleMedium"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@android:id/summary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
app:layout_constraintTop_toBottomOf="@android:id/title" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,51 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools">
<ImageView
android:id="@+id/pagePic"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="64dp"
android:layout_marginBottom="32dp"
android:src="@drawable/ic_baseline_folder_open_24"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/pageTitle"/>
<TextView
android:id="@+id/pageTitle"
style="@style/TextAppearance.Material3.TitleLarge"
android:layout_width="match_parent"
android:layout_height="0dp"
android:gravity="center"
android:paddingHorizontal="16dp"
android:text="@string/setup_location_title"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@id/pageMessage"
app:layout_constraintTop_toBottomOf="@id/pagePic" />
<TextView
android:id="@+id/pageMessage"
style="@style/TextAppearance.Material3.TitleMedium"
android:layout_width="match_parent"
android:layout_height="0dp"
android:gravity="center"
android:paddingHorizontal="16dp"
android:text="@string/setup_location_message"
app:layout_constraintBottom_toTopOf="@id/pageButton"
app:layout_constraintTop_toBottomOf="@id/pageTitle" />
<Button
android:id="@+id/pageButton"
android:layout_width="240dp"
android:layout_marginBottom="128dp"
android:layout_height="wrap_content"
android:text="@string/ok"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/pageMessage" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,51 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools">
<ImageView
android:id="@+id/pagePic"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="64dp"
android:layout_marginBottom="32dp"
android:src="@mipmap/ic_launcher"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/pageTitle"/>
<TextView
android:id="@+id/pageTitle"
style="@style/TextAppearance.Material3.TitleLarge"
android:layout_width="match_parent"
android:layout_height="0dp"
android:gravity="center"
android:textStyle="bold"
android:paddingHorizontal="16dp"
android:text="@string/setup_welcome_title"
app:layout_constraintBottom_toTopOf="@id/pageMessage"
app:layout_constraintTop_toBottomOf="@id/pagePic" />
<TextView
android:id="@+id/pageMessage"
style="@style/TextAppearance.Material3.TitleMedium"
android:layout_width="match_parent"
android:layout_height="0dp"
android:gravity="center"
android:paddingHorizontal="16dp"
android:text="@string/setup_welcome_message"
app:layout_constraintBottom_toTopOf="@id/pageButton"
app:layout_constraintTop_toBottomOf="@id/pageTitle" />
<Button
android:id="@+id/pageButton"
android:layout_width="240dp"
android:layout_marginBottom="128dp"
android:layout_height="wrap_content"
android:text="@string/next"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/pageMessage" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,33 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="32dp">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/steam_guard_code"
android:textAppearance="@style/TextAppearance.Material3.TitleLarge"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/textInputLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/steamCode"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:hint="@string/steam_guard_code" />
</com.google.android.material.textfield.TextInputLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,23 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
style="@style/Widget.Material3.CardView.Filled"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp"
android:layout_marginTop="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@android:id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:textAppearance="@style/TextAppearance.Material3.TitleMedium"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp">
<TextView
android:id="@android:id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.Material3.TitleMedium"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Switch preference" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/switchWidget"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/switchWidget"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,14 +1,9 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_dedicated"
android:icon="@drawable/ic_baseline_terminal_24"
android:title="@string/dedicated_server"
app:showAsAction="always|withText"
android:visible="false"/>
<item
android:id="@+id/action_uninstall"
android:icon="@drawable/ic_baseline_delete_24"
android:title="@string/uninstall"
app:showAsAction="always|withText" />
</menu>
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_dedicated"
android:icon="@drawable/ic_baseline_terminal_24"
android:title="@string/dedicated_server"
android:visible="false"
app:showAsAction="always|withText" />
</menu>

Some files were not shown because too many files have changed in this diff Show More