Compare commits

..

166 Commits

Author SHA1 Message Date
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
211 changed files with 7715 additions and 11485 deletions

View File

@@ -1,32 +0,0 @@
task:
name: freebsd-14-amd64
freebsd_instance:
image_family: freebsd-14-3
setup_script:
- pkg update
- pkg install -y pkgconf git sdl2 python freetype2 libvorbis opusfile bzip2 libbacktrace libogg libvorbis
- git submodule update --init --recursive
test_script:
- ./scripts/cirrus/build_freebsd.sh
task:
name: freebsd-15-amd64
freebsd_instance:
image_family: freebsd-15-0-amd64-zfs
setup_script:
- pkg update
- pkg install -y pkgconf git sdl2 python freetype2 libvorbis opusfile bzip2 libbacktrace libogg libvorbis
- git submodule update --init --recursive
test_script:
- ./scripts/cirrus/build_freebsd.sh
#task:
# name: freebsd-16-amd64
# freebsd_instance:
# image_family: freebsd-16-0-snap
# setup_script:
# - pkg update
# - pkg install -y pkgconf git sdl2 python freetype2 libvorbis opusfile bzip2 libbacktrace libogg libvorbis
# - git submodule update --init --recursive
# test_script:
# - ./scripts/cirrus/build_freebsd.sh

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); }'''
@@ -120,8 +120,8 @@ def configure(conf):
check_frag(FRAGMENT_SYNC, ' __sync extensions', 'HAVE_SYNC_FUNCTIONS'),
check_frag(FRAGMENT_GETPAGESIZE, ' getpagesize function', 'HAVE_DECL_GETPAGESIZE'),
check_frag(FRAGMENT_STRNLEN, ' strnlen function', 'HAVE_DECL_STRNLEN'),
check_frag(FRAGMENT_DL_ITERATE_PHDR % 'link.h', ' dl_iterate_phdr function in link.h', 'HAVE_DL_ITERATE_PHDR', after_tests=['link.h']),
check_frag(FRAGMENT_DL_ITERATE_PHDR % 'sys/link.h', ' dl_iterate_phdr function in sys/link.h', 'HAVE_DL_ITERATE_PHDR', after_tests=['sys/link.h']),
check_frag(FRAGMENT_DL_ITERATE_PHDR % 'link.h', ' dl_iterate_phdr function in link.h', 'HAVE_DL_ITERATE_PHDR_LINK_H', after_tests=['link.h']),
check_frag(FRAGMENT_DL_ITERATE_PHDR % 'sys/link.h', ' dl_iterate_phdr function in sys/link.h', 'HAVE_DL_ITERATE_PHDR_SYS_LINK_H', after_tests=['sys/link.h']),
check_frag(FRAGMENT_FCNTL, 'fnctl function', 'HAVE_FCNTL'),
check_frag(FRAGMENT_GETEXECNAME, 'getexecname function','HAVE_GETEXECNAME'),
check_frag(FRAGMENT_GETIPINFO, '_Unwind_GetIPInfo function', 'HAVE_GETIPINFO'),
@@ -137,6 +137,9 @@ def configure(conf):
msg='Checking for in parallel'
)
if conf.is_defined('HAVE_DL_ITERATE_PHDR_LINK_H') or conf.is_defined('HAVE_DL_ITERATE_PHDR_SYS_LINK_H'):
conf.define('HAVE_DL_ITERATE_PHDR', 1)
conf.env[DEFKEYS].sort()
conf.write_config_header()
@@ -181,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/',

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

@@ -1,8 +1,139 @@
# Steam broker protocol
`sb_connect <ip:port> <server's steam id> <secure> <challenge>` - used for obtaining auth ticket during connection to GoldSrc server
# Steam API Broker Protocol Specification
`sb_disconnect <ip:port> <challenge>` - used for signaling broker about disconnecting from GoldSrc server
## Overview
`sb_gamedir <gamedir>` - used for signaling broker about game startup and for announcing started mod, so broker could choose proper AppID for Steam API initialization
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.
`sb_terminate` - used for signaling broker about game shutdown, ideally broker should be terminated too (and later restarted automatically and waiting for sb_gamedir message)
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

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

@@ -134,7 +134,7 @@ To build you should clone [SDL](https://github.com/libsdl-org/SDL) from `SDL2` b
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 client dylibs to `build/ios/cl_dlls` and any other dylibs to `build/ios/dlls` (You can also run `scripts/ios/buildhlsdk.sh` instead to automatically create an ipa with hlsdk dylibs)
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

View File

@@ -57,6 +57,7 @@
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
@@ -68,6 +69,15 @@
<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/provider_paths" />
</provider>
<activity
android:name=".XashActivity"
android:alwaysRetainTaskState="true"

View File

@@ -1,6 +1,7 @@
package su.xash.engine
import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.NavController
import androidx.navigation.fragment.NavHostFragment
@@ -8,6 +9,11 @@ import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.navigateUp
import androidx.navigation.ui.setupActionBarWithNavController
import su.xash.engine.databinding.ActivityMainBinding
import su.xash.engine.util.CrashReports
import java.io.File
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
@@ -27,9 +33,36 @@ class MainActivity : AppCompatActivity() {
navController = navHostFragment.navController
appBarConfiguration = AppBarConfiguration(navController.graph)
setupActionBarWithNavController(navController, appBarConfiguration)
CrashReports.prune(this)
showPendingCrashReport()
}
override fun onSupportNavigateUp(): Boolean {
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
}
private fun showPendingCrashReport() {
val pending = CrashReports.pendingFile(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 archived = File(historyDir, "crash-$ts.log")
if (!pending.renameTo(archived)) {
// fall back to in-place read if move failed; still consume the file
archived.writeText(pending.readText())
pending.delete()
}
val content = archived.readText()
AlertDialog.Builder(this)
.setTitle(R.string.crash_dialog_title)
.setView(CrashReports.buildContentView(this, content))
.setPositiveButton(R.string.crash_send_to_developers) { _, _ -> CrashReports.sendByEmail(this, content) }
.setNeutralButton(R.string.crash_share) { _, _ -> CrashReports.share(this, archived) }
.setNegativeButton(R.string.crash_dismiss, null)
.show()
}
}

View File

@@ -15,6 +15,7 @@ import org.libsdl.app.SDLActivity;
import su.xash.engine.util.AndroidBug5497Workaround;
import java.io.File;
import java.util.Arrays;
import java.util.List;
@@ -123,6 +124,10 @@ public class XashActivity extends SDLActivity {
// 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 gamedir = getIntent().getStringExtra("gamedir");
if (gamedir == null) gamedir = "valve";
nativeSetenv("XASH3D_GAME", gamedir);
@@ -130,6 +135,14 @@ public class XashActivity extends SDLActivity {
String gamelibdir = getIntent().getStringExtra("gamelibdir");
if (gamelibdir != null) nativeSetenv("XASH3D_GAMELIBDIR", gamelibdir);
String rodir = System.getenv("XASH3D_RODIR");
if (rodir == null) {
// FIXME: we are using rodir as a supplier for downloaded game libraries
rodir = getFilesDir().getAbsolutePath() + "/gamelibs";
nativeSetenv("XASH3D_RODIR", rodir);
}
Log.i(TAG, "XASH3D_RODIR = " + rodir);
String pakfile = getIntent().getStringExtra("pakfile");
if (pakfile != null) nativeSetenv("XASH3D_EXTRAS_PAK2", pakfile);

View File

@@ -6,14 +6,19 @@ import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.view.LayoutInflater
import android.widget.TextView
import androidx.core.net.toUri
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.progressindicator.LinearProgressIndicator
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 java.io.File
import java.io.FileInputStream
import java.util.Arrays
import android.util.Log
class Game(val ctx: Context, val basedir: File, val gameInfoFile: File) {
private var iconName = "game.ico"
@@ -52,7 +57,7 @@ class Game(val ctx: Context, val basedir: File, val gameInfoFile: File) {
fun startEngine(ctx: Context) {
val packageNames = getPackageNamesForGameDir(basedir.name)
var externalGame = false
var commandLineArgs = "";
var commandLineArgs = ""
if (basedir.name != defaultGameDir)
commandLineArgs += "-game ${basedir.name} "
@@ -68,17 +73,18 @@ class Game(val ctx: Context, val basedir: File, val gameInfoFile: File) {
}
}
commandLineArgs += pref.getString("arguments", "-console -log")
commandLineArgs += pref.getString("arguments", "-console -log") ?: ""
var packageName: String? = null
var gameLibDir: String? = null
if (externalGame && packageNames != null) {
var packageName: String? = null
var gameLibDir: String? = null
for (pn in packageNames) {
gameLibDir = try {
getGameLibDir(ctx, pn)
} catch(e: PackageManager.NameNotFoundException) {
} catch (e: PackageManager.NameNotFoundException) {
null
} catch(e: Exception) {
} catch (e: Exception) {
e.printStackTrace()
null
}
@@ -99,12 +105,95 @@ class Game(val ctx: Context, val basedir: File, val gameInfoFile: File) {
ctx.startActivity(intent)
}
show()
return
}
return
}
launchEngine(ctx, commandLineArgs, packageName = packageName, gameLibDir = gameLibDir)
return
}
if (packageNames == null) {
// Unknown game — try to use downloaded libraries from hlsdk-mega-build
val downloader = GameLibDownloader(ctx)
val args = commandLineArgs
if (downloader.isDownloaded(basedir.name)) {
downloader.logExistingLibs(basedir.name)
launchEngine(ctx, args)
return
}
val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
scope.launch {
when (val r = downloader.lookupBuild(basedir.name)) {
is GameLibDownloader.Lookup.Available -> showDownloadDialog(ctx, downloader, args)
is GameLibDownloader.Lookup.NotInManifest -> launchEngine(ctx, args)
is GameLibDownloader.Lookup.Error -> showManifestErrorDialog(ctx, args, r.cause)
}
}
return
}
launchEngine(ctx, commandLineArgs)
}
private fun showDownloadDialog(ctx: Context, downloader: GameLibDownloader, commandLineArgs: String) {
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(R.string.downloading_game_libs)
.setView(view)
.setCancelable(true)
.setNegativeButton(android.R.string.cancel) { d, _ -> d.dismiss() }
.create()
dialog.show()
val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
val job = scope.launch {
val result = downloader.download(basedir.name) { progress ->
progressBar.isIndeterminate = false
progressBar.progress = (progress * 100).toInt()
statusText.text = ctx.getString(R.string.download_progress, (progress * 100).toInt())
}
if (!dialog.isShowing) return@launch
dialog.dismiss()
if (result.isSuccess) {
launchEngine(ctx, commandLineArgs)
} 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()
}
}
dialog.setOnDismissListener { job.cancel() }
}
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
@@ -113,13 +202,8 @@ class Game(val ctx: Context, val basedir: File, val gameInfoFile: File) {
putExtra("usevolume", pref.getBoolean("use_volume_buttons", false))
putExtra("basedir", basedir.parent)
if (gameLibDir != null) {
putExtra("gamelibdir", gameLibDir)
}
if (packageName != null) {
putExtra("package", packageName)
}
if (gameLibDir != null) putExtra("gamelibdir", gameLibDir)
if (packageName != null) putExtra("package", packageName)
})
}
@@ -152,9 +236,6 @@ class Game(val ctx: Context, val basedir: File, val gameInfoFile: File) {
if (mobileHacksGames.any { it.equals(gamedir, ignoreCase = true) })
return arrayOf("su.xash.engine")
// return if (mDbEntry != null) {
// mDbEntry.getPackageName()
// } else null
return null
}

View File

@@ -0,0 +1,373 @@
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: (Float) -> 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
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
if (total > 0) {
val progress = downloaded.toFloat() / total
withContext(Dispatchers.Main) { onProgress(progress) }
}
}
}
}
return null
} catch (e: Exception) {
return e
} finally {
connection?.disconnect()
}
}
suspend fun download(gamedir: String, onProgress: (Float) -> 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 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

@@ -1,6 +1,8 @@
package su.xash.engine.ui.settings
import android.os.Bundle
import androidx.navigation.fragment.findNavController
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import su.xash.engine.R
@@ -8,5 +10,10 @@ class AppSettingsPreferenceFragment() : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
preferenceManager.sharedPreferencesName = "app_preferences";
setPreferencesFromResource(R.xml.app_preferences, rootKey);
findPreference<Preference>("crash_logs")?.setOnPreferenceClickListener {
findNavController().navigate(R.id.action_appSettingsFragment_to_crashLogsFragment)
true
}
}
}

View File

@@ -0,0 +1,66 @@
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 java.io.File
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 files = CrashReports.historyDir(ctx).listFiles()?.sortedByDescending { it.lastModified() } ?: emptyList()
if (files.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)
files.forEach { file ->
preferenceScreen.addPreference(Preference(ctx).apply {
title = fmt.format(Date(file.lastModified()))
summary = file.name
setOnPreferenceClickListener {
showCrashLog(file)
true
}
})
}
}
private fun showCrashLog(file: File) {
val ctx = requireContext()
val content = file.readText()
AlertDialog.Builder(ctx)
.setTitle(file.name)
.setView(CrashReports.buildContentView(ctx, content))
.setPositiveButton(R.string.crash_send_to_developers) { _, _ -> CrashReports.sendByEmail(ctx, content) }
.setNeutralButton(R.string.crash_share) { _, _ -> CrashReports.share(ctx, file) }
.setNegativeButton(R.string.crash_log_delete) { _, _ ->
file.delete()
populate()
}
.show()
}
}

View File

@@ -1,11 +1,17 @@
package su.xash.engine.ui.settings
import android.content.Intent
import android.os.Bundle
import androidx.core.net.toUri
import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreferenceCompat
import su.xash.engine.R
import su.xash.engine.model.Game
import su.xash.engine.model.GameLibDownloader
import java.text.DateFormat
import java.util.Date
class GameSettingsPreferenceFragment(val game: Game) : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
@@ -26,6 +32,8 @@ class GameSettingsPreferenceFragment(val game: Game) : PreferenceFragmentCompat(
enableYaPBBots.isVisible = true
}
populateDownloadedBuildInfo()
val separatePackages = findPreference<SwitchPreferenceCompat>("separate_libraries")!!
val clientPackage = findPreference<ListPreference>("client_package")!!
val serverPackage = findPreference<ListPreference>("server_package")!!
@@ -43,4 +51,43 @@ class GameSettingsPreferenceFragment(val game: Game) : PreferenceFragmentCompat(
true
}
}
private fun populateDownloadedBuildInfo() {
val downloader = GameLibDownloader(requireContext())
val source = downloader.getSourceInfo(game.basedir.name) ?: return
val downloadedAt = downloader.getDownloadTime(game.basedir.name)
val urlPref = findPreference<Preference>("source_url")!!
urlPref.isVisible = true
urlPref.summary = source.url ?: ""
urlPref.isEnabled = source.url != null
urlPref.setOnPreferenceClickListener {
source.url?.let {
startActivity(Intent(Intent.ACTION_VIEW, it.toUri()))
}
true
}
val branchPref = findPreference<Preference>("source_branch")!!
branchPref.isVisible = true
branchPref.summary = source.branch ?: ""
val commitPref = findPreference<Preference>("source_commit")!!
commitPref.isVisible = true
commitPref.summary = source.commit ?: ""
commitPref.isEnabled = source.commit != null && source.url != null
commitPref.setOnPreferenceClickListener {
// FIXME: GitHub-styled URL!
val target = "${source.url!!.trimEnd('/')}/commit/${source.commit}"
startActivity(Intent(Intent.ACTION_VIEW, target.toUri()))
true
}
val timePref = findPreference<Preference>("downloaded_at")!!
timePref.isVisible = true
timePref.summary = if (downloadedAt > 0L)
DateFormat.getDateTimeInstance().format(Date(downloadedAt))
else
""
}
}

View File

@@ -0,0 +1,80 @@
package su.xash.engine.util
import android.content.Context
import android.content.Intent
import android.graphics.Typeface
import android.net.Uri
import android.util.TypedValue
import android.view.View
import android.widget.ScrollView
import android.widget.TextView
import androidx.core.content.FileProvider
import su.xash.engine.BuildConfig
import su.xash.engine.R
import java.io.File
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"
fun pendingFile(ctx: Context): File = File(ctx.filesDir, "crashes/crash.log")
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).listFiles()?.forEach { it.delete() }
pendingFile(ctx).delete()
prefs.edit().putInt(KEY_LAST_VERSION, currentVersion).apply()
return
}
val cutoff = System.currentTimeMillis() - MAX_AGE_MS
historyDir(ctx).listFiles()?.forEach { f ->
if (f.lastModified() < cutoff) f.delete()
}
}
fun sendByEmail(ctx: Context, content: String) {
val addr = D.chunked(2) { (it.toString().toInt(16) xor 0xFF).toChar() }.joinToString("")
val intent = Intent(Intent.ACTION_SENDTO).apply {
data = Uri.fromParts("mailto", addr, null)
putExtra(Intent.EXTRA_SUBJECT, ctx.getString(R.string.crash_email_subject))
putExtra(Intent.EXTRA_TEXT, content)
}
if (intent.resolveActivity(ctx.packageManager) != null) {
ctx.startActivity(intent)
}
}
fun buildContentView(ctx: Context, content: String): View {
val pad = (16 * ctx.resources.displayMetrics.density).toInt()
val text = TextView(ctx).apply {
text = content
typeface = Typeface.MONOSPACE
setTextIsSelectable(true)
setTextSize(TypedValue.COMPLEX_UNIT_SP, 12f)
setPadding(pad, pad, pad, pad)
}
return ScrollView(ctx).apply { addView(text) }
}
fun share(ctx: Context, file: File) {
val authority = "${BuildConfig.APPLICATION_ID}.fileprovider"
val uri = FileProvider.getUriForFile(ctx, authority, file)
val intent = Intent(Intent.ACTION_SEND).apply {
type = "text/plain"
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,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

@@ -16,8 +16,9 @@
<androidx.fragment.app.FragmentContainerView
android:id="@+id/settingsFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="@id/gameCard"
app:layout_constraintBottom_toBottomOf="parent"
tools:layout="@android:layout/list_content">
</androidx.fragment.app.FragmentContainerView>

View File

@@ -22,5 +22,13 @@
<fragment
android:id="@+id/appSettingsFragment"
android:name="su.xash.engine.ui.settings.AppSettingsFragment"
android:label="@string/app_settings" />
android:label="@string/app_settings">
<action
android:id="@+id/action_appSettingsFragment_to_crashLogsFragment"
app:destination="@id/crashLogsFragment" />
</fragment>
<fragment
android:id="@+id/crashLogsFragment"
android:name="su.xash.engine.ui.settings.CrashLogsFragment"
android:label="@string/crash_logs" />
</navigation>

View File

@@ -0,0 +1,31 @@
-----BEGIN CERTIFICATE-----
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
-----END CERTIFICATE-----

View File

@@ -23,4 +23,27 @@
<string name="game_apk_required">Требуется APK с игровыми библиотеками</string>
<string name="game_apk_message">Чтобы запустить эту игру, нужно установить дополнительный APK</string>
<string name="game_apk_install">Скачать</string>
<string name="crash_dialog_title">Xash3D FWGS аварийно завершился в прошлый раз</string>
<string name="crash_send_to_developers">Отправить разработчикам</string>
<string name="crash_share">Поделиться</string>
<string name="crash_dismiss">Закрыть</string>
<string name="crash_logs">Журналы сбоев</string>
<string name="crash_logs_empty">Нет сохранённых журналов сбоев</string>
<string name="crash_log_delete">Удалить</string>
<string name="downloading_game_libs">Загрузка игровых библиотек</string>
<string name="downloading">Загрузка…</string>
<string name="download_progress">Загрузка… %1$d%%</string>
<string name="download_failed">Ошибка загрузки</string>
<string name="download_error">Произошла ошибка при загрузке игровых библиотек.</string>
<string name="update_available">Доступно обновление</string>
<string name="update_available_message">Доступна более новая версия игровых библиотек. Загрузить обновление?</string>
<string name="update_download">Обновить</string>
<string name="update_skip">Пропустить</string>
<string name="manifest_error_title">Не удалось связаться с сервером игровых библиотек</string>
<string name="manifest_error_message">Не удалось загрузить манифест сборок:\n\n%1$s\n\nИгровые библиотеки сейчас недоступны для загрузки. Можно запустить движок и без них, но он, скорее всего, не найдёт игровые DLL.</string>
<string name="launch_anyway">Запустить всё равно</string>
<string name="source_repo">Репозиторий исходников</string>
<string name="source_branch">Ветка</string>
<string name="source_commit">Коммит</string>
<string name="downloaded_at">Загружено</string>
</resources>

View File

@@ -24,4 +24,28 @@
<string name="game_apk_required">Game code APK required</string>
<string name="game_apk_message">To launch this game, you need additional APK installed</string>
<string name="game_apk_install">Download</string>
<string name="crash_dialog_title">Xash3D FWGS crashed last time</string>
<string name="crash_send_to_developers">Send to developers</string>
<string name="crash_share">Share</string>
<string name="crash_dismiss">Dismiss</string>
<string name="crash_logs">Crash logs</string>
<string name="crash_logs_empty">No crash logs recorded</string>
<string name="crash_log_delete">Delete</string>
<string name="crash_email_subject" translatable="false">Xash3D FWGS crash report</string>
<string name="downloading_game_libs">Downloading Game Libraries</string>
<string name="downloading">Downloading…</string>
<string name="download_progress">Downloading… %1$d%%</string>
<string name="download_failed">Download Failed</string>
<string name="download_error">An error occurred while downloading game libraries.</string>
<string name="update_available">Update Available</string>
<string name="update_available_message">A newer version of the game libraries is available. Download the update?</string>
<string name="update_download">Update</string>
<string name="update_skip">Skip</string>
<string name="manifest_error_title">Cannot reach game library server</string>
<string name="manifest_error_message">Failed to download the build manifest:\n\n%1$s\n\nGame libraries cannot be downloaded right now. You can launch the engine anyway, but it likely won\'t find the game DLLs.</string>
<string name="launch_anyway">Launch anyway</string>
<string name="source_repo">Source repository</string>
<string name="source_branch">Branch</string>
<string name="source_commit">Commit</string>
<string name="downloaded_at">Downloaded</string>
</resources>

View File

@@ -11,4 +11,8 @@
app:key="use_icons"
app:layout="@layout/switch_preference"
app:title="@string/preferences_use_icons" />
<Preference
app:key="crash_logs"
app:title="@string/crash_logs" />
</PreferenceScreen>

View File

@@ -46,4 +46,28 @@
app:title="@string/preferences_server_package"
app:useSimpleSummaryProvider="true"
app:isPreferenceVisible="false" />
<Preference
app:key="source_url"
app:layout="@layout/edit_text_preference"
app:title="@string/source_repo"
app:isPreferenceVisible="false" />
<Preference
app:key="source_branch"
app:layout="@layout/edit_text_preference"
app:title="@string/source_branch"
app:isPreferenceVisible="false" />
<Preference
app:key="source_commit"
app:layout="@layout/edit_text_preference"
app:title="@string/source_commit"
app:isPreferenceVisible="false" />
<Preference
app:key="downloaded_at"
app:layout="@layout/edit_text_preference"
app:title="@string/downloaded_at"
app:isPreferenceVisible="false" />
</PreferenceScreen>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config>
<trust-anchors>
<certificates src="system"/>
<certificates src="@raw/isrgrootx1"/>
</trust-anchors>
</base-config>
</network-security-config>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<files-path name="crashes" path="crashes/" />
</paths>

View File

@@ -87,6 +87,7 @@ typedef enum
IL_LOAD_PLAYER_DECAL = BIT(7), // special mode for player decals
IL_KTX2_RAW = BIT(8), // renderer can consume raw KTX2 files (e.g. ref_vk)
IL_ALLOW_WAD3_LUMA = BIT(9), // allow usage of luma textures in wad3 (tilde textures)
IL_HOST_ENDIAN = BIT(10), // buffer is already in host endian, skip byte-swapping
} ilFlags_t;
// goes into rgbdata_t->encode
@@ -116,6 +117,8 @@ typedef enum
IMAGE_MULTILAYER = BIT(8), // to differentiate from 3D texture
IMAGE_ONEBIT_ALPHA = BIT(9), // binary alpha
IMAGE_QUAKEPAL = BIT(10), // image has quake1 palette
IMAGE_PREMULTIPLIED = BIT(11),
IMAGE_PLAYERDECAL = BIT(12), // loaded with IL_LOAD_PLAYER_DECAL (custom player spray)
// Image_Process manipulation flags
IMAGE_FLIP_X = BIT(16), // flip the image by width

View File

@@ -22,6 +22,9 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#ifndef QUAKE_CLIENT_H
#define QUAKE_CLIENT_H
#include <stdint.h>
#include "xash3d_types.h"
// Definitions originally taken from Quake, modified for binary compatibility
typedef struct usercmd_s

View File

@@ -101,7 +101,7 @@ typedef enum
TF_TEXTURE_3D = (1<<20), // this is GL_TEXTURE_3D
TF_ATLAS_PAGE = (1<<21), // bit who indicate lightmap page or deluxemap page
TF_ALPHACONTRAST = (1<<22), // special texture mode for A2C
// reserved
TF_PREMULTIPLIED = (1<<23), // RGBA was stored premultiplied (HL gradient / indexalpha)
// reserved
TF_IMG_UPLOADED = (1<<25), // this is set for first time when called glTexImage, otherwise it will be call glTexSubImage
TF_ARB_FLOAT = (1<<26), // float textures

View File

@@ -159,11 +159,22 @@ typedef struct snd_globals_s
qboolean have_ambient_sfx;
} snd_globals_t;
typedef struct voice_audio_info_s
{
uint width;
uint samplerate;
uint frame_size; // in samples
} voice_audio_info_t;
// API from engine to client (client calls these)
typedef struct sound_api_s
{
qboolean (*CL_GetEntitySpatialization)( channel_t *ch );
sfx_t* (*S_GetSfxByHandle)( sound_t handle );
qboolean (*CL_GetEntitySpatialization)( channel_t *ch );
sfx_t* (*S_GetSfxByHandle)( sound_t handle );
// Voice extensions
void (*pfnS_RawEntSamples)( int entnum, uint samples, uint rate, word width, word channels, const byte *data, int snd_vol, float attn );
void (*pfnSND_ForceInitMouth)( int entnum );
voice_audio_info_t (*pfnVoice_GetAudioInfo)( void );
} sound_api_t;
// Callbacks from client to engine (engine calls these when custom sound is active)

View File

@@ -243,7 +243,10 @@ typedef int qboolean;
#define Swap32Store( x ) ( x = Swap32( x ))
#define Swap16Store( x ) ( x = Swap16( x ))
#ifdef XASH_BIG_ENDIAN
#define LittleFourCC( a, b, c, d ) (((uint32_t)( d ) << 24 ) | ((uint32_t)( c ) << 16 ) | ((uint32_t)( b ) << 8 ) | (uint32_t)( a ))
#define BigFourCC( a, b, c, d ) (((uint32_t)( a ) << 24 ) | ((uint32_t)( b ) << 16 ) | ((uint32_t)( c ) << 8 ) | (uint32_t)( d ))
#if XASH_BIG_ENDIAN
#define LittleLong( x ) Swap32( x )
#define LittleShort( x ) Swap16( x )
#define LittleLongSW( x ) Swap32Store( x )
@@ -252,6 +255,7 @@ typedef int qboolean;
#define BigLong( x ) ( x )
#define BigShort( x ) ( x )
#define BigFloat( x ) ( x )
#define HostFourCC( a, b, c, d ) BigFourCC( a, b, c, d )
#else
#define LittleLong( x ) ( x )
#define LittleShort( x ) ( x )
@@ -261,6 +265,7 @@ typedef int qboolean;
#define BigLong( x ) Swap32( x )
#define BigShort( x ) Swap16( x )
#define BigFloat( x ) SwapFloat( x )
#define HostFourCC( a, b, c, d ) LittleFourCC( a, b, c, d )
#endif
#endif // XASH_TYPES_H

View File

@@ -82,6 +82,7 @@ typedef struct cldll_func_s
void (*pfnLookEvent)( float relyaw, float relpitch );
// Sound API
int (*pfnGetSoundInterface)( int version, const sound_api_t *api, sound_interface_t *callback );
int (*pfnVoice_StartChannel)( int samples, byte *data, int entnum );
} cldll_func_t;
#endif//CDLL_EXP_H

View File

@@ -444,13 +444,16 @@ qboolean AVI_Think( movie_state_t *Avi )
if( Avi->texture == 0 )
{
int cinTexture = SCR_GetCinematicTexture();
int w = Avi->w >= 0 ? Avi->w : refState.width;
int h = Avi->h >= 0 ? Avi->h : refState.height;
ref.dllFuncs.R_DrawStretchRaw( Avi->x, Avi->y, w, h, Avi->xres, Avi->yres, Avi->dst, redraw );
if( redraw )
ref.dllFuncs.GL_UpdateTexture( cinTexture, Avi->xres, Avi->yres, Avi->xres, Avi->yres, Avi->dst, PF_BGRA_32 );
ref.dllFuncs.R_DrawStretchPic( Avi->x, Avi->y, w, h, 0, 0, 1, 1, cinTexture );
}
else if( redraw && Avi->texture > 0 )
ref.dllFuncs.AVI_UploadRawFrame( Avi->texture, Avi->xres, Avi->yres, Avi->w, Avi->h, Avi->dst );
ref.dllFuncs.GL_UpdateTexture( Avi->texture, Avi->xres, Avi->yres, Avi->w, Avi->h, Avi->dst, PF_BGRA_32 );
if( flushing && !decoded )
return false; // probably hit an EOF

View File

@@ -849,6 +849,31 @@ BEAM * GAME_EXPORT R_BeamEnts( int startEnt, int endEnt, int modelIndex, float l
return pbeam;
}
static qboolean R_BeamVisible( const vec3_t start, const vec3_t end )
{
vec3_t mins, maxs;
for( int i = 0; i < 3; i++ )
{
if( start[i] < end[i] )
{
mins[i] = start[i];
maxs[i] = end[i];
}
else
{
mins[i] = end[i];
maxs[i] = start[i];
}
// don't let it be zero sized
if( mins[i] == maxs[i] )
maxs[i] += 1.0f;
}
return Mod_BoxVisible( mins, maxs, ref.dllFuncs.Mod_GetCurrentVis( ));
}
/*
==============
R_BeamPoints
@@ -861,7 +886,7 @@ BEAM * GAME_EXPORT R_BeamPoints( vec3_t start, vec3_t end, int modelIndex, float
{
BEAM *pbeam;
if( life != 0 && ref.dllFuncs.R_BeamCull( start, end, true ))
if( life != 0 && !R_BeamVisible( start, end ))
return NULL;
pbeam = R_BeamAlloc();
@@ -1214,8 +1239,8 @@ particle spray
void GAME_EXPORT R_Blood( const vec3_t org, const vec3_t ndir, int pcolor, int speed )
{
vec3_t pos, dir, vec;
float pspeed = speed * 3.0f;
int i, j;
int pspeed = speed * 3;
particle_t *p;
VectorNormalize2( ndir, dir );
@@ -1225,7 +1250,7 @@ void GAME_EXPORT R_Blood( const vec3_t org, const vec3_t ndir, int pcolor, int s
VectorAddScalar( org, COM_RandomFloat( -3.0f, 3.0f ), pos );
VectorAddScalar( dir, COM_RandomFloat( -0.06f, 0.06f ), vec );
for( j = 0; j < 7; j++ )
for( j = 0; j < 8; j++ )
{
p = R_AllocParticle( NULL );
if( !p ) return;
@@ -1237,6 +1262,8 @@ void GAME_EXPORT R_Blood( const vec3_t org, const vec3_t ndir, int pcolor, int s
VectorAddScalar( pos, COM_RandomFloat( -1.0f, 1.0f ), p->org );
VectorScale( vec, pspeed, p->vel );
}
pspeed -= speed;
}
}
@@ -1308,10 +1335,7 @@ void GAME_EXPORT R_BloodStream( const vec3_t org, const vec3_t ndir, int pcolor,
p->color = pcolor + COM_RandomLong( 0, 9 );
p->type = pt_4x_slowgrav;
p->org[0] = org[0] + COM_RandomFloat( -1.0f, 1.0f );
p->org[1] = org[1] + COM_RandomFloat( -1.0f, 1.0f );
p->org[2] = org[2] + COM_RandomFloat( -1.0f, 1.0f );
VectorAddScalar( org, COM_RandomFloat( -1.0f, 1.0f ), p->org );
VectorCopy( dir, p->vel );
p->vel[2] -= arc;

View File

@@ -17,6 +17,20 @@ GNU General Public License for more details.
#include "filesystem.h"
#include "client.h"
#include "qfont.h"
#include "swaplib.h"
le_struct_begin( charinfo_swap )
le_struct_field( charinfo, startoffset )
le_struct_field( charinfo, charwidth )
le_struct_end();
le_struct_begin( qfont_swap )
le_struct_field( qfont_t, width )
le_struct_field( qfont_t, height )
le_struct_field( qfont_t, rowcount )
le_struct_field( qfont_t, rowheight )
le_struct_array_child( qfont_t, fontinfo, charinfo_swap, NUM_GLYPHS )
le_struct_end();
qboolean CL_FixedFont( cl_font_t *font )
{
@@ -130,6 +144,7 @@ qboolean Con_LoadVariableWidthFont( const char *fontname, cl_font_t *font, float
}
memcpy( &src, pfile, sizeof( src ));
le_struct_swap( qfont_swap, &src );
Mem_Free( pfile );
font->hFontTexture = CL_LoadFontTexture( fontname, texFlags, &font_width );

View File

@@ -981,15 +981,8 @@ qboolean CL_AddVisibleEntity( cl_entity_t *ent, int entityType )
if( !draw_player )
return false;
if( entityType == ET_BEAM )
{
ref.dllFuncs.CL_AddCustomBeam( ent );
return true;
}
else if( !ref.dllFuncs.R_AddEntity( ent, entityType ))
{
if( !ref.dllFuncs.R_AddEntity( ent, entityType ))
return false;
}
// because pTemp->entity.curstate.effects
// is already occupied by FTENT_FLICKER

View File

@@ -1174,7 +1174,7 @@ static void CL_SendConnectPacket( connprotocol_t proto, int challenge )
else
{
const char *qport = Cvar_VariableString( "net_qport" );
int extensions = Host_IsLocalGame() ? 0 : NET_EXT_SPLITSIZE;
int extensions = adrtype == NA_LOOPBACK ? 0 : NET_EXT_SPLITSIZE;
string key;
ID_GetMD5ForAddress( key, adr, sizeof( key ));
@@ -1385,6 +1385,10 @@ static void CL_CreateResourceList( void )
cl.num_resources = 0;
memset( rgucMD5_hash, 0, sizeof( rgucMD5_hash ));
ClearBits( cl_logofile.flags, FCVAR_CHANGED );
ClearBits( cl_logocolor.flags, FCVAR_CHANGED );
ClearBits( cl_logoext.flags, FCVAR_CHANGED );
// sanitize cvar value
if( Q_strcmp( cl_logoext.string, "bmp" ) && Q_strcmp( cl_logoext.string, "png" ))
Cvar_DirectSet( &cl_logoext, "bmp" );
@@ -1400,7 +1404,6 @@ static void CL_CreateResourceList( void )
if( !fp )
return;
MD5_HashFile( rgucMD5_hash, szFileName, NULL );
nSize = FS_FileLength( fp );
if( nSize != 0 )
@@ -1409,6 +1412,7 @@ static void CL_CreateResourceList( void )
if( pNewResource )
{
MD5_HashFile( rgucMD5_hash, szFileName, NULL );
SetBits( pNewResource->ucFlags, RES_CUSTOM );
memcpy( pNewResource->rgucMD5_hash, rgucMD5_hash, 16 );
HPAK_AddLump( false, hpk_custom_file.string, pNewResource, NULL, fp );
@@ -1418,6 +1422,42 @@ static void CL_CreateResourceList( void )
FS_Close( fp );
}
/*
==================
CL_CheckLogoChanged
==================
*/
static void CL_CheckLogoChanged( void )
{
player_info_t *player;
int i;
if( cls.state != ca_active )
return;
if( !FBitSet( cl_logofile.flags | cl_logocolor.flags | cl_logoext.flags, FCVAR_CHANGED ))
return;
CL_CreateResourceList();
if( cl.num_resources == 0 )
return;
player = &cl.players[cl.playernum];
COM_ClearCustomizationList( &player->customdata, true );
for( i = 0; i < cl.num_resources; i++ )
{
resource_t *pResource = &cl.resourcelist[i];
if( !COM_CreateCustomization( &player->customdata, pResource, cl.playernum, 0, NULL, NULL ))
Con_Printf( "Unable to create custom decal\n" );
}
CL_SendResourceList( cl.resourcelist, cl.num_resources );
}
static qboolean CL_StringToProtocol( const char *s, connprotocol_t *proto )
{
if( !Q_stricmp( s, "current" ) || !Q_strcmp( s, "49" ))
@@ -2543,7 +2583,7 @@ static void CL_ServerList( netadr_t from, sizebuf_t *msg )
MSG_ReadBytes( msg, servadr.ip, sizeof( servadr.ip )); // 4 bytes for IP
NET_NetadrSetType( &servadr, NA_IP );
}
servadr.port = MSG_ReadShort( msg ); // 2 bytes for Port
MSG_ReadBytes( msg, &servadr.port, sizeof( servadr.port )); // 2 bytes for Port, in network byte order
// list is ends here
if( !servadr.port )
@@ -2584,10 +2624,6 @@ static void CL_ConnectionlessPacket( netadr_t from, sizebuf_t *msg )
Con_Reportf( "%s: %s : %s\n", __func__, NET_AdrToString( from ), c );
// server connection
if( !Q_strcmp( c, "sb_connect" ))
{
SteamBroker_HandlePacket( from, msg );
}
if( !Q_strcmp( c, S2C_GOLDSRC_CONNECTION ) || !Q_strcmp( c, S2C_CONNECTION ))
{
CL_ClientConnect( cls.legacymode, c, from );
@@ -2818,7 +2854,7 @@ static void CL_ReadPackets( void )
// hot precache and downloading resources
if( cls.signon == SIGNONS && cl.lastresourcecheck < host.realtime )
{
double checktime = Host_IsLocalGame() ? 0.1 : 1.0;
double checktime = Host_IsLocalClient() ? 0.1 : 1.0;
if( !cls.dl.custom && cl.resourcesneeded.pNext != &cl.resourcesneeded )
{
@@ -2920,12 +2956,6 @@ void CL_ProcessFile( qboolean successfully_received, const char *filename )
{
if( filename[0] != '!' )
Con_Printf( "processing %s\n", filename );
if( !Q_strnicmp( filename, DEFAULT_DOWNLOADED_DIRECTORY, sizeof( DEFAULT_DOWNLOADED_DIRECTORY ) - 1 ))
{
// skip "downloaded/" part to avoid mismatch with needed resources list
filename += sizeof( DEFAULT_DOWNLOADED_DIRECTORY ) - 1;
}
}
else if( !successfully_received )
{
@@ -3589,6 +3619,9 @@ void Host_ClientBegin( void )
// finalize connection process if needs
CL_CheckClientState();
// check if logo cvars changed and re-upload if needed
CL_CheckLogoChanged();
// tell the client.dll about client data
CL_UpdateClientData();
@@ -3684,15 +3717,16 @@ void CL_Init( void )
COM_GetCommonLibraryPath( LIBRARY_CLIENT, libpath, sizeof( libpath ));
if( !CL_LoadProgs( libpath ))
Host_Error( "can't initialize %s: %s\n", libpath, COM_GetLibraryError( ));
S_Init(); // init sound
Voice_Init( VOICE_DEFAULT_CODEC, 3, true ); // init voice (do not open the device)
ID_Init();
SteamBroker_Init();
// client must be always initialized last so it can fetch all cvars
if( !CL_LoadProgs( libpath ))
Host_Error( "can't initialize %s: %s\n", libpath, COM_GetLibraryError( ));
cls.build_num = 0;
cls.initialized = true;
cl.maxclients = 1; // allow to drawing player in menu

360
engine/client/cl_sprite.c Normal file
View File

@@ -0,0 +1,360 @@
/*
cl_sprite.c - sprite loading
Copyright (C) 2010 Uncle Mike
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
*/
#include "common.h"
#include "client.h"
#include "sprite.h"
#include "ref_common.h"
static char sprite_name[MAX_QPATH];
static char group_suffix[8];
static uint r_texFlags = 0;
static int sprite_version;
/*
================
R_GetSpriteFrame
assume pModel is valid
================
*/
mspriteframe_t *R_GetSpriteFrame( const model_t *pModel, int frame, float yaw )
{
msprite_t *psprite;
mspritegroup_t *pspritegroup;
mspriteframe_t *pspriteframe = NULL;
float *pintervals, fullinterval;
int i, numframes;
float targettime;
if( !pModel )
return NULL;
psprite = pModel->cache.data;
if( frame < 0 )
{
frame = 0;
}
else if( frame >= psprite->numframes )
{
if( frame > psprite->numframes )
Con_Printf( S_WARN "%s: no such frame %d (%s)\n", __func__, frame, pModel->name );
frame = psprite->numframes - 1;
}
if( psprite->frames[frame].type == SPR_SINGLE )
{
pspriteframe = psprite->frames[frame].frameptr;
}
else if( psprite->frames[frame].type == SPR_GROUP )
{
pspritegroup = (mspritegroup_t *)psprite->frames[frame].frameptr;
pintervals = pspritegroup->intervals;
numframes = pspritegroup->numframes;
fullinterval = pintervals[numframes - 1];
// when loading in Mod_LoadSpriteGroup, we guaranteed all interval values
// are positive, so we don't have to worry about division by zero
targettime = cl.time - ((int)( cl.time / fullinterval )) * fullinterval;
for( i = 0; i < ( numframes - 1 ); i++ )
{
if( pintervals[i] > targettime )
break;
}
pspriteframe = pspritegroup->frames[i];
}
else if( psprite->frames[frame].type == FRAME_ANGLED )
{
int angleframe = (int)( Q_rint(( refState.viewangles[1] - yaw + 45.0f ) / 360 * 8 ) - 4 ) & 7;
// e.g. doom-style sprite monsters
pspritegroup = (mspritegroup_t *)psprite->frames[frame].frameptr;
pspriteframe = pspritegroup->frames[angleframe];
}
return pspriteframe;
}
void R_GetSpriteParms( int *frameWidth, int *frameHeight, int *numFrames, int currentFrame, const model_t *pSprite )
{
mspriteframe_t *pFrame;
if( !pSprite || pSprite->type != mod_sprite )
return;
pFrame = R_GetSpriteFrame( pSprite, currentFrame, 0.0f );
if( frameWidth )
*frameWidth = pFrame->width;
if( frameHeight )
*frameHeight = pFrame->height;
if( numFrames )
*numFrames = pSprite->numframes;
}
int R_GetSpriteTexture( const model_t *m_pSpriteModel, int frame )
{
if( !m_pSpriteModel || m_pSpriteModel->type != mod_sprite || !m_pSpriteModel->cache.data )
return 0;
return R_GetSpriteFrame( m_pSpriteModel, frame, 0.0f )->gl_texturenum;
}
static void Mod_SpriteTextureReplacementReport( const char *modelname, int gl_texturenum, const char *foundpath )
{
if( host_allow_materials.value != 2.0f )
return;
if( gl_texturenum > 0 )
Con_Printf( "Looking for %s tex replacement..." S_GREEN "OK (%s)\n", modelname, foundpath );
else if( gl_texturenum < 0 )
Con_Printf( "Looking for %s tex replacement..." S_YELLOW "MISS (%s)\n", modelname, foundpath );
else
Con_Printf( "Looking for %s tex replacement..." S_RED "FAIL (%s)\n", modelname, foundpath );
}
static qboolean Mod_SpriteSearchForTextureReplacement( char *out, size_t size, const char *modelname, const char *fmt, ... )
{
va_list ap;
int ret;
va_start( ap, fmt );
ret = Q_vsnprintf( out, size, fmt, ap );
va_end( ap );
if( ret < 0 )
{
Mod_SpriteTextureReplacementReport( modelname, -1, "overflow" );
return false;
}
if( FS_FileExists( out, false ))
return true;
Mod_SpriteTextureReplacementReport( modelname, -1, out );
return false;
}
static const byte *Mod_SpriteLoadFrame( model_t *mod, const void *pin, mspriteframe_t **ppframe, int num )
{
dspriteframe_t pinframe;
mspriteframe_t *pspriteframe;
int gl_texturenum = 0;
char texname[128];
int bytes = 1;
memcpy( &pinframe, pin, sizeof( dspriteframe_t ));
if( sprite_version == SPRITE_VERSION_32 )
bytes = 4;
// build unique frame name
if( FBitSet( mod->flags, MODEL_CLIENT )) // it's a HUD sprite
{
Q_snprintf( texname, sizeof( texname ), "#HUD/%s(%s:%i%i).spr", sprite_name, group_suffix, num / 10, num % 10 );
gl_texturenum = ref.dllFuncs.GL_LoadTexture( texname, pin, pinframe.width * pinframe.height * bytes, r_texFlags );
}
else
{
// partial HD-textures support
if( Mod_AllowMaterials( ))
{
if( Mod_SpriteSearchForTextureReplacement( texname, sizeof( texname ), sprite_name, "materials/%s/%s%i%i.tga", sprite_name, group_suffix, num / 10, num % 10 ))
{
gl_texturenum = ref.dllFuncs.GL_LoadTexture( texname, NULL, 0, r_texFlags );
Mod_SpriteTextureReplacementReport( sprite_name, gl_texturenum, texname );
}
}
if( gl_texturenum == 0 )
{
Q_snprintf( texname, sizeof( texname ), "#%s(%s:%i%i).spr", sprite_name, group_suffix, num / 10, num % 10 );
gl_texturenum = ref.dllFuncs.GL_LoadTexture( texname, pin, pinframe.width * pinframe.height * bytes, r_texFlags );
}
}
// setup frame description
pspriteframe = Mem_Malloc( mod->mempool, sizeof( mspriteframe_t ));
pspriteframe->width = pinframe.width;
pspriteframe->height = pinframe.height;
pspriteframe->up = pinframe.origin[1];
pspriteframe->left = pinframe.origin[0];
pspriteframe->down = pinframe.origin[1] - pinframe.height;
pspriteframe->right = pinframe.width + pinframe.origin[0];
pspriteframe->gl_texturenum = gl_texturenum;
*ppframe = pspriteframe;
return (const byte *)pin + sizeof( dspriteframe_t ) + pinframe.width * pinframe.height * bytes;
}
static const byte *Mod_SpriteLoadGroup( model_t *mod, const void *pin, mspriteframe_t **ppframe, int framenum )
{
const dspritegroup_t *pingroup;
mspritegroup_t *pspritegroup;
const dspriteinterval_t *pin_intervals;
float *poutintervals;
int i, groupsize, numframes;
const void *ptemp;
pingroup = (const dspritegroup_t *)pin;
numframes = pingroup->numframes;
groupsize = sizeof( mspritegroup_t ) + ( numframes - 1 ) * sizeof( pspritegroup->frames[0] );
pspritegroup = Mem_Calloc( mod->mempool, groupsize );
pspritegroup->numframes = numframes;
*ppframe = (mspriteframe_t *)pspritegroup;
pin_intervals = (const dspriteinterval_t *)( pingroup + 1 );
poutintervals = Mem_Calloc( mod->mempool, numframes * sizeof( float ));
pspritegroup->intervals = poutintervals;
for( i = 0; i < numframes; i++ )
{
*poutintervals = pin_intervals->interval;
if( *poutintervals <= 0.0f )
*poutintervals = 1.0f; // set error value
poutintervals++;
pin_intervals++;
}
ptemp = (const void *)pin_intervals;
for( i = 0; i < numframes; i++ )
{
ptemp = Mod_SpriteLoadFrame( mod, ptemp, &pspritegroup->frames[i], framenum * 10 + i );
}
return ptemp;
}
void Mod_SpriteLoadTextures( model_t *mod, const void *buffer )
{
const dsprite_t *pin = buffer;
const short *numi = NULL;
const byte *pframetype;
msprite_t *psprite = mod->cache.data;
int i;
if( pin->version == SPRITE_VERSION_Q1 || pin->version == SPRITE_VERSION_32 )
numi = NULL;
else if( pin->version == SPRITE_VERSION_HL )
numi = (const short *)((const byte *)buffer + sizeof( dsprite_hl_t ));
r_texFlags = mod->numtexinfo;
sprite_version = pin->version;
Q_strncpy( sprite_name, mod->name, sizeof( sprite_name ));
COM_StripExtension( sprite_name );
if( numi == NULL )
{
rgbdata_t *pal;
pal = FS_LoadImage( "#id.pal", (byte *)&i, 768 );
pframetype = (const byte *)buffer + sizeof( dsprite_q1_t );
FS_FreeImage( pal );
}
else if( *numi <= 256 )
{
const byte *src = (const byte *)( numi + 1 );
rgbdata_t *pal;
size_t pal_bytes = *numi * 3;
// install palette
switch( psprite->texFormat )
{
case SPR_INDEXALPHA:
pal = FS_LoadImage( "#gradient.pal", src, pal_bytes );
break;
case SPR_ALPHTEST:
pal = FS_LoadImage( "#masked.pal", src, pal_bytes );
break;
default:
pal = FS_LoadImage( "#texgamma.pal", src, pal_bytes );
break;
}
pframetype = (const byte *)( src + pal_bytes );
FS_FreeImage( pal );
}
else
{
Con_DPrintf( S_ERROR "%s has wrong number of palette colors %i (should be less or equal than 256)\n", mod->name, *numi );
return;
}
if( mod->numframes < 1 )
return;
for( i = 0; i < mod->numframes; i++ )
{
frametype_t frametype;
dframetype_t dframetype;
memcpy( &dframetype, pframetype, sizeof( dframetype ));
frametype = dframetype.type;
psprite->frames[i].type = (spriteframetype_t)frametype;
switch( frametype )
{
case FRAME_SINGLE:
Q_strncpy( group_suffix, "frame", sizeof( group_suffix ));
pframetype = Mod_SpriteLoadFrame( mod, pframetype + sizeof( dframetype_t ), &psprite->frames[i].frameptr, i );
break;
case FRAME_GROUP:
Q_strncpy( group_suffix, "group", sizeof( group_suffix ));
pframetype = Mod_SpriteLoadGroup( mod, pframetype + sizeof( dframetype_t ), &psprite->frames[i].frameptr, i );
break;
case FRAME_ANGLED:
Q_strncpy( group_suffix, "angle", sizeof( group_suffix ));
pframetype = Mod_SpriteLoadGroup( mod, pframetype + sizeof( dframetype_t ), &psprite->frames[i].frameptr, i );
break;
}
if( pframetype == NULL )
break;
}
}
void Mod_SpriteUnloadTextures( void *data )
{
msprite_t *psprite = data;
int i;
if( !data )
return;
// release all textures
for( i = 0; i < psprite->numframes; i++ )
{
if( !psprite->frames[i].frameptr )
continue;
if( psprite->frames[i].type == SPR_SINGLE )
{
ref.dllFuncs.GL_FreeTexture( psprite->frames[i].frameptr->gl_texturenum );
}
else
{
mspritegroup_t *pspritegroup = (mspritegroup_t *)psprite->frames[i].frameptr;
int j;
for( j = 0; j < pspritegroup->numframes; j++ )
{
if( pspritegroup->frames[j] )
ref.dllFuncs.GL_FreeTexture( pspritegroup->frames[j]->gl_texturenum );
}
}
}
}

View File

@@ -16,6 +16,8 @@ GNU General Public License for more details.
#include <inttypes.h>
#include "common.h"
#include "client.h"
#include "net_ws.h"
#include "net_ws_private.h"
// What is a broker?
// From Wikipedia, the free encyclopedia:
@@ -31,16 +33,53 @@ GNU General Public License for more details.
// 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.
// Protocol constants
#define SBRK_FRAME_HEADER "SBRK"
#define SBRK_FRAME_HEADER_SIZE (sizeof(SBRK_FRAME_HEADER) - 1)
#define SBRK_FRAME_LENGTH_SIZE 2
#define SBRK_RESPONSE_HEADER "sb_connect\n"
#define SBRK_RESPONSE_HEADER_SIZE (sizeof(SBRK_RESPONSE_HEADER) - 1)
#define SBRK_MAX_FRAME_SIZE 4096
#define SBRK_CONNECT_TIMEOUT 10.0
#define SBRK_CONNECT_RETRY_DELAY 5.0
#define SBRK_TICKET_SIZE_MAX 2048
static CVAR_DEFINE_AUTO( cl_steam_broker_addr, "127.0.0.1:27420", FCVAR_ARCHIVE, "address of steam broker instance" );
static struct
typedef enum
{
SBRK_STATE_IDLE,
SBRK_STATE_CONNECTING,
SBRK_STATE_CONNECTED,
SBRK_STATE_GAMESHUTDOWN
} sbrk_state_t;
typedef struct
{
netadr_t adr;
int socket;
sbrk_state_t state;
int challenge;
netadr_t serveradr;
qboolean announced;
qboolean addr_initialized;
} broker;
double connection_timeout;
double idle_cycle_timeout;
uint8_t rx_buffer[SBRK_MAX_FRAME_SIZE + 64];
uint8_t tx_buffer[SBRK_MAX_FRAME_SIZE + 64];
uint32_t rx_buffer_pos;
uint32_t tx_buffer_pos;
} steam_broker_t;
static steam_broker_t broker;
static void SteamBroker_SetState( sbrk_state_t new_state )
{
if( broker.state != new_state )
{
// we also may logging transitions if needed
broker.state = new_state;
}
}
static qboolean SteamBroker_UpdateBrokerAddress( void )
{
@@ -52,28 +91,387 @@ static qboolean SteamBroker_UpdateBrokerAddress( void )
return true;
}
static void SteamBroker_CloseSocket( void )
{
if( NET_IsSocketValid( broker.socket ))
{
closesocket( broker.socket );
broker.socket = INVALID_SOCKET;
}
broker.rx_buffer_pos = 0;
broker.tx_buffer_pos = 0;
}
static void SteamBroker_Disconnect( void )
{
SteamBroker_CloseSocket();
SteamBroker_SetState( SBRK_STATE_IDLE );
}
static qboolean SteamBroker_ConnectImpl( void )
{
int addr_family;
struct sockaddr_storage addr = { 0 };
if( NET_NetadrType( &broker.adr ) == NA_IP )
{
addr_family = AF_INET;
}
else if( NET_NetadrType( &broker.adr ) == NA_IP6 )
{
addr_family = AF_INET6;
}
else
{
Con_Printf( S_ERROR "%s: unsupported broker address type for %s\n", __func__, cl_steam_broker_addr.string );
return false;
}
broker.socket = socket( addr_family, SOCK_STREAM, IPPROTO_TCP );
if( !NET_IsSocketValid( broker.socket ))
{
Con_Printf( S_ERROR "%s: failed to create socket\n", __func__ );
return false;
}
if( !NET_MakeSocketNonBlocking( broker.socket ))
{
Con_Printf( S_ERROR "%s: failed to set non-blocking mode, error %s\n", __func__, NET_ErrorString( ));
SteamBroker_CloseSocket();
return false;
}
NET_NetadrToSockadr( &broker.adr, &addr );
int result = connect( broker.socket, (struct sockaddr *)&addr, NET_SockAddrLen( &addr ));
if( NET_IsSocketError( result ))
{
int err = WSAGetLastError();
if( err != WSAEWOULDBLOCK && err != WSAEALREADY && err != WSAEINPROGRESS )
{
Con_Printf( S_ERROR "%s: failed to connect to broker at %s with error %s\n", __func__, cl_steam_broker_addr.string, NET_ErrorString( ));
SteamBroker_CloseSocket();
return false;
}
}
broker.connection_timeout = Platform_DoubleTime() + SBRK_CONNECT_TIMEOUT;
SteamBroker_SetState( SBRK_STATE_CONNECTING );
return true;
}
static qboolean SteamBroker_SendFrame( const char *payload, size_t payload_size )
{
if( payload_size > SBRK_MAX_FRAME_SIZE )
{
Con_Printf( S_WARN "%s: payload too large (%zu > %u)\n", __func__, payload_size, SBRK_MAX_FRAME_SIZE );
return false;
}
uint8_t frame[SBRK_MAX_FRAME_SIZE + 6];
sizebuf_t sb;
MSG_Init( &sb, "SteamBroker_SendFrame", frame, sizeof( frame ));
MSG_WriteBytes( &sb, SBRK_FRAME_HEADER, SBRK_FRAME_HEADER_SIZE );
MSG_WriteShort( &sb, payload_size );
MSG_WriteBytes( &sb, payload, payload_size );
size_t frame_size = MSG_GetRealBytesWritten( &sb );
int sent = send( broker.socket, (const char *)frame, frame_size, 0 );
if( NET_IsSocketError( sent ))
{
int err = WSAGetLastError();
if( err != WSAEWOULDBLOCK && err != WSAEALREADY )
{
Con_Printf( S_ERROR "%s: send error %s\n", __func__, NET_ErrorString( ));
SteamBroker_Disconnect( );
return false;
}
sent = 0;
}
// bufferize unsent data for deferred sending
size_t unsent = frame_size - sent;
if( unsent > 0 )
{
size_t available = sizeof( broker.tx_buffer ) - broker.tx_buffer_pos;
if( available < unsent )
{
Con_Printf( S_ERROR "%s: transmit buffer overflow (%zu > %zu)\n", __func__, unsent, available );
SteamBroker_Disconnect( );
return false;
}
memcpy( broker.tx_buffer + broker.tx_buffer_pos, frame + sent, unsent );
broker.tx_buffer_pos += unsent;
}
return true;
}
static qboolean SteamBroker_ProcessFrame( void )
{
if( broker.rx_buffer_pos < SBRK_FRAME_HEADER_SIZE + SBRK_FRAME_LENGTH_SIZE )
return false;
sizebuf_t sb;
MSG_Init( &sb, "SteamBroker_ProcessFrame", broker.rx_buffer, broker.rx_buffer_pos );
// verify frame header
char header[SBRK_FRAME_HEADER_SIZE];
if( !MSG_ReadBytes( &sb, header, SBRK_FRAME_HEADER_SIZE ))
return false;
if( memcmp( header, SBRK_FRAME_HEADER, SBRK_FRAME_HEADER_SIZE ) != 0 )
{
Con_Printf( S_ERROR "%s: invalid frame header\n", __func__ );
SteamBroker_Disconnect( );
return false;
}
uint16_t payload_size = MSG_ReadShort( &sb );
uint32_t frame_size = SBRK_FRAME_HEADER_SIZE + SBRK_FRAME_LENGTH_SIZE + payload_size;
if( MSG_GetNumBytesLeft( &sb ) < payload_size )
return false; // need more data
char response_header[SBRK_RESPONSE_HEADER_SIZE];
if( MSG_ReadBytes( &sb, response_header, SBRK_RESPONSE_HEADER_SIZE ))
{
if( memcmp( response_header, SBRK_RESPONSE_HEADER, SBRK_RESPONSE_HEADER_SIZE ) == 0 )
{
int32_t challenge = MSG_ReadLong( &sb );
if( broker.challenge != challenge )
{
Con_Printf( S_ERROR "%s: challenge mismatch\n", __func__ );
}
else
{
uint64_t steam_id;
MSG_ReadBytes( &sb, &steam_id, sizeof( steam_id ));
uint32_t ticket_size = MSG_ReadDword( &sb );
uint8_t ticket_data[SBRK_TICKET_SIZE_MAX];
if( ticket_size > SBRK_TICKET_SIZE_MAX )
{
Con_Printf( S_ERROR "%s: ticket size exceeds limit (%u)\n", __func__, ticket_size );
}
else if( MSG_ReadBytes( &sb, ticket_data, ticket_size ))
{
Con_Printf( "%s: SteamID: %"PRIu64", ticket: [%d, %d, %d, %d...]\n", __func__, steam_id, ticket_data[0], ticket_data[1], ticket_data[2], ticket_data[3] );
memcpy( cls.steamid, &steam_id, sizeof( cls.steamid ));
CL_SendGoldSrcConnectPacket( broker.serveradr, broker.challenge, ticket_data, ticket_size );
cls.broker_wait = false;
}
else
{
Con_Printf( S_ERROR "%s: failed to read ticket data\n", __func__ );
}
}
}
}
// remove processed frame from buffer
memmove( broker.rx_buffer, broker.rx_buffer + frame_size, broker.rx_buffer_pos - frame_size );
broker.rx_buffer_pos -= frame_size;
return true;
}
static void SteamBroker_HandleDataTx( void )
{
if( broker.tx_buffer_pos == 0 )
return;
int sent = send( broker.socket, (const char *)broker.tx_buffer, broker.tx_buffer_pos, 0 );
if( NET_IsSocketError( sent ))
{
int err = WSAGetLastError();
if( err != WSAEWOULDBLOCK && err != WSAEALREADY )
{
Con_Printf( S_ERROR "%s: send error %s\n", __func__, NET_ErrorString( ));
SteamBroker_Disconnect( );
}
return;
}
if( sent > 0 )
{
// remove sent data from buffer
memmove( broker.tx_buffer, broker.tx_buffer + sent, broker.tx_buffer_pos - sent );
broker.tx_buffer_pos -= sent;
}
}
static void SteamBroker_HandleDataRx( void )
{
int available = sizeof( broker.rx_buffer ) - broker.rx_buffer_pos;
if( available <= 0 )
{
Con_Printf( S_ERROR "%s: receive buffer overflow\n", __func__ );
SteamBroker_Disconnect( );
return;
}
int received = recv( broker.socket, (char *)broker.rx_buffer + broker.rx_buffer_pos, available, 0 );
if( NET_IsSocketError( received ))
{
int err = WSAGetLastError();
if( err != WSAEWOULDBLOCK && err != WSAEALREADY )
{
Con_Printf( S_ERROR "%s: recv error %s\n", __func__, NET_ErrorString( ));
SteamBroker_Disconnect( );
}
return;
}
if( received == 0 )
{
Con_Printf( S_NOTE "%s: connection closed by broker\n", __func__ );
SteamBroker_Disconnect( );
return;
}
broker.rx_buffer_pos += received;
while( SteamBroker_ProcessFrame( ));
}
static void SteamBroker_UpdateIdle( void )
{
if( broker.idle_cycle_timeout < Platform_DoubleTime( ))
{
if( SteamBroker_UpdateBrokerAddress( ))
{
SteamBroker_ConnectImpl( );
}
else
{
Con_Printf( S_ERROR "%s: failed to resolve broker address \"%s\"\n", __func__, cl_steam_broker_addr.string );
}
broker.idle_cycle_timeout = Platform_DoubleTime() + SBRK_CONNECT_RETRY_DELAY;
}
}
void SteamBroker_AnnounceGameStart( const char *gamedir )
{
if( Q_stricmp( cl_ticket_generator.string, "steam" ) != 0 )
return;
if( broker.state != SBRK_STATE_CONNECTED )
return;
// sb_gamedir <gamedir>
char buf[512];
int len = Q_snprintf( buf, sizeof( buf ), "sb_gamedir %s", gamedir );
if( len > 0 )
SteamBroker_SendFrame( buf, len );
}
void SteamBroker_AnnounceGameShutdown( void )
{
if( Q_stricmp( cl_ticket_generator.string, "steam" ) != 0 )
return;
if( broker.state != SBRK_STATE_CONNECTED )
return;
SteamBroker_SendFrame( "sb_terminate", sizeof( "sb_terminate" ) - 1 );
}
static void SteamBroker_UpdateConnecting( void )
{
if( Platform_DoubleTime() > broker.connection_timeout )
{
Con_Printf( S_WARN "%s: connection to %s timed out\n", __func__, cl_steam_broker_addr.string );
SteamBroker_Disconnect();
return;
}
fd_set writefds;
FD_ZERO( &writefds );
FD_SET( broker.socket, &writefds );
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 0;
#if XASH_WIN32
int select_result = select( 0, NULL, &writefds, NULL, &tv );
#else
int select_result = select( broker.socket + 1, NULL, &writefds, NULL, &tv );
#endif
if( select_result == SOCKET_ERROR )
{
Con_Printf( S_ERROR "%s: select() failed\n", __func__ );
SteamBroker_Disconnect();
return;
}
if( FD_ISSET( broker.socket, &writefds ))
{
// socket is writable - connection established or failed
int err = 0;
socklen_t err_len = sizeof( err );
if( NET_IsSocketError( getsockopt( broker.socket, SOL_SOCKET, SO_ERROR, (char *)&err, &err_len )))
{
Con_Printf( S_ERROR "%s: getsockopt() failed\n", __func__ );
SteamBroker_Disconnect();
return;
}
else if( err != 0 )
{
Con_Printf( S_ERROR "%s: connection failed with error %d\n", __func__, err );
SteamBroker_Disconnect();
return;
}
else
{
broker.connection_timeout = 0;
Con_Printf( S_NOTE "%s: connected to broker at %s\n", __func__, cl_steam_broker_addr.string );
SteamBroker_SetState( SBRK_STATE_CONNECTED );
SteamBroker_AnnounceGameStart( GI->gamefolder );
}
}
}
static void SteamBroker_UpdateConnected( void )
{
SteamBroker_HandleDataTx( );
SteamBroker_HandleDataRx( );
}
qboolean SteamBroker_InitiateGameConnection( netadr_t serveradr, int challenge )
{
// only ipv4 supported
if( NET_NetadrType( &serveradr ) != NA_IP )
return false;
if( broker.state != SBRK_STATE_CONNECTED )
{
Con_Printf( S_WARN "%s: broker not connected\n", __func__ );
return false;
}
broker.challenge = challenge;
broker.serveradr = serveradr;
// sb_connect <ip:port> <server's steam id> <secure> <challenge>
// the message calls
// sb_connect <ip:port> <server_steamid> <secure> <challenge>
char buf[512];
int len = Q_snprintf( buf, sizeof( buf ), "sb_connect %s %"PRIu64" %s %d", NET_AdrToString( serveradr ), cls.server_steamid, cls.vac2_secure ? "true" : "false", broker.challenge );
int len = Q_snprintf( buf, sizeof( buf ), "sb_connect %s %"PRIu64" %d %d", NET_AdrToString( serveradr ), cls.server_steamid, cls.vac2_secure ? 1 : 0, challenge );
NET_SendPacket( NS_CLIENT, len, buf, broker.adr );
if( !SteamBroker_SendFrame( buf, len ))
return false;
return true;
}
void SteamBroker_TerminateGameConnection( void )
{
if( NET_NetadrType( &cls.serveradr ) != NA_IP )
if( broker.state != SBRK_STATE_CONNECTED )
return;
if( Q_stricmp( cl_ticket_generator.string, "steam" ) != 0 )
@@ -83,109 +481,52 @@ void SteamBroker_TerminateGameConnection( void )
char buf[512];
int len = Q_snprintf( buf, sizeof( buf ), "sb_disconnect %s %d", NET_AdrToString( cls.serveradr ), broker.challenge );
NET_SendPacket( NS_CLIENT, len, buf, broker.adr );
}
void SteamBroker_AnnounceGameStart( const char *gamedir )
{
if( Q_stricmp( cl_ticket_generator.string, "steam" ) != 0 )
return;
NET_Config( true, true ); // initialize sockets to be able to send packets to broker
char buf[512];
int len = Q_snprintf( buf, sizeof( buf ), "sb_gamedir %s", gamedir );
NET_SendPacket( NS_CLIENT, len, buf, broker.adr );
}
void SteamBroker_AnnounceGameShutdown( netadr_t broker_addr )
{
NET_SendPacket( NS_CLIENT, sizeof( "sb_terminate" ) - 1, "sb_terminate", broker_addr );
SteamBroker_SendFrame( buf, len );
}
void SteamBroker_Frame( void )
{
if( FBitSet( cl_steam_broker_addr.flags | cl_ticket_generator.flags, FCVAR_CHANGED ))
{
ClearBits( cl_ticket_generator.flags, FCVAR_CHANGED );
ClearBits( cl_steam_broker_addr.flags, FCVAR_CHANGED );
if( broker.state != SBRK_STATE_IDLE )
{
SteamBroker_Disconnect();
}
// reinitialize address
NET_NetadrSetType( &broker.adr, NA_UNDEFINED );
}
if( Q_stricmp( cl_ticket_generator.string, "steam" ) != 0 )
return;
qboolean restart = FBitSet( cl_steam_broker_addr.flags|cl_ticket_generator.flags, FCVAR_CHANGED );
if( !broker.addr_initialized )
// update state machine
switch( broker.state )
{
if( !SteamBroker_UpdateBrokerAddress( ))
{
Con_Printf( "%s: failed to resolve broker address \"%s\"\n", __func__, cl_steam_broker_addr.string );
return;
}
else
{
broker.addr_initialized = true;
}
case SBRK_STATE_IDLE:
SteamBroker_UpdateIdle( );
break;
case SBRK_STATE_CONNECTING:
SteamBroker_UpdateConnecting( );
break;
case SBRK_STATE_CONNECTED:
SteamBroker_UpdateConnected( );
break;
case SBRK_STATE_GAMESHUTDOWN:
// do nothing, just wait for game shutdown
break;
}
if( restart )
{
broker.announced = false;
ClearBits( cl_ticket_generator.flags, FCVAR_CHANGED );
ClearBits( cl_steam_broker_addr.flags, FCVAR_CHANGED );
}
if( !broker.announced )
{
netadr_t previous_addr = broker.adr;
if( !SteamBroker_UpdateBrokerAddress( ))
{
Con_Printf( "%s: failed to resolve broker address \"%s\"\n", __func__, cl_steam_broker_addr.string );
}
else
{
if( restart )
{
// terminate old steam broker if instance address has changed
SteamBroker_AnnounceGameShutdown( previous_addr );
}
SteamBroker_AnnounceGameStart( GI->gamefolder );
}
broker.announced = true;
}
}
void SteamBroker_HandlePacket( netadr_t from, sizebuf_t *msg )
{
// message format
// sb_connect\n<4 byte challenge><8 byte steamid><unsigned 4 byte len><len bytes ticket>
int challenge;
uint32_t len;
uint8_t ticket[2048]; // 2048 bytes according to SDK docs
if( !NET_CompareAdr( from, broker.adr ))
return;
challenge = MSG_ReadLong( msg );
if( broker.challenge != challenge ) // TODO: print error
return;
MSG_ReadBytes( msg, cls.steamid, sizeof( cls.steamid ));
len = MSG_ReadDword( msg );
if( len > sizeof( ticket )) // TODO: print error, proceed without ticket?
return;
MSG_ReadBytes( msg, ticket, len );
Con_Printf( "%s: SteamID: %"PRIu64", ticket: [%d, %d, %d, %d...]\n", __func__, *(uint64_t *)cls.steamid, ticket[0], ticket[1], ticket[2], ticket[3] );
CL_SendGoldSrcConnectPacket( broker.serveradr, challenge, ticket, len );
cls.broker_wait = false;
}
void SteamBroker_Init( void )
{
broker.announced = false;
broker.addr_initialized = false;
broker.state = SBRK_STATE_IDLE;
broker.socket = INVALID_SOCKET;
broker.rx_buffer_pos = 0;
broker.tx_buffer_pos = 0;
Cvar_RegisterVariable( &cl_steam_broker_addr );
NET_NetadrSetType( &broker.adr, NA_UNDEFINED );
}
@@ -195,5 +536,6 @@ void SteamBroker_Shutdown( void )
if( Q_stricmp( cl_ticket_generator.string, "steam" ) != 0 )
return;
SteamBroker_AnnounceGameShutdown( broker.adr );
SteamBroker_AnnounceGameShutdown( );
SteamBroker_SetState( SBRK_STATE_GAMESHUTDOWN );
}

View File

@@ -25,6 +25,7 @@ AVI PLAYING
*/
static movie_state_t *cin_state;
static int cin_texture;
/*
==================
@@ -281,6 +282,12 @@ void SCR_InitCinematic( void )
{
AVI_Initailize ();
cin_state = AVI_GetState( CIN_MAIN );
cin_texture = ref.dllFuncs.GL_CreateTexture( "*cintexture", 64, 64, NULL, TF_NOMIPMAP|TF_CLAMP );
}
int SCR_GetCinematicTexture( void )
{
return cin_texture;
}
/*

View File

@@ -360,6 +360,29 @@ qboolean V_PreRender( void )
//============================================================================
/*
====================
V_ApplyRefUnderwater
apply underwater fov effect
====================
*/
static void V_ApplyRefUnderwater( ref_viewpass_t *rvp )
{
if( !FBitSet( rvp->flags, RF_DRAW_CUBEMAP|RF_DRAW_OVERVIEW ))
return;
if( !FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE ))
return;
if( cl.local.waterlevel < 3 )
return;
rvp->fov_x = atan( tan( DEG2RAD( rvp->fov_x ) / 2 ) * ( 0.97f + sin( cl.time * 1.5f ) * 0.03f )) * 2 / (M_PI_F / 180.0f);
rvp->fov_y = atan( tan( DEG2RAD( rvp->fov_y ) / 2 ) * ( 1.03f - sin( cl.time * 1.5f ) * 0.03f )) * 2 / (M_PI_F / 180.0f);
}
/*
==================
V_RenderView
@@ -389,6 +412,7 @@ void V_RenderView( void )
clgame.dllFuncs.pfnCalcRefdef( &rp );
V_GetRefParams( &rp, &rvp );
V_RefApplyOverview( &rvp );
V_ApplyRefUnderwater( &rvp );
if( viewnum == 0 && FBitSet( rvp.flags, RF_ONLY_CLIENTDRAW ))
{

View File

@@ -761,6 +761,7 @@ void CL_AddToResourceList( resource_t *pResource, resource_t *pList );
void CL_RemoveFromResourceList( resource_t *pResource );
void CL_MoveToOnHandList( resource_t *pResource );
void CL_ClearResourceLists( void );
void CL_SendResourceList( const resource_t *list, int count );
//
// cl_debug.c
@@ -874,7 +875,6 @@ struct msurface_s *pfnTraceSurface( int ground, float *vstart, float *vend );
void CL_EnableScissor( scissor_state_t *scissor, int x, int y, int width, int height );
void CL_DisableScissor( scissor_state_t *scissor );
qboolean CL_Scissor( const scissor_state_t *scissor, float *x, float *y, float *width, float *height, float *u0, float *v0, float *u1, float *v1 );
static inline cl_entity_t *CL_EDICT_NUM( int index )
{
if( unlikely( !clgame.entities )) // not in game yet
@@ -972,6 +972,13 @@ void SCR_DrawPos( void );
void SCR_DrawEnts( void );
void SCR_DrawUserCmd( void );
//
// cl_sprite.c
//
mspriteframe_t *R_GetSpriteFrame( const model_t *pModel, int frame, float yaw );
void R_GetSpriteParms( int *frameWidth, int *frameHeight, int *numFrames, int currentFrame, const model_t *pSprite );
int R_GetSpriteTexture( const model_t *m_pSpriteModel, int frame );
//
// cl_netgraph.c
//
@@ -1201,14 +1208,14 @@ void CL_GetSecuredClientAPI( CL_EXPORT_FUNCS F );
void SteamBroker_Init( void );
void SteamBroker_Shutdown( void );
void SteamBroker_Frame( void );
void SteamBroker_HandlePacket( netadr_t from, sizebuf_t *msg );
int SteamBroker_InitiateGameConnection( netadr_t serveradr, int challenge );
qboolean SteamBroker_InitiateGameConnection( netadr_t serveradr, int challenge );
void SteamBroker_TerminateGameConnection( void );
//
// cl_video.c
//
void SCR_InitCinematic( void );
int SCR_GetCinematicTexture( void );
void SCR_FreeCinematic( void );
qboolean SCR_PlayCinematic( const char *name );
qboolean SCR_DrawCinematic( void );
@@ -1234,7 +1241,6 @@ void Key_EnumCmds_f( void );
void Key_SetKeyDest( int key_dest );
void Key_EnableTextInput( qboolean enable, qboolean force );
int Key_ToUpper( int key );
void OSK_Draw( void );
qboolean Cmd_GetKeysList( const char *s, char *completedname, int length, qboolean print_suggestions );
//

View File

@@ -2164,8 +2164,11 @@ void Con_RunConsole( void )
con.vislines = con.showlines;
}
if( FBitSet( con_charset.flags|con_fontscale.flags|con_fontnum.flags|cl_charset.flags|con_oldfont.flags, FCVAR_CHANGED ))
if( FBitSet( con_charset.flags|con_fontscale.flags|con_fontnum.flags|cl_charset.flags|con_oldfont.flags, FCVAR_CHANGED ))
{
if( con_fontscale.value < 1.0f )
Cvar_DirectSet( &con_fontscale, "1" );
// update codepage parameters
if( !Q_stricmp( con_charset.string, "cp1251" ))
{

View File

@@ -23,6 +23,8 @@ GNU General Public License for more details.
#include "client.h"
#include "const.h"
#include "triangleapi.h"
#include "r_studioint.h"
#include "mod_local.h"
#include "r_efx.h"
#include "demo_api.h"
#include "ivoicetweak.h"
@@ -105,7 +107,8 @@ static const dllfunc_t cdll_new_exports[] = // allowed only in SDK 2.3 and high
{ "IN_ClientTouchEvent", (void **)&clgame.dllFuncs.pfnTouchEvent}, // Xash3D FWGS ext
{ "IN_ClientMoveEvent", (void **)&clgame.dllFuncs.pfnMoveEvent}, // Xash3D FWGS ext
{ "IN_ClientLookEvent", (void **)&clgame.dllFuncs.pfnLookEvent}, // Xash3D FWGS ext
{ "HUD_GetSoundInterface", (void **)&clgame.dllFuncs.pfnGetSoundInterface }, // Xash3D ext
{ "HUD_GetSoundInterface", (void **)&clgame.dllFuncs.pfnGetSoundInterface }, // Xash3D FWGS ext
{ "Voice_StartChannel", (void **)&clgame.dllFuncs.pfnVoice_StartChannel }, // Xash3D FWGS ext
};
static void pfnSPR_DrawHoles( int frame, int x, int y, const wrect_t *prc );
@@ -373,13 +376,13 @@ static void SPR_DrawGeneric( int frame, float x, float y, float width, float hei
int w, h;
// assume we get sizes from image
ref.dllFuncs.R_GetSpriteParms( &w, &h, NULL, frame, clgame.ds.pSprite );
R_GetSpriteParms( &w, &h, NULL, frame, clgame.ds.pSprite );
width = w;
height = h;
}
texnum = ref.dllFuncs.R_GetSpriteTexture( clgame.ds.pSprite, frame );
texnum = R_GetSpriteTexture( clgame.ds.pSprite, frame );
if( prc )
{
@@ -1423,7 +1426,7 @@ int EXPORT pfnSPR_Frames( HSPRITE hPic )
{
int numFrames = 0;
ref.dllFuncs.R_GetSpriteParms( NULL, NULL, &numFrames, 0, CL_GetSpritePointer( hPic ));
R_GetSpriteParms( NULL, NULL, &numFrames, 0, CL_GetSpritePointer( hPic ));
return numFrames;
}
@@ -1438,7 +1441,7 @@ static int GAME_EXPORT pfnSPR_Height( HSPRITE hPic, int frame )
{
int sprHeight = 0;
ref.dllFuncs.R_GetSpriteParms( NULL, &sprHeight, NULL, frame, CL_GetSpritePointer( hPic ));
R_GetSpriteParms( NULL, &sprHeight, NULL, frame, CL_GetSpritePointer( hPic ));
return sprHeight;
}
@@ -1453,7 +1456,7 @@ static int GAME_EXPORT pfnSPR_Width( HSPRITE hPic, int frame )
{
int sprWidth = 0;
ref.dllFuncs.R_GetSpriteParms( &sprWidth, NULL, NULL, frame, CL_GetSpritePointer( hPic ));
R_GetSpriteParms( &sprWidth, NULL, NULL, frame, CL_GetSpritePointer( hPic ));
return sprWidth;
}
@@ -2172,7 +2175,7 @@ pfnGetViewModel
=============
*/
static cl_entity_t* GAME_EXPORT CL_GetViewModel( void )
static cl_entity_t *GAME_EXPORT CL_GetViewModel( void )
{
return &clgame.viewent;
}
@@ -3323,7 +3326,7 @@ int TriSpriteTexture( model_t *pSpriteModel, int frame )
{
int gl_texturenum;
if(( gl_texturenum = ref.dllFuncs.R_GetSpriteTexture( pSpriteModel, frame )) <= 0 )
if(( gl_texturenum = R_GetSpriteTexture( pSpriteModel, frame )) <= 0 )
return 0;
ref.dllFuncs.GL_Bind( XASH_TEXTURE0, gl_texturenum );
@@ -3928,6 +3931,69 @@ void CL_UnloadProgs( void )
memset( &clgame, 0, sizeof( clgame ));
}
static model_t *pfnStudio_Mod_ForName( const char *name, int crash )
{
return Mod_ForName( name, crash, false );
}
static void *pfnStudio_Mod_Extradata( model_t *mod )
{
return Mod_StudioExtradata( mod );
}
static void pfnStudio_GetAliasScale( float *x, float *y )
{
if( x ) *x = 1.0f;
if( y ) *y = 1.0f;
}
static float ***pfnStudio_GetAliasTransform( void )
{
return NULL;
}
static model_t *pfnStudio_GetChromeSprite( void )
{
return cl_sprite_shell;
}
static int pfnStudio_IsHardware( void )
{
return 1;
}
static engine_studio_api_t gStudioAPI =
{
.Mem_Calloc = Mod_Calloc,
.Cache_Check = Mod_CacheCheck,
.LoadCacheFile = Mod_LoadCacheFile,
.Mod_ForName = pfnStudio_Mod_ForName,
.Mod_Extradata = pfnStudio_Mod_Extradata,
.GetModelByIndex = CL_ModelHandle,
.GetCvar = pfnCVarGetPointer,
.GetChromeSprite = pfnStudio_GetChromeSprite,
.GetAliasScale = pfnStudio_GetAliasScale,
.StudioGetAliasTransform = pfnStudio_GetAliasTransform,
.GetViewEntity = CL_GetViewModel,
.IsHardware = pfnStudio_IsHardware,
};
static void CL_InitStudioAPI( void )
{
static r_studio_interface_t gDefaultStudioDraw;
r_studio_interface_t *pStudioDraw;
if( !ref.dllFuncs.R_StudioFillAPI( &gStudioAPI, &gDefaultStudioDraw ))
return;
pStudioDraw = &gDefaultStudioDraw;
if( clgame.dllFuncs.pfnGetStudioModelInterface )
clgame.dllFuncs.pfnGetStudioModelInterface( STUDIO_INTERFACE_VERSION, &pStudioDraw, &gStudioAPI );
ref.dllFuncs.R_StudioSetDrawInterface( pStudioDraw );
}
qboolean CL_LoadProgs( const char *name )
{
static playermove_t gpMove;
@@ -4065,7 +4131,8 @@ qboolean CL_LoadProgs( const char *name )
// initialize game
clgame.dllFuncs.pfnInit();
ref.dllFuncs.CL_InitStudioAPI();
CL_InitStudioAPI();
S_InitSoundAPI();
return true;
}

View File

@@ -188,7 +188,7 @@ void UI_ShowConnectionWarning( void )
if( cls.state != ca_connected )
return;
if( Host_IsLocalClient() )
if( Host_IsLocalClient( ))
return;
if( ++cl.lostpackets == 8 )

View File

@@ -172,8 +172,8 @@ intptr_t CL_RenderGetParm( const int parm, const int arg, const qboolean checkRe
return cl.local.waterlevel;
case PARM_LOCAL_HEALTH:
return cl.local.health;
case PARM_LOCAL_GAME:
return Host_IsLocalGame();
case PARM_SINGLEPLAYER_GAME:
return Host_IsSinglePlayerGame();
case PARM_NUMENTITIES:
return pfnNumberOfEntities();
case PARM_GET_CLIENT_PTR:
@@ -214,6 +214,11 @@ static void pfnAVI_StreamSound( movie_state_t *avi, int entnum, float fvol, floa
return; // stub, use AVI_SetParm and AVI_Think to stream AVI sound
}
static void pfnAVI_UploadRawFrame( int texture, int cols, int rows, int width, int height, const byte *data )
{
ref.dllFuncs.GL_UpdateTexture( texture, cols, rows, width, height, data, PF_BGRA_32 );
}
static render_api_t gRenderAPI =
{
pfnRenderGetParm, // GL_RenderGetParm,
@@ -243,7 +248,7 @@ static render_api_t gRenderAPI =
AVI_GetVideoInfo,
AVI_GetVideoFrameNumber,
AVI_GetVideoFrame,
NULL, // R_UploadStretchRaw,
pfnAVI_UploadRawFrame, // AVI_UploadRawFrame
AVI_FreeVideo,
AVI_IsActive,
pfnAVI_StreamSound,
@@ -283,40 +288,6 @@ static render_api_t gRenderAPI =
COM_SetRandomSeed,
};
static void R_FillRenderAPIFromRef( render_api_t *to, const ref_interface_t *from )
{
to->GetDetailScaleForTexture = from->GetDetailScaleForTexture;
to->GetExtraParmsForTexture = from->GetExtraParmsForTexture;
to->GetFrameTime = from->GetFrameTime;
to->R_SetCurrentEntity = from->R_SetCurrentEntity;
to->R_SetCurrentModel = from->R_SetCurrentModel;
to->GL_FindTexture = from->GL_FindTexture;
to->GL_TextureName = from->GL_TextureName;
to->GL_TextureData = from->GL_TextureData;
to->GL_LoadTexture = from->GL_LoadTexture;
to->GL_CreateTexture = from->GL_CreateTexture;
to->GL_LoadTextureArray = from->GL_LoadTextureArray;
to->GL_CreateTextureArray = from->GL_CreateTextureArray;
to->GL_FreeTexture = from->GL_FreeTexture;
to->DrawSingleDecal = from->DrawSingleDecal;
to->R_DecalSetupVerts = from->R_DecalSetupVerts;
to->R_EntityRemoveDecals = from->R_EntityRemoveDecals;
to->AVI_UploadRawFrame = from->AVI_UploadRawFrame;
to->GL_Bind = from->GL_Bind;
to->GL_SelectTexture = from->GL_SelectTexture;
to->GL_LoadTextureMatrix = from->GL_LoadTextureMatrix;
to->GL_TexMatrixIdentity = from->GL_TexMatrixIdentity;
to->GL_CleanUpTextureUnits = from->GL_CleanUpTextureUnits;
to->GL_TexGen = from->GL_TexGen;
to->GL_TextureTarget = from->GL_TextureTarget;
to->GL_TexCoordArrayMode = from->GL_TexCoordArrayMode;
to->GL_UpdateTexSize = from->GL_UpdateTexSize;
to->GL_DrawParticles = from->GL_DrawParticles;
to->LightVec = from->LightVec;
to->StudioGetTexture = from->StudioGetTexture;
to->GL_GetProcAddress = from->R_GetProcAddress;
}
/*
===============
R_InitRenderAPI
@@ -329,8 +300,15 @@ qboolean R_InitRenderAPI( void )
// make sure what render functions is cleared
memset( &clgame.drawFuncs, 0, sizeof( clgame.drawFuncs ));
// fill missing functions from renderer
R_FillRenderAPIFromRef( &gRenderAPI, &ref.dllFuncs );
gRenderAPI.GetDetailScaleForTexture = ref.dllFuncs.R_GetDetailScaleForTexture;
gRenderAPI.GL_FindTexture = ref.dllFuncs.GL_FindTexture;
gRenderAPI.GL_TextureName = ref.dllFuncs.GL_TextureName;
gRenderAPI.GL_TextureData = ref.dllFuncs.GL_TextureData;
gRenderAPI.GL_LoadTexture = ref.dllFuncs.GL_LoadTexture;
gRenderAPI.GL_FreeTexture = ref.dllFuncs.GL_FreeTexture;
gRenderAPI.GL_Bind = ref.dllFuncs.GL_Bind;
ref.dllFuncs.R_FillRenderAPI( &gRenderAPI );
if( clgame.dllFuncs.pfnGetRenderInterface )
{

View File

@@ -287,13 +287,6 @@ static entity_state_t *R_StudioGetPlayerState( int index )
return &cl.frames[cl.parsecountmod].playerstate[index];
}
static int pfnGetStudioModelInterface( int version, struct r_studio_interface_s **ppinterface, struct engine_studio_api_s *pstudio )
{
return clgame.dllFuncs.pfnGetStudioModelInterface ?
clgame.dllFuncs.pfnGetStudioModelInterface( version, ppinterface, pstudio ) :
0;
}
static const bpc_desc_t *pfnImage_GetPFDesc( int idx )
{
return &PFDesc[idx];
@@ -329,11 +322,6 @@ static qboolean R_Init_Video_( ref_graphic_apis_t type )
return R_Init_Video( type );
}
static mleaf_t *pfnMod_PointInLeaf( const vec3_t p, mnode_t *node )
{
// FIXME: get rid of this on next RefAPI update
return Mod_PointInLeaf( p, node, cl.models[1] );
}
static const ref_api_t gEngfuncs =
{
@@ -375,7 +363,7 @@ static const ref_api_t gEngfuncs =
Mod_SampleSizeForFace,
Mod_BoxVisible,
pfnMod_PointInLeaf,
Mod_PointInLeaf,
R_DrawWorldHull,
R_DrawModelHull,
@@ -411,7 +399,6 @@ static const ref_api_t gEngfuncs =
Mod_CacheCheck,
Mod_LoadCacheFile,
Mod_Calloc,
pfnGetStudioModelInterface,
_Mem_AllocPool,
_Mem_FreePool,
@@ -465,6 +452,7 @@ static const ref_api_t gEngfuncs =
&g_fsapi,
R_GetWindowHandle,
R_GetSpriteFrame,
};
static void R_UnloadProgs( void )
@@ -487,28 +475,78 @@ static void R_UnloadProgs( void )
memset( &ref.dllFuncs, 0, sizeof( ref.dllFuncs ));
}
static void CL_FillTriAPIFromRef( triangleapi_t *dst, const ref_interface_t *src )
static void CL_FillTriAPI( triangleapi_t *dst )
{
dst->version = TRI_API_VERSION;
dst->Begin = src->Begin;
dst->RenderMode = TriRenderMode;
dst->End = src->End;
dst->Color4f = TriColor4f;
dst->Color4ub = TriColor4ub;
dst->TexCoord2f = src->TexCoord2f;
dst->Vertex3f = src->Vertex3f;
dst->Vertex3fv = src->Vertex3fv;
dst->Brightness = TriBrightness;
dst->CullFace = TriCullFace;
dst->SpriteTexture = TriSpriteTexture;
dst->WorldToScreen = TriWorldToScreen;
dst->Fog = src->Fog;
dst->ScreenToWorld = src->ScreenToWorld;
dst->GetMatrix = src->GetMatrix;
dst->BoxInPVS = TriBoxInPVS;
dst->LightAtPoint = TriLightAtPoint;
dst->Color4fRendermode = TriColor4fRendermode;
dst->FogParams = src->FogParams;
dst->Begin = ref.dllFuncs.Begin;
dst->End = ref.dllFuncs.End;
dst->Vertex3f = ref.dllFuncs.Vertex3f;
dst->Vertex3fv = ref.dllFuncs.Vertex3fv;
ref.dllFuncs.R_FillTriAPI( dst );
}
static void R_CreateBuiltinTextures( void )
{
// default checkerboard
uint data16x16[256];
for( int y = 0; y < 16; y++ )
{
for( int x = 0; x < 16; x++ )
{
if(( y < 8 ) ^ ( x < 8 ))
data16x16[y * 16 + x] = HostFourCC( 255, 0, 255, 255 );
else
data16x16[y * 16 + x] = HostFourCC( 0, 0, 0, 255 );
}
}
ref.dllFuncs.GL_CreateTexture( REF_DEFAULT_TEXTURE, 16, 16, data16x16, 0 );
// particle texture
uint particle[64];
for( int y = 0; y < 8; y++ )
{
for( int x = 0; x < 8; x++ )
particle[y * 8 + x] = HostFourCC( 255, 255, 255, 0 ); // fill it with invisible white
}
// a1ba: moved one pixel down and to the right to make it look better with linear interpolation
// set to 0 to make it look like in goldsrc
const int corner = 1;
for( int y = corner; y < 4 + corner; y++ )
{
for( int x = corner; x < 4 + corner; x++ )
{
if(( x == corner || x == 3 + corner ) && ( y == corner || y == 3 + corner ))
continue;
particle[y * 8 + x] = HostFourCC( 255, 255, 255, 255 );
}
}
ref.dllFuncs.GL_CreateTexture( REF_PARTICLE_TEXTURE, 8, 8, particle, TF_CLAMP|TF_HAS_ALPHA );
// solid colors
uint data4x4[16];
memset( data4x4, 0xFF, sizeof( data4x4 ));
ref.dllFuncs.GL_CreateTexture( REF_WHITE_TEXTURE, 4, 4, data4x4, 0 );
for( int x = 0; x < 16; x++ )
data4x4[x] = HostFourCC( 127, 127, 127, 255 );
ref.dllFuncs.GL_CreateTexture( REF_GRAY_TEXTURE, 4, 4, data4x4, 0 );
for( int x = 0; x < 16; x++ )
data4x4[x] = HostFourCC( 0, 0, 0, 255 );
ref.dllFuncs.GL_CreateTexture( REF_BLACK_TEXTURE, 4, 4, data4x4, 0 );
}
static qboolean R_LoadProgs( const char *name )
@@ -554,8 +592,8 @@ static qboolean R_LoadProgs( const char *name )
Cvar_FullSet( "host_refloaded", "1", FCVAR_READ_ONLY );
ref.initialized = true;
// initialize TriAPI callbacks
CL_FillTriAPIFromRef( &gTriApi, &ref.dllFuncs );
R_CreateBuiltinTextures();
CL_FillTriAPI( &gTriApi );
return true;
}

View File

@@ -39,6 +39,10 @@ void IN_DeactivateMouse( void );
void IN_MouseSavePos( void );
void IN_MouseRestorePos( void );
void IN_ToggleClientMouse( int newstate, int oldstate );
void IN_GyroInit( void );
void IN_GyroCheckAvailability( void );
void IN_GyroEvent( vec3_t data );
void IN_GyroFinalizeMove( float *fw, float *side, float *dpitch, float *dyaw );
uint IN_CollectInputDevices( void );
void IN_LockInputDevices( qboolean lock );
@@ -95,6 +99,15 @@ qboolean Touch_WantVisibleCursor( void );
void Touch_NotifyResize( void );
#endif
//
// in_osk.c
//
extern convar_t osk_enable;
void OSK_Init( void );
qboolean OSK_KeyEvent( int key, int down );
void OSK_EnableTextInput( qboolean enable, qboolean force );
void OSK_Draw( void );
//
// in_joy.c
//

View File

@@ -0,0 +1,120 @@
/*
in_gyro.c - System gyroscope input code
Copyright (C) 2026 Xash3D FWGS contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
*/
#include "common.h"
#include "input.h"
#include "client.h"
static CVAR_DEFINE_AUTO( gyro_enable, "0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "enables aiming with built-in device gyroscope" );
static CVAR_DEFINE_AUTO( gyro_available, "0", FCVAR_READ_ONLY, "tells whether system gyroscope hardware is available or not" );
static CVAR_DEFINE_AUTO( gyro_pitch, "1.0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "built-in gyroscope sensitivity for looking up and down" );
static CVAR_DEFINE_AUTO( gyro_yaw, "1.0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "built-in gyroscope sensitivity for turning left and right" );
static CVAR_DEFINE_AUTO( gyro_roll, "0.0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "built-in gyroscope sensitivity when tilting the device sideways" );
static CVAR_DEFINE_AUTO( gyro_pitch_deadzone, "0.5", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "built-in gyroscope pitch axis deadzone (deg/s)" );
static CVAR_DEFINE_AUTO( gyro_yaw_deadzone, "0.5", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "built-in gyroscope yaw axis deadzone (deg/s)" );
static CVAR_DEFINE_AUTO( gyro_roll_deadzone, "0.5", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "built-in gyroscope roll axis deadzone (deg/s)" );
// stores the latest instantaneous rotation rates from built-in gyroscope
static vec3_t gyro_speed;
/*
==============
IN_GyroInit
==============
*/
void IN_GyroInit( void )
{
Cvar_RegisterVariable( &gyro_enable );
Cvar_RegisterVariable( &gyro_available );
Cvar_RegisterVariable( &gyro_pitch );
Cvar_RegisterVariable( &gyro_yaw );
Cvar_RegisterVariable( &gyro_roll );
Cvar_RegisterVariable( &gyro_pitch_deadzone );
Cvar_RegisterVariable( &gyro_yaw_deadzone );
Cvar_RegisterVariable( &gyro_roll_deadzone );
}
/*
==============
IN_GyroCheckAvailability
One-time late check called after startup and configs
==============
*/
void IN_GyroCheckAvailability( void )
{
#if XASH_SDL
if( gyro_available.value )
return;
if( SDLash_GyroIsAvailable() )
{
Cvar_FullSet( "gyro_available", "1", FCVAR_READ_ONLY );
}
#endif
}
/*
=============
IN_GyroEvent
System gyroscope events from platform
=============
*/
void IN_GyroEvent( vec3_t data )
{
VectorCopy( data, gyro_speed );
}
/*
=============
IN_GyroFinalizeMove
Apply gyro movement to view angles
=============
*/
void IN_GyroFinalizeMove( float *fw, float *side, float *dpitch, float *dyaw )
{
platform_orientation_t orient;
float orient_scale = 1.0f;
if( !gyro_enable.value || !gyro_available.value )
return;
orient = Platform_GetDisplayOrientation();
if( orient == ORIENTATION_LANDSCAPE_FLIPPED )
orient_scale = -1.0f;
// In Landscape mode axes are swapped relative to natural (Portrait) orientation
// Y axis rotation becomes Pitch (up/down)
// X axis rotation becomes Yaw (left/right)
float pitch_speed = -orient_scale * gyro_speed[1] * ( 180.0f / M_PI );
float yaw_speed = orient_scale * gyro_speed[0] * ( 180.0f / M_PI );
float roll_speed = orient_scale * gyro_speed[2] * ( 180.0f / M_PI );
if( fabs( pitch_speed ) < gyro_pitch_deadzone.value )
pitch_speed = 0.0f;
if( fabs( yaw_speed ) < gyro_yaw_deadzone.value )
yaw_speed = 0.0f;
if( fabs( roll_speed ) < gyro_roll_deadzone.value )
roll_speed = 0.0f;
*dpitch -= gyro_pitch.value * pitch_speed * host.realframetime;
*dyaw += gyro_yaw.value * yaw_speed * host.realframetime;
*dyaw += gyro_roll.value * roll_speed * host.realframetime;
VectorClear( gyro_speed );
}

View File

@@ -65,6 +65,10 @@ static CVAR_DEFINE_AUTO( joy_calibrated, "0", FCVAR_READ_ONLY, "tells whether cu
static CVAR_DEFINE_AUTO( joy_gyro_pitch, "1.0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "gyroscope sensitivity for looking up and down" );
static CVAR_DEFINE_AUTO( joy_gyro_yaw, "1.0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "gyroscope sensitivity for turning left and right" );
static CVAR_DEFINE_AUTO( joy_gyro_roll, "0.0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "gyroscope sensitivity when tilting the device sideways" );
static CVAR_DEFINE_AUTO( joy_gyro_pitch_deadzone, "0.5", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "gyroscope pitch axis deadzone (deg/s)" );
static CVAR_DEFINE_AUTO( joy_gyro_yaw_deadzone, "0.5", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "gyroscope yaw axis deadzone (deg/s)" );
static CVAR_DEFINE_AUTO( joy_gyro_roll_deadzone, "0.5", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "gyroscope roll axis deadzone (deg/s)" );
static CVAR_DEFINE_AUTO( joy_gyro_enable, "1", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "enables aiming with gamepad gyroscope" );
static CVAR_DEFINE_AUTO( joy_debug, "0", 0, "visualize gamepad axes and buttons" );
// stores the latest instantaneous gyroscope rotation rates (in rad/s) from platform input
@@ -194,7 +198,7 @@ static int Joy_GetHatValueForAxis( const engineAxis_t engineAxis )
positive = JOY_HAT_RIGHT;
break;
case JOY_AXIS_FWD:
threshold = joy_side_key_threshold.value;
threshold = joy_forward_key_threshold.value;
negative = JOY_HAT_UP;
positive = JOY_HAT_DOWN;
break;
@@ -315,7 +319,7 @@ void Joy_FinalizeMove( float *fw, float *side, float *dpitch, float *dyaw )
const char *bind = joy_axis_binding.string;
size_t i;
for( i = 0; bind[i]; i++ )
for( i = 0; bind[i] && i < MAX_AXES; i++ )
{
switch( bind[i] )
{
@@ -337,12 +341,19 @@ void Joy_FinalizeMove( float *fw, float *side, float *dpitch, float *dyaw )
*dpitch += joy_pitch.value * (float)joyaxis[JOY_AXIS_PITCH].val/(float)SHRT_MAX * host.realframetime;
*dyaw -= joy_yaw.value * (float)joyaxis[JOY_AXIS_YAW ].val/(float)SHRT_MAX * host.realframetime;
if( joy_have_gyro.value && (int)joy_calibrated.value == JOY_CALIBRATED )
if( joy_gyro_enable.value && joy_have_gyro.value && (int)joy_calibrated.value == JOY_CALIBRATED )
{
float pitch_speed = joy_gyro_speed[0] * ( 180.0f / M_PI );
float yaw_speed = joy_gyro_speed[1] * ( 180.0f / M_PI );
float roll_speed = joy_gyro_speed[2] * ( 180.0f / M_PI );
if( fabs( pitch_speed ) < joy_gyro_pitch_deadzone.value )
pitch_speed = 0.0f;
if( fabs( yaw_speed ) < joy_gyro_yaw_deadzone.value )
yaw_speed = 0.0f;
if( fabs( roll_speed ) < joy_gyro_roll_deadzone.value )
roll_speed = 0.0f;
*dpitch -= joy_gyro_pitch.value * pitch_speed * host.realframetime;
*dyaw += joy_gyro_yaw.value * yaw_speed * host.realframetime;
*dyaw += joy_gyro_roll.value * roll_speed * host.realframetime;
@@ -467,6 +478,12 @@ void Joy_DrawDebug( void )
if( joy_have_gyro.value )
{
static const char *gyronames[3] = { "GYRO P", "GYRO Y", "GYRO R" };
static const convar_t *const gyro_deadzone[3] =
{
&joy_gyro_pitch_deadzone,
&joy_gyro_yaw_deadzone,
&joy_gyro_roll_deadzone,
};
for( int i = 0; i < 3; i++ )
{
@@ -485,10 +502,22 @@ void Joy_DrawDebug( void )
float filled = fval * halfbar_w;
ref.dllFuncs.FillRGBA( kRenderTransTexture, center + Q_min( 0, filled ), y, fabs( filled ), bar_h, bar_fillcolor[0], bar_fillcolor[1], bar_fillcolor[2], bar_fillcolor[3] );
// deadzone in deg/s; display scale is +-5 rad/s
float fthreshold = gyro_deadzone[i]->value * ( M_PI / 180.0f ) / 5.0f;
fthreshold = bound( 0.0f, fthreshold, 1.0f );
if( fthreshold > 0.0f )
{
float thresh_x = fthreshold * halfbar_w;
ref.dllFuncs.FillRGBA( kRenderTransTexture, center - thresh_x, y, thresh_x * 2, bar_h, 180, 40, 40, 120 );
ref.dllFuncs.FillRGBA( kRenderTransTexture, center + thresh_x, y, 1, bar_h, 255, 200, 0, 220 );
ref.dllFuncs.FillRGBA( kRenderTransTexture, center - thresh_x, y, 1, bar_h, 255, 200, 0, 220 );
}
// mark the center
ref.dllFuncs.FillRGBA( kRenderTransTexture, center, y, 1, bar_h, 180, 180, 180, 220 );
// gyro doesn't have deadzones or prevval like axes
// gyro doesn't have prevval like axes
y += bar_h + 2;
}
}
@@ -568,10 +597,16 @@ void Joy_Init( void )
Cvar_RegisterVariable( &joy_have_gyro );
Cvar_RegisterVariable( &joy_calibrated );
Cvar_RegisterVariable( &joy_enable );
Cvar_RegisterVariable( &joy_gyro_enable );
Cvar_RegisterVariable( &joy_gyro_pitch );
Cvar_RegisterVariable( &joy_gyro_yaw );
Cvar_RegisterVariable( &joy_gyro_roll );
Cvar_RegisterVariable( &joy_gyro_pitch_deadzone );
Cvar_RegisterVariable( &joy_gyro_yaw_deadzone );
Cvar_RegisterVariable( &joy_gyro_roll_deadzone );
Cvar_RegisterVariable( &joy_debug );
// renamed from -nojoy to -noenginejoy to not conflict with

View File

@@ -158,9 +158,6 @@ static const keyname_t keynames[] =
{ "INTERNATIONAL9", K_INTERNATIONAL + 8, "" },
};
static void OSK_EnableTextInput( qboolean enable, qboolean force );
static qboolean OSK_KeyEvent( int key, int down );
static CVAR_DEFINE_AUTO( osk_enable, "0", FCVAR_ARCHIVE|FCVAR_FILTERABLE, "enable built-in on-screen keyboard" );
static CVAR_DEFINE_AUTO( key_rotate, "0", FCVAR_ARCHIVE|FCVAR_FILTERABLE, "rotate arrow keys (0-3)" );
/*
@@ -200,7 +197,14 @@ static int Key_StringToKeynum( const char *str )
// check for hex code
if( str[0] == '0' && str[1] == 'x' )
return Q_atoi( str );
{
int key = Q_atoi( str );
if( key >= ARRAYSIZE( keys ))
return -1;
return key;
}
// scan for a text match
for( i = 0; i < ARRAYSIZE( keynames ); i++ )
@@ -520,7 +524,7 @@ qboolean Cmd_GetKeysList( const char *s, char *completedname, int length, qboole
const char *keyname = Key_KeynumToString( i );
if(( *s == '*' ) || !Q_strnicmp( keyname, s, len ))
Q_strncpy( keys_strings[numkeys++], keyname, sizeof( keys[0] ));
Q_strncpy( keys_strings[numkeys++], keyname, sizeof( keys_strings[0] ));
}
if( !numkeys ) return false;
@@ -579,7 +583,6 @@ void Key_Init( void )
for( i = 0; i < ARRAYSIZE( keynames ); i++ )
Key_SetBinding( keynames[i].keynum, keynames[i].binding );
Cvar_RegisterVariable( &osk_enable );
Cvar_RegisterVariable( &key_rotate );
}
@@ -985,288 +988,3 @@ int Key_ToUpper( int keynum )
return Q_toupper( keynum );
}
/* On-screen keyboard:
*
* 4 lines with 13 buttons each
* Left trigger == backspace
* Right trigger == space
* Any button press is button press on keyboard
*
* Our layout:
* 0 1 2 3 4 5 6 7 8 9 10 11 12
* +--+--+--+--+--+--+--+--+--+--+--+--+--+
* |` |1 |2 |3 |4 |5 |6 |7 |8 |9 |0 |- |= | 0
* +--+--+--+--+--+--+--+--+--+--+--+--+--+
* |q |w |e |r |t |y |u |i |o |p |[ |] |\ | 1
* +--+--+--+--+--+--+--+--+--+--+--+--+--+
* |CL|a |s |d |f |g |h |j |k |l |; |' |BS| 2
* +--+--+--+--+--+--+--+--+--+--+--+--+--+
* |SH|z |x |c |v |b |n |m |, |. |/ |SP|EN| 3
* +--+--+--+--+--+--+--+--+--+--+--+--+--+
*/
#define MAX_OSK_ROWS 13
#define MAX_OSK_LINES 4
enum
{
OSK_DEFAULT = 0,
OSK_UPPER, // on caps, shift
/*
OSK_RUSSIAN,
OSK_RUSSIAN_UPPER,
*/
OSK_LAST
};
enum
{
OSK_TAB = 16,
OSK_SHIFT,
OSK_BACKSPACE,
OSK_ENTER,
OSK_SPECKEY_LAST
};
static const char *osk_keylayout[][4] =
{
{
"`1234567890-=", // 13
"qwertyuiop[]\\", // 13
"\x10" "asdfghjkl;'" "\x12", // 11 + caps on a left, enter on a right
"\x11" "zxcvbnm,./ " "\x13" // 10 + esc on left + shift on a left/right
},
{
"~!@#$%^&*()_+",
"QWERTYUIOP{}|",
"\x10" "ASDFGHJKL:\"" "\x12",
"\x11" "ZXCVBNM<>? " "\x13"
}
};
struct osk_s
{
qboolean enable;
int curlayout;
qboolean shift;
qboolean sending;
struct {
signed char x;
signed char y;
char val;
} curbutton;
} osk;
static qboolean OSK_KeyEvent( int key, int down )
{
if( !osk.enable || !osk_enable.value )
return false;
if( osk.sending )
{
osk.sending = false;
return false;
}
if( osk.curbutton.val == 0 )
{
if( key == K_ENTER || key == K_A_BUTTON )
{
osk.curbutton.val = osk_keylayout[osk.curlayout][osk.curbutton.y][osk.curbutton.x];
return true;
}
return false;
}
switch ( key )
{
case K_A_BUTTON:
case K_ENTER:
switch( osk.curbutton.val )
{
case OSK_ENTER:
osk.sending = true;
Key_Event( K_ENTER, down );
//osk_enable = false; // TODO: handle multiline
break;
case OSK_SHIFT:
if( !down )
break;
if( osk.curlayout & 1 )
osk.curlayout--;
else
osk.curlayout++;
osk.shift = true;
osk.curbutton.val = osk_keylayout[osk.curlayout][osk.curbutton.y][osk.curbutton.x];
break;
case OSK_BACKSPACE:
Key_Event( K_BACKSPACE, down ); break;
case OSK_TAB:
Key_Event( K_TAB, down ); break;
default:
{
int ch;
if( !down )
{
if( osk.shift && osk.curlayout & 1 )
osk.curlayout--;
osk.shift = false;
osk.curbutton.val = osk_keylayout[osk.curlayout][osk.curbutton.y][osk.curbutton.x];
break;
}
ch = (byte)osk.curbutton.val;
// do not pass UTF-8 sequence into the engine, convert it here
if( !cls.accept_utf8 )
ch = Con_UtfProcessCharForce( ch );
if( !ch )
break;
CL_CharEvent( ch );
break;
}
}
break;
case K_UPARROW:
if( down && --osk.curbutton.y < 0 )
{
osk.curbutton.y = MAX_OSK_LINES - 1;
osk.curbutton.val = 0;
return true;
}
break;
case K_DOWNARROW:
if( down && ++osk.curbutton.y >= MAX_OSK_LINES )
{
osk.curbutton.y = 0;
osk.curbutton.val = 0;
return true;
}
break;
case K_LEFTARROW:
if( down && --osk.curbutton.x < 0 )
osk.curbutton.x = MAX_OSK_ROWS - 1;
break;
case K_RIGHTARROW:
if( down && ++osk.curbutton.x >= MAX_OSK_ROWS )
osk.curbutton.x = 0;
break;
default:
return false;
}
osk.curbutton.val = osk_keylayout[osk.curlayout][osk.curbutton.y][osk.curbutton.x];
return true;
}
/*
=============
OSK_EnableTextInput
Enables built-in IME
=============
*/
static void OSK_EnableTextInput( qboolean enable, qboolean force )
{
qboolean old = osk.enable;
osk.enable = enable;
if( osk.enable && (!old || force) )
{
osk.curlayout = 0;
osk.curbutton.val = osk_keylayout[osk.curlayout][osk.curbutton.y][osk.curbutton.x];
}
}
#define X_START 0.1347475f
#define Y_START 0.567f
#define X_STEP 0.05625
#define Y_STEP 0.0825
/*
============
Joy_DrawSymbolButton
Draw button with symbol on it
============
*/
static void OSK_DrawSymbolButton( int symb, float x, float y, float width, float height )
{
cl_font_t *font = Con_GetCurFont();
byte color[] = { 255, 255, 255, 255 };
int x1 = x * refState.width,
y1 = y * refState.height,
w = width * refState.width,
h = height * refState.height;
if( symb == osk.curbutton.val )
ref.dllFuncs.FillRGBA( kRenderTransTexture, x1, y1, w, h, 255, 160, 0, 100 );
if( !symb || symb == ' ' || (symb >= OSK_TAB && symb < OSK_SPECKEY_LAST ) )
return;
CL_DrawCharacter(
x1 + width * 0.4 * refState.width,
y1 + height * 0.4 * refState.height,
symb, color, font, 0 );
}
/*
=============
Joy_DrawSpecialButton
Draw special button, like shift, enter or esc
=============
*/
static void OSK_DrawSpecialButton( const char *name, float x, float y, float width, float height )
{
byte color[] = { 0, 255, 0, 255 };
Con_DrawString(
x * refState.width + width * 0.4 * refState.width,
y * refState.height + height * 0.4 * refState.height,
name,
color );
}
/*
=============
Joy_DrawOnScreenKeyboard
Draw on screen keyboard, if enabled
=============
*/
void OSK_Draw( void )
{
const char **curlayout = osk_keylayout[osk.curlayout]; // shortcut :)
float x, y;
int i, j;
if( !osk.enable || !osk_enable.value || !osk.curbutton.val )
return;
// draw keyboard
ref.dllFuncs.FillRGBA( kRenderTransTexture, X_START * refState.width, Y_START * refState.height,
X_STEP * MAX_OSK_ROWS * refState.width,
Y_STEP * MAX_OSK_LINES * refState.height, 100, 100, 100, 100 );
OSK_DrawSpecialButton( "-]", X_START, Y_START + Y_STEP * 2, X_STEP, Y_STEP );
OSK_DrawSpecialButton( "<-", X_START + X_STEP * 12, Y_START + Y_STEP * 2, X_STEP, Y_STEP );
OSK_DrawSpecialButton( "sh", X_START, Y_START + Y_STEP * 3, X_STEP, Y_STEP );
OSK_DrawSpecialButton( "en", X_START + X_STEP * 12, Y_START + Y_STEP * 3, X_STEP, Y_STEP );
for( y = Y_START, j = 0; j < MAX_OSK_LINES; j++, y += Y_STEP )
for( x = X_START, i = 0; i < MAX_OSK_ROWS; i++, x += X_STEP )
OSK_DrawSymbolButton( curlayout[j][i], x, y, X_STEP, Y_STEP );
}

View File

@@ -0,0 +1,312 @@
/*
in_osk.c - on-screen keyboard input
Copyright (C) 2016-2026 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.
*/
#include "common.h"
#include "input.h"
#include "client.h"
CVAR_DEFINE_AUTO( osk_enable, "0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "enable built-in on-screen keyboard" );
/* On-screen keyboard:
*
* 4 lines with 13 buttons each
* Left trigger == backspace
* Right trigger == space
* Any button press is button press on keyboard
*
* Our layout:
* 0 1 2 3 4 5 6 7 8 9 10 11 12
* +--+--+--+--+--+--+--+--+--+--+--+--+--+
* |` |1 |2 |3 |4 |5 |6 |7 |8 |9 |0 |- |= | 0
* +--+--+--+--+--+--+--+--+--+--+--+--+--+
* |q |w |e |r |t |y |u |i |o |p |[ |] |\ | 1
* +--+--+--+--+--+--+--+--+--+--+--+--+--+
* |CL|a |s |d |f |g |h |j |k |l |; |' |BS| 2
* +--+--+--+--+--+--+--+--+--+--+--+--+--+
* |SH|z |x |c |v |b |n |m |, |. |/ |SP|EN| 3
* +--+--+--+--+--+--+--+--+--+--+--+--+--+
*/
#define MAX_OSK_ROWS 13
#define MAX_OSK_LINES 4
enum
{
OSK_DEFAULT = 0,
OSK_UPPER, // on caps, shift
/*
OSK_RUSSIAN,
OSK_RUSSIAN_UPPER,
*/
OSK_LAST
};
enum
{
OSK_TAB = 16,
OSK_SHIFT,
OSK_BACKSPACE,
OSK_ENTER,
OSK_SPECKEY_LAST
};
static const char *osk_keylayout[][4] =
{
{
"`1234567890-=", // 13
"qwertyuiop[]\\", // 13
"\x10" "asdfghjkl;'" "\x12", // 11 + caps on a left, enter on a right
"\x11" "zxcvbnm,./ " "\x13" // 10 + esc on left + shift on a left/right
},
{
"~!@#$%^&*()_+",
"QWERTYUIOP{}|",
"\x10" "ASDFGHJKL:\"" "\x12",
"\x11" "ZXCVBNM<>? " "\x13"
}
};
static struct osk_s
{
qboolean enable;
int curlayout;
qboolean shift;
qboolean sending;
struct
{
signed char x;
signed char y;
char val;
} curbutton;
} osk;
qboolean OSK_KeyEvent( int key, int down )
{
if( !osk.enable || !osk_enable.value )
return false;
if( osk.sending )
{
osk.sending = false;
return false;
}
if( osk.curbutton.val == 0 )
{
if( key == K_ENTER || key == K_A_BUTTON )
{
osk.curbutton.val = osk_keylayout[osk.curlayout][osk.curbutton.y][osk.curbutton.x];
return true;
}
return false;
}
switch( key )
{
case K_A_BUTTON:
case K_ENTER:
switch( osk.curbutton.val )
{
case OSK_ENTER:
osk.sending = true;
Key_Event( K_ENTER, down );
// osk_enable = false; // TODO: handle multiline
break;
case OSK_SHIFT:
if( !down )
break;
if( osk.curlayout & 1 )
osk.curlayout--;
else
osk.curlayout++;
osk.shift = true;
osk.curbutton.val = osk_keylayout[osk.curlayout][osk.curbutton.y][osk.curbutton.x];
break;
case OSK_BACKSPACE:
Key_Event( K_BACKSPACE, down );
break;
case OSK_TAB:
Key_Event( K_TAB, down );
break;
default:
{
int ch;
if( !down )
{
if( osk.shift && osk.curlayout & 1 )
osk.curlayout--;
osk.shift = false;
osk.curbutton.val = osk_keylayout[osk.curlayout][osk.curbutton.y][osk.curbutton.x];
break;
}
ch = (byte)osk.curbutton.val;
// do not pass UTF-8 sequence into the engine, convert it here
if( !cls.accept_utf8 )
ch = Con_UtfProcessCharForce( ch );
if( !ch )
break;
CL_CharEvent( ch );
break;
}
}
break;
case K_UPARROW:
if( down && --osk.curbutton.y < 0 )
{
osk.curbutton.y = MAX_OSK_LINES - 1;
osk.curbutton.val = 0;
return true;
}
break;
case K_DOWNARROW:
if( down && ++osk.curbutton.y >= MAX_OSK_LINES )
{
osk.curbutton.y = 0;
osk.curbutton.val = 0;
return true;
}
break;
case K_LEFTARROW:
if( down && --osk.curbutton.x < 0 )
osk.curbutton.x = MAX_OSK_ROWS - 1;
break;
case K_RIGHTARROW:
if( down && ++osk.curbutton.x >= MAX_OSK_ROWS )
osk.curbutton.x = 0;
break;
default:
return false;
}
osk.curbutton.val = osk_keylayout[osk.curlayout][osk.curbutton.y][osk.curbutton.x];
return true;
}
/*
=============
OSK_EnableTextInput
Enables built-in IME
=============
*/
void OSK_EnableTextInput( qboolean enable, qboolean force )
{
qboolean old = osk.enable;
osk.enable = enable;
if( osk.enable && ( !old || force ))
{
osk.curlayout = 0;
osk.curbutton.val = osk_keylayout[osk.curlayout][osk.curbutton.y][osk.curbutton.x];
}
}
#define X_START 0.1347475f
#define Y_START 0.567f
#define X_STEP 0.05625
#define Y_STEP 0.0825
/*
============
OSK_DrawSymbolButton
Draw button with symbol on it
============
*/
static void OSK_DrawSymbolButton( int symb, float x, float y, float width, float height )
{
cl_font_t *font = Con_GetCurFont();
byte color[] = { 255, 255, 255, 255 };
int x1 = x * refState.width,
y1 = y * refState.height,
w = width * refState.width,
h = height * refState.height;
if( symb == osk.curbutton.val )
ref.dllFuncs.FillRGBA( kRenderTransTexture, x1, y1, w, h, 255, 160, 0, 100 );
if( !symb || symb == ' ' || ( symb >= OSK_TAB && symb < OSK_SPECKEY_LAST ))
return;
CL_DrawCharacter(
x1 + width * 0.4 * refState.width,
y1 + height * 0.4 * refState.height,
symb, color, font, 0 );
}
/*
=============
OSK_DrawSpecialButton
Draw special button, like shift, enter or esc
=============
*/
static void OSK_DrawSpecialButton( const char *name, float x, float y, float width, float height )
{
byte color[] = { 0, 255, 0, 255 };
Con_DrawString(
x * refState.width + width * 0.4 * refState.width,
y * refState.height + height * 0.4 * refState.height,
name,
color );
}
/*
=============
OSK_Draw
Draw on screen keyboard, if enabled
=============
*/
void OSK_Draw( void )
{
const char **curlayout = osk_keylayout[osk.curlayout]; // shortcut :)
float x, y;
int i, j;
if( !osk.enable || !osk_enable.value || !osk.curbutton.val )
return;
// draw keyboard
ref.dllFuncs.FillRGBA( kRenderTransTexture, X_START * refState.width, Y_START * refState.height,
X_STEP * MAX_OSK_ROWS * refState.width,
Y_STEP * MAX_OSK_LINES * refState.height, 100, 100, 100, 100 );
OSK_DrawSpecialButton( "-]", X_START, Y_START + Y_STEP * 2, X_STEP, Y_STEP );
OSK_DrawSpecialButton( "<-", X_START + X_STEP * 12, Y_START + Y_STEP * 2, X_STEP, Y_STEP );
OSK_DrawSpecialButton( "sh", X_START, Y_START + Y_STEP * 3, X_STEP, Y_STEP );
OSK_DrawSpecialButton( "en", X_START + X_STEP * 12, Y_START + Y_STEP * 3, X_STEP, Y_STEP );
for( y = Y_START, j = 0; j < MAX_OSK_LINES; j++, y += Y_STEP )
for( x = X_START, i = 0; i < MAX_OSK_ROWS; i++, x += X_STEP )
OSK_DrawSymbolButton( curlayout[j][i], x, y, X_STEP, Y_STEP );
}
void OSK_Init( void )
{
Cvar_RegisterVariable( &osk_enable );
}

View File

@@ -185,10 +185,10 @@ void Touch_NotifyResize( void )
static inline float Touch_AspectRatio( void )
{
if( touch.config_aspect_ratio )
if( touch.config_aspect_ratio >= 0.25f )
return touch.config_aspect_ratio;
if( touch.actual_aspect_ratio )
if( touch.actual_aspect_ratio >= 0.25f )
return touch.actual_aspect_ratio;
if( refState.width && refState.height )
@@ -369,7 +369,7 @@ static void Touch_ExportConfig_f( void )
string profilebase;
COM_FileBase( name, profilebase, sizeof( profilebase ));
Q_snprintf( profilename, sizeof( profilebase ), "touch_profiles/%s (copy).cfg", profilebase );
Q_snprintf( profilename, sizeof( profilename ), "touch_profiles/%s (copy).cfg", profilebase );
}
else Q_strncpy( profilename, name, sizeof( profilename ));
@@ -588,7 +588,7 @@ static void Touch_RemoveButtonFromList( touchbuttonlist_t *list, const char *nam
IN_TouchEditClear();
while(( button = Touch_FindFirst( &touch.list_user, name, privileged )))
while(( button = Touch_FindFirst( list, name, privileged )))
{
if( button->prev )
button->prev->next = button->next;
@@ -860,8 +860,7 @@ void Touch_AddClientButton( const char *name, const char *texture, const char *c
if( !touch.initialized )
return;
if( round )
IN_TouchCheckCoords( &x1, &y1, &x2, &y2 );
IN_TouchCheckCoords( &x1, &y1, &x2, &y2 );
if( round == round_aspect )
y2 = y1 + ( x2 - x1 ) / (Touch_AspectRatio()) * aspect;
@@ -1068,7 +1067,7 @@ static void Touch_InitEditor( void )
temp = Touch_AddButton( &touch.list_edit, "close", "touch_default/edit_close", "touch_disableedit", 0, y, x, y + 0.1f, color, true );
SetBits( temp->flags, TOUCH_FL_NOEDIT );
temp = Touch_AddButton( &touch.list_edit, "close", "#Close and save", "", x, y, x + 0.2f, y + 0.1f, color, true );
temp = Touch_AddButton( &touch.list_edit, "close_label", "#Close and save", "", x, y, x + 0.2f, y + 0.1f, color, true );
SetBits( temp->flags, TOUCH_FL_NOEDIT );
y += 0.2f;
@@ -1076,7 +1075,7 @@ static void Touch_InitEditor( void )
temp = Touch_AddButton( &touch.list_edit, "cancel", "touch_default/edit_reset", "touch_reloadconfig", 0, y, x, y + 0.1f, color, true );
SetBits( temp->flags, TOUCH_FL_NOEDIT );
temp = Touch_AddButton( &touch.list_edit, "close", "#Cancel and reset", "", x, y, x + 0.2f, y + 0.1f, color, true );
temp = Touch_AddButton( &touch.list_edit, "cancel_label", "#Cancel and reset", "", x, y, x + 0.2f, y + 0.1f, color, true );
SetBits( temp->flags, TOUCH_FL_NOEDIT );
y += 0.2f;
@@ -1256,12 +1255,21 @@ static void Touch_DrawTexture( float x1, float y1, float x2, float y2, int textu
0, 0, 1, 1, texture );
}
#define GRID_COUNT_X ((int)touch_grid_count.value )
#define GRID_COUNT_Y (((int)touch_grid_count.value ) * Touch_AspectRatio( ))
#define GRID_X ( 1.0f / GRID_COUNT_X )
#define GRID_Y ( 1.0f / Touch_AspectRatio() / GRID_COUNT_X )
#define GRID_ROUND_X( x ) ((float)round(( x ) * GRID_COUNT_X ) / GRID_COUNT_X )
#define GRID_ROUND_Y( x ) ((float)round(( x ) * GRID_COUNT_Y ) / GRID_COUNT_Y )
static inline int Touch_GridCountX( void )
{
return Q_max((int)touch_grid_count.value, 1 );
}
static inline int Touch_GridCountY( void )
{
float grid_count_y = touch_grid_count.value * Touch_AspectRatio();
return Q_max((int)grid_count_y, 1 );
}
#define GRID_X ( 1.0f / Touch_GridCountX( ))
#define GRID_Y ( 1.0f / Touch_GridCountY( ))
#define GRID_ROUND_X( x ) ((float)round(( x ) * Touch_GridCountX() ) / Touch_GridCountX())
#define GRID_ROUND_Y( x ) ((float)round(( x ) * Touch_GridCountY() ) / Touch_GridCountY())
static void IN_TouchCheckCoords( float *x1, float *y1, float *x2, float *y2 )
{

View File

@@ -445,6 +445,10 @@ void IN_Init( void )
{
IN_StartupMouse( );
IN_GyroInit();
OSK_Init();
Joy_Init(); // common joystick support init
Touch_Init();
@@ -560,6 +564,7 @@ static void IN_CollectInput( float *forward, float *side, float *pitch, float *y
#endif
}
IN_GyroFinalizeMove( forward, side, pitch, yaw );
Joy_FinalizeMove( forward, side, pitch, yaw );
Touch_GetMove( forward, side, pitch, yaw );

View File

@@ -15,6 +15,7 @@ GNU General Public License for more details.
#include "common.h"
#include "client.h"
#include "mod_local.h"
#include "net_encode.h"
#include "cl_tent.h"
#include "shake.h"
@@ -704,13 +705,35 @@ CL_ParseResourceRequest
==================
*/
static void CL_ParseResourceRequest( sizebuf_t *msg )
void CL_SendResourceList( const resource_t *list, int count )
{
byte buffer[MAX_INIT_MSG];
int i, arg, nStartIndex;
sizebuf_t sbuf;
int i;
MSG_Init( &sbuf, "ResourceBlock", buffer, sizeof( buffer ));
MSG_BeginClientCmd( &sbuf, clc_resourcelist );
MSG_WriteShort( &sbuf, count );
for( i = 0; i < count; i++ )
{
MSG_WriteString( &sbuf, list[i].szFileName );
MSG_WriteByte( &sbuf, list[i].type );
MSG_WriteShort( &sbuf, list[i].nIndex );
MSG_WriteLong( &sbuf, list[i].nDownloadSize );
MSG_WriteByte( &sbuf, list[i].ucFlags );
if( FBitSet( list[i].ucFlags, RES_CUSTOM ))
MSG_WriteBytes( &sbuf, list[i].rgucMD5_hash, 16 );
}
Netchan_CreateFragments( &cls.netchan, &sbuf );
Netchan_FragSend( &cls.netchan );
}
static void CL_ParseResourceRequest( sizebuf_t *msg )
{
int arg, nStartIndex;
arg = MSG_ReadLong( msg );
nStartIndex = MSG_ReadLong( msg );
@@ -718,31 +741,10 @@ static void CL_ParseResourceRequest( sizebuf_t *msg )
if( cl.servercount != arg )
return;
if( nStartIndex < 0 && nStartIndex > cl.num_resources )
if( nStartIndex < 0 || nStartIndex >= cl.num_resources )
return;
MSG_BeginClientCmd( &sbuf, clc_resourcelist );
MSG_WriteShort( &sbuf, cl.num_resources );
for( i = nStartIndex; i < cl.num_resources; i++ )
{
MSG_WriteString( &sbuf, cl.resourcelist[i].szFileName );
MSG_WriteByte( &sbuf, cl.resourcelist[i].type );
MSG_WriteShort( &sbuf, cl.resourcelist[i].nIndex );
MSG_WriteLong( &sbuf, cl.resourcelist[i].nDownloadSize );
MSG_WriteByte( &sbuf, cl.resourcelist[i].ucFlags );
if( FBitSet( cl.resourcelist[i].ucFlags, RES_CUSTOM ))
MSG_WriteBytes( &sbuf, cl.resourcelist[i].rgucMD5_hash, 16 );
}
// a1ba: useless check? MSG_BeginClientCmd and MSG_WriteShort will always
// write to the buffer
// if( MSG_GetNumBytesWritten( &sbuf ) > 0 )
{
Netchan_CreateFragments( &cls.netchan, &sbuf );
Netchan_FragSend( &cls.netchan );
}
CL_SendResourceList( &cl.resourcelist[nStartIndex], cl.num_resources - nStartIndex );
}
/*
@@ -1738,6 +1740,8 @@ void CL_RegisterResources( sizebuf_t *msg, connprotocol_t proto )
// tell rendering system we have a new set of models.
ref.dllFuncs.R_NewMap ();
Mod_LoadDetailTextures( cl.worldmodel );
// check if this map must start from dark screen
CL_StartDark ();

View File

@@ -514,17 +514,26 @@ static void CL_ParseSoundPacketGS( sizebuf_t *msg )
static void CL_ParseSpawnStaticSound( sizebuf_t *msg )
{
vec3_t pos;
int handle, entnum, pitch, flags;
int sound, entnum, pitch, flags;
float volume, attn;
sound_t handle = 0;
MSG_ReadVec3Coord( msg, pos );
handle = MSG_ReadShort( msg );
sound = MSG_ReadShort( msg );
volume = MSG_ReadByte( msg ) * ( 1.0f / 255.0f );
attn = MSG_ReadByte( msg ) * ( 1.0f / 64.0f );
entnum = MSG_ReadShort( msg );
pitch = MSG_ReadByte( msg );
flags = MSG_ReadByte( msg );
if( FBitSet( flags, SND_SENTENCE ))
{
char sentenceName[32];
Q_snprintf( sentenceName, sizeof( sentenceName ), "!%i", sound );
handle = S_RegisterSound( sentenceName );
}
else handle = cl.sound_index[sound]; // see precached sound
S_AmbientSound( pos, entnum, handle, volume, attn, pitch, flags );
}

View File

@@ -99,6 +99,7 @@ void SX_RoomFX( portable_samplepair_t *paint, int num_samples );
void SX_ClearState( void );
qboolean S_Init( void );
qboolean S_InitSoundAPI( void );
void S_Shutdown( void );
void S_SoundList_f( void );
void S_SoundInfo_f( void );
@@ -111,7 +112,7 @@ int S_GetCurrentDynamicSounds( soundlist_t *pout, int size );
sfx_t *S_GetSfxByHandle( sound_t handle );
rawchan_t *S_FindRawChannel( int entnum, qboolean create );
uint S_RawSamplesStereo( portable_samplepair_t *rawsamples, uint rawend, uint max_samples, uint samples, uint rate, word width, word channels, const byte *data );
void S_RawEntSamples( int entnum, uint samples, uint rate, word width, word channels, const byte *data, int snd_vol );
void S_RawEntSamples( int entnum, uint samples, uint rate, word width, word channels, const byte *data, int snd_vol, float attn );
void S_StopSound( int entnum, int channel, const char *soundname );
void S_UpdateFrame( struct ref_viewpass_s *rvp );
void S_StopAllSounds( qboolean ambient );

View File

@@ -1287,7 +1287,7 @@ uint S_RawSamplesStereo( portable_samplepair_t *rawsamples, uint rawend, uint ma
S_RawEntSamples
===================
*/
void S_RawEntSamples( int entnum, uint samples, uint rate, word width, word channels, const byte *data, int snd_vol )
void S_RawEntSamples( int entnum, uint samples, uint rate, word width, word channels, const byte *data, int snd_vol, float attn )
{
rawchan_t *ch;
@@ -1298,7 +1298,7 @@ void S_RawEntSamples( int entnum, uint samples, uint rate, word width, word chan
return;
ch->master_vol = snd_vol;
ch->dist_mult = (ATTN_NONE / SND_CLIP_DISTANCE);
ch->dist_mult = (attn / SND_CLIP_DISTANCE);
ch->s_rawend = S_RawSamplesStereo( ch->rawsamples, ch->s_rawend, ch->max_samples, samples, rate, width, channels, data );
ch->leftvol = ch->rightvol = snd_vol;
}
@@ -1938,6 +1938,9 @@ static void S_VoiceRecordStop_f( void )
static const sound_api_t gSoundAPI = {
CL_GetEntitySpatialization,
S_GetSfxByHandle,
S_RawEntSamples,
SND_ForceInitMouth,
Voice_GetAudioInfo,
};
/*
@@ -1945,7 +1948,7 @@ static const sound_api_t gSoundAPI = {
S_InitSoundAPI
================
*/
static qboolean S_InitSoundAPI( void )
qboolean S_InitSoundAPI( void )
{
// make sure what sound functions is cleared
memset( &clgame.soundFuncs, 0, sizeof( clgame.soundFuncs ));
@@ -1964,6 +1967,9 @@ static qboolean S_InitSoundAPI( void )
memset( &clgame.soundFuncs, 0, sizeof( clgame.soundFuncs ));
}
if( clgame.soundFuncs.pfnS_Init )
clgame.soundFuncs.pfnS_Init( &snd );
return false;
}
@@ -2027,17 +2033,15 @@ qboolean S_Init( void )
S_InitSounds ();
VOX_Init ();
S_InitSoundAPI();
if( clgame.soundFuncs.pfnS_Init )
clgame.soundFuncs.pfnS_Init( &snd );
return true;
}
// =======================================================================
// Shutdown sound engine
// =======================================================================
/*
============
S_Shutdown
============
*/
void S_Shutdown( void )
{
if( !snd.initialized ) return;

View File

@@ -233,7 +233,7 @@ static int VOX_MixChannelToBuffer( portable_samplepair_t *pbuf, channel_t *chan,
static int S_MixNormalChannels( portable_samplepair_t *dst, int end, int rate )
{
const qboolean local = Host_IsLocalGame();
const qboolean sp = Host_IsSinglePlayerGame();
const qboolean ingame = CL_IsInGame();
const int num_samples = ( end - snd.paintedtime ) / ( SOUND_DMA_SPEED / rate );
@@ -261,7 +261,7 @@ static int S_MixNormalChannels( portable_samplepair_t *dst, int end, int rate )
{
// play, playvol
}
else if(( cls.key_dest == key_menu || cl.paused ) && !FBitSet( ch->flags, FL_CHAN_LOCAL_SOUND ) && local )
else if(( cls.key_dest == key_menu || cl.paused ) && !FBitSet( ch->flags, FL_CHAN_LOCAL_SOUND ) && sp )
{
// play only local sounds, keep pause for other
continue;

View File

@@ -253,7 +253,7 @@ void S_StreamBackgroundTrack( void )
{
// add to raw buffer
int music_vol = (int)(255.0f * S_GetMusicVolume());
S_RawEntSamples( S_RAW_SOUND_BACKGROUNDTRACK, fileSamples, info->rate, info->width, info->channels, raw, music_vol );
S_RawEntSamples( S_RAW_SOUND_BACKGROUNDTRACK, fileSamples, info->rate, info->width, info->channels, raw, music_vol, ATTN_NONE );
}
else
{

View File

@@ -403,11 +403,11 @@ static qboolean VOX_ParseWordParams( char *psz, voxword_t *pvoxword, voxword_t *
command = *psz++;
if( !isdigit( *psz ))
if( !isdigit((byte)*psz ))
break;
memset( sznum, 0, sizeof( sznum ));
for( i = 0; i < sizeof( sznum ) - 1 && isdigit( *psz ); i++, psz++ )
for( i = 0; i < sizeof( sznum ) - 1 && isdigit((byte)*psz ); i++, psz++ )
sznum[i] = *psz;
i = Q_atoi( sznum );

View File

@@ -939,8 +939,14 @@ Feed the decoded data to engine sound subsystem
*/
static void Voice_StartChannel( uint samples, byte *data, int entnum )
{
if( clgame.dllFuncs.pfnVoice_StartChannel != NULL )
{
if( clgame.dllFuncs.pfnVoice_StartChannel( samples, data, entnum ))
return;
}
SND_ForceInitMouth( entnum );
S_RawEntSamples( entnum, samples, voice.samplerate, voice.width, VOICE_PCM_CHANNELS, data, bound( 0, 255 * voice_scale.value, 255 ));
S_RawEntSamples( entnum, samples, voice.samplerate, voice.width, VOICE_PCM_CHANNELS, data, bound( 0, 255 * voice_scale.value, 255 ), ATTN_NONE );
Voice_Status( entnum, true );
}
@@ -1194,3 +1200,13 @@ qboolean Voice_Init( const char *pszCodecName, int quality, qboolean preinit )
voice.initialized = true;
return true;
}
voice_audio_info_t Voice_GetAudioInfo( void )
{
voice_audio_info_t info;
info.width = voice.width;
info.samplerate = voice.samplerate;
info.frame_size = voice.frame_size;
return info;
}

View File

@@ -157,7 +157,7 @@ qboolean Sound_LoadOggVorbis( const char *name, const byte *buffer, fs_offset_t
SetBits( sound.flags, SOUND_RESAMPLE );
Sound_ScanVorbisComments( &vorbisFile );
while(( ret = ov_read( &vorbisFile, (char *)sound.wav + written, sound.size - written, 0, sound.width, 1, &section )) != 0 )
while(( ret = ov_read( &vorbisFile, (char *)sound.wav + written, sound.size - written, XASH_BIG_ENDIAN, sound.width, 1, &section )) != 0 )
{
if( ret < 0 )
{
@@ -232,7 +232,7 @@ int Stream_ReadOggVorbis( stream_t *stream, int needBytes, void *buffer )
if( !stream->buffsize )
{
stream->pos = ov_read( &ctx->vf, (char *)stream->temp, OUTBUF_SIZE, 0, stream->width, 1, &section );
stream->pos = ov_read( &ctx->vf, (char *)stream->temp, OUTBUF_SIZE, XASH_BIG_ENDIAN, stream->width, 1, &section );
if( stream->pos == 0 )
{
break; // end of file

View File

@@ -152,6 +152,8 @@ static qboolean StreamFindNextChunk( file_t *file, const char *name, int *last_c
if( FS_Read( file, &iff_chunk_len, sizeof( iff_chunk_len )) != sizeof( iff_chunk_len ))
return false;
iff_chunk_len = LittleLong( iff_chunk_len );
if( iff_chunk_len < 0 )
return false; // didn't find the chunk
@@ -317,6 +319,15 @@ qboolean Sound_LoadWAV( const char *name, const byte *buffer, fs_offset_t filesi
memcpy( sound.wav, buffer + (iff_dataPtr - buffer), sound.size );
// swap 16-bit samples from little endian to native
if( sound.width == 2 )
{
short *p = (short *)sound.wav;
int count = sound.size / 2;
for( int i = 0; i < count; i++ )
p[i] = LittleShort( p[i] );
}
// now convert 8-bit sounds to signed
if( sound.width == 1 )
{
@@ -394,6 +405,7 @@ stream_t *Stream_OpenWAV( const char *filename )
FS_Read( file, chunkName, 4 );
FS_Read( file, &t, sizeof( t ));
t = LittleShort( t );
if( t != 1 )
{
Con_DPrintf( S_ERROR "%s: %s not a microsoft PCM format\n", __func__, filename );
@@ -402,14 +414,15 @@ stream_t *Stream_OpenWAV( const char *filename )
}
FS_Read( file, &t, sizeof( t ));
sound.channels = t;
sound.channels = LittleShort( t );
FS_Read( file, &sound.rate, sizeof( int ));
sound.rate = LittleLong( sound.rate );
FS_Seek( file, 6, SEEK_CUR );
FS_Read( file, &t, sizeof( t ));
sound.width = t / 8;
sound.width = LittleShort( t ) / 8;
sound.loopstart = 0;
@@ -423,7 +436,7 @@ stream_t *Stream_OpenWAV( const char *filename )
}
FS_Read( file, &sound.samples, sizeof( int ));
sound.samples = ( sound.samples / sound.width ) / sound.channels;
sound.samples = ( LittleLong( sound.samples ) / sound.width ) / sound.channels;
// at this point we have valid stream
stream = Mem_Calloc( host.soundpool, sizeof( stream_t ));
@@ -458,6 +471,14 @@ int Stream_ReadWAV( stream_t *stream, int bytes, void *buffer )
stream->pos += bytes;
FS_Read( stream->file, buffer, bytes );
if( stream->width == 2 )
{
short *p = (short *)buffer;
int count = bytes / 2;
for( int i = 0; i < count; i++ )
p[i] = LittleShort( p[i] );
}
return bytes;
}

View File

@@ -131,5 +131,6 @@ void Voice_Disconnect( void );
void Voice_AddIncomingData( int ent, const byte *data, uint size, uint frames );
void Voice_StopChannel( int entnum );
void Voice_LoopbackAck( void ); // sends VOICE_LOOPBACK_INDEX to client, gets disabled on timeout
voice_audio_info_t Voice_GetAudioInfo( void );
#endif // VOICE_H

View File

@@ -615,8 +615,11 @@ void Cmd_TokenizeString( const char *text )
if( !*text )
return;
if( cmd_argc == 1 )
cmd_args = text;
switch( cmd_argc )
{
case 0: cmd_args = ""; break;
case 1: cmd_args = text; break;
}
text = COM_ParseFileSafe( (char*)text, cmd_token, sizeof( cmd_token ), PFILE_IGNOREBRACKET, NULL, NULL );

View File

@@ -58,8 +58,8 @@ GNU General Public License for more details.
// path to saved games
#define DEFAULT_SAVE_DIRECTORY "save/"
// path to download games
#define DEFAULT_DOWNLOADED_DIRECTORY "downloaded/"
// goldsrc compatible suffix for downloads directory
#define DEFAULT_DOWNLOADED_DIRECTORY_SUFFIX "_downloads"
// path to user mod directory
#define DEFAULT_CUSTOM_DIRECTORY "custom/"

View File

@@ -227,7 +227,7 @@ qboolean LZSS_IsCompressed( const byte *source, size_t input_len )
phdr = (const lzss_header_t *)source;
if( phdr && phdr->id == LZSS_ID )
if( phdr && phdr->id == LittleLong( LZSS_ID ))
return true;
return false;
}
@@ -241,8 +241,8 @@ uint LZSS_GetActualSize( const byte *source, size_t input_len )
phdr = (const lzss_header_t *)source;
if( phdr && phdr->id == LZSS_ID )
return phdr->size;
if( phdr && phdr->id == LittleLong( LZSS_ID ))
return LittleLong( phdr->size );
return 0;
}
@@ -296,8 +296,8 @@ static byte *LZSS_CompressNoAlloc( lzss_state_t *state, byte *pInput, int input_
return NULL;
// set LZSS header
header->id = LZSS_ID;
header->size = input_length;
header->id = LittleLong( LZSS_ID );
header->size = LittleLong( input_length );
// create the compression work buffers, small enough (~64K) for stack
state->hash_table = (lzss_list_t *)alloca( 256 * sizeof( lzss_list_t ));
@@ -967,8 +967,8 @@ static void Test_LZSS( void )
ASAN_POISON_MEMORY_REGION( poison3, sizeof( poison3 ));
#endif
hdr->size = sizeof( in ) - sizeof( *hdr );
hdr->id = LZSS_ID;
hdr->size = LittleLong( sizeof( in ) - sizeof( *hdr ));
hdr->id = LittleLong( LZSS_ID );
memset( in + sizeof( *hdr ), 0xff, sizeof( in ) - sizeof( *hdr ));
result = LZSS_Decompress( in, out, sizeof( in ), sizeof( out ));
@@ -978,18 +978,18 @@ static void Test_LZSS( void )
result = LZSS_Decompress( in, out, sizeof( in ), sizeof( out ));
TASSERT_EQi( result, 0 );
hdr->size = 1;
hdr->id = LZSS_ID;
hdr->size = LittleLong( 1 );
hdr->id = LittleLong( LZSS_ID );
result = LZSS_Decompress( in, out, sizeof( in ), sizeof( out ));
TASSERT_EQi( result, 0 );
hdr->size = 999;
hdr->id = LZSS_ID;
hdr->size = LittleLong( 999 );
hdr->id = LittleLong( LZSS_ID );
result = LZSS_Decompress( in, out, sizeof( in ), sizeof( out ));
TASSERT_EQi( result, 0 );
hdr->size = sizeof( in ) - sizeof( *hdr );
hdr->id = 0xa1ba;
hdr->size = LittleLong( sizeof( in ) - sizeof( *hdr ));
hdr->id = LittleLong( 0xa1ba );
result = LZSS_Decompress( in, out, sizeof( in ), sizeof( out ));
TASSERT_EQi( result, 0 );

View File

@@ -852,13 +852,15 @@ static inline connprotocol_t CL_Protocol( void )
}
#endif
static inline qboolean Host_IsLocalGame( void )
// true in singleplayer only but not specific to local game
static inline qboolean Host_IsSinglePlayerGame( void )
{
if( SV_Active( ))
return SV_GetMaxClients() == 1 ? true : false;
return CL_GetMaxClients() == 1 ? true : false;
}
// true when both server and client running on the same host
static inline qboolean Host_IsLocalClient( void )
{
return CL_Initialized( ) && SV_Initialized( ) ? true : false;

View File

@@ -320,6 +320,11 @@ static qboolean FS_DetermineReadOnlyRootDirectory( char *out, size_t size )
return true;
}
#if XASH_IOS
Q_strncpy( out, IOS_GetExecDir(), size );
return true;
#endif
return false;
}

View File

@@ -513,7 +513,7 @@ static double Host_CalcFPS( void )
{
fps = CL_GetDemoFramerate();
}
else if( Host_IsLocalGame( ))
else if( Host_IsSinglePlayerGame( ))
{
if( !gl_vsync.value )
fps = host_maxfps.value;
@@ -642,9 +642,10 @@ static qboolean Host_FilterTime( double time )
oldtime = host.realtime;
// NOTE: allow only in singleplayer while demos are not active
if( host_framerate.value > 0.0f && Host_IsLocalGame() && !CL_IsPlaybackDemo() && !CL_IsRecordDemo( ))
if( host_framerate.value > 0.0f && Host_IsSinglePlayerGame() && !CL_IsPlaybackDemo() && !CL_IsRecordDemo( ))
host.frametime = bound( MIN_FRAMETIME, host_framerate.value * scale, MAX_FRAMETIME );
else host.frametime = bound( MIN_FRAMETIME, host.frametime, MAX_FRAMETIME );
else
host.frametime = bound( MIN_FRAMETIME, host.frametime, MAX_FRAMETIME );
return true;
}
@@ -833,7 +834,7 @@ static uint32_t Host_CheckBugcomp( void )
if( !Sys_CheckParm( "-bugcomp" ))
return 0;
if( Sys_GetParmFromCmdLine( "-bugcomp", args ) && isalpha( args[0] ))
if( Sys_GetParmFromCmdLine( "-bugcomp", args ) && isalpha((byte)args[0] ))
{
Q_splitstr( args, '+', &flags, Host_CheckBugcomp_splitstr_handler );
}
@@ -909,9 +910,7 @@ static qboolean Host_CollectX86Libraries( ECommonLibraryType lib_type,
static void Host_CheckGameLibraries( void )
{
// on Android, game libraries are loaded from APKs and are invisible to FS_FileExists,
// so the check cannot work there; iOS is handled via Platform_LibraryExists -> IOS_LibraryExists
#if !defined( XASH_INTERNAL_GAMELIBS ) && !XASH_ANDROID
#if !defined( XASH_INTERNAL_GAMELIBS )
struct
{
const char *name;
@@ -935,6 +934,11 @@ static void Host_CheckGameLibraries( void )
if( !COM_StringEmpty( libs[i].override ))
continue;
#if XASH_ANDROID
if( libs[i].type == LIBRARY_CLIENT )
continue;
#endif
if( libs[i].type == LIBRARY_SERVER )
{
// missing server library is only critical when singleplayer is available
@@ -1276,6 +1280,10 @@ int EXPORT Host_Main( int argc, char **argv, const char *progname, int bChangeGa
Cbuf_ExecStuffCmds(); // execute stuffcmds (commandline)
SCR_CheckStartupVids(); // must be last
#ifndef XASH_DEDICATED
IN_GyroCheckAvailability();
#endif
if( Sys_GetParmFromCmdLine( "-timedemo", demoname ))
Cbuf_AddTextf( "timedemo %s\n", demoname );

View File

@@ -101,6 +101,13 @@ static int HTTP_FileResolveNS( httpfile_t *file );
static int HTTP_FileSendRequest( httpfile_t *file );
static int HTTP_FileDecompress( httpfile_t *file );
static const char *HTTP_DownloadPath( char *buf, size_t buflen, const char *path, qboolean incomplete )
{
Q_snprintf( buf, buflen, "../%s" DEFAULT_DOWNLOADED_DIRECTORY_SUFFIX "/%s%s",
GI->gamefolder, path, incomplete ? ".incomplete" : "" );
return buf;
}
/*
==============
HTTP_FreeFile
@@ -110,7 +117,7 @@ Skip to next server/file
*/
static void HTTP_FreeFile( httpfile_t *file, qboolean error )
{
char incname[MAX_SYSPATH + 64]; // plus downloaded/ plus .incomplete
char incname[MAX_SYSPATH + 64]; // plus ../{gamedir}_downloads/ plus .incomplete
qboolean was_open = false;
file->blocktime = 0;
@@ -132,7 +139,7 @@ static void HTTP_FreeFile( httpfile_t *file, qboolean error )
file->socket = -1;
Q_snprintf( incname, sizeof( incname ), DEFAULT_DOWNLOADED_DIRECTORY "%s.incomplete", file->path );
HTTP_DownloadPath( incname, sizeof( incname ), file->path, true );
if( error )
{
@@ -149,7 +156,9 @@ static void HTTP_FreeFile( httpfile_t *file, qboolean error )
if( http_autoremove.value == 1 ) // remove broken file
{
Con_Printf( S_ERROR "no servers to download %s\n", file->path );
FS_AllowDirectPaths( true );
FS_Delete( incname );
FS_AllowDirectPaths( false );
}
else // autoremove disabled, keep file
{
@@ -161,14 +170,18 @@ static void HTTP_FreeFile( httpfile_t *file, qboolean error )
{
if( file->compressed )
{
FS_AllowDirectPaths( true );
FS_Delete( incname );
FS_AllowDirectPaths( false );
}
else
{
// Success, rename and process file
char name[MAX_SYSPATH];
Q_snprintf( name, sizeof( name ), DEFAULT_DOWNLOADED_DIRECTORY "%s", file->path );
HTTP_DownloadPath( name, sizeof( name ), file->path, false );
FS_AllowDirectPaths( true );
FS_Rename( incname, name );
FS_AllowDirectPaths( false );
}
}
@@ -195,9 +208,13 @@ static int HTTP_FileQueue( httpfile_t *file )
}
Con_Reportf( "HTTP: Starting download %s from %s:%d\n", file->path, file->server->host, file->server->port );
Q_snprintf( name, sizeof( name ), DEFAULT_DOWNLOADED_DIRECTORY "%s.incomplete", file->path );
HTTP_DownloadPath( name, sizeof( name ), file->path, true );
if( !( file->file = FS_Open( name, "wb+", true )))
FS_AllowDirectPaths( true );
file->file = FS_Open( name, "wb+", true );
FS_AllowDirectPaths( false );
if( !file->file )
{
Con_Printf( S_ERROR "HTTP: cannot open %s!\n", name );
HTTP_FreeFile( file, true );
@@ -249,9 +266,6 @@ static int HTTP_FileResolveNS( httpfile_t *file )
static int HTTP_FileCreateSocket( httpfile_t *file )
{
uint mode = 1;
int res;
file->socket = socket( file->addr.ss_family, SOCK_STREAM, IPPROTO_TCP );
if( file->socket < 0 )
@@ -261,33 +275,13 @@ static int HTTP_FileCreateSocket( httpfile_t *file )
return 0;
}
if( ioctlsocket( file->socket, FIONBIO, (void *)&mode ) < 0 )
if( !NET_MakeSocketNonBlocking( file->socket ))
{
Con_Printf( S_ERROR "%s: ioctl() returned %s\n", __func__, NET_ErrorString());
Con_Printf( S_ERROR "%s: failed to make socket non-blocking, error %s\n", __func__, NET_ErrorString());
HTTP_FreeFile( file, true );
return 0;
}
#if XASH_LINUX
res = fcntl( file->socket, F_GETFL, 0 );
if( res < 0 )
{
Con_Printf( S_ERROR "%s: fcntl( F_GETFL ) returned %s\n", __func__, NET_ErrorString());
HTTP_FreeFile( file, true );
return 0;
}
// SOCK_NONBLOCK is not portable, so use fcntl
if( fcntl( file->socket, F_SETFL, res | O_NONBLOCK ) < 0 )
{
Con_Printf( S_ERROR "%s: fcntl( F_SETFL ) returned %s\n", __func__, NET_ErrorString());
HTTP_FreeFile( file, true );
return 0;
}
#endif
http.active_count++;
file->pfn_process = HTTP_FileConnect;
return 1;
@@ -481,7 +475,7 @@ static int HTTP_FileDecompress( httpfile_t *file )
data_in = Mem_Malloc( host.mempool, compressed_len + 1 );
data_out = Mem_Malloc( host.mempool, decompressed_len + 1 );
Q_snprintf( name, sizeof( name ), DEFAULT_DOWNLOADED_DIRECTORY "%s", file->path );
HTTP_DownloadPath( name, sizeof( name ), file->path, false );
memset( &decompress_stream, 0, sizeof( decompress_stream ));
decompress_stream.total_in = decompress_stream.avail_in = compressed_len;
@@ -506,7 +500,9 @@ static int HTTP_FileDecompress( httpfile_t *file )
if( zlib_result == Z_OK || zlib_result == Z_STREAM_END )
{
FS_AllowDirectPaths( true );
g_fsapi.WriteFile( name, data_out, decompressed_len );
FS_AllowDirectPaths( false );
HTTP_FreeFile( file, false );
}
else HTTP_FreeFile( file, true );

View File

@@ -16,6 +16,24 @@ GNU General Public License for more details.
#include "imagelib.h"
#include "xash3d_mathlib.h"
#include "img_bmp.h"
#include "swaplib.h"
le_struct_begin( bmp_swap )
le_struct_field( bmp_t, fileSize )
le_struct_field( bmp_t, reserved0 )
le_struct_field( bmp_t, bitmapDataOffset )
le_struct_field( bmp_t, bitmapHeaderSize )
le_struct_field( bmp_t, width )
le_struct_field( bmp_t, height )
le_struct_field( bmp_t, planes )
le_struct_field( bmp_t, bitsPerPixel )
le_struct_field( bmp_t, compression )
le_struct_field( bmp_t, bitmapDataSize )
le_struct_field( bmp_t, hRes )
le_struct_field( bmp_t, vRes )
le_struct_field( bmp_t, colors )
le_struct_field( bmp_t, importantColors )
le_struct_end();
/*
=============
@@ -41,6 +59,7 @@ qboolean Image_LoadBMP( const char *name, const byte *buffer, fs_offset_t filesi
buf_p = (byte *)buffer;
memcpy( &bhdr, buf_p, sizeof( bmp_t ));
le_struct_swap( bmp_swap, &bhdr );
buf_p += BI_FILE_HEADER_SIZE + bhdr.bitmapHeaderSize;
// bogus file header check
@@ -282,7 +301,8 @@ qboolean Image_LoadBMP( const char *name, const byte *buffer, fs_offset_t filesi
}
break;
case 16:
shortPixel = *(word *)buf_p, buf_p += 2;
shortPixel = buf_p[0] | (buf_p[1] << 8);
buf_p += 2;
*pixbuf++ = blue = (shortPixel & ( 31 << 10 )) >> 7;
*pixbuf++ = green = (shortPixel & ( 31 << 5 )) >> 2;
*pixbuf++ = red = (shortPixel & ( 31 )) << 3;
@@ -396,7 +416,9 @@ qboolean Image_SaveBMP( const char *name, rgbdata_t *pix )
hdr.colors = ( pixel_size == 1 ) ? 256 : 0;
hdr.importantColors = 0;
le_struct_swap( bmp_swap, &hdr );
FS_Write( pfile, &hdr, sizeof( bmp_t ));
le_struct_swap( bmp_swap, &hdr );
pbBmpBits = Mem_Malloc( host.imagepool, cbBmpBits );

View File

@@ -16,6 +16,49 @@ GNU General Public License for more details.
#include "imagelib.h"
#include "xash3d_mathlib.h"
#include "img_dds.h"
#include "swaplib.h"
le_struct_begin( dds_pixf_swap )
le_struct_field( dds_pixf_t, dwSize )
le_struct_field( dds_pixf_t, dwFlags )
le_struct_field( dds_pixf_t, dwFourCC )
le_struct_field( dds_pixf_t, dwRGBBitCount )
le_struct_field( dds_pixf_t, dwRBitMask )
le_struct_field( dds_pixf_t, dwGBitMask )
le_struct_field( dds_pixf_t, dwBBitMask )
le_struct_field( dds_pixf_t, dwABitMask )
le_struct_end();
le_struct_begin( dds_caps_swap )
le_struct_field( dds_caps_t, dwCaps1 )
le_struct_field( dds_caps_t, dwCaps2 )
le_struct_field( dds_caps_t, dwCaps3 )
le_struct_field( dds_caps_t, dwCaps4 )
le_struct_end();
le_struct_begin( dds_swap )
le_struct_field( dds_t, dwIdent )
le_struct_field( dds_t, dwSize )
le_struct_field( dds_t, dwFlags )
le_struct_field( dds_t, dwHeight )
le_struct_field( dds_t, dwWidth )
le_struct_field( dds_t, dwLinearSize )
le_struct_field( dds_t, dwDepth )
le_struct_field( dds_t, dwMipMapCount )
le_struct_field( dds_t, dwAlphaBitDepth )
le_struct_array( dds_t, dwReserved1, 10 )
le_struct_child( dds_t, dsPixelFormat, dds_pixf_swap )
le_struct_child( dds_t, dsCaps, dds_caps_swap )
le_struct_field( dds_t, dwTextureStage )
le_struct_end();
le_struct_begin( dds_dxt10_swap )
le_struct_field( dds_header_dxt10_t, dxgiFormat )
le_struct_field( dds_header_dxt10_t, resourceDimension )
le_struct_field( dds_header_dxt10_t, miscFlag )
le_struct_field( dds_header_dxt10_t, arraySize )
le_struct_field( dds_header_dxt10_t, miscFlags2 )
le_struct_end();
static qboolean Image_CheckDXT3Alpha( dds_t *hdr, byte *fin )
{
@@ -295,6 +338,7 @@ qboolean Image_LoadDDS( const char *name, const byte *buffer, fs_offset_t filesi
return false;
memcpy( &header, buffer, sizeof( header ));
le_struct_swap( dds_swap, &header );
if( header.dwIdent != DDSHEADER )
return false; // it's not a dds file, just skip it
@@ -315,6 +359,7 @@ qboolean Image_LoadDDS( const char *name, const byte *buffer, fs_offset_t filesi
if( header.dsPixelFormat.dwFourCC == TYPE_DX10 )
{
memcpy( &header2, buffer + sizeof( header ), sizeof( header2 ));
le_struct_swap( dds_dxt10_swap, &header2 );
headersOffset += sizeof( header2 );
}

View File

@@ -16,6 +16,34 @@ GNU General Public License for more details.
#include "imagelib.h"
#include "xash3d_mathlib.h"
#include "img_ktx2.h"
#include "swaplib.h"
le_struct_begin( ktx2_header_swap )
le_struct_field( ktx2_header_t, vkFormat )
le_struct_field( ktx2_header_t, typeSize )
le_struct_field( ktx2_header_t, pixelWidth )
le_struct_field( ktx2_header_t, pixelHeight )
le_struct_field( ktx2_header_t, pixelDepth )
le_struct_field( ktx2_header_t, layerCount )
le_struct_field( ktx2_header_t, faceCount )
le_struct_field( ktx2_header_t, levelCount )
le_struct_field( ktx2_header_t, supercompressionScheme )
le_struct_end();
le_struct_begin( ktx2_index_swap )
le_struct_field( ktx2_index_t, dfdByteOffset )
le_struct_field( ktx2_index_t, dfdByteLength )
le_struct_field( ktx2_index_t, kvdByteOffset )
le_struct_field( ktx2_index_t, kvdByteLength )
le_struct_field( ktx2_index_t, sgdByteOffset )
le_struct_field( ktx2_index_t, sgdByteLength )
le_struct_end();
le_struct_begin( ktx2_level_swap )
le_struct_field( ktx2_level_t, byteOffset )
le_struct_field( ktx2_level_t, byteLength )
le_struct_field( ktx2_level_t, uncompressedByteLength )
le_struct_end();
static void Image_KTX2Format( uint32_t ktx2_format )
{
@@ -125,6 +153,7 @@ static qboolean Image_KTX2Parse( const ktx2_header_t *header, const byte *buffer
}
memcpy( &index, buffer + KTX2_IDENTIFIER_SIZE + sizeof( ktx2_header_t ), sizeof( index ));
le_struct_swap( ktx2_index_swap, &index );
for( mip = 0; mip < header->levelCount; ++mip )
{
@@ -134,6 +163,7 @@ static qboolean Image_KTX2Parse( const ktx2_header_t *header, const byte *buffer
ktx2_level_t level;
memcpy( &level, levels_begin + mip * sizeof( level ), sizeof( level ));
le_struct_swap( ktx2_level_swap, &level );
if( mip_size != level.byteLength )
{
@@ -159,6 +189,7 @@ static qboolean Image_KTX2Parse( const ktx2_header_t *header, const byte *buffer
{
ktx2_level_t level;
memcpy( &level, levels_begin + mip * sizeof( level ), sizeof( level ));
le_struct_swap( ktx2_level_swap, &level );
memcpy( image.rgba + cursor, buffer + level.byteOffset, level.byteLength );
cursor += level.byteLength;
}
@@ -180,6 +211,7 @@ qboolean Image_LoadKTX2( const char *name, const byte *buffer, fs_offset_t files
}
memcpy( &header, buffer + KTX2_IDENTIFIER_SIZE, sizeof( header ));
le_struct_swap( ktx2_header_swap, &header );
image.width = header.pixelWidth;
image.height = header.pixelHeight;

View File

@@ -173,6 +173,9 @@ static MALLOC_LIKE( FS_FreeImage, 1 ) rgbdata_t *ImagePack( const char *name )
Image_ReportLookupsCount( name );
if( Image_CheckFlag( IL_LOAD_PLAYER_DECAL ))
SetBits( image.flags, IMAGE_PLAYERDECAL );
// clear any force flags
image.force_flags = 0;

View File

@@ -16,6 +16,16 @@ GNU General Public License for more details.
#include "imagelib.h"
#include "xash3d_mathlib.h"
#include "img_tga.h"
#include "swaplib.h"
le_struct_begin( tga_swap )
le_struct_field( tga_t, colormap_index )
le_struct_field( tga_t, colormap_length )
le_struct_field( tga_t, x_origin )
le_struct_field( tga_t, y_origin )
le_struct_field( tga_t, width )
le_struct_field( tga_t, height )
le_struct_end();
/*
=============
@@ -44,10 +54,10 @@ qboolean Image_LoadTGA( const char *name, const byte *buffer, fs_offset_t filesi
targa_header.colormap_index = buf_p[0] + buf_p[1] * 256; buf_p += 2;
targa_header.colormap_length = buf_p[0] + buf_p[1] * 256; buf_p += 2;
targa_header.colormap_size = *buf_p; buf_p += 1;
targa_header.x_origin = *(short *)buf_p; buf_p += 2;
targa_header.y_origin = *(short *)buf_p; buf_p += 2;
targa_header.width = image.width = *(short *)buf_p; buf_p += 2;
targa_header.height = image.height = *(short *)buf_p; buf_p += 2;
targa_header.x_origin = buf_p[0] + buf_p[1] * 256; buf_p += 2;
targa_header.y_origin = buf_p[0] + buf_p[1] * 256; buf_p += 2;
targa_header.width = image.width = buf_p[0] + buf_p[1] * 256; buf_p += 2;
targa_header.height = image.height = buf_p[0] + buf_p[1] * 256; buf_p += 2;
targa_header.pixel_size = *buf_p++;
targa_header.attributes = *buf_p++;
if( targa_header.id_length != 0 ) buf_p += targa_header.id_length; // skip TARGA image comment
@@ -284,6 +294,7 @@ qboolean Image_SaveTGA( const char *name, rgbdata_t *pix )
out = buffer;
le_struct_swap( tga_swap, &targa_header );
memcpy( out, &targa_header, sizeof( tga_t ) );
out += sizeof( tga_t );

View File

@@ -285,66 +285,34 @@ int Image_ComparePalette( const byte *pal )
static void Image_SetPalette( const byte *pal, uint *d_table )
{
byte rgba[4];
uint uirgba; // TODO: palette looks byte-swapped on big-endian
int i;
// setup palette
switch( image.d_rendermode )
{
case LUMP_NORMAL:
for( i = 0; i < 256; i++ )
{
memcpy( rgba, &pal[i * 3], 3 );
rgba[3] = 0xFF;
memcpy( &uirgba, rgba, sizeof( uirgba ));
d_table[i] = uirgba;
}
for( int i = 0; i < 256; i++ )
d_table[i] = HostFourCC( pal[i * 3 + 0], pal[i * 3 + 1], pal[i * 3 + 2], 0xFF );
break;
case LUMP_TEXGAMMA:
for( i = 0; i < 256; i++ )
for( int i = 0; i < 256; i++ )
{
rgba[0] = TextureToGamma( pal[i * 3 + 0] );
rgba[1] = TextureToGamma( pal[i * 3 + 1] );
rgba[2] = TextureToGamma( pal[i * 3 + 2] );
rgba[3] = 0xFF;
memcpy( &uirgba, rgba, sizeof( uirgba ));
d_table[i] = uirgba;
d_table[i] = HostFourCC(
TextureToGamma( pal[i * 3 + 0] ),
TextureToGamma( pal[i * 3 + 1] ),
TextureToGamma( pal[i * 3 + 2] ),
0xFF );
}
break;
case LUMP_GRADIENT:
for( i = 0; i < 256; i++ )
{
rgba[0] = pal[765];
rgba[1] = pal[766];
rgba[2] = pal[767];
rgba[3] = i;
memcpy( &uirgba, rgba, sizeof( uirgba ));
d_table[i] = uirgba;
}
for( int i = 0; i < 256; i++ )
d_table[i] = HostFourCC( pal[765], pal[766], pal[767], i );
break;
case LUMP_MASKED:
for( i = 0; i < 255; i++ )
{
rgba[0] = pal[i*3+0];
rgba[1] = pal[i*3+1];
rgba[2] = pal[i*3+2];
rgba[3] = 0xFF;
memcpy( &uirgba, rgba, sizeof( uirgba ));
d_table[i] = uirgba;
}
for( int i = 0; i < 255; i++ )
d_table[i] = HostFourCC( pal[i * 3 + 0], pal[i * 3 + 1], pal[i * 3 + 2], 0xFF );
d_table[255] = 0;
break;
case LUMP_EXTENDED:
for( i = 0; i < 256; i++ )
{
rgba[0] = pal[i*4+0];
rgba[1] = pal[i*4+1];
rgba[2] = pal[i*4+2];
rgba[3] = pal[i*4+3];
memcpy( &uirgba, rgba, sizeof( uirgba ));
d_table[i] = uirgba;
}
for( int i = 0; i < 256; i++ )
d_table[i] = HostFourCC( pal[i * 4 + 0], pal[i * 4 + 1], pal[i * 4 + 2], pal[i * 4 + 3] );
break;
}
}

View File

@@ -19,6 +19,43 @@ GNU General Public License for more details.
#include "studio.h"
#include "sprite.h"
#include "qfont.h"
#include "swaplib.h"
le_struct_begin( charinfo_swap )
le_struct_field( charinfo, startoffset )
le_struct_field( charinfo, charwidth )
le_struct_end();
le_struct_begin( qfont_swap )
le_struct_field( qfont_t, width )
le_struct_field( qfont_t, height )
le_struct_field( qfont_t, rowcount )
le_struct_field( qfont_t, rowheight )
le_struct_array_child( qfont_t, fontinfo, charinfo_swap, NUM_GLYPHS )
le_struct_end();
le_struct_begin( lmp_swap )
le_struct_field( lmp_t, width )
le_struct_field( lmp_t, height )
le_struct_end();
le_struct_begin( mip_swap )
le_struct_field( mip_t, width )
le_struct_field( mip_t, height )
le_struct_array( mip_t, offsets, 4 )
le_struct_end();
le_struct_begin( dwadinfo_swap )
le_struct_field( dwadinfo_t, ident )
le_struct_field( dwadinfo_t, numlumps )
le_struct_field( dwadinfo_t, infotableofs )
le_struct_end();
le_struct_begin( dlumpinfo_swap )
le_struct_field( dlumpinfo_t, filepos )
le_struct_field( dlumpinfo_t, disksize )
le_struct_field( dlumpinfo_t, size )
le_struct_end();
/*
============
@@ -98,6 +135,7 @@ qboolean Image_LoadFNT( const char *name, const byte *buffer, fs_offset_t filesi
return false;
memcpy( &font, buffer, sizeof( font ));
le_struct_swap( qfont_swap, &font );
// last sixty four bytes - what the hell ????
size = sizeof( qfont_t ) - 4 + ( font.height * font.width * QCHAR_WIDTH ) + sizeof( short ) + 768 + 64;
@@ -120,7 +158,8 @@ qboolean Image_LoadFNT( const char *name, const byte *buffer, fs_offset_t filesi
fin = buffer + sizeof( font ) - 4;
pal = fin + (image.width * image.height);
numcolors = *(short *)pal, pal += sizeof( short );
numcolors = pal[0] | (pal[1] << 8);
pal += sizeof( short );
if( numcolors == 768 || numcolors == 256 )
{
@@ -250,6 +289,8 @@ qboolean Image_LoadSPR( const char *name, const byte *buffer, fs_offset_t filesi
SetBits( image.flags, IMAGE_ONEBIT_ALPHA );
// intentionally fallthrough
case LUMP_GRADIENT:
SetBits( image.flags, IMAGE_HAS_ALPHA );
break;
case LUMP_QUAKE1:
SetBits( image.flags, IMAGE_HAS_ALPHA );
break;
@@ -303,6 +344,7 @@ qboolean Image_LoadLMP( const char *name, const byte *buffer, fs_offset_t filesi
{
fin = (byte *)buffer;
memcpy( &lmp, fin, sizeof( lmp ));
le_struct_swap( lmp_swap, &lmp );
image.width = lmp.width;
image.height = lmp.height;
rendermode = LUMP_NORMAL;
@@ -335,7 +377,7 @@ qboolean Image_LoadLMP( const char *name, const byte *buffer, fs_offset_t filesi
}
}
pal = fin + pixels;
numcolors = *(short *)pal;
numcolors = pal[0] | (pal[1] << 8);
if( numcolors != 256 ) pal = NULL; // corrupted lump ?
else pal += sizeof( short );
}
@@ -395,6 +437,8 @@ qboolean Image_LoadMIP( const char *name, const byte *buffer, fs_offset_t filesi
return false;
memcpy( &mip, buffer, sizeof( mip ));
if( !Image_CheckFlag( IL_HOST_ENDIAN ))
le_struct_swap( mip_swap, &mip );
image.width = mip.width;
image.height = mip.height;
@@ -409,7 +453,7 @@ qboolean Image_LoadMIP( const char *name, const byte *buffer, fs_offset_t filesi
// half-life 1.0.0.1 mip version with palette
fin = (byte *)buffer + mip.offsets[0];
pal = (byte *)buffer + mip.offsets[0] + (((image.width * image.height) * 85)>>6);
numcolors = *(short *)pal;
numcolors = pal[0] | (pal[1] << 8);
if( numcolors != 256 ) pal = NULL; // corrupted mip ?
else pal += sizeof( short ); // skip colorsize
@@ -470,7 +514,7 @@ qboolean Image_LoadMIP( const char *name, const byte *buffer, fs_offset_t filesi
// three checks here: check if we can load luma, check the texture name and, finally,
// validate that palette isn't NULL
if( Image_CheckFlag( IL_ALLOW_WAD3_LUMA )
&& ( mip.name[0] == '~' || ( mip.name[0] == '+' && isdigit( mip.name[1] ) && mip.name[2] == '~' ))
&& ( mip.name[0] == '~' || ( mip.name[0] == '+' && isdigit((byte)mip.name[1] ) && mip.name[2] == '~' ))
&& pal != NULL )
{
SetBits( image.flags, IMAGE_HAS_LUMA );
@@ -594,24 +638,23 @@ Image_LoadWAD
*/
qboolean Image_LoadWAD( const char *name, const byte *buffer, fs_offset_t filesize )
{
const dwadinfo_t *header;
const dlumpinfo_t *lumps;
dwadinfo_t whdr;
const unsigned char *mipdata;
int i, j;
if( !buffer || filesize < sizeof( dwadinfo_t ))
return false;
header = (const dwadinfo_t *)buffer;
if( header->numlumps <= 0 || header->infotableofs <= 0 || header->infotableofs >= (int)filesize )
memcpy( &whdr, buffer, sizeof( whdr ));
le_struct_swap( dwadinfo_swap, &whdr );
if( whdr.numlumps <= 0 || whdr.infotableofs <= 0 || whdr.infotableofs >= (int)filesize )
return false;
lumps = (const dlumpinfo_t *)((const unsigned char *)buffer + header->infotableofs );
for( i = 0; i < header->numlumps; ++i )
for( i = 0; i < whdr.numlumps; ++i )
{
const unsigned char *pixels, *palette, *use_palette;
unsigned char grad_palette[256 * 3];
dlumpinfo_t lump;
int mip_size;
mip_t mip;
uint32_t width, height, offset0;
@@ -621,16 +664,20 @@ qboolean Image_LoadWAD( const char *name, const byte *buffer, fs_offset_t filesi
float t;
byte idx;
if( lumps[i].type != TYP_MIPTEX && lumps[i].type != TYP_PALETTE )
memcpy( &lump, buffer + whdr.infotableofs + i * sizeof( dlumpinfo_t ), sizeof( lump ));
le_struct_swap( dlumpinfo_swap, &lump );
if( lump.type != TYP_MIPTEX && lump.type != TYP_PALETTE )
continue;
// get lump data and validate
mipdata = (const unsigned char *)buffer + lumps[i].filepos;
mip_size = lumps[i].disksize;
if( lumps[i].filepos < 0 || lumps[i].filepos + mip_size > (int)filesize )
mipdata = (const unsigned char *)buffer + lump.filepos;
mip_size = lump.disksize;
if( lump.filepos < 0 || lump.filepos + mip_size > (int)filesize )
continue;
memcpy( &mip, mipdata, sizeof( mip ));
le_struct_swap( mip_swap, &mip );
width = mip.width;
height = mip.height;
@@ -650,7 +697,7 @@ qboolean Image_LoadWAD( const char *name, const byte *buffer, fs_offset_t filesi
use_palette = palette;
// handle gradient palette
if( lumps[i].type == TYP_PALETTE )
if( lump.type == TYP_PALETTE )
{
// gradient palette
const unsigned char *frontColorPtr = palette + 255 * 3;
@@ -753,12 +800,16 @@ qboolean Image_SaveWAD( const char *name, rgbdata_t *pix )
header.ident = IDWAD3HEADER;
header.numlumps = 1;
le_struct_swap( dwadinfo_swap, &header );
FS_Write( f, &header, sizeof( header ));
le_struct_swap( mip_swap, &miptex );
FS_Write( f, &miptex, sizeof( mip_t ));
le_struct_swap( mip_swap, &miptex );
FS_Write( f, pix->buffer, m0size );
FS_Write( f, mip1_data, m1size );
FS_Write( f, mip2_data, m2size );
FS_Write( f, mip3_data, m3size );
palette_size = LittleShort( palette_size );
FS_Write( f, &palette_size, sizeof( short ));
if( lump_type == TYP_PALETTE )
@@ -791,10 +842,11 @@ qboolean Image_SaveWAD( const char *name, rgbdata_t *pix )
lump.type = (char)lump_type;
lump.attribs = 0;
Q_strncpy( lump.name, "tempdecal", sizeof( lump.name ));
le_struct_swap( dlumpinfo_swap, &lump );
FS_Write( f, &lump, sizeof( lump ));
FS_Seek( f, offsetof( dwadinfo_t, infotableofs ), SEEK_SET );
infotableofs32 = (int)infotableofs;
infotableofs32 = LittleLong((int)infotableofs );
FS_Write( f, &infotableofs32, sizeof( int ));
FS_Close( f );

View File

@@ -327,7 +327,7 @@ static char *COM_GetItaniumName( const char * const in_name )
{
// parse symbol length marker
len = 0;
for( ; isdigit( *f ) && remaining > 0; f++, remaining-- )
for( ; isdigit((byte)*f ) && remaining > 0; f++, remaining-- )
len = len * 10 + ( *f - '0' );
// sane value
@@ -344,7 +344,7 @@ static char *COM_GetItaniumName( const char * const in_name )
if( *f == 'E' )
break;
if( !isdigit( *f ) || remaining <= 0 )
if( !isdigit((byte)*f ) || remaining <= 0 )
goto invalid_format;
}

View File

@@ -23,6 +23,7 @@ GNU General Public License for more details.
#include "enginefeatures.h"
#include "client.h"
#include "server.h" // LUMP_ error codes
#include "swaplib.h"
#include "ref_common.h"
#if defined( HAVE_OPENMP )
#include <omp.h>
@@ -165,35 +166,188 @@ typedef enum
LOADLUMP_BSPX, // ... from BSPX data
} loadlump_source_t;
#define LUMP_SAVESTATS BIT( 0 )
#define LUMP_TESTONLY BIT( 1 )
#define LUMP_SILENT BIT( 2 )
#define LUMP_BSP30EXT BIT( 3 ) // extra marker for Mod_LoadLump
#define LUMP_BSPX BIT( 4 )
#define LUMP_SAVESTATS BIT( 0 )
#define LUMP_BSHIFT_SWAP BIT( 1 )
#define LUMP_SILENT BIT( 2 )
#define LUMP_BSP30EXT BIT( 3 ) // extra marker for Mod_LoadLump
#define LUMP_BSPX BIT( 4 )
typedef struct
{
int lumpnumber;
const int lumpnumber;
// BSPX
const char lumpname[24];
int flags;
const int flags;
const size_t mincount;
const size_t maxcount;
const int entrysize;
const int entrysize32; // extended size (0 if not available)
const char *loadname;
const void **dataptr;
size_t *count;
const size_t dataofs; // offsetof into dbspmodel_t for data pointer
const size_t countofs; // offsetof into dbspmodel_t for count/size
#if XASH_BIG_ENDIAN // do not waste memory on little endian
const swap_struct_def_t *swap;
const size_t swaplen;
const swap_struct_def_t *swap32;
const size_t swaplen32;
#endif
} mlumpinfo_t;
// all these macros are ugly af
#if XASH_BIG_ENDIAN
#define LUMP_SWAP( x ) .swap = x, .swaplen = ARRAYSIZE( x )
#define LUMP_SWAP32( x, y ) .swap = x, .swaplen = ARRAYSIZE( x ), .swap32 = y, .swaplen32 = ARRAYSIZE( y )
#else
#define LUMP_SWAP( x )
#define LUMP_SWAP32( x, y )
#endif
le_struct_begin( dlump_swap )
le_struct_field( dlump_t, fileofs )
le_struct_field( dlump_t, filelen )
le_struct_end();
le_struct_begin( dheader_swap )
le_struct_field( dheader_t, version )
le_struct_array_child( dheader_t, lumps, dlump_swap, HEADER_LUMPS )
le_struct_end();
le_struct_begin( dextrahdr_swap )
le_struct_field( dextrahdr_t, id )
le_struct_field( dextrahdr_t, version )
le_struct_array_child( dextrahdr_t, lumps, dlump_swap, EXTRA_LUMPS )
le_struct_end();
le_struct_begin( dbspx_hdr_swap )
le_struct_field( dbspx_hdr_t, id )
le_struct_field( dbspx_hdr_t, numlumps )
// flexible array member omitted
le_struct_end();
le_struct_begin( dbspx_lump_swap )
le_struct_field( dbspx_lump_t, fileofs )
le_struct_field( dbspx_lump_t, filelen )
le_struct_end();
le_struct_begin( dplane_swap )
le_struct_array( dplane_t, normal, 3 )
le_struct_field( dplane_t, dist )
le_struct_field( dplane_t, type )
le_struct_end();
le_struct_begin( dvertex_swap )
le_struct_array( dvertex_t, point, 3 )
le_struct_end();
le_struct_begin( dnode_swap )
le_struct_field( dnode_t, planenum )
le_struct_array( dnode_t, children, 2 )
le_struct_array( dnode_t, mins, 3 )
le_struct_array( dnode_t, maxs, 3 )
le_struct_field( dnode_t, firstface )
le_struct_field( dnode_t, numfaces )
le_struct_end();
le_struct_begin( dnode32_swap )
le_struct_field( dnode32_t, planenum )
le_struct_array( dnode32_t, children, 2 )
le_struct_array( dnode32_t, mins, 3 )
le_struct_array( dnode32_t, maxs, 3 )
le_struct_field( dnode32_t, firstface )
le_struct_field( dnode32_t, numfaces )
le_struct_end();
le_struct_begin( dtexinfo_swap )
le_struct_array( dtexinfo_t, vecs[0], 4 )
le_struct_array( dtexinfo_t, vecs[1], 4 )
le_struct_field( dtexinfo_t, miptex )
le_struct_field( dtexinfo_t, flags )
le_struct_field( dtexinfo_t, faceinfo )
le_struct_end();
le_struct_begin( dface_swap )
le_struct_field( dface_t, planenum )
le_struct_field( dface_t, side )
le_struct_field( dface_t, firstedge )
le_struct_field( dface_t, numedges )
le_struct_field( dface_t, texinfo )
le_struct_field( dface_t, lightofs )
le_struct_end();
le_struct_begin( dface32_swap )
le_struct_field( dface32_t, planenum )
le_struct_field( dface32_t, side )
le_struct_field( dface32_t, firstedge )
le_struct_field( dface32_t, numedges )
le_struct_field( dface32_t, texinfo )
le_struct_field( dface32_t, lightofs )
le_struct_end();
le_struct_begin( dclipnode_swap )
le_struct_field( dclipnode_t, planenum )
le_struct_array( dclipnode_t, children, 2 )
le_struct_end();
le_struct_begin( dclipnode32_swap )
le_struct_field( dclipnode32_t, planenum )
le_struct_array( dclipnode32_t, children, 2 )
le_struct_end();
le_struct_begin( dleaf_swap )
le_struct_field( dleaf_t, contents )
le_struct_field( dleaf_t, visofs )
le_struct_array( dleaf_t, mins, 3 )
le_struct_array( dleaf_t, maxs, 3 )
le_struct_field( dleaf_t, firstmarksurface )
le_struct_field( dleaf_t, nummarksurfaces )
le_struct_end();
le_struct_begin( dleaf32_swap )
le_struct_field( dleaf32_t, contents )
le_struct_field( dleaf32_t, visofs )
le_struct_array( dleaf32_t, mins, 3 )
le_struct_array( dleaf32_t, maxs, 3 )
le_struct_field( dleaf32_t, firstmarksurface )
le_struct_field( dleaf32_t, nummarksurfaces )
le_struct_end();
le_struct_begin( dedge_swap )
le_struct_array( dedge_t, v, 2 )
le_struct_end();
le_struct_begin( dedge32_swap )
le_struct_array( dedge32_t, v, 2 )
le_struct_end();
le_struct_begin( dmodel_swap )
le_struct_array( dmodel_t, mins, 3 )
le_struct_array( dmodel_t, maxs, 3 )
le_struct_array( dmodel_t, origin, 3 )
le_struct_array( dmodel_t, headnode, MAX_MAP_HULLS )
le_struct_field( dmodel_t, visleafs )
le_struct_field( dmodel_t, firstface )
le_struct_field( dmodel_t, numfaces )
le_struct_end();
le_struct_begin( dfaceinfo_swap )
le_struct_field( dfaceinfo_t, texture_step )
le_struct_field( dfaceinfo_t, max_extent )
le_struct_field( dfaceinfo_t, groupid )
le_struct_end();
le_struct_begin( mip_swap )
le_struct_field( mip_t, width )
le_struct_field( mip_t, height )
le_struct_array( mip_t, offsets, 4 )
le_struct_end();
world_static_t world;
static dbspmodel_t srcmodel;
static loadstat_t loadstat;
static model_t *worldmodel;
static byte g_visdata[(MAX_MAP_LEAFS+7)/8]; // intermediate buffer
static mlumpinfo_t srclumps[HEADER_LUMPS] =
static const mlumpinfo_t srclumps[HEADER_LUMPS] =
{
{
.lumpnumber = LUMP_ENTITIES,
@@ -201,8 +355,8 @@ static mlumpinfo_t srclumps[HEADER_LUMPS] =
.maxcount = MAX_MAP_ENTSTRING,
.entrysize = sizeof( byte ),
.loadname = "entities",
.dataptr = (const void **)&srcmodel.entdata,
.count = &srcmodel.entdatasize,
.dataofs = offsetof( dbspmodel_t, entdata ),
.countofs = offsetof( dbspmodel_t, entdatasize ),
},
{
.lumpnumber = LUMP_PLANES,
@@ -210,8 +364,9 @@ static mlumpinfo_t srclumps[HEADER_LUMPS] =
.maxcount = MAX_MAP_PLANES,
.entrysize = sizeof( dplane_t ),
.loadname = "planes",
.dataptr = (const void **)&srcmodel.planes,
.count = &srcmodel.numplanes,
.dataofs = offsetof( dbspmodel_t, planes ),
.countofs = offsetof( dbspmodel_t, numplanes ),
LUMP_SWAP( dplane_swap )
},
{
.lumpnumber = LUMP_TEXTURES,
@@ -219,24 +374,25 @@ static mlumpinfo_t srclumps[HEADER_LUMPS] =
.maxcount = MAX_MAP_MIPTEX,
.entrysize = sizeof( byte ),
.loadname = "textures",
.dataptr = (const void **)&srcmodel.textures,
.count = &srcmodel.texdatasize,
.dataofs = offsetof( dbspmodel_t, textures ),
.countofs = offsetof( dbspmodel_t, texdatasize ),
},
{
.lumpnumber = LUMP_VERTEXES,
.maxcount = MAX_MAP_VERTS,
.entrysize = sizeof( dvertex_t ),
.loadname = "vertexes",
.dataptr = (const void **)&srcmodel.vertexes,
.count = &srcmodel.numvertexes,
.dataofs = offsetof( dbspmodel_t, vertexes ),
.countofs = offsetof( dbspmodel_t, numvertexes ),
LUMP_SWAP( dvertex_swap )
},
{
.lumpnumber = LUMP_VISIBILITY,
.maxcount = MAX_MAP_VISIBILITY,
.entrysize = sizeof( byte ),
.loadname = "visibility",
.dataptr = (const void **)&srcmodel.visdata,
.count = &srcmodel.visdatasize,
.dataofs = offsetof( dbspmodel_t, visdata ),
.countofs = offsetof( dbspmodel_t, visdatasize ),
},
{
.lumpnumber = LUMP_NODES,
@@ -246,8 +402,9 @@ static mlumpinfo_t srclumps[HEADER_LUMPS] =
.entrysize32 = sizeof( dnode32_t ),
.loadname = "nodes",
.flags = CHECK_OVERFLOW,
.dataptr = (const void **)&srcmodel.nodes,
.count = &srcmodel.numnodes,
.dataofs = offsetof( dbspmodel_t, nodes ),
.countofs = offsetof( dbspmodel_t, numnodes ),
LUMP_SWAP32( dnode_swap, dnode32_swap )
},
{
.lumpnumber = LUMP_TEXINFO,
@@ -256,8 +413,9 @@ static mlumpinfo_t srclumps[HEADER_LUMPS] =
.entrysize = sizeof( dtexinfo_t ),
.loadname = "texinfo",
.flags = CHECK_OVERFLOW,
.dataptr = (const void **)&srcmodel.texinfo,
.count = &srcmodel.numtexinfo,
.dataofs = offsetof( dbspmodel_t, texinfo ),
.countofs = offsetof( dbspmodel_t, numtexinfo ),
LUMP_SWAP( dtexinfo_swap )
},
{
.lumpnumber = LUMP_FACES,
@@ -267,8 +425,9 @@ static mlumpinfo_t srclumps[HEADER_LUMPS] =
.entrysize32 = sizeof( dface32_t ),
.loadname = "faces",
.flags = CHECK_OVERFLOW,
.dataptr = (const void **)&srcmodel.surfaces,
.count = &srcmodel.numsurfaces,
.dataofs = offsetof( dbspmodel_t, surfaces ),
.countofs = offsetof( dbspmodel_t, numsurfaces ),
LUMP_SWAP32( dface_swap, dface32_swap )
},
{
.lumpnumber = LUMP_LIGHTING,
@@ -277,8 +436,8 @@ static mlumpinfo_t srclumps[HEADER_LUMPS] =
.entrysize = sizeof( byte ),
.loadname = "lightmaps",
.flags = 0,
.dataptr = (const void **)&srcmodel.lightdata,
.count = &srcmodel.lightdatasize,
.dataofs = offsetof( dbspmodel_t, lightdata ),
.countofs = offsetof( dbspmodel_t, lightdatasize ),
},
{
.lumpnumber = LUMP_CLIPNODES,
@@ -288,8 +447,9 @@ static mlumpinfo_t srclumps[HEADER_LUMPS] =
.entrysize32 = sizeof( dclipnode32_t ),
.loadname = "clipnodes",
.flags = 0,
.dataptr = (const void **)&srcmodel.clipnodes,
.count = &srcmodel.numclipnodes,
.dataofs = offsetof( dbspmodel_t, clipnodes ),
.countofs = offsetof( dbspmodel_t, numclipnodes ),
LUMP_SWAP32( dclipnode_swap, dclipnode32_swap )
},
{
.lumpnumber = LUMP_LEAFS,
@@ -299,8 +459,9 @@ static mlumpinfo_t srclumps[HEADER_LUMPS] =
.entrysize32 = sizeof( dleaf32_t ),
.loadname = "leafs",
.flags = CHECK_OVERFLOW,
.dataptr = (const void **)&srcmodel.leafs,
.count = &srcmodel.numleafs,
.dataofs = offsetof( dbspmodel_t, leafs ),
.countofs = offsetof( dbspmodel_t, numleafs ),
LUMP_SWAP32( dleaf_swap, dleaf32_swap )
},
{
.lumpnumber = LUMP_MARKSURFACES,
@@ -310,8 +471,8 @@ static mlumpinfo_t srclumps[HEADER_LUMPS] =
.entrysize32 = sizeof( dmarkface32_t ),
.loadname = "markfaces",
.flags = 0,
.dataptr = (const void **)&srcmodel.markfaces,
.count = &srcmodel.nummarkfaces,
.dataofs = offsetof( dbspmodel_t, markfaces ),
.countofs = offsetof( dbspmodel_t, nummarkfaces ),
},
{
.lumpnumber = LUMP_EDGES,
@@ -321,8 +482,9 @@ static mlumpinfo_t srclumps[HEADER_LUMPS] =
.entrysize32 = sizeof( dedge32_t ),
.loadname = "edges",
.flags = 0,
.dataptr = (const void **)&srcmodel.edges,
.count = &srcmodel.numedges,
.dataofs = offsetof( dbspmodel_t, edges ),
.countofs = offsetof( dbspmodel_t, numedges ),
LUMP_SWAP32( dedge_swap, dedge32_swap )
},
{
.lumpnumber = LUMP_SURFEDGES,
@@ -331,8 +493,8 @@ static mlumpinfo_t srclumps[HEADER_LUMPS] =
.entrysize = sizeof( dsurfedge_t ),
.loadname = "surfedges",
.flags = 0,
.dataptr = (const void **)&srcmodel.surfedges,
.count = &srcmodel.numsurfedges,
.dataofs = offsetof( dbspmodel_t, surfedges ),
.countofs = offsetof( dbspmodel_t, numsurfedges ),
},
{
.lumpnumber = LUMP_MODELS,
@@ -341,8 +503,9 @@ static mlumpinfo_t srclumps[HEADER_LUMPS] =
.entrysize = sizeof( dmodel_t ),
.loadname = "models",
.flags = CHECK_OVERFLOW,
.dataptr = (const void **)&srcmodel.submodels,
.count = &srcmodel.numsubmodels,
.dataofs = offsetof( dbspmodel_t, submodels ),
.countofs = offsetof( dbspmodel_t, numsubmodels ),
LUMP_SWAP( dmodel_swap )
},
};
@@ -354,8 +517,8 @@ static const mlumpinfo_t extlumps[EXTRA_LUMPS] =
.maxcount = MAX_MAP_LIGHTING,
.entrysize = sizeof( byte ),
.loadname = "deluxmaps",
.dataptr = (const void **)&srcmodel.deluxdata,
.count = &srcmodel.deluxdatasize,
.dataofs = offsetof( dbspmodel_t, deluxdata ),
.countofs = offsetof( dbspmodel_t, deluxdatasize ),
},
{
.lumpnumber = LUMP_FACEINFO,
@@ -364,8 +527,9 @@ static const mlumpinfo_t extlumps[EXTRA_LUMPS] =
.entrysize = sizeof( dfaceinfo_t ),
.loadname = "faceinfos",
.flags = CHECK_OVERFLOW,
.dataptr = (const void **)&srcmodel.faceinfo,
.count = &srcmodel.numfaceinfo,
.dataofs = offsetof( dbspmodel_t, faceinfo ),
.countofs = offsetof( dbspmodel_t, numfaceinfo ),
LUMP_SWAP( dfaceinfo_swap )
},
{
.lumpnumber = LUMP_SHADOWMAP,
@@ -373,8 +537,8 @@ static const mlumpinfo_t extlumps[EXTRA_LUMPS] =
.maxcount = MAX_MAP_LIGHTING / 3,
.entrysize = sizeof( byte ),
.loadname = "shadowmap",
.dataptr = (const void **)&srcmodel.shadowdata,
.count = &srcmodel.shadowdatasize,
.dataofs = offsetof( dbspmodel_t, shadowdata ),
.countofs = offsetof( dbspmodel_t, shadowdatasize ),
},
};
@@ -385,16 +549,16 @@ static const mlumpinfo_t bspxlumps[] =
.maxcount = MAX_MAP_LIGHTING,
.entrysize = sizeof( byte ),
.loadname = "rgblighting",
.dataptr = (const void **)&srcmodel.rgblightdata,
.count = &srcmodel.rgblightdatasize,
.dataofs = offsetof( dbspmodel_t, rgblightdata ),
.countofs = offsetof( dbspmodel_t, rgblightdatasize ),
},
{
.lumpname = "LIGHTINGDIR",
.maxcount = MAX_MAP_LIGHTING,
.entrysize = sizeof( byte ),
.loadname = "lightingdir",
.dataptr = (const void **)&srcmodel.deluxdata,
.count = &srcmodel.deluxdatasize,
.dataofs = offsetof( dbspmodel_t, deluxdata ),
.countofs = offsetof( dbspmodel_t, deluxdatasize ),
},
};
@@ -593,7 +757,7 @@ Mod_LoadLump
generic loader
=================
*/
static void Mod_LoadLump( const void *in, const mlumpinfo_t *info, mlumpstat_t *stat, int flags, loadlump_source_t source, const void *bspx_data )
static void Mod_LoadLump( const void *in, const mlumpinfo_t *info, mlumpstat_t *stat, int flags, loadlump_source_t source, const void *bspx_data, dbspmodel_t *bmod )
{
int version = ((const dheader_t *)in)->version, i;
size_t numelems, real_entrysize;
@@ -604,7 +768,16 @@ static void Mod_LoadLump( const void *in, const mlumpinfo_t *info, mlumpstat_t *
case LOADLUMP_STANDARD:
{
const dheader_t *header = in;
l = header->lumps[info->lumpnumber];
int lumpnumber = info->lumpnumber;
if( FBitSet( flags, LUMP_BSHIFT_SWAP ))
{
if( lumpnumber == LUMP_ENTITIES )
lumpnumber = LUMP_PLANES;
else if( lumpnumber == LUMP_PLANES )
lumpnumber = LUMP_ENTITIES;
}
l = header->lumps[lumpnumber];
break;
}
case LOADLUMP_BSP30EXT:
@@ -671,7 +844,7 @@ static void Mod_LoadLump( const void *in, const mlumpinfo_t *info, mlumpstat_t *
}
// bmodels not required the visibility
if( !FBitSet( flags, LUMP_TESTONLY ) && !world.loading && info->lumpnumber == LUMP_VISIBILITY )
if( info->lumpnumber == LUMP_VISIBILITY && !world.loading && bmod )
SetBits( flags, LUMP_SILENT ); // shut up warning
// fill the stats for world
@@ -749,15 +922,33 @@ static void Mod_LoadLump( const void *in, const mlumpinfo_t *info, mlumpstat_t *
}
}
if( FBitSet( flags, LUMP_TESTONLY ))
return; // don't fill the intermediate struct
// if no bmod is passed, we are only testing if BSP lumps are not corrupted
if( !bmod )
return;
byte *data = (byte *)in + l.fileofs;
// all checks are passed, store pointers
if( info->dataptr )
*info->dataptr = (void *)((byte *)in + l.fileofs);
*(byte **)((byte *)bmod + info->dataofs) = data;
*(size_t *)((byte *)bmod + info->countofs) = numelems;
if( info->count )
*info->count = numelems;
// finally, process the data
#if XASH_BIG_ENDIAN
const swap_struct_def_t *swap = real_entrysize == info->entrysize32 ? info->swap32 : info->swap;
size_t swaplen = real_entrysize == info->entrysize32 ? info->swaplen32 : info->swaplen;
if( swap )
{
for( size_t j = 0; j < numelems; j++ )
swap_struct_( swap, swaplen, data + j * real_entrysize );
}
// some lumps don't need a swapdef, as all needed data in the lump info
else if( real_entrysize > 1 )
{
for( size_t j = 0; j < numelems; j++ )
swap_field_( data + j * real_entrysize, real_entrysize );
}
#endif
}
/*
@@ -2014,7 +2205,7 @@ static void Mod_SetupSubmodels( model_t *mod, dbspmodel_t *bmod )
SetBits( mod->flags, MODEL_HAS_ORIGIN );
#ifdef HACKS_RELATED_HLMODS
// c2a1 doesn't have origin brush it's just placed at center of the level
if( !Q_stricmp( name, "maps/c2a1.bsp" ) && ( i == 11 ))
if( i == 11 && !Q_stricmp( name, "maps/c2a1.bsp" ))
SetBits( mod->flags, MODEL_HAS_ORIGIN );
#endif
}
@@ -2216,6 +2407,9 @@ static void Mod_LoadEntities( model_t *mod, const dbspmodel_t *bmod )
world.wadlist = NULL;
world.wadcount = 0;
world.litwater_minlight = -1;
world.litwater_scale = -1.0f;
// parse all the wads for loading textures in right ordering
while(( pfile = COM_ParseFile( pfile, token, sizeof( token ))) != NULL )
{
@@ -2259,6 +2453,15 @@ static void Mod_LoadEntities( model_t *mod, const dbspmodel_t *bmod )
Mem_Free( world.generator );
world.generator = copystring( token );
}
else if( !Q_stricmp( keyname, "_litwater" ))
{
if( Q_atoi( token ) != 0 )
SetBits( world.flags, FWORLD_HAS_LITWATER );
}
else if( !Q_stricmp( keyname, "_litwater_minlight" ))
world.litwater_minlight = Q_atoi( token );
else if( !Q_stricmp( keyname, "_litwater_scale" ))
world.litwater_scale = Q_atof( token );
}
return; // all done
}
@@ -2535,6 +2738,7 @@ static void Mod_InitSkyClouds( model_t *mod, const mip_t *mt, texture_t *tx, qbo
if( custom_palette )
size += sizeof( short ) + 768;
Image_SetForceFlags( IL_HOST_ENDIAN );
r_sky = FS_LoadImage( texname, (byte *)mt, size );
}
else
@@ -2717,7 +2921,7 @@ static void Mod_LoadTextureData( model_t *mod, dbspmodel_t *bmod, int textureInd
const size_t size = Mod_CalculateMipTexSize( mipTex, usesCustomPalette );
Q_snprintf( texName, sizeof( texName ), "#%s:%s.mip", loadstat.name, mipTex->name );
Image_SetForceFlags( texture_force_flags );
Image_SetForceFlags( texture_force_flags | IL_HOST_ENDIAN );
texture->gl_texturenum = ref.dllFuncs.GL_LoadTexture( texName, (byte *)mipTex, size, txFlags );
}
@@ -2750,7 +2954,7 @@ static void Mod_LoadTextureData( model_t *mod, dbspmodel_t *bmod, int textureInd
Q_snprintf( texName, sizeof( texName ), "#%s:%s_luma.mip", loadstat.name, mipTex->name );
Image_SetForceFlags( texture_force_flags );
Image_SetForceFlags( texture_force_flags | IL_HOST_ENDIAN );
if( mipTex->offsets[0] > 0 )
{
@@ -2975,6 +3179,24 @@ static void Mod_LoadTextures( model_t *mod, dbspmodel_t *bmod )
lump = bmod->textures;
#if XASH_BIG_ENDIAN
if( lump )
{
lump->nummiptex = LittleLong( lump->nummiptex );
for( int i = 0; i < lump->nummiptex; i++ )
{
lump->dataofs[i] = LittleLong( lump->dataofs[i] );
if( lump->dataofs[i] >= 0 )
{
mip_t *mt = (mip_t *)((byte *)lump + lump->dataofs[i]);
le_struct_swap( mip_swap, mt );
}
}
}
#endif
if( bmod->texdatasize < 1 || !lump || lump->nummiptex < 1 )
{
// no textures
@@ -2989,6 +3211,105 @@ static void Mod_LoadTextures( model_t *mod, dbspmodel_t *bmod )
Mod_SequenceAllAnimatedTextures( mod );
}
#if !XASH_DEDICATED
static void Mod_ParseDetailTextures( model_t *mod )
{
byte *afile;
char *pfile;
string token, texname;
string detail_texname;
string detail_path;
float xScale, yScale;
texture_t *tex;
int i;
string filepath;
Q_strncpy( filepath, mod->name, sizeof( filepath ));
COM_StripExtension( filepath );
Q_strncat( filepath, "_detail.txt", sizeof( filepath ));
afile = FS_LoadFile( filepath, NULL, false );
if( !afile )
return;
pfile = (char *)afile;
// format: 'texturename' 'detailtexture' 'xScale' 'yScale'
while(( pfile = COM_ParseFile( pfile, token, sizeof( token ))) != NULL )
{
texname[0] = '\0';
detail_texname[0] = '\0';
// read texname
if( token[0] == '{' )
{
// NOTE: COM_ParseFile handled some symbols seperately
// this code will be fix it
pfile = COM_ParseFile( pfile, token, sizeof( token ));
Q_snprintf( texname, sizeof( texname ), "{%s", token );
}
else
Q_strncpy( texname, token, sizeof( texname ));
// read detailtexture name
pfile = COM_ParseFile( pfile, token, sizeof( token ));
Q_strncpy( detail_texname, token, sizeof( detail_texname ));
// trying the scales or '{'
pfile = COM_ParseFile( pfile, token, sizeof( token ));
// read second part of detailtexture name
if( token[0] == '{' )
{
Q_strncat( detail_texname, token, sizeof( detail_texname ));
pfile = COM_ParseFile( pfile, token, sizeof( token )); // read scales
Q_strncat( detail_texname, token, sizeof( detail_texname ));
pfile = COM_ParseFile( pfile, token, sizeof( token )); // parse scales
}
Q_snprintf( detail_path, sizeof( detail_path ), "gfx/%s", detail_texname );
// read scales
xScale = Q_atof( token );
pfile = COM_ParseFile( pfile, token, sizeof( token ));
yScale = Q_atof( token );
if( xScale <= 0.0f || yScale <= 0.0f )
continue;
// search for existing texture and uploading detail texture
for( i = 0; i < mod->numtextures; i++ )
{
tex = mod->textures[i];
if( Q_stricmp( tex->name, texname ))
continue;
tex->dt_texturenum = ref.dllFuncs.GL_LoadTexture( detail_path, NULL, 0, TF_FORCE_COLOR|TF_NOFLIP_TGA );
if( tex->dt_texturenum )
ref.dllFuncs.R_SetDetailScaleForTexture( tex->gl_texturenum, xScale, yScale );
break;
}
}
Mem_Free( afile );
}
void Mod_LoadDetailTextures( model_t *mod )
{
convar_t *r_detailtextures = Cvar_FindVar( "r_detailtextures" );
if( !r_detailtextures || !r_detailtextures->value )
return;
Mod_ParseDetailTextures( mod );
}
#endif // !XASH_DEDICATED
/*
=================
Mod_LoadTexInfo
@@ -3746,6 +4067,103 @@ static int Mod_LumpLooksLikeEntities( const char *lump, const size_t lumplen )
return Q_memmem( lump, lumplen, "\"classname\"", sizeof( "\"classname\"" ) - 1 ) != NULL ? 1 : 0;
}
static void Mod_SwapBSPLumps( byte *mod_base, size_t bufferlen )
{
dheader_t *header = (dheader_t *)mod_base;
le_struct_swap( dheader_swap, header );
// BSP30ext pass
if( header->version == HLBSP_VERSION && bufferlen > sizeof( *header ) + sizeof( dextrahdr_t ))
{
dextrahdr_t *ext = (dextrahdr_t *)( mod_base + sizeof( *header ));
if( ext->id == LittleLong( IDEXTRAHEADER ))
le_struct_swap( dextrahdr_swap, ext );
}
}
/*
=================
CRC32_MapFile
compute CRC for the map lump data
=================
*/
qboolean CRC32_MapFile( dword *crcvalue, const char *filename, qboolean multiplayer )
{
byte headbuf[sizeof( dheader_t )];
char buffer[1024];
int i, num_bytes, lumplen;
dheader_t *header;
file_t *f;
if( !crcvalue )
return false;
// always calc same checksum for singleplayer
if( multiplayer == false )
{
*crcvalue = (('H'<<24)+('S'<<16)+('A'<<8)+'X');
return true;
}
f = FS_Open( filename, "rb", false );
if( !f )
return false;
num_bytes = FS_Read( f, headbuf, sizeof( headbuf ));
if( num_bytes != sizeof( headbuf ))
{
FS_Close( f );
return false;
}
header = (dheader_t *)headbuf;
le_struct_swap( dheader_swap, header );
switch( header->version )
{
case Q1BSP_VERSION:
case HLBSP_VERSION:
case QBSP2_VERSION:
break;
default:
FS_Close( f );
return false;
}
CRC32_Init( crcvalue );
for( i = LUMP_PLANES; i < HEADER_LUMPS; i++ )
{
lumplen = header->lumps[i].filelen;
FS_Seek( f, header->lumps[i].fileofs, SEEK_SET );
while( lumplen > 0 )
{
if( lumplen >= sizeof( buffer ))
num_bytes = FS_Read( f, buffer, sizeof( buffer ));
else
num_bytes = FS_Read( f, buffer, lumplen );
if( num_bytes > 0 )
{
lumplen -= num_bytes;
CRC32_ProcessBuffer( crcvalue, buffer, num_bytes );
}
if( FS_Eof( f ))
break;
}
}
FS_Close( f );
return true;
}
/*
=================
Mod_FindEndOfBSPFile
@@ -3790,20 +4208,26 @@ Mod_FindBSPX
find BSPX header position, returns -1 on error
=================
*/
static fs_offset_t Mod_FindBSPX( const byte *mod_base, size_t bufferlen )
static fs_offset_t Mod_FindBSPX( byte *mod_base, size_t bufferlen )
{
fs_offset_t max_offset = Mod_FindEndOfBSPFile( mod_base, bufferlen );
const dbspx_hdr_t *bspx_header;
max_offset = ALIGN( max_offset, 4 ); // force 32-bit boundary
if( max_offset + sizeof( dbspx_hdr_t ) > bufferlen )
return -1;
bspx_header = (const dbspx_hdr_t *)( mod_base + max_offset );
dbspx_hdr_t *bspx_header = (dbspx_hdr_t *)( mod_base + max_offset );
if( bspx_header->id != LittleLong( IDBSPXHEADER ))
return -1;
bspx_header->id = LittleLong( bspx_header->id );
bspx_header->numlumps = LittleLong( bspx_header->numlumps );
for( int i = 0; i < bspx_header->numlumps; i++ )
le_struct_swap( dbspx_lump_swap, &bspx_header->lumps[i] );
Con_DPrintf( "Found valid BSPX signature at %lld\n", (long long)max_offset );
return max_offset;
}
@@ -3819,22 +4243,19 @@ static qboolean Mod_LoadBmodelLumps( model_t *mod, byte *mod_base, size_t buffer
{
dheader_t *header = (dheader_t *)mod_base;
int *extident = (int *)(mod_base + sizeof( dheader_t ));
dbspmodel_t *bmod = &srcmodel;
char wadvalue[2048];
size_t len = 0;
int i, stat_index = 0, ret, flags = 0;
fs_offset_t bspx_header_offset;
// always reset the intermediate struct
memset( bmod, 0, sizeof( *bmod ));
memset( &loadstat, 0, sizeof( loadstat ));
Q_strncpy( loadstat.name, mod->name, sizeof( loadstat.name ));
wadvalue[0] = '\0';
// restore default lump numbers
srclumps[0].lumpnumber = LUMP_ENTITIES;
srclumps[1].lumpnumber = LUMP_PLANES;
// byte-swap BSP header and lump directory from little-endian
Mod_SwapBSPLumps( mod_base, bufferlen );
switch( header->version )
{
@@ -3848,8 +4269,7 @@ static qboolean Mod_LoadBmodelLumps( model_t *mod, byte *mod_base, size_t buffer
Mod_LumpLooksLikeEntities( mod_base + header->lumps[LUMP_PLANES].fileofs, header->lumps[LUMP_PLANES].filelen ))
{
// blue-shift swapped lumps
srclumps[0].lumpnumber = LUMP_PLANES;
srclumps[1].lumpnumber = LUMP_ENTITIES;
SetBits( flags, LUMP_BSHIFT_SWAP );
}
break;
case Q1BSP_VERSION:
@@ -3863,6 +4283,7 @@ static qboolean Mod_LoadBmodelLumps( model_t *mod, byte *mod_base, size_t buffer
return false;
}
dbspmodel_t *bmod = Mem_Calloc( mod->mempool, sizeof( *bmod ));
bmod->version = header->version; // share up global
if( isworld )
{
@@ -3875,17 +4296,17 @@ static qboolean Mod_LoadBmodelLumps( model_t *mod, byte *mod_base, size_t buffer
// loading base lumps
for( i = 0; i < ARRAYSIZE( srclumps ); i++, stat_index++ )
Mod_LoadLump( mod_base, &srclumps[i], &worldstats[stat_index], flags, LOADLUMP_STANDARD, NULL );
Mod_LoadLump( mod_base, &srclumps[i], &worldstats[stat_index], flags, LOADLUMP_STANDARD, NULL, bmod );
// loading extralumps
for( i = 0; i < ARRAYSIZE( extlumps ); i++, stat_index++ )
Mod_LoadLump( mod_base, &extlumps[i], &worldstats[stat_index], flags, LOADLUMP_BSP30EXT, NULL );
Mod_LoadLump( mod_base, &extlumps[i], &worldstats[stat_index], flags, LOADLUMP_BSP30EXT, NULL, bmod );
// loading bspx lumps
if( bspx_header_offset >= 0 )
{
for( i = 0; i < ARRAYSIZE( bspxlumps ); i++, stat_index++ )
Mod_LoadLump( mod_base, &bspxlumps[i], &worldstats[stat_index], flags, LOADLUMP_BSPX, mod_base + bspx_header_offset );
Mod_LoadLump( mod_base, &bspxlumps[i], &worldstats[stat_index], flags, LOADLUMP_BSPX, mod_base + bspx_header_offset, bmod );
}
if( !bmod->isworld ) // a1ba: why world excluded here?
@@ -3893,6 +4314,7 @@ static qboolean Mod_LoadBmodelLumps( model_t *mod, byte *mod_base, size_t buffer
if( loadstat.numerrors )
{
Con_DPrintf( "%s: %i error(s), %i warning(s)\n", __func__, loadstat.numerrors, loadstat.numwarnings );
Mem_Free( bmod );
return false; // there were errors, we can't load this map
}
@@ -3957,6 +4379,7 @@ static qboolean Mod_LoadBmodelLumps( model_t *mod, byte *mod_base, size_t buffer
Con_Reportf( "Wad files required to run the map: \"%s\"\n", wadvalue );
}
Mem_Free( bmod );
return true;
}
@@ -3999,7 +4422,7 @@ qboolean Mod_TestBmodelLumps( file_t *f, const char *name, byte *mod_base, size_
{
dheader_t *header = (dheader_t *)mod_base;
int *extident = (int *)( mod_base + sizeof( dheader_t ));
int i, flags = LUMP_TESTONLY, stat_index = 0;
int i, flags = 0, stat_index = 0;
// always reset the intermediate struct
memset( &loadstat, 0, sizeof( loadstat_t ));
@@ -4012,9 +4435,8 @@ qboolean Mod_TestBmodelLumps( file_t *f, const char *name, byte *mod_base, size_
if( buffersize < sizeof( *header ))
return false;
// restore default lump numbers
srclumps[0].lumpnumber = LUMP_ENTITIES;
srclumps[1].lumpnumber = LUMP_PLANES;
// byte-swap BSP header and lump directory from little-endian
Mod_SwapBSPLumps( mod_base, buffersize );
switch( header->version )
{
@@ -4037,11 +4459,7 @@ qboolean Mod_TestBmodelLumps( file_t *f, const char *name, byte *mod_base, size_
return false;
if( ret )
{
// blue-shift swapped lumps
srclumps[0].lumpnumber = LUMP_PLANES;
srclumps[1].lumpnumber = LUMP_ENTITIES;
}
SetBits( flags, LUMP_BSHIFT_SWAP );
}
}
break;
@@ -4057,15 +4475,15 @@ qboolean Mod_TestBmodelLumps( file_t *f, const char *name, byte *mod_base, size_
}
// get entities lump to caller
*entities = header->lumps[srclumps[0].lumpnumber];
*entities = header->lumps[FBitSet( flags, LUMP_BSHIFT_SWAP ) ? LUMP_PLANES : LUMP_ENTITIES];
// loading base lumps
for( i = 0; i < ARRAYSIZE( srclumps ); i++, stat_index++ )
Mod_LoadLump( mod_base, &srclumps[i], &worldstats[stat_index], flags, LOADLUMP_STANDARD, NULL );
Mod_LoadLump( mod_base, &srclumps[i], &worldstats[stat_index], flags, LOADLUMP_STANDARD, NULL, NULL );
// loading extralumps
for( i = 0; i < ARRAYSIZE( extlumps ); i++, stat_index++ )
Mod_LoadLump( mod_base, &extlumps[i], &worldstats[stat_index], flags, LOADLUMP_BSP30EXT, NULL );
Mod_LoadLump( mod_base, &extlumps[i], &worldstats[stat_index], flags, LOADLUMP_BSP30EXT, NULL, NULL );
// FIXME: BSPX testing

View File

@@ -124,6 +124,10 @@ typedef struct world_static_s
wadentry_t *wadlist;
int wadcount;
// lightmapped water extra info
float litwater_scale;
int litwater_minlight;
} world_static_t;
#ifndef REF_DLL
@@ -156,6 +160,7 @@ model_t *Mod_ForName( const char *name, qboolean crash, qboolean trackCRC );
qboolean Mod_ValidateCRC( const char *name, uint32_t crc );
void Mod_NeedCRC( const char *name, qboolean needCRC );
void Mod_FreeUnused( void );
void Mod_LoadDetailTextures( model_t *mod );
//
// mod_alias.c
@@ -180,7 +185,7 @@ void Mod_PrintWorldStats_f( void );
//
// mod_studio.c
//
void Mod_LoadStudioModel( model_t *mod, const void *buffer, qboolean *loaded );
void Mod_LoadStudioModel( model_t *mod, void *buffer, size_t buffersize, qboolean *loaded );
void Mod_InitStudioAPI( void );
void Mod_InitStudioHull( void );
void Mod_ResetStudioAPI( void );
@@ -190,13 +195,16 @@ void Mod_StudioGetAttachment( const edict_t *e, int iAttachment, float *org, flo
void Mod_GetBonePosition( const edict_t *e, int iBone, float *org, float *ang );
hull_t *Mod_HullForStudio( model_t *m, float frame, int seq, vec3_t ang, vec3_t org, vec3_t size, byte *pcnt, byte *pbl, int *hitboxes, edict_t *ed );
void *R_StudioGetAnim( studiohdr_t *m_pStudioHeader, model_t *m_pSubModel, mstudioseqdesc_t *pseqdesc );
void Mod_SwapStudioSeqGroupAnims( studiohdr_t *phdr, mstudioseqdesc_t *pseq, byte *buf );
int Mod_HitgroupForStudioHull( int index );
void Mod_ClearStudioCache( void );
//
// mod_sprite.c
//
void Mod_LoadSpriteModel( model_t *mod, const void *buffer, size_t buffersize, qboolean *loaded );
#endif
void Mod_LoadSpriteModel( model_t *mod, void *buffer, size_t buffersize, qboolean *loaded );
void Mod_SpriteLoadTextures( model_t *mod, const void *buffer );
void Mod_SpriteUnloadTextures( void *data );
#endif
#endif//MOD_LOCAL_H

View File

@@ -17,11 +17,169 @@ GNU General Public License for more details.
#include "common.h"
#include "sprite.h"
#include "studio.h"
#if !XASH_DEDICATED
#include "ref_common.h"
#endif // XASH_DEDICATED
#include "mod_local.h"
#include "swaplib.h"
le_struct_begin( dsprite_q1_swap )
le_struct_field( dsprite_q1_t, ident )
le_struct_field( dsprite_q1_t, version )
le_struct_field( dsprite_q1_t, type )
le_struct_field( dsprite_q1_t, boundingradius )
le_struct_array( dsprite_q1_t, bounds, 2 )
le_struct_field( dsprite_q1_t, numframes )
le_struct_field( dsprite_q1_t, beamlength )
le_struct_field( dsprite_q1_t, synctype )
le_struct_end();
le_struct_begin( dsprite_hl_swap )
le_struct_field( dsprite_hl_t, ident )
le_struct_field( dsprite_hl_t, version )
le_struct_field( dsprite_hl_t, type )
le_struct_field( dsprite_hl_t, texFormat )
le_struct_field( dsprite_hl_t, boundingradius )
le_struct_array( dsprite_hl_t, bounds, 2 )
le_struct_field( dsprite_hl_t, numframes )
le_struct_field( dsprite_hl_t, facetype )
le_struct_field( dsprite_hl_t, synctype )
le_struct_end();
le_struct_begin( dspriteframe_swap )
le_struct_array( dspriteframe_t, origin, 2 )
le_struct_field( dspriteframe_t, width )
le_struct_field( dspriteframe_t, height )
le_struct_end();
static byte *Mod_SwapSpriteFrame( byte *p, byte *end, int bytes )
{
dspriteframe_t frame;
if( p + sizeof( frame ) > end )
return NULL;
memcpy( &frame, p, sizeof( frame ));
le_struct_swap( dspriteframe_swap, &frame );
#if XASH_BIG_ENDIAN
memcpy( p, &frame, sizeof( frame ));
#endif
p += sizeof( frame );
// skip pixel data
if( p + frame.width * frame.height * bytes > end )
return NULL;
p += frame.width * frame.height * bytes;
return p;
}
static byte *Mod_SwapSpriteGroup( byte *p, byte *end, int bytes )
{
dspritegroup_t *group;
if( p + sizeof( *group ) > end )
return NULL;
group = (dspritegroup_t *)p;
group->numframes = LittleLong( group->numframes );
p += sizeof( *group );
// swap intervals
int numframes = group->numframes;
if( p + numframes * sizeof( dspriteinterval_t ) > end )
return NULL;
for( int i = 0; i < numframes; i++ )
{
dspriteinterval_t *interval = (dspriteinterval_t *)p;
interval->interval = LittleFloat( interval->interval );
p += sizeof( *interval );
}
// swap each frame in the group
for( int i = 0; i < numframes; i++ )
{
p = Mod_SwapSpriteFrame( p, end, bytes );
if( !p )
return NULL;
}
return p;
}
static qboolean Mod_SwapSprite( void *buffer, size_t buffersize, int *out_version )
{
byte *end = (byte *)buffer + buffersize;
int version, numframes, bytes;
byte *p;
if( buffersize < sizeof( dsprite_t ))
return false;
// peek at ident + version before full swap
version = LittleLong(((dsprite_t *)buffer)->version );
switch( version )
{
case SPRITE_VERSION_Q1:
case SPRITE_VERSION_32:
if( buffersize < sizeof( dsprite_q1_t ))
return false;
le_struct_swap( dsprite_q1_swap, buffer );
numframes = ((dsprite_q1_t *)buffer)->numframes;
p = (byte *)buffer + sizeof( dsprite_q1_t );
break;
case SPRITE_VERSION_HL:
{
if( buffersize < sizeof( dsprite_hl_t ))
return false;
le_struct_swap( dsprite_hl_swap, buffer );
numframes = ((dsprite_hl_t *)buffer)->numframes;
p = (byte *)buffer + sizeof( dsprite_hl_t );
// HL sprites have a palette count (short) + palette data
if( p + sizeof( short ) > end )
return false;
short numi = LittleShort( *(short *)p );
*(short *)p = numi;
p += sizeof( short ) + numi * 3;
break;
}
default:
return false;
}
*out_version = version;
bytes = ( version == SPRITE_VERSION_32 ) ? 4 : 1;
// swap all frames
for( int i = 0; i < numframes && p && p < end; i++ )
{
dframetype_t *frametype;
if( p + sizeof( *frametype ) > end )
return false;
frametype = (dframetype_t *)p;
frametype->type = LittleLong( frametype->type );
p += sizeof( *frametype );
switch( frametype->type )
{
case FRAME_SINGLE:
p = Mod_SwapSpriteFrame( p, end, bytes );
break;
case FRAME_GROUP:
case FRAME_ANGLED:
p = Mod_SwapSpriteGroup( p, end, bytes );
break;
default:
return false;
}
}
return true;
}
/*
====================
@@ -30,46 +188,18 @@ Mod_LoadSpriteModel
load sprite model
====================
*/
void Mod_LoadSpriteModel( model_t *mod, const void *buffer, size_t buffersize, qboolean *loaded )
void Mod_LoadSpriteModel( model_t *mod, void *buffer, size_t buffersize, qboolean *loaded )
{
const dsprite_t *pin = buffer;
msprite_t *psprite;
char poolname[MAX_VA_STRING];
int version;
if( loaded )
*loaded = false;
if( buffersize < sizeof( dsprite_t ))
if( !Mod_SwapSprite( buffer, buffersize, &version ))
{
Con_DPrintf( S_ERROR "%s: %s have incorrect file size %zu should be greater than %zu (%s)\n", __func__, mod->name, buffersize, sizeof( dsprite_t ), "basic header" );
return;
}
if( pin->ident != IDSPRITEHEADER )
{
Con_DPrintf( S_ERROR "%s: %s has wrong id (0x%x should be 0x%x)\n", __func__, mod->name, pin->ident, IDSPRITEHEADER );
return;
}
switch( pin->version )
{
case SPRITE_VERSION_Q1:
case SPRITE_VERSION_32:
if( buffersize < sizeof( dsprite_q1_t ))
{
Con_DPrintf( S_ERROR "%s: %s have incorrect file size %zu should be greater than %zu (%s)\n", __func__, mod->name, buffersize, sizeof( dsprite_q1_t ), "q1 header" );
return;
}
break;
case SPRITE_VERSION_HL:
if( buffersize < sizeof( dsprite_hl_t ))
{
Con_DPrintf( S_ERROR "%s: %s have incorrect file size %zu should be greater than %zu (%s)\n", __func__, mod->name, buffersize, sizeof( dsprite_hl_t ), "hl header" );
return;
}
break;
default:
Con_DPrintf( S_ERROR "%s: %s has wrong version number (%i should be %i, %i or %i)\n", __func__, mod->name, pin->version, SPRITE_VERSION_Q1, SPRITE_VERSION_32, SPRITE_VERSION_HL );
Con_DPrintf( S_ERROR "%s: %s is not a valid sprite\n", __func__, mod->name );
return;
}
@@ -77,9 +207,9 @@ void Mod_LoadSpriteModel( model_t *mod, const void *buffer, size_t buffersize, q
Q_snprintf( poolname, sizeof( poolname ), "^2%s^7", mod->name );
mod->mempool = Mem_AllocPool( poolname );
if( pin->version == SPRITE_VERSION_Q1 || pin->version == SPRITE_VERSION_32 )
if( version == SPRITE_VERSION_Q1 || version == SPRITE_VERSION_32 )
{
const dsprite_q1_t *pinq1 = buffer;
dsprite_q1_t *pinq1 = buffer;
size_t size;
if( pinq1->numframes == 0 )
@@ -109,9 +239,9 @@ void Mod_LoadSpriteModel( model_t *mod, const void *buffer, size_t buffersize, q
mod->mins[2] = -pinq1->bounds[1] * 0.5f;
mod->maxs[2] = pinq1->bounds[1] * 0.5f;
}
else // if( pin->version == SPRITE_VERSION_HL )
else // if( version == SPRITE_VERSION_HL )
{
const dsprite_hl_t *pinhl = buffer;
dsprite_hl_t *pinhl = buffer;
size_t size;
if( pinhl->numframes == 0 )
@@ -147,4 +277,8 @@ void Mod_LoadSpriteModel( model_t *mod, const void *buffer, size_t buffersize, q
psprite->numframes = 0;
return;
}
#if !XASH_DEDICATED
Mod_SpriteLoadTextures( mod, buffer );
#endif
}

View File

@@ -19,6 +19,7 @@ GNU General Public License for more details.
#include "r_studioint.h"
#include "library.h"
#include "ref_common.h"
#include "swaplib.h"
typedef int (*STUDIOAPI)( int, sv_blending_interface_t**, server_studio_api_t*, float (*transform)[3][4], float (*bones)[MAXSTUDIOBONES][3][4] );
@@ -58,6 +59,151 @@ static int cache_current;
static int cache_current_hull;
static int cache_current_plane;
le_struct_begin( studiohdr_swap )
le_struct_field( studiohdr_t, ident )
le_struct_field( studiohdr_t, version )
le_struct_field( studiohdr_t, length )
le_struct_array( studiohdr_t, eyeposition, 3 )
le_struct_array( studiohdr_t, min, 3 )
le_struct_array( studiohdr_t, max, 3 )
le_struct_array( studiohdr_t, bbmin, 3 )
le_struct_array( studiohdr_t, bbmax, 3 )
le_struct_field( studiohdr_t, flags )
le_struct_field( studiohdr_t, numbones )
le_struct_field( studiohdr_t, boneindex )
le_struct_field( studiohdr_t, numbonecontrollers )
le_struct_field( studiohdr_t, bonecontrollerindex )
le_struct_field( studiohdr_t, numhitboxes )
le_struct_field( studiohdr_t, hitboxindex )
le_struct_field( studiohdr_t, numseq )
le_struct_field( studiohdr_t, seqindex )
le_struct_field( studiohdr_t, numseqgroups )
le_struct_field( studiohdr_t, seqgroupindex )
le_struct_field( studiohdr_t, numtextures )
le_struct_field( studiohdr_t, textureindex )
le_struct_field( studiohdr_t, texturedataindex )
le_struct_field( studiohdr_t, numskinref )
le_struct_field( studiohdr_t, numskinfamilies )
le_struct_field( studiohdr_t, skinindex )
le_struct_field( studiohdr_t, numbodyparts )
le_struct_field( studiohdr_t, bodypartindex )
le_struct_field( studiohdr_t, numattachments )
le_struct_field( studiohdr_t, attachmentindex )
le_struct_field( studiohdr_t, studiohdr2index )
le_struct_field( studiohdr_t, numtransitions )
le_struct_field( studiohdr_t, transitionindex )
le_struct_end();
le_struct_begin( mstudiobone_swap )
le_struct_field( mstudiobone_t, parent )
le_struct_field( mstudiobone_t, unused )
le_struct_array( mstudiobone_t, bonecontroller, 6 )
le_struct_array( mstudiobone_t, value, 6 )
le_struct_array( mstudiobone_t, scale, 6 )
le_struct_end();
le_struct_begin( mstudiobonecontroller_swap )
le_struct_field( mstudiobonecontroller_t, bone )
le_struct_field( mstudiobonecontroller_t, type )
le_struct_field( mstudiobonecontroller_t, start )
le_struct_field( mstudiobonecontroller_t, end )
le_struct_field( mstudiobonecontroller_t, unused )
le_struct_field( mstudiobonecontroller_t, index )
le_struct_end();
le_struct_begin( mstudiobbox_swap )
le_struct_field( mstudiobbox_t, bone )
le_struct_field( mstudiobbox_t, group )
le_struct_array( mstudiobbox_t, bbmin, 3 )
le_struct_array( mstudiobbox_t, bbmax, 3 )
le_struct_end();
le_struct_begin( mstudioseqgroup_swap )
// should we even swap seqgroup?
le_struct_field( mstudioseqgroup_t, unused )
le_struct_field( mstudioseqgroup_t, unused2 )
le_struct_end();
le_struct_begin( mstudioseqdesc_swap )
le_struct_field( mstudioseqdesc_t, fps )
le_struct_field( mstudioseqdesc_t, flags )
le_struct_field( mstudioseqdesc_t, activity )
le_struct_field( mstudioseqdesc_t, actweight )
le_struct_field( mstudioseqdesc_t, numevents )
le_struct_field( mstudioseqdesc_t, eventindex )
le_struct_field( mstudioseqdesc_t, numframes )
le_struct_field( mstudioseqdesc_t, weightlistindex )
le_struct_field( mstudioseqdesc_t, iklockindex )
le_struct_field( mstudioseqdesc_t, motiontype )
le_struct_field( mstudioseqdesc_t, motionbone )
le_struct_array( mstudioseqdesc_t, linearmovement, 3 )
le_struct_field( mstudioseqdesc_t, autolayerindex )
le_struct_field( mstudioseqdesc_t, keyvalueindex )
le_struct_array( mstudioseqdesc_t, bbmin, 3 )
le_struct_array( mstudioseqdesc_t, bbmax, 3 )
le_struct_field( mstudioseqdesc_t, numblends )
le_struct_field( mstudioseqdesc_t, animindex )
le_struct_array( mstudioseqdesc_t, blendtype, 2 )
le_struct_array( mstudioseqdesc_t, blendstart, 2 )
le_struct_array( mstudioseqdesc_t, blendend, 2 )
le_struct_field( mstudioseqdesc_t, seqgroup )
le_struct_field( mstudioseqdesc_t, entrynode )
le_struct_field( mstudioseqdesc_t, exitnode )
le_struct_field( mstudioseqdesc_t, animdescindex )
le_struct_end();
le_struct_begin( mstudioevent_swap )
le_struct_field( mstudioevent_t, frame )
le_struct_field( mstudioevent_t, event )
le_struct_field( mstudioevent_t, unused )
le_struct_end();
le_struct_begin( mstudioattachment_swap )
le_struct_field( mstudioattachment_t, flags )
le_struct_field( mstudioattachment_t, bone )
le_struct_array( mstudioattachment_t, org, 3 )
le_struct_end();
le_struct_begin( mstudiobodyparts_swap )
le_struct_field( mstudiobodyparts_t, nummodels )
le_struct_field( mstudiobodyparts_t, base )
le_struct_field( mstudiobodyparts_t, modelindex )
le_struct_end();
le_struct_begin( mstudiotexture_swap )
le_struct_field( mstudiotexture_t, flags )
le_struct_field( mstudiotexture_t, width )
le_struct_field( mstudiotexture_t, height )
le_struct_field( mstudiotexture_t, index )
le_struct_end();
le_struct_begin( mstudiomodel_swap )
le_struct_field( mstudiomodel_t, unused )
le_struct_field( mstudiomodel_t, unused2 )
le_struct_field( mstudiomodel_t, nummesh )
le_struct_field( mstudiomodel_t, meshindex )
le_struct_field( mstudiomodel_t, numverts )
le_struct_field( mstudiomodel_t, vertinfoindex )
le_struct_field( mstudiomodel_t, vertindex )
le_struct_field( mstudiomodel_t, numnorms )
le_struct_field( mstudiomodel_t, norminfoindex )
le_struct_field( mstudiomodel_t, normindex )
le_struct_field( mstudiomodel_t, blendvertinfoindex )
le_struct_field( mstudiomodel_t, blendnorminfoindex )
le_struct_end();
le_struct_begin( mstudiomesh_swap )
le_struct_field( mstudiomesh_t, numtris )
le_struct_field( mstudiomesh_t, triindex )
le_struct_field( mstudiomesh_t, skinref )
le_struct_field( mstudiomesh_t, numnorms )
le_struct_field( mstudiomesh_t, unused )
le_struct_end();
le_struct_begin( mstudioanim_swap )
le_struct_array( mstudioanim_t, offset, 6 )
le_struct_end();
/*
====================
Mod_InitStudioHull
@@ -404,6 +550,38 @@ static void Mod_StudioCalcRotations( int boneused[], int numbones, const byte *p
}
void Mod_SwapStudioSeqGroupAnims( studiohdr_t *phdr, mstudioseqdesc_t *pseq, byte *buf )
{
mstudioanim_t *panim = (mstudioanim_t *)( buf + pseq->animindex );
int numanims = pseq->numblends * phdr->numbones;
for( int j = 0; j < numanims; j++, panim++ )
{
le_struct_swap( mstudioanim_swap, panim );
for( int k = 0; k < 6; k++ )
{
if( panim->offset[k] == 0 )
continue;
mstudioanimvalue_t *panimvalue = (mstudioanimvalue_t *)((byte *)panim + panim->offset[k] );
int frames = pseq->numframes;
while( frames > 0 )
{
int valid = panimvalue->num.valid;
int total = panimvalue->num.total;
for( int l = 1; l <= valid; l++ )
panimvalue[l].value = LittleShort( panimvalue[l].value );
panimvalue += valid + 1;
frames -= total;
}
}
}
}
/*
====================
StudioGetAnim
@@ -412,17 +590,10 @@ StudioGetAnim
*/
void *R_StudioGetAnim( studiohdr_t *m_pStudioHeader, model_t *m_pSubModel, mstudioseqdesc_t *pseqdesc )
{
mstudioseqgroup_t *pseqgroup;
cache_user_t *paSequences;
fs_offset_t filesize;
byte *buf;
pseqgroup = (mstudioseqgroup_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqgroupindex) + pseqdesc->seqgroup;
if( pseqdesc->seqgroup == 0 )
return ((byte *)m_pStudioHeader + pseqdesc->animindex);
paSequences = (cache_user_t *)m_pSubModel->submodels;
cache_user_t *paSequences = (cache_user_t *)m_pSubModel->submodels;
if( paSequences == NULL )
{
paSequences = (cache_user_t *)Mem_Calloc( com_studiocache, MAXSTUDIOGROUPS * sizeof( cache_user_t ));
@@ -432,23 +603,31 @@ void *R_StudioGetAnim( studiohdr_t *m_pStudioHeader, model_t *m_pSubModel, mstud
// check for already loaded
if( !Mod_CacheCheck(( cache_user_t *)&( paSequences[pseqdesc->seqgroup] )))
{
string filepath, modelname, modelpath;
string modelname;
COM_FileBase( m_pSubModel->name, modelname, sizeof( modelname ));
string modelpath;
COM_ExtractFilePath( m_pSubModel->name, modelpath );
// NOTE: here we build real sub-animation filename because stupid user may rename model without recompile
string filepath;
Q_snprintf( filepath, sizeof( filepath ), "%s/%s%i%i.mdl", modelpath, modelname, pseqdesc->seqgroup / 10, pseqdesc->seqgroup % 10 );
buf = FS_LoadFile( filepath, &filesize, false );
if( !buf || !filesize ) Host_Error( "%s: can't load %s\n", __func__, filepath );
if( IDSEQGRPHEADER != *(uint *)buf ) Host_Error( "%s: %s is corrupted\n", __func__, filepath );
fs_offset_t filesize;
byte *buf = FS_LoadFile( filepath, &filesize, false );
if( !buf || !filesize )
Host_Error( "%s: can't load %s\n", __func__, filepath );
if( LittleLong( IDSEQGRPHEADER ) != *(uint *)buf )
Host_Error( "%s: %s is corrupted\n", __func__, filepath );
Con_Printf( "loading: %s\n", filepath );
paSequences[pseqdesc->seqgroup].data = Mem_Calloc( com_studiocache, filesize );
memcpy( paSequences[pseqdesc->seqgroup].data, buf, filesize );
Mem_Free( buf );
Mod_SwapStudioSeqGroupAnims( m_pStudioHeader, pseqdesc,
(byte *)paSequences[pseqdesc->seqgroup].data );
}
return ((byte *)paSequences[pseqdesc->seqgroup].data + pseqdesc->animindex);
@@ -690,7 +869,6 @@ static void Mod_StudioComputeBounds( void *buffer, vec3_t mins, vec3_t maxs, qbo
vec3_t bone_mins, bone_maxs;
vec3_t vert_mins, vert_maxs;
int vert_count, bone_count;
int bodyCount = 0;
vec3_t pos, *pverts;
vert_count = bone_count = 0;
@@ -706,18 +884,16 @@ static void Mod_StudioComputeBounds( void *buffer, vec3_t mins, vec3_t maxs, qbo
// each body part has nummodels variations so there are as many total variations as there
// are in a matrix of each part by each other part
for( i = 0; i < pstudiohdr->numbodyparts; i++ )
bodyCount += pbodypart[i].nummodels;
// The studio models we want are vec3_t mins, vec3_t maxsight after the bodyparts (still need to
// find a detailed breakdown of the mdl format). Move pointer there.
m_pSubModel = (mstudiomodel_t *)(&pbodypart[pstudiohdr->numbodyparts]);
for( i = 0; i < bodyCount; i++ )
{
pverts = (vec3_t *)((byte *)pstudiohdr + m_pSubModel[i].vertindex);
m_pSubModel = (mstudiomodel_t *)((byte *)pstudiohdr + pbodypart[i].modelindex);
for( j = 0; j < m_pSubModel[i].numverts; j++ )
Mod_StudioBoundVertex( bone_mins, bone_maxs, &vert_count, pverts[j] );
for( j = 0; j < pbodypart[i].nummodels; j++ )
{
pverts = (vec3_t *)((byte *)pstudiohdr + m_pSubModel[j].vertindex);
for( k = 0; k < m_pSubModel[j].numverts; k++ )
Mod_StudioBoundVertex( bone_mins, bone_maxs, &vert_count, pverts[k] );
}
}
pbones = (mstudiobone_t *)((byte *)pstudiohdr + pstudiohdr->boneindex);
@@ -820,26 +996,120 @@ static int Mod_StudioBodyVariations( model_t *mod )
return count;
}
static qboolean Mod_SwapStudioModel( const char *name, void *buffer, size_t buffersize )
{
studiohdr_t *phdr = buffer;
byte *mod_base = buffer;
if( buffersize < sizeof( studiohdr_t ))
return false;
le_struct_swap( studiohdr_swap, phdr );
if( phdr->ident != IDSTUDIOHEADER || phdr->version != STUDIO_VERSION )
return false;
#if XASH_BIG_ENDIAN
if( phdr->studiohdr2index > 0 && phdr->studiohdr2index < phdr->length )
{
Con_Printf( S_ERROR "byteswapping extended studio model \"%s\" is unsupoprted\n", name );
return false;
}
#endif
for( int i = 0; i < phdr->numbones; i++ )
le_struct_swap( mstudiobone_swap, (mstudiobone_t *)( mod_base + phdr->boneindex ) + i );
for( int i = 0; i < phdr->numbonecontrollers; i++ )
le_struct_swap( mstudiobonecontroller_swap, (mstudiobonecontroller_t *)( mod_base + phdr->bonecontrollerindex ) + i );
for( int i = 0; i < phdr->numhitboxes; i++ )
le_struct_swap( mstudiobbox_swap, (mstudiobbox_t *)( mod_base + phdr->hitboxindex ) + i );
for( int i = 0; i < phdr->numseqgroups; i++ )
le_struct_swap( mstudioseqgroup_swap, (mstudioseqgroup_t *)( mod_base + phdr->seqgroupindex ) + i );
for( int i = 0; i < phdr->numattachments; i++ )
{
mstudioattachment_t *pattach = (mstudioattachment_t *)( mod_base + phdr->attachmentindex ) + i;
le_struct_swap( mstudioattachment_swap, pattach );
le_array_swap((float *)pattach->vectors, 3 * 3 );
}
for( int i = 0; i < phdr->numtextures; i++ )
le_struct_swap( mstudiotexture_swap, (mstudiotexture_t *)( mod_base + phdr->textureindex ) + i );
for( int i = 0; i < phdr->numbodyparts; i++ )
le_struct_swap( mstudiobodyparts_swap, (mstudiobodyparts_t *)( mod_base + phdr->bodypartindex ) + i );
for( int i = 0; i < phdr->numseq; i++ )
{
mstudioseqdesc_t *pseq = (mstudioseqdesc_t *)( mod_base + phdr->seqindex ) + i;
le_struct_swap( mstudioseqdesc_swap, pseq );
for( int j = 0; j < pseq->numevents; j++ )
le_struct_swap( mstudioevent_swap, (mstudioevent_t *)( mod_base + pseq->eventindex ) + j );
if( pseq->seqgroup != 0 )
continue;
Mod_SwapStudioSeqGroupAnims( phdr, pseq, mod_base );
}
for( int i = 0; i < phdr->numbodyparts; i++ )
{
mstudiobodyparts_t *pbodypart = (mstudiobodyparts_t *)( mod_base + phdr->bodypartindex ) + i;
for( int j = 0; j < pbodypart->nummodels; j++ )
{
mstudiomodel_t *pmodel = (mstudiomodel_t *)( mod_base + pbodypart->modelindex ) + j;
le_struct_swap( mstudiomodel_swap, pmodel );
le_array_swap((float *)( mod_base + pmodel->vertindex ), pmodel->numverts * 3 );
le_array_swap((float *)( mod_base + pmodel->normindex ), pmodel->numnorms * 3 );
for( int k = 0; k < pmodel->nummesh; k++ )
{
mstudiomesh_t *pmesh = (mstudiomesh_t *)( mod_base + pmodel->meshindex ) + k;
le_struct_swap( mstudiomesh_swap, pmesh );
short *ptricmds = (short *)( mod_base + pmesh->triindex );
short n;
while(( n = LittleShort( *ptricmds )))
{
*ptricmds++ = n;
int count = abs( n ) * 4;
for( int l = 0; l < count; l++, ptricmds++ )
*ptricmds = LittleShort( *ptricmds );
}
*ptricmds = 0;
}
}
}
short *pskinref = (short *)( mod_base + phdr->skinindex );
for( int i = 0; i < phdr->numskinfamilies * phdr->numskinref; i++ )
pskinref[i] = LittleShort( pskinref[i] );
return true;
}
/*
=================
R_StudioLoadHeader
=================
*/
static studiohdr_t *R_StudioLoadHeader( model_t *mod, const void *buffer )
static studiohdr_t *R_StudioLoadHeader( model_t *mod, void *buffer, size_t buffersize )
{
byte *pin;
studiohdr_t *phdr;
int i;
studiohdr_t *phdr;
if( !buffer ) return NULL;
if( !buffer )
return NULL;
pin = (byte *)buffer;
phdr = (studiohdr_t *)pin;
i = phdr->version;
if( i != STUDIO_VERSION )
if( !Mod_SwapStudioModel( mod->name, buffer, buffersize ))
{
Con_Printf( S_ERROR "%s has wrong version number (%i should be %i)\n", mod->name, i, STUDIO_VERSION );
phdr = (studiohdr_t *)buffer;
Con_Printf( S_ERROR "%s has wrong version number (%i should be %i)\n", mod->name, phdr->version, STUDIO_VERSION );
return NULL;
}
@@ -867,7 +1137,7 @@ static studiohdr_t *Mod_MaybeTruncateStudioTextureData( model_t *mod )
Mod_LoadStudioModel
=================
*/
void Mod_LoadStudioModel( model_t *mod, const void *buffer, qboolean *loaded )
void Mod_LoadStudioModel( model_t *mod, void *buffer, size_t buffersize, qboolean *loaded )
{
char poolname[MAX_VA_STRING];
studiohdr_t *phdr;
@@ -879,7 +1149,7 @@ void Mod_LoadStudioModel( model_t *mod, const void *buffer, qboolean *loaded )
mod->mempool = Mem_AllocPool( poolname );
mod->type = mod_studio;
phdr = R_StudioLoadHeader( mod, buffer );
phdr = R_StudioLoadHeader( mod, buffer, buffersize );
if( !phdr || phdr->length < sizeof( studiohdr_t )) // garbage value in length
return; // bad model
@@ -888,9 +1158,10 @@ void Mod_LoadStudioModel( model_t *mod, const void *buffer, qboolean *loaded )
{
studiohdr_t *thdr;
void *buffer2;
fs_offset_t size2_len;
buffer2 = FS_LoadFile( Mod_StudioTexName( mod->name ), NULL, false );
thdr = R_StudioLoadHeader( mod, buffer2 );
buffer2 = FS_LoadFile( Mod_StudioTexName( mod->name ), &size2_len, false );
thdr = R_StudioLoadHeader( mod, buffer2, size2_len );
if( thdr != NULL )
{

View File

@@ -88,6 +88,22 @@ static void Mod_Modellist_f( void )
Con_Printf( "\n" );
}
static void Mod_UnloadRenderData( model_t *mod )
{
#if !XASH_DEDICATED
switch( mod->type )
{
case mod_sprite:
Mod_SpriteUnloadTextures( mod->cache.data );
break;
default:
break;
}
ref.dllFuncs.Mod_ProcessRenderData( mod, false, NULL, 0 );
#endif
}
/*
================
Mod_FreeUserData
@@ -107,12 +123,10 @@ static void Mod_FreeUserData( model_t *mod )
svgame.physFuncs.Mod_ProcessUserData( mod, false, NULL );
}
}
#if !XASH_DEDICATED
else
{
ref.dllFuncs.Mod_ProcessRenderData( mod, false, NULL, 0 );
Mod_UnloadRenderData( mod );
}
#endif
}
/*
@@ -333,7 +347,7 @@ model_t *Mod_LoadModel( model_t *mod, qboolean crash )
switch( *(uint *)buf )
{
case LittleLong( IDSTUDIOHEADER ):
Mod_LoadStudioModel( mod, buf, &loaded );
Mod_LoadStudioModel( mod, buf, length, &loaded );
break;
case LittleLong( IDSPRITEHEADER ):
Mod_LoadSpriteModel( mod, buf, length, &loaded );
@@ -594,6 +608,31 @@ void GAME_EXPORT Mod_LoadCacheFile( const char *filename, cache_user_t *cu )
cu->data = Mem_Malloc( com_studiocache, size );
memcpy( cu->data, buf, size );
Mem_Free( buf );
// this handles when studio model renderer tries to load sequence files on it's own
// which is what they always do in HLSDK
#if XASH_BIG_ENDIAN
if( size >= sizeof( int ) && LittleLong( IDSEQGRPHEADER ) == *(uint *)cu->data )
{
studiohdr_t *phdr = (studiohdr_t *)REF_GET_PARM( PARM_GET_STUDIO_HDR, 0 );
if( !phdr )
return;
mstudioseqdesc_t *pseq = (mstudioseqdesc_t *)((byte *)phdr + phdr->seqindex );
for( int i = 0; i < phdr->numseq; i++ )
{
if( pseq[i].seqgroup == 0 )
continue;
mstudioseqgroup_t *pgrp = (mstudioseqgroup_t *)((byte *)phdr + phdr->seqgroupindex ) + pseq[i].seqgroup;
// assuming filename passes seqgroup's name
if( !Q_stricmp( pgrp->name, filename ))
Mod_SwapStudioSeqGroupAnims( phdr, &pseq[i], (byte *)cu->data );
}
}
#endif
}
/*

View File

@@ -321,8 +321,10 @@ void MSG_WriteUBitLong( sizebuf_t *sb, uint curData, int numbits )
}
iCurBitMasked = iCurBit & 31;
((uint32_t *)sb->pData)[iDWord] &= BitWriteMasks[iCurBitMasked][nBitsLeft-1];
((uint32_t *)sb->pData)[iDWord] |= curData << iCurBitMasked;
uint32_t dword = LittleLong(((uint32_t *)sb->pData)[iDWord] );
dword &= BitWriteMasks[iCurBitMasked][nBitsLeft-1];
dword |= curData << iCurBitMasked;
((uint32_t *)sb->pData)[iDWord] = LittleLong( dword );
// did it span a dword?
nBitsWritten = 32 - iCurBitMasked;
@@ -334,8 +336,10 @@ void MSG_WriteUBitLong( sizebuf_t *sb, uint curData, int numbits )
curData >>= nBitsWritten;
iCurBitMasked = iCurBit & 31;
((uint32_t *)sb->pData)[iDWord+1] &= BitWriteMasks[iCurBitMasked][nBitsLeft-1];
((uint32_t *)sb->pData)[iDWord+1] |= curData << iCurBitMasked;
uint32_t dword2 = LittleLong(((uint32_t *)sb->pData)[iDWord+1] );
dword2 &= BitWriteMasks[iCurBitMasked][nBitsLeft-1];
dword2 |= curData << iCurBitMasked;
((uint32_t *)sb->pData)[iDWord+1] = LittleLong( dword2 );
}
sb->iCurBit += numbits;
}
@@ -396,7 +400,8 @@ qboolean MSG_WriteBits( sizebuf_t *sb, const void *pData, int nBits )
// read dwords.
while( nBitsLeft >= 32 )
{
MSG_WriteUBitLong( sb, *(( uint32_t *)pOut ), 32 );
uint32_t dword = *((uint32_t *)pOut);
MSG_WriteUBitLong( sb, LittleLong( dword ), 32 );
pOut += sizeof( uint32_t );
nBitsLeft -= 32;
@@ -519,7 +524,7 @@ void MSG_WriteDword( sizebuf_t *sb, uint val )
void MSG_WriteFloat( sizebuf_t *sb, float val )
{
MSG_WriteBits( sb, &val, sizeof( val ) << 3 );
MSG_WriteUBitLong( sb, FloatAsUint( val ), sizeof( val ) << 3 );
}
qboolean MSG_WriteBytes( sizebuf_t *sb, const void *pBuf, int nBytes )
@@ -593,7 +598,7 @@ uint MSG_ReadUBitLong( sizebuf_t *sb, int numbits )
// Read the current dword.
idword1 = sb->iCurBit >> 5;
dword1 = ((uint *)sb->pData)[idword1];
dword1 = LittleLong(((uint *)sb->pData)[idword1] );
dword1 >>= ( sb->iCurBit & 31 ); // get the bits we're interested in.
sb->iCurBit += numbits;
@@ -608,7 +613,7 @@ uint MSG_ReadUBitLong( sizebuf_t *sb, int numbits )
else
{
int nExtraBits = sb->iCurBit & 31;
uint dword2 = ((uint *)sb->pData)[idword1+1] & ExtraMasks[nExtraBits];
uint dword2 = LittleLong(((uint *)sb->pData)[idword1+1] ) & ExtraMasks[nExtraBits];
// no need to mask since we hit the end of the dword.
// shift the second dword's part into the high bits.
@@ -633,7 +638,8 @@ qboolean MSG_ReadBits( sizebuf_t *sb, void *pOutData, int nBits )
// read dwords.
while( nBitsLeft >= 32 )
{
*((uint32_t *)pOut) = MSG_ReadUBitLong( sb, 32 );
uint32_t dword = MSG_ReadUBitLong( sb, 32 );
*((uint32_t *)pOut) = LittleLong( dword );
pOut += sizeof( uint32_t );
nBitsLeft -= 32;
}
@@ -790,11 +796,7 @@ uint MSG_ReadDword( sizebuf_t *sb )
float MSG_ReadFloat( sizebuf_t *sb )
{
float ret;
MSG_ReadBits( sb, &ret, sizeof( ret ) << 3 );
return ret;
return UintAsFloat( MSG_ReadUBitLong( sb, sizeof( float ) << 3 ));
}
qboolean MSG_ReadBytes( sizebuf_t *sb, void *pOut, int nBytes )

View File

@@ -617,7 +617,10 @@ void Netchan_AddBufferToList( fragbuf_t **pplist, fragbuf_t *pbuf )
if( id1 > id2 )
{
// insert here
pbuf->next = n->next;
// pbuf->next = n->next;
// a1ba: this seems to be incorrect insertion, as `n` gets removed from list and effectively leaked
pbuf->next = n;
pprev->next = pbuf;
return;
}
@@ -1237,20 +1240,24 @@ qboolean Netchan_CopyFileFragments( netchan_t *chan, sizebuf_t *msg )
return false;
}
if( filename[0] != '!' )
{
string temp_filename;
Q_snprintf( temp_filename, sizeof( temp_filename ), DEFAULT_DOWNLOADED_DIRECTORY "%s", filename );
Q_strncpy( filename, temp_filename, sizeof( filename ));
}
Q_strncpy( chan->incomingfilename, filename, sizeof( chan->incomingfilename ));
if( filename[0] != '!' && FS_FileExists( filename, false ))
if( filename[0] != '!' )
{
Con_Printf( S_ERROR "can't download %s, already exists\n", filename );
Netchan_FlushIncoming( chan, FRAG_FILE_STREAM );
return true;
string write_path;
Q_snprintf( write_path, sizeof( write_path ), "../%s" DEFAULT_DOWNLOADED_DIRECTORY_SUFFIX "/%s", GI->gamefolder, filename );
Q_strncpy( filename, write_path, sizeof( filename ));
FS_AllowDirectPaths( true );
qboolean exists = FS_FileExists( filename, false );
FS_AllowDirectPaths( false );
if( exists )
{
Con_Printf( S_ERROR "can't download %s, already exists\n", filename );
Netchan_FlushIncoming( chan, FRAG_FILE_STREAM );
return true;
}
}
// create file from buffers
@@ -1314,14 +1321,14 @@ qboolean Netchan_CopyFileFragments( netchan_t *chan, sizebuf_t *msg )
Host_Error( "%s: BZ2 compression is not supported for server", __func__ );
#endif
}
else if( chan->use_lzss && LZSS_IsCompressed( buffer, nsize + 1 ))
else if( chan->use_lzss && LZSS_IsCompressed( buffer, nsize ))
{
byte *uncompressedBuffer;
uncompressedSize = LZSS_GetActualSize( buffer, nsize + 1 ) + 1;
uncompressedSize = LZSS_GetActualSize( buffer, nsize );
uncompressedBuffer = Mem_Calloc( net_mempool, uncompressedSize );
nsize = LZSS_Decompress( buffer, uncompressedBuffer, nsize + 1, uncompressedSize );
nsize = LZSS_Decompress( buffer, uncompressedBuffer, nsize, uncompressedSize );
Mem_Free( buffer );
buffer = uncompressedBuffer;
}
@@ -1336,8 +1343,9 @@ qboolean Netchan_CopyFileFragments( netchan_t *chan, sizebuf_t *msg )
}
else
{
// g-cont. it's will be stored downloaded files directly into game folder
FS_AllowDirectPaths( true );
FS_WriteFile( filename, buffer, nsize );
FS_AllowDirectPaths( false );
Mem_Free( buffer );
}

View File

@@ -99,6 +99,7 @@ typedef struct
int sequence_number;
int ip_sockets[NS_COUNT];
int ip6_sockets[NS_COUNT];
qboolean rr_state[NS_COUNT];
qboolean initialized;
qboolean threads_initialized;
qboolean configured;
@@ -132,7 +133,7 @@ static CVAR_DEFINE_AUTO( net6_address, "0", FCVAR_PRIVILEGED|FCVAR_READ_ONLY, "c
static void NET_ClearLagData( qboolean bClient, qboolean bServer );
static inline qboolean NET_IsSocketError( int retval )
qboolean NET_IsSocketError( int retval )
{
#if XASH_WIN32 || XASH_DOS4GW
return retval == SOCKET_ERROR ? true : false;
@@ -141,7 +142,7 @@ static inline qboolean NET_IsSocketError( int retval )
#endif
}
static inline qboolean NET_IsSocketValid( int socket )
qboolean NET_IsSocketValid( int socket )
{
#if XASH_WIN32 || XASH_DOS4GW
return socket != INVALID_SOCKET;
@@ -150,6 +151,24 @@ static inline qboolean NET_IsSocketValid( int socket )
#endif
}
qboolean NET_MakeSocketNonBlocking( int socket_fd )
{
#if XASH_LINUX
int res = fcntl( socket_fd, F_GETFL, 0 );
if( NET_IsSocketError( res ))
return false;
// SOCK_NONBLOCK is not portable, so use fcntl
if( NET_IsSocketError( fcntl( socket_fd, F_SETFL, res | O_NONBLOCK )))
return false;
#else
uint mode = 1;
if( NET_IsSocketError( ioctlsocket( socket_fd, FIONBIO, (void*)&mode )))
return false;
#endif
return true;
}
void NET_NetadrToIP6Bytes( uint8_t *ip6, const netadr_t *adr )
{
memcpy( &ip6[0], adr->ip6_0, 2 );
@@ -177,7 +196,7 @@ static int NET_NetadrIP6Compare( const netadr_t *a, const netadr_t *b )
NET_NetadrToSockadr
====================
*/
static void NET_NetadrToSockadr( netadr_t *a, struct sockaddr_storage *s )
void NET_NetadrToSockadr( netadr_t *a, struct sockaddr_storage *s )
{
netadrtype_t type = NET_NetadrType( a );
@@ -214,7 +233,7 @@ static void NET_NetadrToSockadr( netadr_t *a, struct sockaddr_storage *s )
NET_SockadrToNetAdr
====================
*/
static void NET_SockadrToNetadr( const struct sockaddr_storage *s, netadr_t *a )
void NET_SockadrToNetadr( const struct sockaddr_storage *s, netadr_t *a )
{
if( s->ss_family == AF_INET )
{
@@ -1330,70 +1349,60 @@ NET_QueuePacket
queue normal and lagged packets
==================
*/
static qboolean NET_QueuePacket( netsrc_t sock, netadr_t *from, byte *data, size_t *length )
static qboolean NET_QueuePacket( int net_socket, netsrc_t sock, netadr_t *from, byte *data, size_t *length )
{
byte buf[NET_MAX_FRAGMENT];
int ret, protocol;
int net_socket;
int ret;
WSAsize_t addr_len;
struct sockaddr_storage addr = { 0 };
*length = 0;
for( protocol = 0; protocol < 2; protocol++ )
if( !NET_IsSocketValid( net_socket ))
return NET_LagPacket( false, sock, from, length, data );
addr_len = sizeof( addr );
ret = recvfrom( net_socket, buf, sizeof( buf ), 0, (struct sockaddr *)&addr, &addr_len );
NET_SockadrToNetadr( &addr, from );
if( !NET_IsSocketError( ret ))
{
switch( protocol )
if( ret < NET_MAX_FRAGMENT )
{
case 0: net_socket = net.ip_sockets[sock]; break;
case 1: net_socket = net.ip6_sockets[sock]; break;
}
if( !NET_IsSocketValid( net_socket ))
continue;
addr_len = sizeof( addr );
ret = recvfrom( net_socket, buf, sizeof( buf ), 0, (struct sockaddr *)&addr, &addr_len );
NET_SockadrToNetadr( &addr, from );
if( !NET_IsSocketError( ret ))
{
if( ret < NET_MAX_FRAGMENT )
{
// Transfer data
memcpy( data, buf, ret );
*length = ret;
// Transfer data
memcpy( data, buf, ret );
*length = ret;
#if !XASH_DEDICATED
// check for split message
if( sock == NS_CLIENT && *(int *)data == NET_HEADER_SPLITPACKET )
return NET_GetLong( data, ret, length, CL_GetSplitSize( ), CL_Protocol( ));
// check for split message
if( sock == NS_CLIENT && *(int *)data == NET_HEADER_SPLITPACKET )
return NET_GetLong( data, ret, length, CL_GetSplitSize( ), CL_Protocol( ));
#endif
// lag the packet, if needed
return NET_LagPacket( true, sock, from, length, data );
}
else
{
Con_Reportf( "%s: oversize packet from %s\n", __func__, NET_AdrToString( *from ));
}
// lag the packet, if needed
return NET_LagPacket( true, sock, from, length, data );
}
else
{
int err = WSAGetLastError();
Con_Reportf( "%s: oversize packet from %s\n", __func__, NET_AdrToString( *from ));
}
}
else
{
int err = WSAGetLastError();
switch( err )
{
case WSAEWOULDBLOCK:
case WSAECONNRESET:
case WSAECONNREFUSED:
case WSAEMSGSIZE:
case WSAETIMEDOUT:
break;
default: // let's continue even after errors
Con_DPrintf( S_ERROR "%s: %s from %s\n", __func__, NET_ErrorString(), NET_AdrToString( *from ));
break;
}
switch( err )
{
case WSAEWOULDBLOCK:
case WSAECONNRESET:
case WSAECONNREFUSED:
case WSAEMSGSIZE:
case WSAETIMEDOUT:
break;
default: // let's continue even after errors
Con_DPrintf( S_ERROR "%s: %s from %s\n", __func__, NET_ErrorString(), NET_AdrToString( *from ));
break;
}
}
@@ -1420,7 +1429,20 @@ qboolean NET_GetPacket( netsrc_t sock, netadr_t *from, byte *data, size_t *lengt
}
else
{
return NET_QueuePacket( sock, from, data, length );
// Round-robin the socket to prevent constant activity on one starving the other.
int i = net.rr_state[sock];
int net_socket = i ? net.ip_sockets[sock] : net.ip6_sockets[sock];
if( NET_QueuePacket( net_socket, sock, from, data, length ))
{
net.rr_state[sock] = !i;
return true;
}
// try the other one before giving up
net_socket = i ? net.ip6_sockets[sock] : net.ip_sockets[sock];
net.rr_state[sock] = !i;
return NET_QueuePacket( net_socket, sock, from, data, length );
}
}
@@ -1591,7 +1613,7 @@ static int NET_IPSocket( const char *net_iface, int port, int family )
return INVALID_SOCKET;
}
if( NET_IsSocketError( ioctlsocket( net_socket, FIONBIO, (void*)&_true )))
if( !NET_MakeSocketNonBlocking( net_socket ))
{
struct timeval timeout;
@@ -1994,6 +2016,7 @@ void NET_Init( void )
net.lagdata[i].next = &net.lagdata[i];
net.ip_sockets[i] = INVALID_SOCKET;
net.ip6_sockets[i] = INVALID_SOCKET;
net.rr_state[i] = false;
}
#if XASH_WIN32

View File

@@ -74,6 +74,9 @@ void NET_SendPacket( netsrc_t sock, size_t length, const void *data, netadr_t to
void NET_SendPacketEx( netsrc_t sock, size_t length, const void *data, netadr_t to, size_t splitsize );
void NET_IP6BytesToNetadr( netadr_t *adr, const uint8_t *ip6 );
void NET_NetadrToIP6Bytes( uint8_t *ip6, const netadr_t *adr );
qboolean NET_IsSocketError( int retval );
qboolean NET_IsSocketValid( int socket );
qboolean NET_MakeSocketNonBlocking( int socket_fd );
static inline qboolean NET_IsLocalAddress( netadr_t adr )
{

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