Compare commits

..

109 Commits

Author SHA1 Message Date
Alibek Omarov
8c9cd59c71 engine: http: fix the bug where maxconnections wasn't followed when many files were queued at once, bump it back to 5 concurrent connections 2026-06-07 17:29:41 +05:00
Alibek Omarov
c79b29687c engine: http: cache resolve result in httpserver_t 2026-06-07 17:18:28 +05:00
Alibek Omarov
1eb922a0f1 engine: http: also lazy allocate buffer for query/response 2026-06-07 17:17:54 +05:00
Alibek Omarov
f6e36994be engine: http: lazy open download files, to avoid wasting too much file descriptors 2026-06-07 17:09:59 +05:00
Alibek Omarov
e83ba6509e 3rdparty: mbedtls: make common compat.c for all platforms we want to support 2026-06-07 07:08:24 +05:00
Alibek Omarov
5cfb67940b 3rdparty: mbedtls: add psvita support 2026-06-07 06:39:50 +05:00
Alibek Omarov
e31c893852 wscript: enable bcrypt on Windows, add entropy generator that avoids usage of bcrypt for our 32-bit builds, allowing them to run on Windows XP 2026-06-07 06:39:50 +05:00
Alibek Omarov
85e5f032d8 scripts: gha: remove bundled mbedTLS on Switch 2026-06-07 06:39:50 +05:00
Alibek Omarov
92fd51ad99 engine: http: don't allow CRLF in URLs, fix memory leaks 2026-06-07 06:11:39 +05:00
Alibek Omarov
5480055997 engine: http: use thin TLS backend in HTTP client 2026-06-07 05:56:54 +05:00
Alibek Omarov
d634c6aad5 engine: http: use separate mempool for HTTP client 2026-06-07 05:56:54 +05:00
Alibek Omarov
034efb3b31 engine: http: add TLS backend using mbedTLS 2026-06-07 05:56:54 +05:00
Alibek Omarov
eae45b2e81 engine: wscript: link in mbedtls 2026-06-07 05:56:54 +05:00
Alibek Omarov
567d279007 engine: wscript: drop unfinished work on curl 2026-06-07 05:56:54 +05:00
Alibek Omarov
27f8385bb1 wscript: wire up mbedtls 2026-06-07 05:56:54 +05:00
Alibek Omarov
1cfabd5ec9 3rdparty: mbedtls: initial add 2026-06-07 05:56:54 +05:00
Alibek Omarov
ab6672fb8a 3rdparty: xash-extras: update in preparation for TLS integration 2026-06-07 03:46:11 +05:00
Alibek Omarov
2d39a7fa24 engine: fix skewed ping calculation, drop --debug-all-servers 2026-06-07 03:17:45 +05:00
Alibek Omarov
7db12ade3a 3rdparty: mainui: update 2026-06-07 03:16:57 +05:00
Alibek Omarov
61a84f6953 engine: cache negative master server DNS failures longer, remove dead UDP master server addresses 2026-06-07 02:24:30 +05:00
Alibek Omarov
75ffba64c5 engine: http: add support for redirects 2026-06-06 22:55:47 +05:00
Alibek Omarov
c134840148 engine: masterlist: add UDP master server deprecation notice, fix static master list address 2026-06-06 19:53:37 +05:00
Alibek Omarov
51d1bfbe08 3rdparty: mainui :update 2026-06-06 03:30:26 +05:00
Alibek Omarov
79ff8332ac 3rdparty: mainui: update 2026-06-06 02:51:41 +05:00
Alibek Omarov
f155005bb1 engine: move out MAX_CLIENTS definition 2026-06-06 02:50:30 +05:00
Alibek Omarov
a8de8cf5e6 Documentation: protocol: document new static master protocol 2026-06-06 02:47:39 +05:00
Alibek Omarov
74fcab9bfb engine: implement new static master server protocol 2026-06-06 02:47:39 +05:00
Alibek Omarov
efad71bcc9 engine: in-memory HTTP downloader 2026-06-06 02:47:39 +05:00
Alibek Omarov
c444ab882a engine: client: drop key ms protocol extension 2026-06-06 02:47:39 +05:00
🗣🗣: "ùồầỳ hợp lý quãi chất thế nhờ"🤫🗿🔥🔥
8fad3bbad6 public: wrap macro arguments in parentheses 2026-06-05 21:55:59 +05:00
Alibek Omarov
4cd6d25ee6 engine: common: account for header size when sending first file fragment (which contains filename + compressor on goldsrc) 2026-06-05 08:42:46 +05:00
Alibek Omarov
33e47ef5a9 engine: fix engine.log output 2026-06-02 06:44:39 +05:00
Alibek Omarov
d19e9606e9 3rdparty: mainui: update 2026-06-02 06:25:52 +05:00
Alibek Omarov
fe1827f5e4 engine: common: don't let executing @ prefixed commands and cvars for unprivileged mode 2026-06-02 06:13:59 +05:00
Alibek Omarov
918323fcea engine: upload logo on connect always, add @cl_logoupdate cvar to clearly signal logo changes from DLL side 2026-06-02 06:10:24 +05:00
Alibek Omarov
e75256ec9d 3rdparty: mainui: update 2026-06-02 05:59:09 +05:00
Alibek Omarov
47582182e6 engine: server: set flag that we are expecting resource list to come from the client to not apply resource list rate limit 2026-06-02 02:56:19 +05:00
Alibek Omarov
4a2c218cfc engine: common: xrcon: oops, fix build, forgot to stage the change 2026-06-01 21:32:45 +05:00
Alibek Omarov
376817478d engine: platform: sdl2: add expiremntal flag -nosteaminput that would remove set by Steam SDL environment variables and let us use gamepad directly 2026-06-01 18:55:38 +05:00
Alibek Omarov
b96dc61e31 engine: common: xrcon: do not print misleading "client disconnected" message on engine shutdown 2026-06-01 18:49:50 +05:00
Alibek Omarov
b997d5657b engine: common: xrcon: refactor macros and magic numbers 2026-06-01 18:49:50 +05:00
Alibek Omarov
e9f5bca5c4 engine: common: make single shared print buffer with stripped colorcodes
- in xrcon, make delay and timeout macros into cvars
2026-06-01 18:49:50 +05:00
Alibek Omarov
b36f107a2e engine: common: xrcon: use uint32_t for message types instead of null terminated small strings 2026-06-01 18:49:50 +05:00
Alibek Omarov
a305ffb33f engine: common: xrcon: replace XRCON_STATE with XRCON_PARSER for parser state machine types 2026-06-01 18:49:50 +05:00
Alibek Omarov
1cc1b60b10 engine: common: xrcon: add NUL at the end of print message 2026-06-01 18:49:50 +05:00
Alibek Omarov
648db4a2b3 engine: common: xrcon: fix potential overflow and another recursion case 2026-06-01 18:49:50 +05:00
SNMetamorph
2c0c20dfe6 Documentation: added XRCON protocol description 2026-06-01 18:49:50 +05:00
SNMetamorph
17e167432a engine: common: xrcon: various code fixes 2026-06-01 18:49:50 +05:00
SNMetamorph
97b8ca6b38 engine: common: xrcon: strip color codes before sending message to client 2026-06-01 18:49:50 +05:00
SNMetamorph
6dabc6f2c5 engine: common: initial implementation of XRCON server 2026-06-01 18:49:50 +05:00
SNMetamorph
e808038fee engine: common: add function NET_MakeSocketReuseAddr 2026-06-01 18:49:50 +05:00
Владислав Сухов
a60d0d142d engine: add support for legacy 'm' query responses 2026-05-31 03:11:01 +05:00
Alibek Omarov
bf5d88d9a4 engine: net_chan: reset file fragment buffers early, right after they are consumed 2026-05-30 03:57:12 +05:00
Alibek Omarov
adca77672a engine: client: fix crash when loading a save that references non-existing word 2026-05-30 03:53:16 +05:00
Alibek Omarov
ef5295c5ab engine: workaround the misalignment issue when reading textures in BSP 2026-05-30 03:53:16 +05:00
Alibek Omarov
37da317133 filesystem: tests: fix build on Windows 2026-05-27 15:17:20 +05:00
Alibek Omarov
c93cb49a9e engine: client: fix typo in COM_RandomLong 2026-05-27 01:06:27 +05:00
Alibek Omarov
4f342fb19a filesystem: tests: reduce boilerplate, add filecopy test 2026-05-27 00:39:07 +05:00
Alibek Omarov
8ee3544507 filesystem: use sendfile for faster FS_FileCopy on Linux 2026-05-27 00:34:49 +05:00
Alibek Omarov
d44e6d5db7 android: drop 64-bit x86 build, as no known 64-bit only (i.e. with no 32-bit support) x86 deployments of Android are known 2026-05-26 23:11:23 +05:00
Alibek Omarov
21cc9feca7 engine: client: don't require cookie echo when server didn't negotiate NET_EXT_NETCHAN_COOKIE 2026-05-26 03:10:31 +05:00
Alibek Omarov
e87b14468d ref: when entity model is NULL, the entity is player and we're drawing in menu, skip this entity silently
The menu relies on the fact that model is set from player's userinfo, and if it has bogus model, it will
trip on the nullptr model assert
2026-05-26 02:57:47 +05:00
Alibek Omarov
e01921dd10 engine: server: bump datagram overflow messages to -dev 2 2026-05-26 02:57:47 +05:00
Alibek Omarov
e5c4e49782 engine: client: introduce cookie protocol extension, when client connects to the server, it provides randomized 64-bit value to be prefixed into netchan messages
* Prevent clientuseragent from leaking data.
* Process qport in netchan, cleaning up code
2026-05-26 02:14:58 +05:00
Alibek Omarov
d61983a5d2 engine: randomize outgoing_sequence on server side 2026-05-26 02:14:58 +05:00
Alibek Omarov
c3f4eb026b engine: only allow net_sequence_window packets, prevent sudden, usually impossible, jumps 2026-05-26 02:14:58 +05:00
SNMetamorph
fe9b8bcaca engine: enable warning when client or server tries to access non-existent cvar 2026-05-25 13:44:26 +05:00
Czarintax
33cd20b21b engine: server: fix SV_ProcessFile crash from short malformed filename (#2606) 2026-05-25 13:42:03 +05:00
SNMetamorph
78a3b958dd engine: client: disable saving testing cvars that potentially could break game 2026-05-24 15:14:15 +04:00
SNMetamorph
0e56fa10f9 engine: client: make cvar cl_ticket_generator privileged 2026-05-24 15:14:15 +04:00
SNMetamorph
b6a67b56b0 engine: client: make cvar cl_steam_broker_addr privileged
(cherry picked from commit e97442f1b04c81ee406779963bd7d58cd586aecf)
2026-05-24 15:14:15 +04:00
Alibek Omarov
eb9de1cac8 engine: fix NET_IsReservedAdr checks for IPv6, treat IPv4-mapped-as-IPv6 as IPv4 2026-05-23 21:23:10 +05:00
Alibek Omarov
24abd2deb2 engine: host: improve missing game libraries message so it's easier to understand 2026-05-23 21:23:10 +05:00
Alibek Omarov
996dcc637d engine: tighten file fragment msg by the (possibly) trunctated frag_message 2026-05-23 21:23:10 +05:00
Alibek Omarov
8b89bdca6f engine: validate size of the message before passing it to COM_UnMunge2 2026-05-23 21:23:10 +05:00
Alibek Omarov
3a6e3ba3ca engine: change Xash split packet id to unsigned short, as it never should be negative 2026-05-23 21:23:10 +05:00
Alibek Omarov
42ab099a07 engine: check server address if client received split packet 2026-05-23 21:23:10 +05:00
Alibek Omarov
09f15042fe engine: add target buffer size argument to MSG_ReadBits and MSG_ReadBytes 2026-05-23 21:23:10 +05:00
SNMetamorph
344712b990 3rdparty: update extras 2026-05-23 21:09:31 +05:00
SNMetamorph
2904c3e88b scripts: gha: set default touch configuration file for PS Vita platform 2026-05-23 21:09:31 +05:00
Alibek Omarov
25ca2ecb12 engine: fix Netchan_Validate units bug allowing oversized fragments 2026-05-23 03:12:23 +05:00
Alibek Omarov
f6b28e32e2 engine: fix use-after-free in Netchan_CopyNormalFragments on decompression failure; reject oversized fragmented messages. 2026-05-23 02:53:26 +05:00
Alibek Omarov
e118376735 engine: take total size to decompressors in Netchan_CopyNormalFragments, fix underflow when decompressing bzip2 stream 2026-05-23 02:42:28 +05:00
Alibek Omarov
5ef6123f03 engine: append log to the crash log on Android 2026-05-22 18:47:31 +05:00
Владислав Сухов
e4600b5fe0 engine: input: fix PS Vita gyro axis mapping and add gyro debug visualization 2026-05-22 00:53:11 +05:00
Xav101
074bd8b2b3 public: add unaligned access handlers for new big endian port 2026-05-21 14:25:33 +05:00
Alibek Omarov
fca347dd00 engine: platform: psvita: add Atlas_AllocBlock to the vrtld export list temporarily 2026-05-21 03:07:15 +05:00
SNMetamorph
2a284090ef engine: common: add console command "fs_find" 2026-05-21 02:45:27 +05:00
SNMetamorph
ecd74a0a7f filesystem: add implementation of "fs_find" console command 2026-05-21 02:45:27 +05:00
Alibek Omarov
853a67b0c5 scripts: gha: lock PSVita SDL2 fork to last known good revision 2026-05-21 01:04:43 +05:00
Alibek Omarov
80ec1c83f1 engine: platform: psvita: add dummy Atlas_AllocBlock call 2026-05-21 01:04:12 +05:00
Alibek Omarov
41681229f9 3rdparty: gl4es: update from upstream, add patch for fixing pixelated textures on Adreno 2026-05-21 00:53:51 +05:00
Alibek Omarov
7fce99481f ref: gl: fix crash in gl2_shim caused by MapBufferRange returning NULL 2026-05-21 00:03:36 +05:00
Alibek Omarov
142dc7b87a ref: use vector initializer macros 2026-05-20 23:24:36 +05:00
Alibek Omarov
3e24d5a347 engine: use vector initializer macros 2026-05-20 23:24:36 +05:00
Alibek Omarov
64b1b54f5b ref: use designated initializers where possible 2026-05-20 23:24:36 +05:00
Alibek Omarov
1fed340330 filesystem: use designated initializers where possible 2026-05-20 23:24:36 +05:00
Alibek Omarov
64be4251c8 engine: use designated initializers where possible 2026-05-20 23:24:36 +05:00
Alibek Omarov
c5a8160c32 public: add Vec2, Vec3 and Vec4 macros used for initializers 2026-05-20 23:24:36 +05:00
Alibek Omarov
3c1cf81c2d engine: platform: refactor variable declarations 2026-05-20 23:24:36 +05:00
Alibek Omarov
c6c1163aa0 engine: common: refactor variable declarations 2026-05-20 23:24:36 +05:00
Alibek Omarov
e8845fc404 engine: client: refactor variable declarations 2026-05-20 23:24:36 +05:00
Alibek Omarov
d49178d5b6 engine: server: refactor variable declarations 2026-05-20 23:24:36 +05:00
Alibek Omarov
e2cf580818 ref: gl: refactor variable declarations 2026-05-20 23:24:36 +05:00
Alibek Omarov
d0a98447c4 ref: soft: refactor variable declarations 2026-05-20 23:24:36 +05:00
Alibek Omarov
29a9fcf76c ref: common: refactor variable declarations 2026-05-20 23:24:36 +05:00
Alibek Omarov
37d9791f00 filesystem: refactor variable declarations 2026-05-20 23:24:36 +05:00
Alibek Omarov
c5fbc98d16 public: refactor variable declarations 2026-05-20 23:24:36 +05:00
a1batross
4c2302ec6c engine: pass customization file fragments into IsSafeFileToDownload
This function already validates customization filenames.
2026-05-20 01:03:03 +05:00
78 changed files with 3722 additions and 775 deletions

3
.gitmodules vendored
View File

@@ -43,3 +43,6 @@
[submodule "3rdparty/library_suffix"]
path = 3rdparty/library_suffix
url = https://github.com/FWGS/library-suffix.git
[submodule "3rdparty/mbedtls/mbedtls"]
path = 3rdparty/mbedtls/mbedtls
url = https://github.com/FWGS/mbedtls-releases.git

View File

@@ -23,6 +23,6 @@ def build(bld):
bld(features='zip',
name = 'extras.pk3',
files = srcdir.ant_glob('**/*'),
files = srcdir.ant_glob('**/*', excl=['scripts/**']),
relative_to = srcdir,
install_path = install_path)

107
3rdparty/mbedtls/compat.c vendored Normal file
View File

@@ -0,0 +1,107 @@
/*
compat.c - mbedTLS platform overrides for targets upstream doesn't cover
Copyright (C) 2026 Xash3D FWGS contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
*/
/*
WinXP: legacy CryptGenRandom via advapi32
Source: ttps://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptgenrandom
PSVita: sceKernelGetRandomNumber from psp2/kernel/rng.h (64-byte cap).
Source: https://github.com/vitasdk/vita-headers/blob/master/include/psp2/kernel/rng.h
NSwitch: randomGet from libnx.
Source: https://github.com/switchbrew/libnx/blob/master/nx/include/switch/kernel/random.h
*/
#include "mbedtls/platform.h"
#include "psa/crypto.h"
#if defined( MBEDTLS_PLATFORM_MS_TIME_ALT )
mbedtls_ms_time_t mbedtls_ms_time( void )
{
extern double Platform_DoubleTime( void );
return (mbedtls_ms_time_t)( Platform_DoubleTime() * 1000.0 );
}
#endif /* MBEDTLS_PLATFORM_MS_TIME_ALT */
#if defined( MBEDTLS_PSA_DRIVER_GET_ENTROPY )
#if defined( _WIN32 ) && !defined( _WIN64 )
#include <windows.h>
#include <wincrypt.h>
int mbedtls_platform_get_entropy( psa_driver_get_entropy_flags_t flags, size_t *estimate_bits, unsigned char *output, size_t output_size )
{
if( flags != 0 )
return PSA_ERROR_NOT_SUPPORTED;
HCRYPTPROV prov;
if( !CryptAcquireContextW( &prov, NULL, NULL, PROV_RSA_FULL,
CRYPT_VERIFYCONTEXT | CRYPT_SILENT ))
return PSA_ERROR_INSUFFICIENT_ENTROPY;
if( !CryptGenRandom( prov, (DWORD)output_size, output ))
{
CryptReleaseContext( prov, 0 );
return PSA_ERROR_INSUFFICIENT_ENTROPY;
}
CryptReleaseContext( prov, 0 );
*estimate_bits = 8 * output_size;
return 0;
}
#elif defined( __vita__ )
#include <psp2/kernel/rng.h>
int mbedtls_platform_get_entropy( psa_driver_get_entropy_flags_t flags, size_t *estimate_bits, unsigned char *output, size_t output_size )
{
if( flags != 0 )
return PSA_ERROR_NOT_SUPPORTED;
size_t total = 0;
while( total < output_size )
{
size_t chunk = output_size - total;
if( chunk > 64 )
chunk = 64;
if( sceKernelGetRandomNumber( output + total, chunk ) != 0 )
return PSA_ERROR_INSUFFICIENT_ENTROPY;
total += chunk;
}
*estimate_bits = 8 * output_size;
return 0;
}
#elif defined( __SWITCH__ )
#include <switch.h>
int mbedtls_platform_get_entropy( psa_driver_get_entropy_flags_t flags, size_t *estimate_bits, unsigned char *output, size_t output_size )
{
if( flags != 0 )
return PSA_ERROR_NOT_SUPPORTED;
randomGet( output, output_size );
*estimate_bits = 8 * output_size;
return 0;
}
#else
#error "MBEDTLS_PSA_DRIVER_GET_ENTROPY enabled but no platform impl in compat.c"
#endif
#endif /* MBEDTLS_PSA_DRIVER_GET_ENTROPY */

1
3rdparty/mbedtls/mbedtls vendored Submodule

67
3rdparty/mbedtls/wscript vendored Normal file
View File

@@ -0,0 +1,67 @@
#! /usr/bin/env python
# encoding: utf-8
def options(opt):
grp = opt.add_option_group('mbedTLS options')
grp.add_option('--disable-mbedtls', action='store_false', dest='MBEDTLS', default=True,
help='disable bundled mbedTLS and built-in HTTPS support [default: enabled]')
def configure(conf):
conf.env.MBEDTLS = conf.options.MBEDTLS
if not conf.env.MBEDTLS:
return
if not conf.path.find_dir('mbedtls') or not conf.path.find_dir('mbedtls/library'):
conf.fatal('Can\'t find mbedtls submodule. Run `git submodule update --init --recursive`.')
if not conf.path.find_dir('mbedtls/tf-psa-crypto/core'):
conf.fatal('mbedTLS nested submodule tf-psa-crypto is missing. Run `git submodule update --init --recursive`.')
def build(bld):
if not bld.env.MBEDTLS:
return
sources = bld.path.ant_glob([
'mbedtls/library/*.c',
'mbedtls/tf-psa-crypto/core/*.c',
'mbedtls/tf-psa-crypto/extras/*.c',
'mbedtls/tf-psa-crypto/platform/*.c',
'mbedtls/tf-psa-crypto/utilities/*.c',
'mbedtls/tf-psa-crypto/drivers/builtin/src/*.c',
])
sources += ['compat.c']
defines = [
'MBEDTLS_USER_CONFIG_FILE="xash_mbedtls_config.h"',
'TF_PSA_CRYPTO_USER_CONFIG_FILE="xash_psa_config.h"',
]
includes = [
'.',
'mbedtls/include',
'mbedtls/library',
'mbedtls/tf-psa-crypto/include',
'mbedtls/tf-psa-crypto/drivers/builtin/include',
'mbedtls/tf-psa-crypto/core',
'mbedtls/tf-psa-crypto/drivers/builtin/src',
'mbedtls/tf-psa-crypto/utilities',
'mbedtls/tf-psa-crypto/extras',
'mbedtls/tf-psa-crypto/platform',
'mbedtls/tf-psa-crypto/dispatch',
]
bld.stlib(
source = sources,
target = 'mbedtls',
features = 'c',
includes = includes,
defines = defines,
export_defines = ['XASH_MBEDTLS'],
export_includes = [
'.',
'mbedtls/include',
'mbedtls/tf-psa-crypto/include',
'mbedtls/tf-psa-crypto/drivers/builtin/include',
]
)

36
3rdparty/mbedtls/xash_mbedtls_config.h vendored Normal file
View File

@@ -0,0 +1,36 @@
#ifndef XASH_MBEDTLS_CONFIG_H
#define XASH_MBEDTLS_CONFIG_H
#undef MBEDTLS_NET_C
#undef MBEDTLS_PKCS7_C
#undef MBEDTLS_TIMING_C
#undef MBEDTLS_SSL_SRV_C
#undef MBEDTLS_SSL_PROTO_DTLS
#undef MBEDTLS_SSL_DTLS_ANTI_REPLAY
#undef MBEDTLS_SSL_DTLS_CONNECTION_ID
#undef MBEDTLS_SSL_DTLS_HELLO_VERIFY
#undef MBEDTLS_SSL_DTLS_SRTP
#undef MBEDTLS_SSL_DTLS_CLIENT_PORT_REUSE
#undef MBEDTLS_SSL_COOKIE_C
#undef MBEDTLS_SSL_CACHE_C
#undef MBEDTLS_SSL_TICKET_C
#undef MBEDTLS_SSL_SESSION_TICKETS
#undef MBEDTLS_SSL_RENEGOTIATION
#undef MBEDTLS_SSL_CONTEXT_SERIALIZATION
#undef MBEDTLS_SSL_KEYING_MATERIAL_EXPORT
#undef MBEDTLS_SSL_MAX_FRAGMENT_LENGTH
#undef MBEDTLS_SSL_ALL_ALERT_MESSAGES
#undef MBEDTLS_SSL_ALPN
#undef MBEDTLS_SSL_ENCRYPT_THEN_MAC
#undef MBEDTLS_KEY_EXCHANGE_PSK_ENABLED
#undef MBEDTLS_KEY_EXCHANGE_ECDHE_PSK_ENABLED
#undef MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_PSK_ENABLED
#undef MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_PSK_EPHEMERAL_ENABLED
#undef MBEDTLS_X509_CREATE_C
#undef MBEDTLS_X509_CRT_WRITE_C
#undef MBEDTLS_X509_CSR_PARSE_C
#undef MBEDTLS_X509_CSR_WRITE_C
#undef MBEDTLS_X509_CRL_PARSE_C
#undef MBEDTLS_VERSION_FEATURES
#endif /* XASH_MBEDTLS_CONFIG_H */

82
3rdparty/mbedtls/xash_psa_config.h vendored Normal file
View File

@@ -0,0 +1,82 @@
#ifndef XASH_PSA_CONFIG_H
#define XASH_PSA_CONFIG_H
#if (defined( _WIN32 ) && !defined( _WIN64 )) || defined( __vita__ ) || defined( __SWITCH__ )
/* WinXP, PSVita and NSwitch use entropy paths upstream doesn't cover.
compat.c provides mbedtls_platform_get_entropy() for all three. */
#undef MBEDTLS_PSA_BUILTIN_GET_ENTROPY
#define MBEDTLS_PSA_DRIVER_GET_ENTROPY
#endif
#if defined( __vita__ ) || defined( __SWITCH__ )
/* Upstream has no Vita/NSW support; compat.c fills in */
#define MBEDTLS_PLATFORM_MS_TIME_ALT
#endif
#undef MBEDTLS_FS_IO
#undef MBEDTLS_PSA_ITS_FILE_C
#undef MBEDTLS_PSA_CRYPTO_STORAGE_C
#undef MBEDTLS_SELF_TEST
#undef MBEDTLS_HMAC_DRBG_C
#undef MBEDTLS_LMS_C
#undef MBEDTLS_NIST_KW_C
#undef MBEDTLS_PKCS5_C
#undef PSA_WANT_ALG_CCM
#undef PSA_WANT_ALG_CCM_STAR_NO_TAG
#undef PSA_WANT_ALG_CBC_NO_PADDING
#undef PSA_WANT_ALG_CBC_PKCS7
#undef PSA_WANT_ALG_CFB
#undef PSA_WANT_ALG_CMAC
#undef PSA_WANT_ALG_CTR
#undef PSA_WANT_ALG_ECB_NO_PADDING
#undef PSA_WANT_ALG_OFB
#undef PSA_WANT_ALG_STREAM_CIPHER
#undef PSA_WANT_ALG_JPAKE
#undef PSA_WANT_ALG_TLS12_ECJPAKE_TO_PMS
#undef PSA_WANT_ALG_TLS12_PSK_TO_MS
#undef PSA_WANT_ALG_FFDH
#undef PSA_WANT_ALG_PBKDF2_HMAC
#undef PSA_WANT_ALG_PBKDF2_AES_CMAC_PRF_128
#undef PSA_WANT_KEY_TYPE_PASSWORD
#undef PSA_WANT_KEY_TYPE_PASSWORD_HASH
#undef PSA_WANT_ALG_SHA3_224
#undef PSA_WANT_ALG_SHA3_256
#undef PSA_WANT_ALG_SHA3_384
#undef PSA_WANT_ALG_SHA3_512
#undef PSA_WANT_ALG_SHAKE128
#undef PSA_WANT_ALG_SHAKE256
#undef PSA_WANT_ALG_SHA_224
#undef PSA_WANT_ALG_SHA_1
#undef PSA_WANT_ALG_MD5
#undef PSA_WANT_ALG_RIPEMD160
#undef PSA_WANT_KEY_TYPE_ARIA
#undef PSA_WANT_KEY_TYPE_CAMELLIA
#undef PSA_WANT_ECC_BRAINPOOL_P_R1_256
#undef PSA_WANT_ECC_BRAINPOOL_P_R1_384
#undef PSA_WANT_ECC_BRAINPOOL_P_R1_512
#undef PSA_WANT_ECC_SECP_K1_256
#undef PSA_WANT_ECC_MONTGOMERY_448
#undef PSA_WANT_ECC_SECP_R1_521
#undef PSA_WANT_DH_RFC7919_2048
#undef PSA_WANT_DH_RFC7919_3072
#undef PSA_WANT_DH_RFC7919_4096
#undef PSA_WANT_DH_RFC7919_6144
#undef PSA_WANT_DH_RFC7919_8192
#undef PSA_WANT_KEY_TYPE_DH_KEY_PAIR_BASIC
#undef PSA_WANT_KEY_TYPE_DH_KEY_PAIR_EXPORT
#undef PSA_WANT_KEY_TYPE_DH_KEY_PAIR_GENERATE
#undef PSA_WANT_KEY_TYPE_DH_KEY_PAIR_IMPORT
#undef PSA_WANT_KEY_TYPE_DH_PUBLIC_KEY
#undef PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_EXPORT
#undef PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_IMPORT
#undef PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_EXPORT
#undef PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_GENERATE
#undef PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_IMPORT
#undef MBEDTLS_PEM_WRITE_C
#undef MBEDTLS_PK_WRITE_C
#undef MBEDTLS_PK_PARSE_EC_COMPRESSED
#undef MBEDTLS_PK_PARSE_EC_EXTENDED
#undef MBEDTLS_ASN1_WRITE_C
#endif /* XASH_PSA_CONFIG_H */

View File

@@ -0,0 +1,181 @@
# XRCON Protocol Specification
## Overview
XRCON is a TCP-based remote console protocol implemented as a server within the engine. It provides an alternative to the legacy RCON (which operates over UDP as out-of-band packets). XRCON uses a framed binary protocol over a single persistent TCP connection, accepting at most one client at a time.
Key characteristics:
- Based on TCP (stream-oriented, reliable)
- Default port is 27000
- Maximum only one concurrent client
- Binary framed protocol with a 12-byte header + variable-length payload
## Configuration
The XRCON server is controlled by two configuration variables, both restricted to privileged users:
| Variable | Default | Description |
|---|---|---|
| `xrcon_enable` | `0` (disabled) | Master switch; when enabled, the server starts listening for connections |
| `xrcon_address` | `127.0.0.1:27000` | Bind address and port; supports both IPv4 and IPv6. Changing this at runtime triggers a XRCON server restart (stop + rebind) |
By default, XRCON binds only to localhost (`127.0.0.1`), providing a degree of access control. The server will not start listening until explicitly enabled.
## Frame Format
All XRCON messages are encapsulated in a framed binary format. Each frame consists of a fixed 12-byte header followed by a variable-length payload.
### Header (12 bytes)
| Offset | Size | Field | Description |
|---|---|---|---|
| 0 | 4 | Type | 4-character message type string (null-terminated, occupies 5 bytes in the structure but the 5th byte is padding) |
| 4 | 4 | Version | Protocol version (32-bit unsigned integer, network byte order) |
| 8 | 2 | Length | Total frame length in bytes, including the header (16-bit unsigned integer, network byte order) |
| 10 | 2 | Handle | Handle or sequence number (16-bit unsigned integer, network byte order; currently always zero) |
### Protocol Version
The current protocol version is `0x000000D4` (212 decimal). The server also accepts `0x00D40000` for compatibility with certain third-party clients (e.g., CS2RemoteConsole).
### Maximum Frame Size
- Maximum payload per frame: 4096 bytes
- Maximum total packet size (header + payload): approximately 4136 bytes
- Receive buffer size: 4096 + 64 bytes
- Transmit buffer size: 16384 bytes (4 × maximum frame size)
### Byte Order
Multi-byte integer fields (version, length, handle) are transmitted in **network byte order** (big-endian).
## Message Types
| Type | Direction | Description |
|---|---|---|
| `CMND` | Client → Server | Execute a console command |
| `PRNT` | Server → Client | Console output / print message |
| `CHAN` | Server → Client | Channel list (channel metadata) |
| `AINF` | Server → Client | Application / server info |
| `ADON` | Server → Client | Additional info / server name |
### CMND — Client Command
**Direction**: Client → Server
Sent by the client to execute a console command on the server.
| Section | Size | Description |
|---|---|---|
| Header | 12 bytes | Type = `"CMND"`, version, length, handle |
| Payload | Variable | Raw command string (null-terminated) |
The command string is injected into the engine's command buffer for execution. There is **no authentication** — any connected client may execute arbitrary commands.
### PRNT — Server Print
**Direction**: Server → Client
Sent by the server to stream console output to the connected client.
| Section | Size | Description |
|---|---|---|
| Header | 12 bytes | Type = `"PRNT"`, version, length, handle |
| Channel ID | 4 bytes | Channel identifier (unsigned 32-bit integer; always 0 for "Console") |
| Padding | 20 bytes | Reserved (5 × 32-bit unsigned integers, all zero) |
| Red | 1 byte | Red color component (always 255) |
| Green | 1 byte | Green color component (always 255) |
| Blue | 1 byte | Blue color component (always 255) |
| Alpha | 1 byte | Alpha / opacity component (always 255) |
| Text | Variable | The console output text (up to 4096 bytes) |
### CHAN — Channel List
**Direction**: Server → Client
Sent by the server during the initial handshake (and potentially later) to describe the available console output channels.
| Section | Size | Description |
|---|---|---|
| Header | 12 bytes | Type = `"CHAN"`, version, length, handle |
| Channel Count | 2 bytes | Number of channel records (unsigned 16-bit integer; always 1) |
Each channel record (58 bytes):
| Field | Size | Description |
|---|---|---|
| Channel ID | 4 bytes | Unique channel identifier (unsigned 32-bit integer; always 0) |
| Unknown 1 | 4 bytes | Reserved (unsigned 32-bit integer; always 0) |
| Unknown 2 | 4 bytes | Reserved (unsigned 32-bit integer; always 0) |
| Default Verbosity | 4 bytes | Default verbosity level (unsigned 32-bit integer; always 5) |
| Current Verbosity | 4 bytes | Current verbosity level (unsigned 32-bit integer; always 5) |
| Red | 1 byte | Red color component for the channel (always 255) |
| Green | 1 byte | Green color component (always 255) |
| Blue | 1 byte | Blue color component (always 255) |
| Alpha | 1 byte | Alpha component (always 255) |
| Name | 34 bytes | Channel name string, null-padded (always `"Console"`) |
Total payload size for this frame: 2 + 58 = 60 bytes.
### AINF — Application Info
**Direction**: Server → Client
Sent by the server immediately after a client connects, before `ADON` and `CHAN`.
| Section | Size | Description |
|---|---|---|
| Header | 12 bytes | Type = `"AINF"`, version, length, handle |
| Payload | 77 bytes | All zeros (placeholder / reserved for structured application information) |
This packet currently serves as a placeholder and contains no meaningful data. It may be extended in future versions to carry structured metadata about the server application.
### ADON — Additional Info
**Direction**: Server → Client
Sent by the server immediately after `AINF` during the connection handshake.
| Section | Size | Description |
|---|---|---|
| Header | 12 bytes | Type = `"ADON"`, version, length, handle |
| Unknown | 2 bytes | Reserved (unsigned 16-bit integer; always 0) |
| Name Length | 2 bytes | Length of the name string (unsigned 16-bit integer) |
| Name | Variable | Server name / identifier string (e.g., `"HLDS"`) |
This packet carries the server application name, which is a short string identifying the server type (e.g., `"HLDS"` for Half-Life Dedicated Server).
## Security
XRCON has **no built-in authentication or encryption**. There is no password validation or transport-layer security. Any client that can establish a TCP connection to the XRCON port has full command execution access to the engine console.
The only security measures are:
1. **Default localhost binding**: The server binds to `127.0.0.1` by default, accepting connections only from the local machine.
2. **Disabled by default**: The `xrcon_enable` variable defaults to `0`, so the server does not listen unless explicitly enabled.
3. **Privileged variables**: Both configuration variables are restricted to privileged users and cannot be modified through unprivileged console commands or external access.
## Comparison with Legacy RCON
| Feature | XRCON | Legacy RCON |
|---|---|---|
| Transport | TCP (stream) | UDP (datagram, out-of-band) |
| Port | Configurable, default 27000 | Game port (default 27015) |
| Framing | Binary frame header | Plain text over UDP |
| Authentication | None | Password-based |
| Max clients | 1 | Multiple |
| Interaction format | Full console access | Request-response only for submitted command |
| Supported environments | Client and server | Server only |

View File

@@ -0,0 +1,71 @@
# Static HTTP server list
Replacement for the UDP `S2M_SCAN_REQUEST` / `M2A_SERVERSLIST` exchange
described in [02-connectionless.md](02-connectionless.md). Read-only HTTP,
no NAT punching, no filtering or pagination in the request.
## URL
`xashcomm.lst` carries a base URL per source:
```
masterstatic http://master.example.org/server-list
```
The engine appends `/v1/servers/<gamedir>` and `GET`s the result. For
`gamedir = valve`:
```
GET http://master.example.org/server-list/v1/servers/valve
```
Trailing slashes on the base URL are stripped. Multiple `masterstatic`
lines are allowed; results are merged.
The request is bare: no body, no auth, no cookies, no compressed encodings.
Only the standard `User-Agent` is set. `POST` / `PUT` / `DELETE` are not
used; servers register out of band.
## Response
UTF-8 text, tokenized with `COM_ParseFileSafe` (whitespace separates,
`//` and `#` start line comments, `"..."` quotes a token). One directive
per record:
* `ip <address>` — Xash3D server (protocol 49).
* `gs <address>` — GoldSrc server (protocol 48).
`<address>` is parsed by `NET_StringToAdr` (`1.2.3.4:27015`,
`[2001:db8::1]:27015`, hostnames). Port defaults to `27015`. Unknown
directives are skipped together with one operand so new keywords can be
added without breaking older clients.
`Content-Type` is not inspected, `text/plain; charset=utf-8` expected.
### Example
```
# diffusion servers
ip 192.0.2.10:27015
ip [2001:db8::1]:27015
gs 198.51.100.5:27015
```
A file with zero records is valid and represents an empty list.
### Versioning
The `/v1/` segment is fixed in this revision. A future protocol revision
adds a sibling `/v2/...` resource without breaking older clients.
## Client behaviour
Each `ip` / `gs` record from the response triggers a probe to the listed
address, identical to a server discovered through the UDP master. DNS
errors, non-200 responses, and malformed bodies are reported to the
console.
## Server behaviour
Any static HTTP server works. A typical setup is a periodic job that
probes a set of known addresses and writes `v1/servers/<gamedir>`.

View File

@@ -25,7 +25,7 @@ extensions.configure<ApplicationExtension> {
externalNativeBuild {
val engineRoot = projectDir.parentFile.parent
experimentalProperties["ninja.abiFilters"] = setOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
experimentalProperties["ninja.abiFilters"] = setOf("armeabi-v7a", "arm64-v8a", "x86")
experimentalProperties["ninja.path"] = File(engineRoot, "wscript").path
experimentalProperties["ninja.configure"] = "run-python"
experimentalProperties["ninja.arguments"] = setOf(

View File

@@ -153,6 +153,7 @@ class MainActivity : AppCompatActivity() {
moveOrCopy(pending, File(entryDir, CrashReports.STACKTRACE_NAME))
moveOrCopy(CrashReports.pendingSysinfo(this), File(entryDir, CrashReports.SYSINFO_NAME))
moveOrCopy(CrashReports.pendingIntent(this), File(entryDir, CrashReports.INTENT_NAME))
moveOrCopy(CrashReports.pendingEngineLog(this), File(entryDir, CrashReports.ENGINELOG_NAME))
val entry = CrashReports.Entry(entryDir)
AlertDialog.Builder(this)
@@ -171,7 +172,7 @@ class MainActivity : AppCompatActivity() {
if (src.renameTo(dst))
return
dst.writeText(src.readText())
src.copyTo(dst, overwrite = true)
src.delete()
}

View File

@@ -24,6 +24,7 @@ object CrashReports {
const val STACKTRACE_NAME = "crash.log"
const val SYSINFO_NAME = "sysinfo.txt"
const val INTENT_NAME = "intent.txt"
const val ENGINELOG_NAME = "engine.log"
class Entry(val dir: File) {
val name: String get() = dir.name
@@ -31,8 +32,9 @@ object CrashReports {
val stacktrace: File get() = File(dir, STACKTRACE_NAME)
val sysinfo: File get() = File(dir, SYSINFO_NAME)
val intent: File get() = File(dir, INTENT_NAME)
val engineLog: File get() = File(dir, ENGINELOG_NAME)
fun attachments(): List<File> = listOf(stacktrace, sysinfo, intent).filter { it.exists() && it.length() > 0 }
fun attachments(): List<File> = listOf(stacktrace, sysinfo, intent, engineLog).filter { it.exists() && it.length() > 0 }
fun summary(): String = buildString {
if (stacktrace.exists())
@@ -52,6 +54,7 @@ object CrashReports {
fun pendingStacktrace(ctx: Context): File = File(pendingDir(ctx), STACKTRACE_NAME)
fun pendingSysinfo(ctx: Context): File = File(pendingDir(ctx), SYSINFO_NAME)
fun pendingIntent(ctx: Context): File = File(pendingDir(ctx), INTENT_NAME)
fun pendingEngineLog(ctx: Context): File = File(pendingDir(ctx), ENGINELOG_NAME)
fun historyDir(ctx: Context): File = File(ctx.filesDir, "crashes/history")
// wipe everything on app update; otherwise drop logs older than 30 days
@@ -65,6 +68,7 @@ object CrashReports {
pendingStacktrace(ctx).delete()
pendingSysinfo(ctx).delete()
pendingIntent(ctx).delete()
pendingEngineLog(ctx).delete()
prefs.edit().putInt(KEY_LAST_VERSION, currentVersion).apply()
return
}

View File

@@ -16,6 +16,10 @@
#define CS_SIZE 64 // size of one config string
#define CS_TIME 16 // size of time string
// FIXME: find better place for the shared definition
#define MAX_CLIENT_BITS 5
#define MAX_CLIENTS (1<<MAX_CLIENT_BITS)// 5 bits == 32 clients ( int32 limit )
// platform-specific alignment for types, to not break ABI
#if XASH_PSP
#define MAYBE_ALIGNED( x ) __attribute__(( aligned( 16 )))
@@ -273,4 +277,11 @@ typedef int qboolean;
#define HostFourCC( a, b, c, d ) LittleFourCC( a, b, c, d )
#endif
static inline short UnalignedShort( short *x )
{
short y;
memcpy( &y, x, sizeof( y ) );
return y;
}
#endif // XASH_TYPES_H

View File

@@ -13,6 +13,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
*/
#include <inttypes.h>
#include "common.h"
#include "client.h"
#include "net_encode.h"
@@ -38,6 +39,7 @@ CVAR_DEFINE_AUTO( cl_download_ingame, "1", FCVAR_ARCHIVE, "allow to downloading
static CVAR_DEFINE_AUTO( cl_logofile, "lambda", FCVAR_ARCHIVE, "player logo name" );
static CVAR_DEFINE_AUTO( cl_logocolor, "255 120 24", FCVAR_ARCHIVE, "player logo color" );
static CVAR_DEFINE_AUTO( cl_logoext, "bmp", FCVAR_ARCHIVE, "temporary cvar to tell engine which logo must be packed" );
static CVAR_DEFINE( cl_logoupdate, "@cl_logoupdate", "0", 0, "set by menu to trigger clan logo update" );
CVAR_DEFINE_AUTO( cl_logomaxdim, "96", FCVAR_ARCHIVE, "maximum decal dimension" );
static CVAR_DEFINE_AUTO( cl_test_bandwidth, "1", FCVAR_ARCHIVE, "test network bandwith before connection" );
@@ -47,18 +49,18 @@ CVAR_DEFINE( cl_draw_beams, "r_drawbeams", "1", FCVAR_CHEAT, "render beams" );
static CVAR_DEFINE_AUTO( rcon_address, "", FCVAR_PRIVILEGED, "remote control address" );
CVAR_DEFINE_AUTO( cl_timeout, "60", 0, "connect timeout (in-seconds)" );
CVAR_DEFINE_AUTO( cl_nopred, "0", FCVAR_ARCHIVE|FCVAR_USERINFO, "disable client movement prediction" );
CVAR_DEFINE_AUTO( cl_nopred, "0", FCVAR_USERINFO, "disable client movement prediction" );
static CVAR_DEFINE_AUTO( cl_nodelta, "0", 0, "disable delta-compression for server messages" );
CVAR_DEFINE( cl_crosshair, "crosshair", "1", FCVAR_ARCHIVE, "show weapon chrosshair" );
static CVAR_DEFINE_AUTO( cl_cmdbackup, "10", FCVAR_ARCHIVE, "how many additional history commands are sent" );
CVAR_DEFINE_AUTO( cl_showerror, "0", FCVAR_ARCHIVE, "show prediction error" );
CVAR_DEFINE_AUTO( cl_bmodelinterp, "1", FCVAR_ARCHIVE, "enable bmodel interpolation" );
CVAR_DEFINE_AUTO( cl_bmodelinterp, "1", 0, "enable bmodel interpolation" );
static CVAR_DEFINE_AUTO( cl_lightstyle_lerping, "0", FCVAR_ARCHIVE, "enables animated light lerping (perfomance option)" );
CVAR_DEFINE_AUTO( cl_idealpitchscale, "0.8", 0, "how much to look up/down slopes and stairs when not using freelook" );
CVAR_DEFINE_AUTO( cl_nosmooth, "0", FCVAR_ARCHIVE, "disable smooth up stair climbing" );
CVAR_DEFINE_AUTO( cl_smoothtime, "0.1", FCVAR_ARCHIVE, "time to smooth up" );
CVAR_DEFINE_AUTO( cl_clockreset, "0.1", FCVAR_ARCHIVE, "frametime delta maximum value before reset" );
static CVAR_DEFINE_AUTO( cl_fixtimerate, "7.5", FCVAR_ARCHIVE, "time in msec to client clock adjusting" );
CVAR_DEFINE_AUTO( cl_nosmooth, "0", 0, "disable smooth up stair climbing" );
CVAR_DEFINE_AUTO( cl_smoothtime, "0.1", 0, "time to smooth up" );
CVAR_DEFINE_AUTO( cl_clockreset, "0.1", 0, "frametime delta maximum value before reset" );
static CVAR_DEFINE_AUTO( cl_fixtimerate, "7.5", 0, "time in msec to client clock adjusting" );
CVAR_DEFINE_AUTO( hud_fontscale, "1.0", FCVAR_ARCHIVE|FCVAR_LATCH, "scale hud font texture" );
CVAR_DEFINE_AUTO( hud_fontrender, "0", FCVAR_ARCHIVE, "hud font render mode (0: additive, 1: holes, 2: trans)" );
CVAR_DEFINE_AUTO( hud_scale, "0", FCVAR_ARCHIVE|FCVAR_LATCH, "scale hud at current resolution" );
@@ -94,7 +96,7 @@ static CVAR_DEFINE_AUTO( topcolor, "0", FCVAR_USERINFO|FCVAR_ARCHIVE|FCVAR_FILTE
static CVAR_DEFINE_AUTO( bottomcolor, "0", FCVAR_USERINFO|FCVAR_ARCHIVE|FCVAR_FILTERABLE, "player bottom color" );
CVAR_DEFINE_AUTO( rate, "25000", FCVAR_USERINFO|FCVAR_ARCHIVE|FCVAR_FILTERABLE, "player network rate" );
CVAR_DEFINE_AUTO( cl_ticket_generator, "revemu2013", FCVAR_ARCHIVE, "you wouldn't steal a car" );
CVAR_DEFINE_AUTO( cl_ticket_generator, "revemu2013", FCVAR_ARCHIVE|FCVAR_PRIVILEGED, "you wouldn't steal a car" );
static CVAR_DEFINE_AUTO( cl_advertise_engine_in_name, "1", FCVAR_ARCHIVE|FCVAR_PRIVILEGED, "add [Xash3D] to the nickname when connecting to GoldSrc servers" );
static CVAR_DEFINE_AUTO( cl_log_outofband, "0", FCVAR_ARCHIVE, "log out of band messages, can be useful for server admins and for engine debugging" );
static CVAR_DEFINE_AUTO( cl_autorecord, "0", 0, "automatically start recording a demo after joining the server" );
@@ -195,6 +197,120 @@ void CL_SetCheatState( qboolean multiplayer, qboolean allow_cheats )
}
}
static resource_t *CL_AddResource( resourcetype_t type, const char *name, int size, qboolean bFatalIfMissing, int index )
{
resource_t *r = &cl.resourcelist[cl.num_resources];
if( cl.num_resources >= MAX_RESOURCES )
Host_Error( "Too many resources on client\n" );
cl.num_resources++;
Q_strncpy( r->szFileName, name, sizeof( r->szFileName ));
r->ucFlags |= bFatalIfMissing ? RES_FATALIFMISSING : 0;
r->nDownloadSize = size;
r->nIndex = index;
r->type = type;
return r;
}
static void CL_CreateResourceList( void )
{
char szFileName[MAX_OSPATH];
byte rgucMD5_hash[16] = { 0 };
HPAK_FlushHostQueue();
cl.num_resources = 0;
memset( rgucMD5_hash, 0, sizeof( rgucMD5_hash ));
ClearBits( cl_logoupdate.flags, FCVAR_CHANGED );
#if 1 // FIXME: deprecated, remove later
ClearBits( cl_logofile.flags, FCVAR_CHANGED );
ClearBits( cl_logocolor.flags, FCVAR_CHANGED );
ClearBits( cl_logoext.flags, FCVAR_CHANGED );
#endif
// sanitize cvar value
if( Q_strcmp( cl_logoext.string, "bmp" ) && Q_strcmp( cl_logoext.string, "png" ))
Cvar_DirectSet( &cl_logoext, "bmp" );
Q_snprintf( szFileName, sizeof( szFileName ), "logos/remapped.%s", cl_logoext.string );
if( cls.legacymode == PROTO_GOLDSRC )
{
CL_ConvertImageToWAD3( szFileName );
Q_strncpy( szFileName, "tempdecal.wad", sizeof( szFileName ));
}
file_t *fp = FS_Open( szFileName, "rb", true );
if( !fp )
return;
int nSize = FS_FileLength( fp );
if( nSize != 0 )
{
resource_t *pNewResource = CL_AddResource( t_decal, szFileName, nSize, false, 0 );
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 );
}
}
FS_Close( fp );
}
/*
==================
CL_UpdateLogo
repackage the clan logo and upload it to the server
==================
*/
static void CL_UpdateLogo( void )
{
if( cls.state != ca_active )
return;
CL_CreateResourceList();
if( cl.num_resources == 0 )
return;
player_info_t *player = &cl.players[cl.playernum];
COM_ClearCustomizationList( &player->customdata, true );
for( int 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 void CL_CheckLogoChanged( void )
{
if( FBitSet( cl_logoupdate.flags, FCVAR_CHANGED ))
{
CL_UpdateLogo();
return;
}
#if 1 // FIXME: deprecated, remove later
if( FBitSet( cl_logofile.flags | cl_logocolor.flags | cl_logoext.flags, FCVAR_CHANGED ))
{
CL_UpdateLogo();
return;
}
#endif
}
/*
===============
CL_CheckClientState
@@ -213,6 +329,8 @@ static void CL_CheckClientState( void )
cls.changedemo = false; // changedemo is done
cl.first_frame = true; // first rendering frame
CL_UpdateLogo();
SCR_MakeLevelShot(); // make levelshot if needs
Cvar_SetValue( "scr_loading", 0.0f ); // reset progress bar
Netchan_ReportFlow( &cls.netchan );
@@ -1135,6 +1253,7 @@ static void CL_SendConnectPacket( connprotocol_t proto, int challenge )
}
cls.broker_wait = false;
cls.netchan_pending_cookie = 0;
if( proto == PROTO_GOLDSRC )
{
@@ -1156,7 +1275,7 @@ static void CL_SendConnectPacket( connprotocol_t proto, int challenge )
else
{
const char *qport = Cvar_VariableString( "net_qport" );
int extensions = adrtype == NA_LOOPBACK ? 0 : NET_EXT_SPLITSIZE;
int extensions = adrtype == NA_LOOPBACK ? 0 : ( NET_EXT_SPLITSIZE | NET_EXT_NETCHAN_COOKIE );
string key;
ID_GetMD5ForAddress( key, adr, sizeof( key ));
@@ -1171,6 +1290,16 @@ static void CL_SendConnectPacket( connprotocol_t proto, int challenge )
Info_SetValueForKey( protinfo, "qport", qport, sizeof( protinfo ));
Info_SetValueForKeyf( protinfo, "ext", sizeof( protinfo ), "%d", extensions );
if( FBitSet( extensions, NET_EXT_NETCHAN_COOKIE ))
{
uint64_t a = COM_RandomLong( 0, 0xFFFF );
uint64_t b = COM_RandomLong( 0, 0xFFFF );
uint64_t c = COM_RandomLong( 0, 0xFFFF );
uint64_t d = COM_RandomLong( 0, 0xFFFF );
cls.netchan_pending_cookie = ( a << 48 ) | ( b << 32 ) | ( c << 16 ) | d;
Info_SetValueForKeyf( protinfo, "cookie", sizeof( protinfo ), "%016"PRIx64, cls.netchan_pending_cookie );
}
Netchan_OutOfBandPrint( NS_CLIENT, adr, C2S_CONNECT" %i %i \"%s\" \"%s\"\n", PROTOCOL_VERSION, challenge, protinfo, cls.userinfo );
Con_Printf( "Trying to connect with modern protocol\n" );
}
@@ -1236,13 +1365,7 @@ static void CL_CheckForResend( void )
netadr_t adr;
if( cls.internetservers_wait )
{
cls.internetservers_wait = NET_MasterQuery(
cls.internetservers_key,
cls.internetservers_nat,
cls.internetservers_customfilter
);
}
cls.internetservers_wait = NET_MasterQuery( 1, cls.internetservers_nat, cls.internetservers_customfilter );
// if the local server is running and we aren't then connect
if( cls.state == ca_disconnected && SV_Active( ))
@@ -1337,102 +1460,6 @@ static void CL_CheckForResend( void )
}
}
static resource_t *CL_AddResource( resourcetype_t type, const char *name, int size, qboolean bFatalIfMissing, int index )
{
resource_t *r = &cl.resourcelist[cl.num_resources];
if( cl.num_resources >= MAX_RESOURCES )
Host_Error( "Too many resources on client\n" );
cl.num_resources++;
Q_strncpy( r->szFileName, name, sizeof( r->szFileName ));
r->ucFlags |= bFatalIfMissing ? RES_FATALIFMISSING : 0;
r->nDownloadSize = size;
r->nIndex = index;
r->type = type;
return r;
}
static void CL_CreateResourceList( void )
{
char szFileName[MAX_OSPATH];
byte rgucMD5_hash[16] = { 0 };
HPAK_FlushHostQueue();
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" );
Q_snprintf( szFileName, sizeof( szFileName ), "logos/remapped.%s", cl_logoext.string );
if( cls.legacymode == PROTO_GOLDSRC )
{
CL_ConvertImageToWAD3( szFileName );
Q_strncpy( szFileName, "tempdecal.wad", sizeof( szFileName ));
}
file_t *fp = FS_Open( szFileName, "rb", true );
if( !fp )
return;
int nSize = FS_FileLength( fp );
if( nSize != 0 )
{
resource_t *pNewResource = CL_AddResource( t_decal, szFileName, nSize, false, 0 );
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 );
}
}
FS_Close( fp );
}
/*
==================
CL_CheckLogoChanged
==================
*/
static void CL_CheckLogoChanged( void )
{
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_info_t *player = &cl.players[cl.playernum];
COM_ClearCustomizationList( &player->customdata, true );
for( int 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" ))
@@ -1664,10 +1691,19 @@ void CL_SetupNetchanForProtocol( connprotocol_t proto )
if( FBitSet( cls.extensions, NET_EXT_SPLITSIZE ))
Con_Reportf( "^2NET_EXT_SPLITSIZE enabled^7 (packet size is %d)\n", (int)cl_dlmax.value );
if( FBitSet( cls.extensions, NET_EXT_NETCHAN_COOKIE ))
{
Con_Reportf( "^2NET_EXT_NETCHAN_COOKIE enabled^7\n" );
SetBits( flags, NETCHAN_USE_COOKIE );
}
break;
}
Netchan_Setup( NS_CLIENT, &cls.netchan, net_from, Cvar_VariableInteger( "net_qport" ), NULL, pfnBlockSize, flags );
if( FBitSet( flags, NETCHAN_USE_COOKIE ))
Netchan_SetCookie( &cls.netchan, cls.netchan_pending_cookie );
}
/*
@@ -1822,27 +1858,10 @@ static void CL_InternetServers_f( void )
cls.internetservers_nat = cl_nat.value != 0.0f;
cls.internetservers_pending = true;
cls.internetservers_key = COM_RandomLong( 0, 0xFFFFFFFF );
Q_strncpy( cls.internetservers_customfilter, Cmd_Argv( 1 ), sizeof( cls.internetservers_customfilter ));
cls.internetservers_wait = NET_MasterQuery(
cls.internetservers_key,
cls.internetservers_nat,
cls.internetservers_customfilter
);
}
static void CL_QueryServer( netadr_t adr, connprotocol_t proto )
{
switch( proto )
{
case PROTO_GOLDSRC:
Netchan_OutOfBand( NS_CLIENT, adr, sizeof( A2S_GOLDSRC_INFO ), A2S_GOLDSRC_INFO ); // includes null terminator!
break;
case PROTO_CURRENT:
Netchan_OutOfBandPrint( NS_CLIENT, adr, A2A_INFO" %i", PROTOCOL_VERSION );
break;
}
// the key is dead extension, keep for compatibility until we use UDP based master server protocol
cls.internetservers_wait = NET_MasterQuery( 1, cls.internetservers_nat, cls.internetservers_customfilter );
}
static void CL_QueryServer_f( void )
@@ -1870,7 +1889,7 @@ static void CL_QueryServer_f( void )
if( !CL_StringToProtocol( Cmd_Argv( 2 ), &proto ))
return;
CL_QueryServer( adr, proto );
NET_QueryServerByAddress( adr, proto );
}
/*
@@ -2053,7 +2072,7 @@ static void CL_ParseStatusMessage( netadr_t from, sizebuf_t *msg )
UI_AddServerToList( from, infostring );
}
static void CL_ParseGoldSrcStatusMessage( netadr_t from, sizebuf_t *msg )
static void CL_ParseGoldSrcStatusMessage( netadr_t from, sizebuf_t *msg, qboolean legacy_format )
{
static char s[512+8];
int p, numcl, maxcl, password, remaining, bots;
@@ -2063,19 +2082,58 @@ static void CL_ParseGoldSrcStatusMessage( netadr_t from, sizebuf_t *msg )
// set to beginning but skip header
MSG_SeekToBit( msg, (sizeof( uint32_t ) + sizeof( uint8_t )) << 3, SEEK_SET );
p = MSG_ReadByte( msg );
Q_strncpy( host, MSG_ReadString( msg ), sizeof( host ));
Q_strncpy( map, MSG_ReadString( msg ), sizeof( map ));
Q_strncpy( gamedir, MSG_ReadString( msg ), sizeof( gamedir ));
MSG_ReadString( msg ); // game description
MSG_ReadShort( msg ); // app id
numcl = MSG_ReadByte( msg );
maxcl = MSG_ReadByte( msg );
bots = MSG_ReadByte( msg ); // bots count
MSG_ReadByte( msg ); // dedicated
MSG_ReadByte( msg ); // operating system
password = MSG_ReadByte( msg );
Q_strncpy( version, MSG_ReadString( msg ), sizeof( version ));
if( legacy_format )
{
string address;
int mod;
p = MSG_ReadByte( msg );
Q_strncpy( address, MSG_ReadString( msg ), sizeof( address ));
Q_strncpy( host, MSG_ReadString( msg ), sizeof( host ));
Q_strncpy( map, MSG_ReadString( msg ), sizeof( map ));
Q_strncpy( gamedir, MSG_ReadString( msg ), sizeof( gamedir ));
MSG_ReadString( msg ); // game description
numcl = MSG_ReadByte( msg );
maxcl = MSG_ReadByte( msg );
MSG_ReadByte( msg ); // protocol version
MSG_ReadByte( msg ); // server type
MSG_ReadByte( msg ); // operating system
password = MSG_ReadByte( msg );
mod = MSG_ReadByte( msg ); // mod flag
if( mod == 1 )
{
MSG_ReadString( msg ); // mod URL
MSG_ReadString( msg ); // mod download URL
MSG_ReadLong( msg ); // mod version
MSG_ReadLong( msg ); // mod size
MSG_ReadByte( msg ); // mod type (SP/MP)
MSG_ReadByte( msg ); // custom DLL flag
Q_strncpy( version, MSG_ReadString( msg ), sizeof( version ));
bots = MSG_ReadByte( msg ); // bots count
}
else
{
Q_strncpy( version, MSG_ReadString( msg ), sizeof( version ));
bots = MSG_ReadByte( msg ); // bots count
}
}
else
{
p = MSG_ReadByte( msg );
Q_strncpy( host, MSG_ReadString( msg ), sizeof( host ));
Q_strncpy( map, MSG_ReadString( msg ), sizeof( map ));
Q_strncpy( gamedir, MSG_ReadString( msg ), sizeof( gamedir ));
MSG_ReadString( msg ); // game description
MSG_ReadShort( msg ); // app id
numcl = MSG_ReadByte( msg );
maxcl = MSG_ReadByte( msg );
bots = MSG_ReadByte( msg ); // bots count
MSG_ReadByte( msg ); // server type
MSG_ReadByte( msg ); // operating system
password = MSG_ReadByte( msg );
Q_strncpy( version, MSG_ReadString( msg ), sizeof( version ));
}
// sanity check
if( maxcl > MAX_CLIENTS || numcl > MAX_CLIENTS || bots > MAX_CLIENTS || numcl > maxcl || bots > maxcl )
@@ -2284,7 +2342,7 @@ CL_IsFromConnectingServer
Used for connectionless packets, when netchan may not be ready.
=================
*/
static qboolean CL_IsFromConnectingServer( netadr_t from )
qboolean CL_IsFromConnectingServer( netadr_t from )
{
return NET_IsLocalAddress( from ) ||
NET_CompareAdr( cls.serveradr, from );
@@ -2324,7 +2382,7 @@ static void CL_HandleTestPacket( netadr_t from, sizebuf_t *msg )
}
// reading test buffer
MSG_ReadBytes( msg, recv_buf, realsize );
MSG_ReadBytes( msg, recv_buf, sizeof( recv_buf ), realsize );
// procssing the CRC
CRC32_ProcessBuffer( &crcValue2, recv_buf, realsize );
@@ -2385,6 +2443,39 @@ static void CL_ClientConnect( connprotocol_t proto, const char *c, netadr_t from
return;
}
if( cls.netchan_pending_cookie != 0 )
{
int server_extensions = Q_atoi( Info_ValueForKey( Cmd_Argv( 1 ), "ext" ));
if( FBitSet( server_extensions, NET_EXT_NETCHAN_COOKIE ))
{
const char *cookie_str = Info_ValueForKey( Cmd_Argv( 1 ), "cookie" );
if( Q_strlen( cookie_str ) != 16 )
{
Con_Reportf( S_WARN "%s: missing cookie echo from %s, ignoring (possible spoof)\n", __func__, NET_AdrToString( from ));
return;
}
byte buf[8];
COM_HexConvert( cookie_str, 16, buf );
uint64_t echoed = 0;
for( int i = 0; i < 8; i++ )
echoed = ( echoed << 8 ) | buf[i];
if( echoed != cls.netchan_pending_cookie )
{
Con_Reportf( S_WARN "%s: invalid cookie echo from %s, ignoring (possible spoof)\n", __func__, NET_AdrToString( from ));
return;
}
}
else
{
cls.netchan_pending_cookie = 0;
}
}
cls.build_num = 0; // not used in Xash3D protocols
cls.allow_cheats = Q_atoi( Info_ValueForKey( Cmd_Argv( 1 ), "cheats" ));
}
@@ -2503,6 +2594,21 @@ static void CL_Reject( const char *c, const char *args, netadr_t from )
CL_Disconnect_f();
}
/*
=================
CL_NotifyServerListResponse
=================
*/
void CL_NotifyServerListResponse( void )
{
if( !cls.internetservers_pending )
return;
UI_ResetPing();
cls.internetservers_pending = false;
}
static void CL_ServerList( netadr_t from, sizebuf_t *msg )
{
connprotocol_t proto;
@@ -2513,25 +2619,13 @@ static void CL_ServerList( netadr_t from, sizebuf_t *msg )
return;
}
// check the extra header
if( proto == PROTO_CURRENT )
{
// dead extension
if( MSG_ReadByte( msg ) == 0x7f )
{
uint32_t key = MSG_ReadDword( msg );
if( cls.internetservers_key != key )
{
Con_Printf( S_WARN "unexpected server list packet from %s (invalid key)\n", NET_AdrToString( from ));
return;
}
MSG_ReadByte( msg ); // reserved byte
}
else
{
Con_Printf( S_WARN "invalid server list packet from %s (missing extra header)\n", NET_AdrToString( from ));
return;
MSG_ReadDword( msg ); // was key
MSG_ReadByte( msg ); // was reserved
}
}
@@ -2543,30 +2637,27 @@ static void CL_ServerList( netadr_t from, sizebuf_t *msg )
if( NET_NetadrType( &from ) == NA_IP6 ) // IPv6 master server only sends IPv6 addresses
{
MSG_ReadBytes( msg, addr, sizeof( addr ));
MSG_ReadBytes( msg, addr, sizeof( addr ), sizeof( addr ));
NET_IP6BytesToNetadr( &servadr, addr );
NET_NetadrSetType( &servadr, NA_IP6 );
}
else
{
MSG_ReadBytes( msg, servadr.ip, sizeof( servadr.ip )); // 4 bytes for IP
MSG_ReadBytes( msg, servadr.ip, sizeof( servadr.ip ), sizeof( servadr.ip )); // 4 bytes for IP
NET_NetadrSetType( &servadr, NA_IP );
}
MSG_ReadBytes( msg, &servadr.port, sizeof( servadr.port )); // 2 bytes for Port, in network byte order
MSG_ReadBytes( msg, &servadr.port, sizeof( servadr.port ), sizeof( servadr.port )); // 2 bytes for Port, in network byte order
// list is ends here
if( !servadr.port )
break;
NET_Config( true, false ); // allow remote
CL_QueryServer( servadr, proto );
NET_QueryServerByAddress( servadr, proto );
}
if( cls.internetservers_pending )
{
UI_ResetPing();
cls.internetservers_pending = false;
}
CL_NotifyServerListResponse();
}
/*
@@ -2603,7 +2694,11 @@ static void CL_ConnectionlessPacket( netadr_t from, sizebuf_t *msg )
}
else if( c[0] == S2A_GOLDSRC_INFO )
{
CL_ParseGoldSrcStatusMessage( from, msg );
CL_ParseGoldSrcStatusMessage( from, msg, false );
}
else if( c[0] == S2A_GOLDSRC_LEGACY_INFO )
{
CL_ParseGoldSrcStatusMessage( from, msg, true );
}
else if( !Q_strcmp( c, A2A_NETINFO ))
{
@@ -3397,6 +3492,7 @@ static void CL_InitLocal( void )
Cvar_RegisterVariable( &cl_logofile );
Cvar_RegisterVariable( &cl_logocolor );
Cvar_RegisterVariable( &cl_logoext );
Cvar_RegisterVariable( &cl_logoupdate );
Cvar_RegisterVariable( &cl_logomaxdim );
Cvar_RegisterVariable( &cl_test_bandwidth );

View File

@@ -45,7 +45,7 @@ GNU General Public License for more details.
#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 CVAR_DEFINE_AUTO( cl_steam_broker_addr, "127.0.0.1:27420", FCVAR_PRIVILEGED|FCVAR_ARCHIVE, "address of steam broker instance" );
typedef enum
{
@@ -220,7 +220,7 @@ static qboolean SteamBroker_ProcessFrame( void )
// verify frame header
char header[SBRK_FRAME_HEADER_SIZE];
if( !MSG_ReadBytes( &sb, header, SBRK_FRAME_HEADER_SIZE ))
if( !MSG_ReadBytes( &sb, header, sizeof( header ), SBRK_FRAME_HEADER_SIZE ))
return false;
if( memcmp( header, SBRK_FRAME_HEADER, SBRK_FRAME_HEADER_SIZE ) != 0 )
@@ -237,7 +237,7 @@ static qboolean SteamBroker_ProcessFrame( void )
return false; // need more data
char response_header[SBRK_RESPONSE_HEADER_SIZE];
if( MSG_ReadBytes( &sb, response_header, SBRK_RESPONSE_HEADER_SIZE ))
if( MSG_ReadBytes( &sb, response_header, sizeof( response_header ), SBRK_RESPONSE_HEADER_SIZE ))
{
if( memcmp( response_header, SBRK_RESPONSE_HEADER, SBRK_RESPONSE_HEADER_SIZE ) == 0 )
{
@@ -249,7 +249,7 @@ static qboolean SteamBroker_ProcessFrame( void )
else
{
uint64_t steam_id;
MSG_ReadBytes( &sb, &steam_id, sizeof( steam_id ));
MSG_ReadBytes( &sb, &steam_id, sizeof( steam_id ), sizeof( steam_id ));
uint32_t ticket_size = MSG_ReadDword( &sb );
uint8_t ticket_data[SBRK_TICKET_SIZE_MAX];
@@ -257,7 +257,7 @@ static qboolean SteamBroker_ProcessFrame( void )
{
Con_Printf( S_ERROR "%s: ticket size exceeds limit (%u)\n", __func__, ticket_size );
}
else if( MSG_ReadBytes( &sb, ticket_data, ticket_size ))
else if( MSG_ReadBytes( &sb, ticket_data, sizeof( 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] );

View File

@@ -1865,7 +1865,7 @@ void CL_ParseTempEntity( sizebuf_t *msg, connprotocol_t proto )
}
// parse user message into buffer
MSG_ReadBytes( msg, msg_data, iSize );
MSG_ReadBytes( msg, msg_data, sizeof( msg_data ), iSize );
// init a safe tempbuffer
MSG_Init( &buf, "TempEntity", msg_data, iSize );

View File

@@ -554,6 +554,7 @@ void V_PostRender( void )
SCR_DrawNetGraph();
SCR_DrawUserCmd();
Joy_DrawDebug();
IN_GyroDrawDebug();
SV_DrawOrthoTriangles();
CL_DrawDemoRecording();
CL_DrawHUD( CL_CHANGELEVEL );

View File

@@ -579,6 +579,7 @@ typedef struct
sizebuf_t datagram; // unreliable stuff. gets sent in CL_Move about cl_cmdrate times per second.
byte datagram_buf[MAX_DATAGRAM];
uint64_t netchan_pending_cookie; // random NET_EXT_NETCHAN_COOKIE
netchan_t netchan;
float packet_loss;
@@ -644,7 +645,6 @@ typedef struct
qboolean internetservers_pending; // if true, clean master server pings
qboolean internetservers_nat;
string internetservers_customfilter;
uint32_t internetservers_key; // compare key to validate master server reply
// multiprotocol support
connprotocol_t legacymode;
@@ -792,6 +792,7 @@ void CL_SignonReply( connprotocol_t proto );
void CL_ClearState( void );
void CL_SetCheatState( qboolean multiplayer, qboolean allow_cheats );
void CL_SendGoldSrcConnectPacket( netadr_t adr, int challenge, const void *ticket, size_t ticketlen );
void CL_NotifyServerListResponse( void );
//
// cl_demo.c
@@ -1154,7 +1155,7 @@ void S_StopStreaming( void );
void S_BeginRegistration( void );
sound_t S_RegisterSound( const char *sample );
void S_EndRegistration( void );
void S_RestoreSound( const vec3_t pos, int ent, int chan, sound_t handle, float fvol, float attn, int pitch, int flags, double sample, double end, int wordIndex );
void S_RestoreSound( const vec3_t pos, int ent, int chan, sound_t handle, float fvol, float attn, int pitch, int flags, double sample, double end, uint wordIndex );
void S_StartSound( const vec3_t pos, int ent, int chan, sound_t sfx, float vol, float attn, int pitch, int flags );
void S_AmbientSound( const vec3_t pos, int ent, sound_t handle, float fvol, float attn, int pitch, int flags );
void S_SoundFade( int fade_percent, int hold_time, int fade_out_seconds, int fade_in_seconds );

View File

@@ -3568,6 +3568,16 @@ static void GAME_EXPORT VGui_ViewportPaintBackground( int extents[4] )
// stub
}
static cvar_t* GAME_EXPORT CL_CvarGetPointer( const char *szVarName )
{
cvar_t *result = (cvar_t *)Cvar_FindVar( szVarName );
if( !result )
Con_DPrintf( S_WARN "%s: client tried to get non-existent cvar \"%s\"\n", __func__, szVarName );
return result;
}
// shared between client and server
triangleapi_t gTriApi;
@@ -3790,7 +3800,7 @@ static cl_enginefunc_t gEngfuncs =
pfnHookEvent,
Con_Visible,
pfnGetGameDirectory,
pfnCVarGetPointer,
CL_CvarGetPointer,
Key_LookupBinding,
pfnGetLevelName,
pfnGetScreenFade,
@@ -3924,7 +3934,7 @@ static engine_studio_api_t gStudioAPI =
.Mod_ForName = pfnStudio_Mod_ForName,
.Mod_Extradata = pfnStudio_Mod_Extradata,
.GetModelByIndex = CL_ModelHandle,
.GetCvar = pfnCVarGetPointer,
.GetCvar = CL_CvarGetPointer,
.GetChromeSprite = pfnStudio_GetChromeSprite,
.GetAliasScale = pfnStudio_GetAliasScale,
.StudioGetAliasTransform = pfnStudio_GetAliasTransform,

View File

@@ -43,6 +43,7 @@ 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 );
void IN_GyroDrawDebug( void );
uint IN_CollectInputDevices( void );
void IN_LockInputDevices( qboolean lock );

View File

@@ -25,9 +25,11 @@ static CVAR_DEFINE_AUTO( gyro_roll, "0.0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "bu
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)" );
static CVAR_DEFINE_AUTO( gyro_debug, "0", 0, "visualize built-in device gyroscope" );
// stores the latest instantaneous rotation rates from built-in gyroscope
static vec3_t gyro_speed;
static vec3_t gyro_speed_display;
/*
==============
@@ -45,6 +47,7 @@ void IN_GyroInit( void )
Cvar_RegisterVariable( &gyro_pitch_deadzone );
Cvar_RegisterVariable( &gyro_yaw_deadzone );
Cvar_RegisterVariable( &gyro_roll_deadzone );
Cvar_RegisterVariable( &gyro_debug );
}
/*
@@ -79,6 +82,28 @@ void IN_GyroEvent( vec3_t data )
VectorCopy( data, gyro_speed );
}
static void IN_GyroMap( const vec3_t src, vec3_t dst )
{
float orient_scale = 1.0f;
platform_orientation_t orient = Platform_GetDisplayOrientation();
if( orient == ORIENTATION_LANDSCAPE_FLIPPED )
orient_scale = -1.0f;
#if XASH_ANDROID || XASH_IOS
// 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)
dst[0] = -orient_scale * src[1] * ( 180.0f / M_PI );
dst[1] = orient_scale * src[0] * ( 180.0f / M_PI );
dst[2] = orient_scale * src[2] * ( 180.0f / M_PI );
#else
dst[0] = orient_scale * src[0] * ( 180.0f / M_PI );
dst[1] = orient_scale * src[1] * ( 180.0f / M_PI );
dst[2] = orient_scale * src[2] * ( 180.0f / M_PI );
#endif
}
/*
=============
IN_GyroFinalizeMove
@@ -88,32 +113,102 @@ Apply gyro movement to view angles
*/
void IN_GyroFinalizeMove( float *fw, float *side, float *dpitch, float *dyaw )
{
float orient_scale = 1.0f;
vec3_t mapped_speed;
if( !gyro_enable.value || !gyro_available.value )
return;
platform_orientation_t orient = Platform_GetDisplayOrientation();
if( orient == ORIENTATION_LANDSCAPE_FLIPPED )
orient_scale = -1.0f;
IN_GyroMap( gyro_speed, mapped_speed );
VectorCopy( mapped_speed, gyro_speed_display );
// 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( mapped_speed[0] ) < gyro_pitch_deadzone.value )
mapped_speed[0] = 0.0f;
if( fabs( mapped_speed[1] ) < gyro_yaw_deadzone.value )
mapped_speed[1] = 0.0f;
if( fabs( mapped_speed[2] ) < gyro_roll_deadzone.value )
mapped_speed[2] = 0.0f;
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;
*dpitch -= gyro_pitch.value * mapped_speed[0] * host.realframetime;
*dyaw += gyro_yaw.value * mapped_speed[1] * host.realframetime;
*dyaw += gyro_roll.value * mapped_speed[2] * host.realframetime;
VectorClear( gyro_speed );
}
void IN_GyroDrawDebug( void )
{
cl_font_t *font = Con_GetCurFont();
if( !gyro_debug.value || !gyro_available.value )
return;
float x = 8;
float y = 100;
const float bar_w = 100;
const float halfbar_w = bar_w * 0.5f;
const float bar_h = font->charHeight - 2;
const float center = x + halfbar_w;
platform_orientation_t orient = Platform_GetDisplayOrientation();
const char *orient_name = "UNKNOWN";
switch( orient )
{
case ORIENTATION_LANDSCAPE:
orient_name = "LANDSCAPE";
break;
case ORIENTATION_LANDSCAPE_FLIPPED:
orient_name = "LANDSCAPE FLIPPED";
break;
case ORIENTATION_PORTRAIT:
orient_name = "PORTRAIT";
break;
case ORIENTATION_PORTRAIT_FLIPPED:
orient_name = "PORTRAIT FLIPPED";
break;
default:
orient_name = "UNKNOWN";
break;
}
char orient_text[32];
Q_snprintf( orient_text, sizeof( orient_text ), "ORIENT: %s", orient_name );
const rgba_t bar_backcolor = { 40, 40, 40, 180 };
const rgba_t bar_fillcolor = { 100, 200, 100, 200 };
static const char *gyronames[3] = { "GYRO P", "GYRO Y", "GYRO R" };
static const convar_t *const gyro_deadzone[3] =
{
&gyro_pitch_deadzone,
&gyro_yaw_deadzone,
&gyro_roll_deadzone,
};
for( int i = 0; i < 3; i++ )
{
float fval = gyro_speed_display[i] / 180.0f;
fval = bound( -1.0f, fval, 1.0f );
CL_DrawString( x, y, gyronames[i], g_color_table[7], font, 0 );
y += font->charHeight;
ref.dllFuncs.FillRGBA( kRenderTransTexture, x, y, bar_w, bar_h, bar_backcolor[0], bar_backcolor[1], bar_backcolor[2], bar_backcolor[3] );
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] );
float fthreshold = gyro_deadzone[i]->value / 180.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 );
}
ref.dllFuncs.FillRGBA( kRenderTransTexture, center, y, 1, bar_h, 180, 180, 180, 220 );
y += bar_h + 2;
}
CL_DrawString( x, y, orient_text, g_color_table[1], font, 0 );
}

View File

@@ -60,10 +60,6 @@ CL_ParseSoundPacket
*/
static void CL_ParseSoundPacket( sizebuf_t *msg, qboolean restore )
{
int wordIndex = 0;
sound_t handle = 0;
double samplePos = 0, forcedEnd = 0;
int flags = MSG_ReadUBitLong( msg, MAX_SND_FLAGS_BITS );
int sound = MSG_ReadUBitLong( msg, MAX_SOUND_BITS );
int chan = MSG_ReadUBitLong( msg, MAX_SND_CHAN_BITS );
@@ -90,6 +86,7 @@ static void CL_ParseSoundPacket( sizebuf_t *msg, qboolean restore )
vec3_t pos;
MSG_ReadVec3Coord( msg, pos );
sound_t handle = 0;
if( FBitSet( flags, SND_SENTENCE ))
{
char sentenceName[32];
@@ -102,13 +99,15 @@ static void CL_ParseSoundPacket( sizebuf_t *msg, qboolean restore )
}
else handle = cl.sound_index[sound]; // see precached sound
uint wordIndex = 0;
double samplePos = 0, forcedEnd = 0;
if( restore )
{
wordIndex = MSG_ReadByte( msg );
// 16 bytes here
MSG_ReadBytes( msg, &samplePos, sizeof( samplePos ));
MSG_ReadBytes( msg, &forcedEnd, sizeof( forcedEnd ));
MSG_ReadBytes( msg, &samplePos, sizeof( samplePos ), sizeof( samplePos ));
MSG_ReadBytes( msg, &forcedEnd, sizeof( forcedEnd ), sizeof( forcedEnd ));
}
if( !cl.audio_prepped )
@@ -617,7 +616,7 @@ static void CL_ParseCustomization( sizebuf_t *msg )
pRes->pNext = pRes->pPrev = NULL;
if( FBitSet( pRes->ucFlags, RES_CUSTOM ))
MSG_ReadBytes( msg, pRes->rgucMD5_hash, 16 );
MSG_ReadBytes( msg, pRes->rgucMD5_hash, sizeof( pRes->rgucMD5_hash ), 16 );
pRes->playernum = i;
if( !cl_allow_download.value )
@@ -814,7 +813,7 @@ static void CL_ParseServerData( sizebuf_t *msg, connprotocol_t proto )
byte clientdllmd5[16];
const char *s;
MSG_ReadBytes( msg, clientdllmd5, sizeof( clientdllmd5 ));
MSG_ReadBytes( msg, clientdllmd5, sizeof( clientdllmd5 ), sizeof( clientdllmd5 ));
cl.maxclients = MSG_ReadByte( msg );
cl.playernum = MSG_ReadByte( msg );
COM_UnMunge3((byte *)&cl.checksum, sizeof( cl.checksum ), ( 0xff - cl.playernum ) & 0xff );
@@ -1334,7 +1333,7 @@ static void CL_RegisterUserMessage( sizebuf_t *msg, connprotocol_t proto )
char *pszName;
if( proto == PROTO_GOLDSRC )
{
MSG_ReadBytes( msg, szName, sizeof( szName ) - 1 );
MSG_ReadBytes( msg, szName, sizeof( szName ), sizeof( szName ) - 1 );
szName[16] = 0;
pszName = szName;
}
@@ -1381,7 +1380,7 @@ static void CL_UpdateUserinfo( sizebuf_t *msg, connprotocol_t proto )
player->topcolor = Q_atoi( Info_ValueForKey( player->userinfo, "topcolor" ));
player->bottomcolor = Q_atoi( Info_ValueForKey( player->userinfo, "bottomcolor" ));
player->spectator = Q_atoi( Info_ValueForKey( player->userinfo, "*hltv" ));
MSG_ReadBytes( msg, player->hashedcdkey, sizeof( player->hashedcdkey ));
MSG_ReadBytes( msg, player->hashedcdkey, sizeof( player->hashedcdkey ), sizeof( player->hashedcdkey ));
if( proto == PROTO_GOLDSRC && ( COM_StringEmpty( player->userinfo ) || COM_StringEmpty( player->name )))
active = false;
@@ -1421,10 +1420,10 @@ void CL_ParseResource( sizebuf_t *msg )
pResource->ucFlags = MSG_ReadUBitLong( msg, 3 ) & ~RES_WASMISSING;
if( FBitSet( pResource->ucFlags, RES_CUSTOM ))
MSG_ReadBytes( msg, pResource->rgucMD5_hash, sizeof( pResource->rgucMD5_hash ));
MSG_ReadBytes( msg, pResource->rgucMD5_hash, sizeof( pResource->rgucMD5_hash ), sizeof( pResource->rgucMD5_hash ));
if( MSG_ReadOneBit( msg ))
MSG_ReadBytes( msg, pResource->rguc_reserved, sizeof( pResource->rguc_reserved ));
MSG_ReadBytes( msg, pResource->rguc_reserved, sizeof( pResource->rguc_reserved ), sizeof( pResource->rguc_reserved ));
if( pResource->type == t_sound && pResource->nIndex >= MAX_SOUNDS )
{
@@ -1836,10 +1835,10 @@ void CL_ParseResourceList( sizebuf_t *msg, connprotocol_t proto )
pResource->ucFlags = MSG_ReadUBitLong( msg, 3 ) & ~RES_WASMISSING;
if( FBitSet( pResource->ucFlags, RES_CUSTOM ))
MSG_ReadBytes( msg, pResource->rgucMD5_hash, sizeof( pResource->rgucMD5_hash ));
MSG_ReadBytes( msg, pResource->rgucMD5_hash, sizeof( pResource->rgucMD5_hash ), sizeof( pResource->rgucMD5_hash ));
if( MSG_ReadOneBit( msg ))
MSG_ReadBytes( msg, pResource->rguc_reserved, sizeof( pResource->rguc_reserved ));
MSG_ReadBytes( msg, pResource->rguc_reserved, sizeof( pResource->rguc_reserved ), sizeof( pResource->rguc_reserved ));
CL_AddToResourceList( pResource, &cl.resourcesneeded );
}
@@ -1908,7 +1907,7 @@ static void CL_ParseVoiceData( sizebuf_t *msg, connprotocol_t proto )
if ( !size )
return;
MSG_ReadBytes( msg, received, size );
MSG_ReadBytes( msg, received, sizeof( received ), size );
Voice_AddIncomingData( idx, received, size, frames );
}
@@ -1988,7 +1987,7 @@ static void CL_ParseDirector( sizebuf_t *msg )
byte pbuf[256];
// parse user message into buffer
MSG_ReadBytes( msg, pbuf, iSize );
MSG_ReadBytes( msg, pbuf, sizeof( pbuf ), iSize );
clgame.dllFuncs.pfnDirectorMessage( iSize, pbuf );
}
@@ -2272,7 +2271,7 @@ void CL_ParseUserMessage( sizebuf_t *msg, int svc_num, connprotocol_t proto )
}
// parse user message into buffer
MSG_ReadBytes( msg, pbuf, iSize );
MSG_ReadBytes( msg, pbuf, sizeof( pbuf ), iSize );
if( cl_trace_messages.value )
{

View File

@@ -740,7 +740,7 @@ S_RestoreSound
Restore a sound effect for the given entity on the given channel
====================
*/
void S_RestoreSound( const vec3_t pos, int ent, int chan, sound_t handle, float fvol, float attn, int pitch, int flags, double sample, double end, int wordIndex )
void S_RestoreSound( const vec3_t pos, int ent, int chan, sound_t handle, float fvol, float attn, int pitch, int flags, double sample, double end, uint wordIndex )
{
wavdata_t *pSource;
sfx_t *sfx = NULL;
@@ -804,15 +804,30 @@ void S_RestoreSound( const vec3_t pos, int ent, int chan, sound_t handle, float
// not a first word in sentence!
if( wordIndex != 0 )
{
VOX_FreeWord( target_chan ); // release first loaded word
target_chan->word_index = wordIndex; // restore current word
VOX_LoadWord( target_chan );
uint word_count = 0;
if( !FBitSet( target_chan->flags, FL_CHAN_SENTENCE_FINISHED ))
if( target_chan->words )
{
target_chan->sfx = target_chan->words[target_chan->word_index].sfx;
sfx = target_chan->sfx;
pSource = sfx->cache;
while( word_count < CVOXWORDMAX && target_chan->words[word_count].sfx )
word_count++;
}
if( wordIndex >= word_count )
{
SetBits( target_chan->flags, FL_CHAN_SENTENCE_FINISHED );
}
else
{
VOX_FreeWord( target_chan ); // release first loaded word
target_chan->word_index = wordIndex; // restore current word
VOX_LoadWord( target_chan );
if( !FBitSet( target_chan->flags, FL_CHAN_SENTENCE_FINISHED ))
{
target_chan->sfx = target_chan->words[target_chan->word_index].sfx;
sfx = target_chan->sfx;
pSource = sfx->cache;
}
}
}
else

View File

@@ -834,7 +834,7 @@ static qboolean Cmd_ShouldAllowCommand( cmd_t *cmd, qboolean isPrivileged )
return true;
// never allow local only commands from remote
if( FBitSet( cmd->flags, CMD_PRIVILEGED ))
if( FBitSet( cmd->flags, CMD_PRIVILEGED ) || cmd->name[0] == '@' )
return false;
// allow engine commands if user don't mind

View File

@@ -685,18 +685,6 @@ void GAME_EXPORT pfnGetModelBounds( model_t *mod, float *mins, float *maxs )
}
}
/*
=============
pfnCVarGetPointer
can return NULL
=============
*/
cvar_t *GAME_EXPORT pfnCVarGetPointer( const char *szVarName )
{
return (cvar_t *)Cvar_FindVar( szVarName );
}
/*
=============
pfnCompareFileTime

View File

@@ -634,7 +634,6 @@ byte COM_Nibble( char c );
int COM_SaveFile( const char *filename, const void *data, int len );
byte *COM_LoadFileForMe( const char *filename, int *pLength ) MALLOC_LIKE( free, 1 );
qboolean COM_IsSafeFileToDownload( const char *filename );
cvar_t *pfnCVarGetPointer( const char *szVarName );
int pfnDrawConsoleString( int x, int y, char *string );
void pfnDrawSetTextColor( float r, float g, float b );
void pfnDrawConsoleStringLen( const char *pText, int *length, int *height );
@@ -902,6 +901,7 @@ void NET_MasterClear( void );
void NET_MasterShutdown( void );
qboolean NET_GetMaster( netadr_t from, uint *challenge, double *last_heartbeat );
qboolean NET_MasterQuery( uint32_t key, qboolean net, const char *filter );
void NET_QueryServerByAddress( netadr_t adr, connprotocol_t proto );
//
// munge.c
@@ -940,6 +940,15 @@ const char *SoundList_Get( soundlst_group_t group, int idx );
void SoundList_Init( void );
void SoundList_Shutdown( void );
//
// xrcon.c
//
void XRcon_Init( void );
void XRcon_Shutdown( void );
void XRcon_Frame( void );
void XRcon_Print( const char *msg );
qboolean XRcon_IsActive( void );
#ifdef REF_DLL
#error "common.h in ref_dll"
#endif

View File

@@ -943,7 +943,7 @@ static qboolean Cvar_ShouldSetCvar( convar_t *v, qboolean isPrivileged )
if( isPrivileged )
return true;
if( FBitSet( v->flags, FCVAR_PRIVILEGED ))
if( FBitSet( v->flags, FCVAR_PRIVILEGED ) || v->name[0] == '@' )
return false;
if( cl_filterstuffcmd.value <= 0.0f )

View File

@@ -175,6 +175,16 @@ static void FS_Path_f_( void )
FS_Path_f();
}
static void FS_FindFile_f_( void )
{
if( Cmd_Argc() < 2 )
{
Con_Printf( S_USAGE "fs_find <filepath>\n" );
return;
}
g_fsapi.FindFile_f( Cmd_Argv( 1 ));
}
static void FS_MakeGameInfo_f( void )
{
g_fsapi.MakeGameInfo();
@@ -374,6 +384,7 @@ void FS_Init( void )
Cmd_AddRestrictedCommand( "fs_rescan", FS_Rescan_f, "rescan filesystem search pathes" );
Cmd_AddRestrictedCommand( "fs_path", FS_Path_f_, "show filesystem search pathes" );
Cmd_AddRestrictedCommand( "fs_find", FS_FindFile_f_, "find file across search pathes and show all occurences" );
Cmd_AddRestrictedCommand( "fs_clearpaths", FS_ClearPaths_f, "clear filesystem search pathes" );
Cmd_AddRestrictedCommand( "fs_make_gameinfo", FS_MakeGameInfo_f, "create gameinfo.txt for current running game" );

View File

@@ -662,6 +662,7 @@ void Host_Frame( double time )
Host_ServerFrame (); // server frame
Host_ClientFrame (); // client frame
HTTP_Run(); // both server and client
XRcon_Frame();
host.framecount++;
host.pureframetime = Platform_DoubleTime() - t1;
@@ -871,7 +872,7 @@ static qboolean Host_CollectX86Libraries( ECommonLibraryType lib_type,
#if !( XASH_WIN32 && XASH_X86 )
if( !COM_StringEmpty( win_path ) && FS_FileExists( win_path, true ))
{
Q_strncat( found, "Windows (x86)", found_size );
Q_strncat( found, "Windows", found_size );
has_any = true;
}
#endif
@@ -881,7 +882,7 @@ static qboolean Host_CollectX86Libraries( ECommonLibraryType lib_type,
{
if( has_any )
Q_strncat( found, ", ", found_size );
Q_strncat( found, "GNU/Linux (x86)", found_size );
Q_strncat( found, "GNU/Linux", found_size );
has_any = true;
}
#endif
@@ -891,7 +892,7 @@ static qboolean Host_CollectX86Libraries( ECommonLibraryType lib_type,
{
if( has_any )
Q_strncat( found, ", ", found_size );
Q_strncat( found, "macOS (x86)", found_size );
Q_strncat( found, "macOS", found_size );
has_any = true;
}
#endif
@@ -914,7 +915,9 @@ static void Host_CheckGameLibraries( void )
};
char details[MAX_VA_STRING];
char missing[MAX_VA_STRING];
details[0] = 0;
missing[0] = 0;
for( int i = 0; i < ARRAYSIZE( libs ); i++ )
{
@@ -953,20 +956,36 @@ static void Host_CheckGameLibraries( void )
if( ret )
{
size_t len = Q_strlen( details );
Q_snprintf( details + len, sizeof( details ) - len, "- %s: %s\n", libs[i].name, found );
size_t dlen = Q_strlen( details );
Q_snprintf( details + dlen, sizeof( details ) - dlen, " %-6s : %s\n", libs[i].name, found );
if( !COM_StringEmpty( missing ))
Q_strncat( missing, ", ", sizeof( missing ));
Q_strncat( missing, libs[i].name, sizeof( missing ));
}
}
if( COM_StringEmpty( details ))
return;
Sys_Warn( "No native game libraries found for current platform (%s-%s),\n"
"but found libraries for other platforms:\n"
Sys_Warn( "Xash3D: missing game library\n"
"\n"
"Required : %s-%s\n"
"Missing : %s\n"
"\n"
"Found %s libraries for these operating systems:\n"
"%s"
"The game may fail to load or work incorrectly.\n"
"Consider using a mod version built for this platform.",
Q_buildos(), Q_buildarch(), details );
"\n"
"Install \"%s\" game build for %s-%s.",
Q_buildos(), Q_buildarch(),
missing,
#if XASH_AMD64
"32-bit",
#else
"32-bit x86",
#endif
details,
GI->gamefolder, Q_buildos(), Q_buildarch() );
#endif // XASH_INTERNAL_GAMELIBS
}
@@ -1064,6 +1083,8 @@ static void Host_InitCommon( int argc, char **argv, const char *progname, qboole
Sys_InitLog();
Con_Init(); // early console running to catch all the messages
XRcon_Init();
if( !Sys_CheckParm( "-noch" ))
Sys_SetupCrashHandler( argv[0] );
@@ -1351,6 +1372,7 @@ void Host_ShutdownWithReason( const char *reason )
SoundList_Shutdown();
Mod_Shutdown();
XRcon_Shutdown();
NET_Shutdown();
HTTP_Shutdown();
Host_FreeCommon();

View File

@@ -0,0 +1,274 @@
/*
net_http_tls.c - TLS backend for the built-in HTTP client (mbedTLS)
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 "net_ws_private.h"
#include "net_http_tls.h"
#if XASH_MBEDTLS
#include <psa/crypto.h>
#include <mbedtls/ssl.h>
#include <mbedtls/x509_crt.h>
#include <mbedtls/debug.h>
#include <mbedtls/error.h>
extern poolhandle_t http_mempool;
static CVAR_DEFINE_AUTO( http_tls_cafile, "cacert.pem", FCVAR_PRIVILEGED, "path to CA bundle (PEM) used to verify HTTPS servers" );
static CVAR_DEFINE_AUTO( http_tls_insecure, "0", FCVAR_PRIVILEGED, "skip HTTPS certificate verification (debug only)" );
static CVAR_DEFINE_AUTO( http_tls_verbose, "0", FCVAR_PRIVILEGED, "mbedTLS debug verbosity (0=off, 1=error, 2=state, 3=info, 4=trace)" );
// FIXME: implement certificate pinning
typedef int (*pin_verify_fn_t)( void *cert, int depth, uint32_t *flags );
static struct
{
qboolean inited;
qboolean has_ca;
mbedtls_x509_crt cacert;
pin_verify_fn_t pin_verify; // reserved
} g_tls;
struct tlsctx_s
{
mbedtls_ssl_context ssl;
mbedtls_ssl_config conf;
int socket; // not owned; caller closes
};
static void HTTP_TlsLogDebug( void *ctx, int level, const char *file, int line, const char *str )
{
Con_Printf( "TLS[%d] %s:%d %s", level, COM_FileWithoutPath( file ), line, str );
}
static void HTTP_TlsLogErr( const char *what, int ret )
{
char msg[128];
mbedtls_strerror( ret, msg, sizeof( msg ));
Con_Printf( S_ERROR "TLS %s failed: %s (-0x%04x)\n", what, msg, (unsigned int)-ret );
}
static int HTTP_TlsBioSend( void *ctx, const unsigned char *buf, size_t len )
{
int fd = *(int *)ctx;
int n = send( fd, buf, len, 0 );
if( n >= 0 )
return n;
int err = WSAGetLastError();
if( err == WSAEWOULDBLOCK || err == WSAEINPROGRESS )
return MBEDTLS_ERR_SSL_WANT_WRITE;
return MBEDTLS_ERR_SSL_INTERNAL_ERROR;
}
static int HTTP_TlsBioRecv( void *ctx, unsigned char *buf, size_t len )
{
int fd = *(int *)ctx;
int n = recv( fd, buf, len, 0 );
if( n > 0 )
return n;
if( n == 0 )
return MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY;
int err = WSAGetLastError();
if( err == WSAEWOULDBLOCK || err == WSAEINPROGRESS )
return MBEDTLS_ERR_SSL_WANT_READ;
return MBEDTLS_ERR_SSL_INTERNAL_ERROR;
}
static qboolean HTTP_TlsLoadCA( void )
{
const char *path = http_tls_cafile.string;
fs_offset_t len = 0;
byte *data = FS_LoadFile( path, &len, false );
if( !data || len <= 0 )
{
Con_Printf( S_WARN "TLS: CA bundle '%s' not found; HTTPS verification will fail\n", path );
Mem_Free( data );
return false;
}
int ret = mbedtls_x509_crt_parse( &g_tls.cacert, data, len + 1 );
Mem_Free( data );
if( ret < 0 )
{
HTTP_TlsLogErr( "x509_crt_parse", ret );
return false;
}
if( ret > 0 )
Con_Reportf( S_WARN "TLS: %d certificate(s) in '%s' failed to parse\n", ret, path );
return true;
}
void HTTP_TlsInit( void )
{
if( g_tls.inited )
return;
Cvar_RegisterVariable( &http_tls_cafile );
Cvar_RegisterVariable( &http_tls_insecure );
Cvar_RegisterVariable( &http_tls_verbose );
psa_status_t pstatus = psa_crypto_init();
if( pstatus != PSA_SUCCESS )
{
Con_Printf( S_ERROR "TLS psa_crypto_init failed (status %d)\n", (int)pstatus );
return;
}
mbedtls_x509_crt_init( &g_tls.cacert );
g_tls.has_ca = HTTP_TlsLoadCA();
g_tls.pin_verify = NULL;
g_tls.inited = true;
}
void HTTP_TlsShutdown( void )
{
if( !g_tls.inited )
return;
mbedtls_x509_crt_free( &g_tls.cacert );
mbedtls_psa_crypto_free();
g_tls.inited = false;
g_tls.has_ca = false;
}
qboolean HTTP_TlsAvailable( void )
{
return g_tls.inited;
}
tlsctx_t *HTTP_TlsNew( int socket, const char *hostname )
{
if( !g_tls.inited )
return NULL;
tlsctx_t *ctx = Mem_Calloc( http_mempool, sizeof( *ctx ));
ctx->socket = socket;
mbedtls_ssl_init( &ctx->ssl );
mbedtls_ssl_config_init( &ctx->conf );
int ret = mbedtls_ssl_config_defaults( &ctx->conf, MBEDTLS_SSL_IS_CLIENT, MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT );
if( ret != 0 )
{
HTTP_TlsLogErr( "ssl_config_defaults", ret );
HTTP_TlsFree( ctx );
return NULL;
}
int authmode = MBEDTLS_SSL_VERIFY_REQUIRED;
if( http_tls_insecure.value != 0 || !g_tls.has_ca )
{
if( !g_tls.has_ca )
Con_Printf( S_WARN "TLS: no CA bundle loaded, peer cert will not be verified\n" );
else
Con_Printf( S_WARN "TLS: http_tls_insecure is set, peer cert will not be verified\n" );
authmode = MBEDTLS_SSL_VERIFY_NONE;
}
mbedtls_ssl_conf_authmode( &ctx->conf, authmode );
mbedtls_ssl_conf_ca_chain( &ctx->conf, &g_tls.cacert, NULL );
mbedtls_ssl_conf_dbg( &ctx->conf, HTTP_TlsLogDebug, NULL );
mbedtls_debug_set_threshold( http_tls_verbose.value );
ret = mbedtls_ssl_setup( &ctx->ssl, &ctx->conf );
if( ret != 0 )
{
HTTP_TlsLogErr( "ssl_setup", ret );
HTTP_TlsFree( ctx );
return NULL;
}
ret = mbedtls_ssl_set_hostname( &ctx->ssl, ( hostname && *hostname ) ? hostname : NULL );
if( ret != 0 )
{
HTTP_TlsLogErr( "ssl_set_hostname", ret );
HTTP_TlsFree( ctx );
return NULL;
}
mbedtls_ssl_set_bio( &ctx->ssl, &ctx->socket, HTTP_TlsBioSend, HTTP_TlsBioRecv, NULL );
return ctx;
}
void HTTP_TlsFree( tlsctx_t *ctx )
{
if( !ctx )
return;
mbedtls_ssl_free( &ctx->ssl );
mbedtls_ssl_config_free( &ctx->conf );
Mem_Free( ctx );
}
int HTTP_TlsHandshake( tlsctx_t *ctx )
{
int ret = mbedtls_ssl_handshake( &ctx->ssl );
if( ret == 0 )
return HTTP_TLS_OK;
if( ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE )
return HTTP_TLS_WANT;
HTTP_TlsLogErr( "handshake", ret );
return HTTP_TLS_ERROR;
}
int HTTP_TlsSend( tlsctx_t *ctx, const void *buf, int len )
{
int ret = mbedtls_ssl_write( &ctx->ssl, (const unsigned char *)buf, (size_t)len );
if( ret >= 0 )
return ret;
if( ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE )
return HTTP_TLS_WANT;
HTTP_TlsLogErr( "send", ret );
return HTTP_TLS_ERROR;
}
int HTTP_TlsRecv( tlsctx_t *ctx, void *buf, int len )
{
int ret = mbedtls_ssl_read( &ctx->ssl, (unsigned char *)buf, (size_t)len );
if( ret > 0 )
return ret;
if( ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY )
return 0;
if( ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE )
return HTTP_TLS_WANT;
HTTP_TlsLogErr( "recv", ret );
return HTTP_TLS_ERROR;
}
#endif // XASH_MBEDTLS

View File

@@ -0,0 +1,53 @@
/*
net_http_tls.h - TLS plumbing for HTTPS in the built-in HTTP client
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.
*/
#ifndef NET_HTTP_TLS_H
#define NET_HTTP_TLS_H
#include "xash3d_types.h"
typedef struct tlsctx_s tlsctx_t;
enum
{
HTTP_TLS_OK = 0, // handshake/op completed
HTTP_TLS_WANT = -1, // would block, retry next frame
HTTP_TLS_ERROR = -2 // permanent failure, tear the connection down
};
#if XASH_MBEDTLS
void HTTP_TlsInit( void );
void HTTP_TlsShutdown( void );
qboolean HTTP_TlsAvailable( void );
tlsctx_t *HTTP_TlsNew( int socket, const char *hostname );
void HTTP_TlsFree( tlsctx_t *ctx );
int HTTP_TlsHandshake( tlsctx_t *ctx );
int HTTP_TlsSend( tlsctx_t *ctx, const void *buf, int len );
int HTTP_TlsRecv( tlsctx_t *ctx, void *buf, int len );
#else // !XASH_MBEDTLS
static inline void HTTP_TlsInit( void ) { }
static inline void HTTP_TlsShutdown( void ) { }
static inline qboolean HTTP_TlsAvailable( void ) { return false; }
static inline tlsctx_t *HTTP_TlsNew( int socket, const char *hostname ) { return NULL; }
static inline void HTTP_TlsFree( tlsctx_t *ctx ) { }
static inline int HTTP_TlsHandshake( tlsctx_t *ctx ) { return HTTP_TLS_ERROR; }
static inline int HTTP_TlsSend( tlsctx_t *ctx, const void *buf, int len ) { return HTTP_TLS_ERROR; }
static inline int HTTP_TlsRecv( tlsctx_t *ctx, void *buf, int len ) { return HTTP_TLS_ERROR; }
#endif // XASH_MBEDTLS
#endif // NET_HTTP_TLS_H

View File

@@ -19,6 +19,7 @@ GNU General Public License for more details.
#include "netchan.h"
#include "xash3d_mathlib.h"
#include "net_ws_private.h"
#include "net_http_tls.h"
#include "miniz.h"
/*
@@ -31,12 +32,16 @@ HTTP downloader
#define MAX_HTTP_BUFFER_SIZE (BIT( 16 ))
#define MAX_HTTP_DECOMPRESSED_SIZE ( 64 * 1024 * 1024 )
#define MAX_HTTP_MEMORY_SIZE ( 4 * 1024 * 1024 )
typedef struct httpserver_s
{
char host[256];
int port;
char path[MAX_SYSPATH];
qboolean secure;
qboolean resolved;
struct sockaddr_storage addr;
struct httpserver_s *next;
} httpserver_t;
@@ -66,11 +71,24 @@ typedef struct httpfile_s
resource_t *resource;
http_process_fn_t pfn_process;
struct sockaddr_storage addr;
int redirects_followed;
qboolean url_in_server;
tlsctx_t *tls;
// in-memory response mode (set by HTTP_GetToMemory)
qboolean to_memory;
qboolean own_server;
byte *mem_data;
size_t mem_size;
size_t mem_cap;
http_memory_cb_t mem_cb;
void *mem_user;
char url[1024];
char query_backup[1024];
// query or response
char buf[MAX_HTTP_BUFFER_SIZE+1];
// query or response, allocated when socket is created, freed in HTTP_FreeFile
char *buf;
int header_size, query_length, bytes_sent;
} httpfile_t;
@@ -85,12 +103,14 @@ static struct http_static_s
qboolean resolving;
} http;
poolhandle_t http_mempool;
static CVAR_DEFINE_AUTO( http_useragent, "", FCVAR_ARCHIVE | FCVAR_PRIVILEGED, "User-Agent string" );
static CVAR_DEFINE_AUTO( http_autoremove, "1", FCVAR_ARCHIVE | FCVAR_PRIVILEGED, "remove broken files" );
static CVAR_DEFINE_AUTO( http_timeout, "45", FCVAR_ARCHIVE | FCVAR_PRIVILEGED, "timeout for http downloader" );
static CVAR_DEFINE_AUTO( http_maxconnections, "2", FCVAR_ARCHIVE | FCVAR_PRIVILEGED, "maximum http connection number" );
static CVAR_DEFINE_AUTO( http_maxconnections, "5", FCVAR_ARCHIVE | FCVAR_PRIVILEGED, "maximum http connection number" );
static CVAR_DEFINE_AUTO( http_show_headers, "0", FCVAR_ARCHIVE | FCVAR_PRIVILEGED, "show HTTP headers (request and response)" );
static CVAR_DEFINE_AUTO( http_max_redirects, "5", FCVAR_ARCHIVE | FCVAR_PRIVILEGED, "maximum HTTP redirects to follow per request" );
static int HTTP_FileFree( httpfile_t *file );
static int HTTP_FileConnect( httpfile_t *file );
@@ -99,7 +119,10 @@ static int HTTP_FileProcessStream( httpfile_t *file );
static int HTTP_FileQueue( httpfile_t *file );
static int HTTP_FileResolveNS( httpfile_t *file );
static int HTTP_FileSendRequest( httpfile_t *file );
static int HTTP_FileTlsHandshake( httpfile_t *file );
static int HTTP_FileDecompress( httpfile_t *file );
static httpserver_t *HTTP_ParseURL( const char *url_, qboolean full_path );
static qboolean HTTP_FileRedirect( httpfile_t *file, const char *location );
static const char *HTTP_DownloadPath( char *buf, size_t buflen, const char *path, qboolean incomplete )
{
@@ -131,6 +154,12 @@ static void HTTP_FreeFile( httpfile_t *file, qboolean error )
file->file = NULL;
if( file->tls )
{
HTTP_TlsFree( file->tls );
file->tls = NULL;
}
if( file->socket != -1 )
{
closesocket( file->socket );
@@ -139,6 +168,34 @@ static void HTTP_FreeFile( httpfile_t *file, qboolean error )
file->socket = -1;
if( file->buf )
{
Mem_Free( file->buf );
file->buf = NULL;
}
if( file->to_memory )
{
if( file->mem_cb )
file->mem_cb( file->url, !error, error ? NULL : file->mem_data, error ? 0 : file->mem_size, file->mem_user );
if( file->mem_data )
{
Mem_Free( file->mem_data );
file->mem_data = NULL;
}
if( file->own_server && file->server )
{
Mem_Free( file->server );
file->server = NULL;
}
file->pfn_process = HTTP_FileFree;
file->success = !error;
return;
}
HTTP_DownloadPath( incname, sizeof( incname ), file->path, true );
if( error )
@@ -146,7 +203,14 @@ static void HTTP_FreeFile( httpfile_t *file, qboolean error )
// switch to next fastdl server if present
if( file->server && was_open )
{
file->server = file->server->next;
httpserver_t *next = file->server->next;
if( file->own_server )
{
Mem_Free( file->server );
file->own_server = false;
}
file->server = next;
file->pfn_process = HTTP_FileQueue; // Reset download state, HTTP_Run() will open file again
return;
@@ -196,31 +260,12 @@ static int HTTP_FileFree( httpfile_t *file )
static int HTTP_FileQueue( httpfile_t *file )
{
char name[MAX_SYSPATH];
if( http.active_count > http_maxconnections.value )
return 0;
if( !file->server )
{
HTTP_FreeFile( file, true );
return 0;
}
Con_Reportf( "HTTP: Starting download %s from %s:%d\n", file->path, file->server->host, file->server->port );
HTTP_DownloadPath( name, sizeof( name ), file->path, 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 );
return 0;
}
file->pfn_process = HTTP_FileResolveNS;
file->blocktime = file->downloaded = file->lastchecksize = file->checktime = 0;
return 1;
@@ -228,6 +273,13 @@ static int HTTP_FileQueue( httpfile_t *file )
static int HTTP_FileResolveNS( httpfile_t *file )
{
if( file->server->resolved )
{
file->addr = file->server->addr;
file->pfn_process = HTTP_FileCreateSocket;
return 1;
}
if( http.resolving )
return 0;
@@ -258,12 +310,23 @@ static int HTTP_FileResolveNS( httpfile_t *file )
return 0;
}
file->server->addr = file->addr;
file->server->resolved = true;
file->pfn_process = HTTP_FileCreateSocket;
return 1;
}
static int HTTP_FileCreateSocket( httpfile_t *file )
{
if( http.active_count >= http_maxconnections.value )
return 0;
if( file->to_memory )
Con_Reportf( "HTTP: Starting in-memory GET %s\n", file->url );
else
Con_Reportf( "HTTP: Starting download %s from %s:%d\n", file->path, file->server->host, file->server->port );
file->socket = socket( file->addr.ss_family, SOCK_STREAM, IPPROTO_TCP );
if( file->socket < 0 )
@@ -280,6 +343,9 @@ static int HTTP_FileCreateSocket( httpfile_t *file )
return 0;
}
if( !file->buf )
file->buf = Mem_Calloc( http_mempool, MAX_HTTP_BUFFER_SIZE + 1 );
http.active_count++;
file->pfn_process = HTTP_FileConnect;
return 1;
@@ -323,25 +389,81 @@ static int HTTP_FileConnect( httpfile_t *file )
}
else Q_strncpy( useragent, http_useragent.string, sizeof( useragent ));
file->query_length = Q_snprintf( file->buf, sizeof( file->buf ),
"GET %s%s HTTP/1.1\r\n"
"Host: %s:%d\r\n"
"User-Agent: %s\r\n"
"Accept-Encoding: gzip, deflate\r\n"
"Accept: */*\r\n\r\n",
file->server->path, file->path,
file->server->host, file->server->port,
useragent );
const char *path_suffix = file->url_in_server ? "" : file->path;
if( file->to_memory )
{
file->query_length = Q_snprintf( file->buf, MAX_HTTP_BUFFER_SIZE + 1,
"GET %s%s HTTP/1.1\r\n"
"Host: %s:%d\r\n"
"User-Agent: %s\r\n"
"Accept: */*\r\n\r\n",
file->server->path, path_suffix,
file->server->host, file->server->port,
useragent );
}
else
{
file->query_length = Q_snprintf( file->buf, MAX_HTTP_BUFFER_SIZE + 1,
"GET %s%s HTTP/1.1\r\n"
"Host: %s:%d\r\n"
"User-Agent: %s\r\n"
"Accept-Encoding: gzip, deflate\r\n"
"Accept: */*\r\n\r\n",
file->server->path, path_suffix,
file->server->host, file->server->port,
useragent );
}
Q_strncpy( file->query_backup, file->buf, sizeof( file->query_backup ));
file->bytes_sent = 0;
file->header_size = 0;
file->pfn_process = HTTP_FileSendRequest;
if( file->server->secure )
{
file->tls = HTTP_TlsNew( file->socket, file->server->host );
if( !file->tls )
{
Con_Printf( S_ERROR "TLS context allocation failed for %s\n", file->server->host );
HTTP_FreeFile( file, true );
return 0;
}
file->pfn_process = HTTP_FileTlsHandshake;
}
else file->pfn_process = HTTP_FileSendRequest;
return 1;
}
static int HTTP_FileTlsHandshake( httpfile_t *file )
{
int ret = HTTP_TlsHandshake( file->tls );
if( ret == HTTP_TLS_OK )
{
file->blocktime = 0;
file->pfn_process = HTTP_FileSendRequest;
return 1;
}
if( ret == HTTP_TLS_WANT )
{
file->blocktime += host.frametime;
file->blockreason = "TLS handshake";
return 0;
}
HTTP_FreeFile( file, true );
return 0;
}
static int HTTP_FileSendRequest( httpfile_t *file )
{
int res = send( file->socket, file->buf + file->bytes_sent, file->query_length - file->bytes_sent, 0 );
int res;
if( file->tls )
res = HTTP_TlsSend( file->tls, file->buf + file->bytes_sent, file->query_length - file->bytes_sent );
else
res = send( file->socket, file->buf + file->bytes_sent, file->query_length - file->bytes_sent, 0 );
if( res >= 0 )
{
@@ -354,11 +476,22 @@ static int HTTP_FileSendRequest( httpfile_t *file )
Con_Reportf( "HTTP: Request sent! (size %d data %s)\n", file->bytes_sent, file->buf );
else
Con_Reportf( "HTTP: Request sent!\n" );
memset( file->buf, 0, sizeof( file->buf ));
memset( file->buf, 0, MAX_HTTP_BUFFER_SIZE + 1);
file->pfn_process = HTTP_FileProcessStream;
return 1;
}
}
else if( file->tls )
{
if( res != HTTP_TLS_WANT )
{
HTTP_FreeFile( file, true );
return 0;
}
file->blocktime += host.frametime;
file->blockreason = "request send";
}
else
{
int err = WSAGetLastError();
@@ -469,8 +602,8 @@ static int HTTP_FileDecompress( httpfile_t *file )
return 0;
}
byte *data_in = Mem_Malloc( host.mempool, compressed_len + 1 );
byte *data_out = Mem_Malloc( host.mempool, decompressed_len + 1 );
byte *data_in = Mem_Malloc( http_mempool, compressed_len + 1 );
byte *data_out = Mem_Malloc( http_mempool, decompressed_len + 1 );
HTTP_DownloadPath( name, sizeof( name ), file->path, false );
@@ -563,6 +696,9 @@ static void HTTP_AutoClean( void )
continue;
}
// unlink before running callbacks (in-memory cb may queue another GET, which prepends to first_file)
*prev = cur->next;
#if !XASH_DEDICATED
if( cur->process )
{
@@ -580,7 +716,6 @@ static void HTTP_AutoClean( void )
Con_Printf( "successfully downloaded %s!\n", cur->path );
}
*prev = cur->next;
Mem_Free( cur );
}
@@ -605,7 +740,7 @@ static int HTTP_FileSaveReceivedData( httpfile_t *file, int pos, int length )
{
char *begin = &file->buf[pos];
if( begin[0] == '\r' && begin[1] == '\r' )
if( begin[0] == '\r' && begin[1] == '\n' )
begin += 2;
file->chunksize = Q_atoi_hex( 1, begin );
@@ -618,6 +753,11 @@ static int HTTP_FileSaveReceivedData( httpfile_t *file, int pos, int length )
file->pfn_process = HTTP_FileDecompress;
return 1;
}
else if( file->to_memory )
{
HTTP_FreeFile( file, false );
return 1;
}
else
{
fs_offset_t filelen = FS_FileLength( file->file );
@@ -663,13 +803,42 @@ static int HTTP_FileSaveReceivedData( httpfile_t *file, int pos, int length )
len_to_write = Q_min( length, file->chunksize );
else len_to_write = length;
int ret = FS_Write( file->file, &file->buf[pos], len_to_write );
if( ret != len_to_write )
int ret;
if( file->to_memory )
{
// close it and go to next
Con_Printf( S_ERROR "write failed for %s!\n", file->path );
HTTP_FreeFile( file, true );
return 0;
if( file->mem_size + len_to_write > MAX_HTTP_MEMORY_SIZE )
{
Con_Printf( S_ERROR "%s: response too large (>%d bytes)\n", file->url, MAX_HTTP_MEMORY_SIZE );
HTTP_FreeFile( file, true );
return 0;
}
if( file->mem_size + len_to_write > file->mem_cap )
{
size_t newcap = file->mem_cap ? file->mem_cap * 2 : 4096;
while( newcap < file->mem_size + len_to_write )
newcap *= 2;
file->mem_data = Mem_Realloc( http_mempool, file->mem_data, newcap );
file->mem_cap = newcap;
}
memcpy( file->mem_data + file->mem_size, &file->buf[pos], len_to_write );
file->mem_size += len_to_write;
ret = len_to_write;
}
else
{
ret = FS_Write( file->file, &file->buf[pos], len_to_write );
if( ret != len_to_write )
{
// close it and go to next
Con_Printf( S_ERROR "write failed for %s!\n", file->path );
HTTP_FreeFile( file, true );
return 0;
}
}
length -= len_to_write;
@@ -692,13 +861,23 @@ process incoming data
*/
static int HTTP_FileProcessStream( httpfile_t *curfile )
{
char buf[sizeof( curfile->buf )];
char buf[MAX_HTTP_BUFFER_SIZE + 1];
char *begin = 0;
int res;
// if we got there, we are receiving data
while(( res = recv( curfile->socket, buf, sizeof( buf ) - curfile->header_size - 1, 0 )) > 0 )
while( 1 )
{
int rlen = sizeof( buf ) - curfile->header_size - 1;
if( curfile->tls )
res = HTTP_TlsRecv( curfile->tls, buf, rlen );
else
res = recv( curfile->socket, buf, rlen, 0 );
if( res <= 0 )
break;
curfile->blocktime = 0;
if( !curfile->got_response ) // Response still not received
@@ -721,27 +900,45 @@ static int HTTP_FileProcessStream( httpfile_t *curfile )
*begin = 0; // cut string to print out response
if( !Q_strstr( curfile->buf, "200 OK" ))
int num = -1;
// don't assume the response is valid HTTP
if( !Q_strncmp( curfile->buf, "HTTP/1.", 7 ))
{
int num = -1;
char tmp[4];
Q_strncpy( tmp, curfile->buf + 9, sizeof( tmp ));
if( Q_isdigit( tmp ))
num = Q_atoi( tmp );
}
if( num != 200 )
{
if( num == 301 || num == 302 || num == 303 || num == 307 || num == 308 )
{
char *loc = Q_stristr( curfile->buf, "Location:" );
if( loc )
{
loc += sizeof( "Location:" ) - 1;
while( *loc == ' ' || *loc == '\t' )
loc++;
char *eol = Q_strchr( loc, '\r' );
if( !eol ) eol = Q_strchr( loc, '\n' );
if( eol ) *eol = 0;
if( HTTP_FileRedirect( curfile, loc ))
return 1;
}
}
char *p = Q_strchr( curfile->buf, '\r' );
if( !p ) p = Q_strchr( curfile->buf, '\n' );
if( p ) *p = 0;
// extract the error code, don't assume the response is valid HTTP
if( !Q_strncmp( curfile->buf, "HTTP/1.", 7 ))
{
char tmp[4];
Q_strncpy( tmp, curfile->buf + 9, sizeof( tmp ));
if( Q_isdigit( tmp ))
num = Q_atoi( tmp );
}
switch( num )
{
// TODO: handle redirects
case 404:
Con_Printf( S_ERROR "%s: file not found\n", curfile->path );
break;
@@ -761,7 +958,14 @@ static int HTTP_FileProcessStream( httpfile_t *curfile )
{
content_encoding += sizeof( "Content-Encoding: " ) - 1;
if( !Q_strnicmp( content_encoding, "gzip", 4 ) && ( content_encoding[4] == '\0' || content_encoding[4] == '\n' || content_encoding[4] == '\r' ))
if( curfile->to_memory )
{
// in-memory mode never advertises gzip and has no decompressor
Con_Printf( S_ERROR "%s: server sent Content-Encoding for an in-memory request\n", curfile->url );
HTTP_FreeFile( curfile, true );
return 0;
}
else if( !Q_strnicmp( content_encoding, "gzip", 4 ) && ( content_encoding[4] == '\0' || content_encoding[4] == '\n' || content_encoding[4] == '\r' ))
curfile->compressed = true;
else
{
@@ -785,7 +989,7 @@ static int HTTP_FileProcessStream( httpfile_t *curfile )
content_length += sizeof( "Content-Length: " ) - 1;
int size = Q_atoi( content_length );
Con_Reportf( "HTTP: Got 200 OK! File size is %d%s\n", curfile->size, curfile->compressed ? ", compressed" : "" );
Con_Reportf( "HTTP: Got 200 OK! File size is %d%s\n", size, curfile->compressed ? ", compressed" : "" );
if( !curfile->compressed )
{
@@ -809,6 +1013,25 @@ static int HTTP_FileProcessStream( httpfile_t *curfile )
Con_Reportf( "Response headers: %s\n", curfile->buf );
curfile->got_response = true; // got response, let's start download
if( !curfile->to_memory && !curfile->file )
{
char name[MAX_SYSPATH];
HTTP_DownloadPath( name, sizeof( name ), curfile->path, true );
FS_AllowDirectPaths( true );
curfile->file = FS_Open( name, "wb+", true );
FS_AllowDirectPaths( false );
if( !curfile->file )
{
Con_Printf( S_ERROR "HTTP: cannot open %s!\n", name );
HTTP_FreeFile( curfile, true );
return 0;
}
}
begin += 4;
if( res - ( begin - curfile->buf ) > 0 )
@@ -863,19 +1086,31 @@ static int HTTP_FileProcessStream( httpfile_t *curfile )
if( res == 0 )
{
curfile->blocktime += host.frametime;
curfile->blockreason = "waiting for data";
Con_Printf( S_ERROR "connection closed prematurely for %s\n", curfile->to_memory ? curfile->url : curfile->path );
HTTP_FreeFile( curfile, true );
return 0;
}
if( res < 0 )
{
int err = WSAGetLastError();
if( err != WSAEWOULDBLOCK && err != WSAEINPROGRESS )
if( curfile->tls )
{
Con_Reportf( "problem downloading %s: %s\n", curfile->path, NET_ErrorString( ));
HTTP_FreeFile( curfile, true );
return 0;
if( res != HTTP_TLS_WANT )
{
HTTP_FreeFile( curfile, true );
return 0;
}
}
else
{
int err = WSAGetLastError();
if( err != WSAEWOULDBLOCK && err != WSAEINPROGRESS )
{
Con_Reportf( "problem downloading %s: %s\n", curfile->path, NET_ErrorString( ));
HTTP_FreeFile( curfile, true );
return 0;
}
}
curfile->blocktime += host.frametime;
@@ -913,7 +1148,8 @@ void HTTP_Run( void )
if( curfile->blocktime > http_timeout.value )
{
Con_Printf( S_ERROR "timeout on %s (file: %s)\n", curfile->blockreason, curfile->path );
Con_Printf( S_ERROR "timeout on %s (file: %s)\n", curfile->blockreason,
curfile->to_memory ? curfile->url : curfile->path );
HTTP_FreeFile( curfile, true );
}
}
@@ -940,13 +1176,19 @@ void HTTP_AddDownload( const char *path, int size, qboolean process, resource_t
return;
}
if( Q_strpbrk( path, "\r\n" ))
{
Con_Printf( S_ERROR "%s: refused to download, path contains CRLF\n", __func__ );
return;
}
if( !http.first_server )
{
Con_Printf( S_ERROR "no servers to download %s\n", path );
return;
}
httpfile_t *httpfile = Z_Calloc( sizeof( *httpfile ));
httpfile_t *httpfile = Mem_Calloc( http_mempool, sizeof( *httpfile ));
Con_Reportf( "File %s queued to download\n", path );
@@ -964,6 +1206,50 @@ void HTTP_AddDownload( const char *path, int size, qboolean process, resource_t
http.first_file = httpfile;
}
/*
===================
HTTP_GetToMemory
One-shot async GET. The full response body is collected into a heap buffer
and handed to the callback when the request completes (success or failure).
===================
*/
qboolean HTTP_GetToMemory( const char *url, http_memory_cb_t cb, void *userdata )
{
if( Q_strpbrk( url, "\r\n" ))
{
Con_Printf( S_ERROR "%s: refused, URL contains CRLF\n", __func__ );
return false;
}
httpserver_t *server = HTTP_ParseURL( url, true );
if( !server )
{
Con_Printf( S_ERROR "%s: \"%s\" is not a valid URL\n", __func__, url );
return false;
}
httpfile_t *httpfile = Mem_Calloc( http_mempool, sizeof( *httpfile ));
httpfile->size = -1;
httpfile->reported_size = -1;
httpfile->socket = -1;
httpfile->server = server;
httpfile->own_server = true;
httpfile->to_memory = true;
httpfile->mem_cb = cb;
httpfile->mem_user = userdata;
httpfile->pfn_process = HTTP_FileQueue;
Q_strncpy( httpfile->url, url, sizeof( httpfile->url ));
// file->path is empty; the full path lives in server->path
httpfile->next = http.first_file;
http.first_file = httpfile;
return true;
}
/*
===============
HTTP_Download_f
@@ -987,29 +1273,42 @@ static void HTTP_Download_f( void )
HTTP_ParseURL
==============
*/
static httpserver_t *HTTP_ParseURL( const char *url_ )
static httpserver_t *HTTP_ParseURL( const char *url_, qboolean full_path )
{
const char *url = Q_strstr( url_, "http://" );
qboolean secure = false;
const char *url = NULL;
if( url )
url += 7;
else
if( !Q_strnicmp( url_, "https://", 8 ))
{
url = Q_strstr( url_, "https://" );
if( url )
url += 8;
url = url_ + 8;
secure = true;
}
else if( !Q_strnicmp( url_, "http://", 7 ))
{
url = url_ + 7;
}
if( !url )
return NULL;
httpserver_t *server = Z_Calloc( sizeof( httpserver_t ));
if( secure && !HTTP_TlsAvailable( ))
{
Con_Printf( S_ERROR "HTTPS not available, can't fetch %s\n", url_ );
return NULL;
}
httpserver_t *server = Mem_Calloc( http_mempool, sizeof( httpserver_t ));
int i = 0;
server->secure = secure;
while( *url && ( *url != ':' ) && ( *url != '/' ) && ( *url != '\r' ) && ( *url != '\n' ))
{
if( i > sizeof( server->host ))
if( i >= sizeof( server->host ) - 1 )
{
Mem_Free( server );
return NULL;
}
server->host[i++] = *url++;
}
@@ -1024,19 +1323,25 @@ static httpserver_t *HTTP_ParseURL( const char *url_ )
url++;
}
else
server->port = 80;
server->port = secure ? 443 : 80;
i = 0;
// leave room for the optional trailing '/' and the '\0'
while( *url && ( *url != '\r' ) && ( *url != '\n' ))
{
if( i > sizeof( server->path ) - 1 )
if( i >= sizeof( server->path ) - 2 )
{
Mem_Free( server );
return NULL;
}
server->path[i++] = *url++;
}
if( i == 0 || server->path[i-1] != '/' )
// fastdl base URLs are appended to per-file paths and must end with a slash;
// full URLs (one-shot GETs) are used as-is.
if( !full_path && ( i == 0 || server->path[i-1] != '/' ))
server->path[i++] = '/';
server->path[i] = 0;
server->next = NULL;
@@ -1044,6 +1349,88 @@ static httpserver_t *HTTP_ParseURL( const char *url_ )
return server;
}
static qboolean HTTP_FileRedirect( httpfile_t *file, const char *location )
{
if( !location || !*location )
return false;
if( file->redirects_followed >= http_max_redirects.value )
{
Con_Printf( S_ERROR "too many redirects for %s\n", file->to_memory ? file->url : file->path );
return false;
}
// silent http -> https upgrade is OK; reject downgrade
qboolean target_secure = !Q_strnicmp( location, "https://", 8 );
qboolean target_plain = !Q_strnicmp( location, "http://", 7 );
if( !target_secure && !target_plain )
{
Con_Printf( S_ERROR "redirect to non-absolute URL not supported: %s\n", location );
return false;
}
if( file->server->secure && !target_secure )
{
Con_Printf( S_ERROR "refusing https -> http redirect: %s\n", location );
return false;
}
httpserver_t *newserver = HTTP_ParseURL( location, true );
if( !newserver )
{
Con_Printf( S_ERROR "redirect target %s is not a valid URL\n", location );
return false;
}
Con_Reportf( "HTTP: redirect %s -> %s\n", file->to_memory ? file->url : file->path, location );
// tear down current connection but keep file/mem buffers
if( file->tls )
{
HTTP_TlsFree( file->tls );
file->tls = NULL;
}
if( file->socket != -1 )
{
closesocket( file->socket );
http.active_count--;
file->socket = -1;
}
if( file->own_server && file->server )
Mem_Free( file->server );
file->server = newserver;
file->own_server = true;
// truncate the partial download; we'll restart from the new server
if( file->file )
{
g_fsapi.Seek( file->file, 0, SEEK_SET );
}
file->mem_size = 0;
file->downloaded = 0;
file->lastchecksize = 0;
file->header_size = 0;
file->bytes_sent = 0;
file->got_response = false;
file->compressed = false;
file->chunked = false;
file->chunksize = 0;
file->size = file->reported_size;
file->url_in_server = true;
if( file->to_memory )
Q_strncpy( file->url, location, sizeof( file->url ));
file->redirects_followed++;
file->blocktime = 0;
file->pfn_process = HTTP_FileResolveNS;
return true;
}
/*
=======================
HTTP_AddCustomServer
@@ -1051,7 +1438,13 @@ HTTP_AddCustomServer
*/
void HTTP_AddCustomServer( const char *url )
{
httpserver_t *server = HTTP_ParseURL( url );
if( Q_strpbrk( url, "\r\n" ))
{
Con_Printf( S_ERROR "%s: refused, URL contains CRLF\n", __func__ );
return;
}
httpserver_t *server = HTTP_ParseURL( url, false );
if( !server )
{
@@ -1098,9 +1491,25 @@ static void HTTP_Clear_f( void )
if( file->file )
FS_Close( file->file );
if( file->tls )
HTTP_TlsFree( file->tls );
if( file->socket != -1 )
closesocket( file->socket );
if( file->buf )
Mem_Free( file->buf );
if( file->to_memory )
{
if( file->mem_cb )
file->mem_cb( file->url, false, NULL, 0, file->mem_user );
if( file->mem_data )
Mem_Free( file->mem_data );
if( file->own_server && file->server )
Mem_Free( file->server );
}
Mem_Free( file );
}
}
@@ -1156,7 +1565,8 @@ static void HTTP_List_f( void )
{
for( httpserver_t *server = file->server; server; server = server->next )
{
Con_Printf( "\thttp://%s:%d/%s%s\n", file->server->host, file->server->port,
Con_Printf( "\t%s://%s:%d/%s%s\n", file->server->secure ? "https" : "http",
file->server->host, file->server->port,
file->server->path, file->path );
}
}
@@ -1184,6 +1594,9 @@ HTTP_Init
void HTTP_Init( void )
{
http.first_file = NULL;
http_mempool = Mem_AllocPool( "HTTP" );
HTTP_TlsInit();
Cmd_AddRestrictedCommand( "http_download", HTTP_Download_f, "add file to download queue" );
Cmd_AddRestrictedCommand( "http_skip", HTTP_Skip_f, "skip current download server" );
@@ -1197,6 +1610,7 @@ void HTTP_Init( void )
Cvar_RegisterVariable( &http_timeout );
Cvar_RegisterVariable( &http_maxconnections );
Cvar_RegisterVariable( &http_show_headers );
Cvar_RegisterVariable( &http_max_redirects );
}
/*
@@ -1215,4 +1629,8 @@ void HTTP_Shutdown( void )
http.first_server = http.first_server->next;
Mem_Free( tmp );
}
HTTP_TlsShutdown();
Mem_FreePool( &http_mempool );
}

View File

@@ -14,7 +14,9 @@ GNU General Public License for more details.
*/
#include "common.h"
#include "netchan.h"
#include "net_ws.h"
#include "server.h"
#include "client.h"
typedef struct master_s
{
@@ -32,16 +34,26 @@ typedef struct master_s
double resolve_time;
} master_t;
typedef struct masterstatic_s
{
struct masterstatic_s *next;
qboolean save;
qboolean in_flight;
char base[];
} masterstatic_t;
static struct masterlist_s
{
master_t *head, *tail;
masterstatic_t *static_head, *static_tail;
qboolean modified;
} ml;
static CVAR_DEFINE_AUTO( sv_verbose_heartbeats, "0", 0, "print every heartbeat to console" );
#define HEARTBEAT_SECONDS ((sv_nat.value > 0.0f) ? 60.0f : 300.0f) // 1 or 5 minutes
#define RESOLVE_EXPIRE_SECONDS (60.0f) // 1 minute to expire
#define HEARTBEAT_SECONDS ((sv_nat.value > 0.0f) ? 60.0f : 300.0f) // 1 or 5 minutes
#define RESOLVE_EXPIRE_SECONDS (60.0f) // positive cache: 1 minute
#define NEGATIVE_RESOLVE_EXPIRE_SECONDS (300.0f) // negative cache: 5 minutes
static size_t NET_BuildMasterServerScanRequest( char *buf, size_t size, uint32_t key, qboolean nat, const char *filter, connprotocol_t proto )
{
@@ -53,9 +65,7 @@ static size_t NET_BuildMasterServerScanRequest( char *buf, size_t size, uint32_t
Q_strncpy( info, filter, remaining );
#ifndef XASH_ALL_SERVERS
Info_SetValueForKey( info, "gamedir", GI->gamefolder, remaining );
#endif // XASH_ALL_SERVERS
if( proto != PROTO_GOLDSRC )
{
@@ -86,11 +96,10 @@ NET_GetMasterHostByName
*/
static net_gai_state_t NET_GetMasterHostByName( master_t *m )
{
if( host.realtime > m->resolve_time )
m->adr.type = 0;
if( host.realtime < m->resolve_time )
return m->adr.type ? NET_EAI_OK : NET_EAI_NONAME;
if( m->adr.type )
return NET_EAI_OK;
m->adr.type = 0;
net_gai_state_t res = NET_StringToAdrNB( m->address, &m->adr, m->v6only );
@@ -100,10 +109,13 @@ static net_gai_state_t NET_GetMasterHostByName( master_t *m )
return res;
}
m->adr.type = 0;
if( res == NET_EAI_NONAME )
{
Con_Reportf( "Can't resolve adr: %s\n", m->address );
m->resolve_time = host.realtime + NEGATIVE_RESOLVE_EXPIRE_SECONDS;
}
m->adr.type = 0;
return res;
}
@@ -199,9 +211,123 @@ void NET_MasterClear( void )
m->last_heartbeat = MAX_HEARTBEAT;
}
/*
========================
NET_QueryServerByAddress
========================
*/
void NET_QueryServerByAddress( netadr_t adr, connprotocol_t proto )
{
if( proto == PROTO_GOLDSRC )
Netchan_OutOfBand( NS_CLIENT, adr, sizeof( A2S_GOLDSRC_INFO ), (const byte *)A2S_GOLDSRC_INFO ); // includes null terminator
else
Netchan_OutOfBandPrint( NS_CLIENT, adr, A2A_INFO " %i", PROTOCOL_VERSION );
}
static int NET_ParseMasterStaticBody( char *body )
{
char token[MAX_TOKEN];
char *pfile = body;
int count = 0;
while(( pfile = COM_ParseFileSafe( pfile, token, sizeof( token ), PFILE_HASH_AS_COMMENT, NULL, NULL )))
{
qboolean gs;
if( !Q_strcmp( token, "ip" ))
gs = false;
else if( !Q_strcmp( token, "gs" ))
gs = true;
else
{
pfile = COM_ParseFileSafe( pfile, token, sizeof( token ), PFILE_HASH_AS_COMMENT, NULL, NULL );
if( !pfile )
break;
continue;
}
pfile = COM_ParseFileSafe( pfile, token, sizeof( token ), PFILE_HASH_AS_COMMENT, NULL, NULL );
if( !pfile )
break;
netadr_t adr = { 0 };
if( !NET_StringToAdr( token, &adr ))
{
Con_Reportf( S_WARN "masterstatic: can't parse address \"%s\"\n", token );
continue;
}
if( adr.port == 0 )
adr.port = MSG_BigShort( PORT_SERVER );
NET_QueryServerByAddress( adr, gs );
count++;
}
return count;
}
/*
========================
NET_MasterStaticResponse
========================
*/
static void NET_MasterStaticResponse( const char *url, qboolean success, const byte *data, size_t size, void *userdata )
{
masterstatic_t *ms = (masterstatic_t *)userdata;
if( ms )
ms->in_flight = false;
if( !success || !data || size == 0 )
{
Con_Reportf( "masterstatic: %s returned no data\n", url );
return;
}
// HTTP buffer isn't NUL-terminated; COM_ParseFileSafe needs one.
char *body = Mem_Malloc( host.mempool, size + 1 );
memcpy( body, data, size );
body[size] = 0;
NET_Config( true, false ); // allow remote sends
int count = NET_ParseMasterStaticBody( body );
Mem_Free( body );
Con_Reportf( "masterstatic: %s yielded %d server(s)\n", url, count );
#if !XASH_DEDICATED
CL_NotifyServerListResponse();
#endif
}
static void NET_MasterStaticQuery( void )
{
const char *gamedir = GI ? GI->gamefolder : "valve";
for( masterstatic_t *ms = ml.static_head; ms; ms = ms->next )
{
char url[1024];
if( ms->in_flight )
continue;
Q_snprintf( url, sizeof( url ), "%s/v1/servers/%s", ms->base, gamedir );
if( HTTP_GetToMemory( url, NET_MasterStaticResponse, ms ))
ms->in_flight = true;
}
}
/*
=================
NET_MasterQuery
=================
*/
qboolean NET_MasterQuery( uint32_t key, qboolean nat, const char *filter )
@@ -217,6 +343,8 @@ qboolean NET_MasterQuery( uint32_t key, qboolean nat, const char *filter )
wait |= NET_SendToMasters( NS_CLIENT, len, buf, PROTO_GOLDSRC );
}
NET_MasterStaticQuery();
if( !wait )
NET_ClearSendState();
@@ -234,6 +362,28 @@ void NET_MasterHeartbeat( void )
if(( !public_server.value && !sv_nat.value ) || svs.maxclients == 1 )
return; // only public servers send heartbeats
if( Host_IsDedicated() && public_server.value )
{
static qboolean shown_serverlist_notice = false;
if( !shown_serverlist_notice )
{
Con_Printf( "\n" );
Con_Printf( "********************************************************************************\n" );
Con_Printf( "* *\n" );
Con_Printf( "* You set `public 1` on a dedicated server. *\n" );
Con_Printf( "* *\n" );
Con_Printf( "* Legacy UDP master servers are deprecated. To make your server visible in *\n" );
Con_Printf( "* the new HTTPS-based public server list, open a Pull Request to: *\n" );
Con_Printf( "* *\n" );
Con_Printf( "* https://github.com/FWGS/server-list *\n" );
Con_Printf( "* *\n" );
Con_Printf( "********************************************************************************\n" );
Con_Printf( "\n" );
shown_serverlist_notice = true;
}
}
for( master_t *m = ml.head; m; m = m->next )
{
if( host.realtime - m->last_heartbeat < HEARTBEAT_SECONDS )
@@ -298,6 +448,7 @@ static master_t *NET_GetMasterFromAdr( netadr_t adr )
/*
========================
NET_GetMaster
========================
*/
qboolean NET_GetMaster( netadr_t from, uint *challenge, double *last_heartbeat )
@@ -380,6 +531,54 @@ static void NET_AddMaster_f( void )
ml.modified = true; // save config
}
static masterstatic_t *NET_AddMasterStatic( const char *base )
{
size_t base_len = Q_strlen( base );
while( base_len > 0 && base[base_len - 1] == '/' )
base_len--;
if( base_len == 0 )
return NULL;
for( masterstatic_t *ms = ml.static_head; ms; ms = ms->next )
{
if( Q_strlen( ms->base ) == base_len && !Q_strnicmp( ms->base, base, base_len ))
return ms;
}
masterstatic_t *ms = Mem_Calloc( host.mempool, sizeof( *ms ) + base_len + 1 );
memcpy( ms->base, base, base_len );
if( ml.static_tail )
{
ml.static_tail->next = ms;
ml.static_tail = ms;
}
else
{
ml.static_head = ml.static_tail = ms;
}
return ms;
}
static void NET_AddMasterStatic_f( void )
{
if( Cmd_Argc() != 2 )
{
Msg( S_USAGE "addmasterstatic <base-url>\n" );
return;
}
masterstatic_t *ms = NET_AddMasterStatic( Cmd_Argv( 1 ));
if( !ms )
return;
ms->save = true;
ml.modified = true;
}
/*
========================
NET_ClearMasters
@@ -397,6 +596,15 @@ static void NET_ClearMasters_f( void )
}
ml.tail = NULL;
while( ml.static_head )
{
masterstatic_t *head = ml.static_head;
ml.static_head = ml.static_head->next;
Mem_Free( head );
}
ml.static_tail = NULL;
}
/*
@@ -425,6 +633,13 @@ static void NET_ListMasters_f( void )
Con_Printf( "\n" );
}
if( ml.static_head )
Con_Printf( "Static master base URLs:\n" );
i = 1;
for( masterstatic_t *ms = ml.static_head; ms; i++, ms = ms->next )
Con_Printf( "%d\t%s%s\n", i, ms->base, ms->in_flight ? " (in flight)" : "" );
}
/*
@@ -472,6 +687,16 @@ static void NET_LoadMasters( void )
master = NET_AddMaster( token );
master->gs = true;
}
else if( !Q_strcmp( token, "masterstatic" ))
{
pfile = COM_ParseFile( pfile, token, sizeof( token ));
if( pfile )
{
masterstatic_t *ms = NET_AddMasterStatic( token );
if( ms )
ms->save = true;
}
}
if( master )
master->save = true;
@@ -519,6 +744,14 @@ void NET_SaveMasters( void )
FS_Printf( f, "%s %s\n", key, m->address );
}
for( masterstatic_t *ms = ml.static_head; ms; ms = ms->next )
{
if( !ms->save )
continue;
FS_Printf( f, "masterstatic \"%s\"\n", ms->base );
}
FS_Close( f );
}
@@ -532,26 +765,20 @@ Initialize master server list
void NET_InitMasters( void )
{
Cmd_AddRestrictedCommand( "addmaster", NET_AddMaster_f, "add address to masterserver list" );
Cmd_AddRestrictedCommand( "addmasterstatic", NET_AddMasterStatic_f, "add static HTTP masterserver base URL" );
Cmd_AddRestrictedCommand( "clearmasters", NET_ClearMasters_f, "clear masterserver list" );
Cmd_AddCommand( "listmasters", NET_ListMasters_f, "list masterservers" );
Cvar_RegisterVariable( &sv_verbose_heartbeats );
{ // IPv4-only
NET_AddMaster( "mentality.rip:27010" );
NET_AddMaster( "ms2.mentality.rip:27010" );
NET_AddMaster( "ms3.mentality.rip:27010" );
}
{ // IPv6-only
NET_AddMaster( "aaaa.mentality.rip:27010" )->v6only = true;
NET_AddMaster( "aaaa.ms2.mentality.rip:27010" )->v6only = true;
}
{ // testing servers, might be offline
NET_AddMaster( "mentality.rip:27011" );
NET_AddMaster( "aaaa.mentality.rip:27011" )->v6only = true;
}
#if 0
NET_AddMasterStatic( "http://meltdown.lan/test" );
#endif
// NOTE: do not use fwgs.github.io for GitHub pages
// because org-wide URL is xash.su (for legacy reasons)
NET_AddMasterStatic( "http://xash.su/server-list" );
// FIXME: https raw.githubcontent source
// FIXME: cloudflare'd sources both HTTP and HTTPS
NET_LoadMasters();
}

View File

@@ -601,7 +601,7 @@ const mclipnode32_t box_clipnodes32[6] = { BOX_CLIPNODES_INITIALIZER };
===============================================================================
*/
static mip_t *Mod_GetMipTexForTexture( dbspmodel_t *bmod, int i )
static const byte *Mod_GetMipTexForTexture( dbspmodel_t *bmod, int i, mip_t *out )
{
if( i < 0 || i >= bmod->textures->nummiptex )
return NULL;
@@ -609,7 +609,9 @@ static mip_t *Mod_GetMipTexForTexture( dbspmodel_t *bmod, int i )
if( bmod->textures->dataofs[i] == -1 )
return NULL;
return (mip_t *)((byte *)bmod->textures + bmod->textures->dataofs[i] );
const byte *raw = (byte *)bmod->textures + bmod->textures->dataofs[i];
memcpy( out, raw, sizeof( *out ));
return raw;
}
// Returns index of WAD that texture was found in, or -1 if not found.
@@ -669,13 +671,13 @@ static fs_offset_t Mod_CalculateMipTexSize( const mip_t *mt, qboolean palette )
static qboolean Mod_CalcMipTexUsesCustomPalette( model_t *mod, dbspmodel_t *bmod, int textureIndex )
{
mip_t *mipTex = Mod_GetMipTexForTexture( bmod, textureIndex );
mip_t mipTex;
if( !mipTex || mipTex->offsets[0] <= 0 )
if( !Mod_GetMipTexForTexture( bmod, textureIndex, &mipTex ) || mipTex.offsets[0] <= 0 )
return false;
// Calculate the size assuming we are not using a custom palette.
fs_offset_t size = Mod_CalculateMipTexSize( mipTex, false );
fs_offset_t size = Mod_CalculateMipTexSize( &mipTex, false );
fs_offset_t remainingBytes;
// Compute next data offset to determine allocated miptex space
@@ -2784,21 +2786,22 @@ static void Mod_LoadTextureData( model_t *mod, dbspmodel_t *bmod, int textureInd
// don't load texture data on dedicated server, as there is no renderer.
// but count the wadusage for automatic precache
texture_t *texture = mod->textures[textureIndex];
const mip_t *mipTex = Mod_GetMipTexForTexture( bmod, textureIndex );
mip_t mipTex;
const byte *mipRaw = Mod_GetMipTexForTexture( bmod, textureIndex, &mipTex );
const qboolean usesCustomPalette = Mod_CalcMipTexUsesCustomPalette( mod, bmod, textureIndex );
const qboolean iswater = Mod_LooksLikeWaterTexture( mipTex->name );
const qboolean iswater = Mod_LooksLikeWaterTexture( mipTex.name );
const uint texture_force_flags = r_allow_wad3_luma.value ? IL_ALLOW_WAD3_LUMA : 0;
// check for multi-layered sky texture (quake1 specific)
if( bmod->isworld && Q_strncmp( mipTex->name, "sky", 3 ) == 0 && ( mipTex->width / mipTex->height ) == 2 )
if( bmod->isworld && Q_strncmp( mipTex.name, "sky", 3 ) == 0 && ( mipTex.width / mipTex.height ) == 2 )
{
Mod_InitSkyClouds( mod, mipTex, texture, usesCustomPalette ); // load quake sky
Mod_InitSkyClouds( mod, &mipTex, texture, usesCustomPalette ); // load quake sky
return;
}
// FIXME: for ENGINE_IMPROVED_LINETRACE we need to load textures on server too
// but there is no facility for this yet
if( FBitSet( host.features, ENGINE_IMPROVED_LINETRACE ) && mipTex->name[0] == '{' )
if( FBitSet( host.features, ENGINE_IMPROVED_LINETRACE ) && mipTex.name[0] == '{' )
SetBits( txFlags, TF_KEEP_SOURCE ); // Paranoia2 texture alpha-tracing
// check if this is water to keep the source texture and expand it to RGBA (so ripple effect works)
@@ -2811,7 +2814,7 @@ static void Mod_LoadTextureData( model_t *mod, dbspmodel_t *bmod, int textureInd
// 3. Internal from map
texture->gl_texturenum = 0;
Q_strncpy( safemtname, mipTex->name, sizeof( safemtname ));
Q_strncpy( safemtname, mipTex.name, sizeof( safemtname ));
if( safemtname[0] == '*' )
safemtname[0] = '!'; // replace unexpected symbol
@@ -2828,10 +2831,10 @@ static void Mod_LoadTextureData( model_t *mod, dbspmodel_t *bmod, int textureInd
}
// Try WAD texture (force while r_wadtextures is 1)
if( !texture->gl_texturenum && (( r_wadtextures.value && world.wadcount > 0 ) || mipTex->offsets[0] <= 0 ))
if( !texture->gl_texturenum && (( r_wadtextures.value && world.wadcount > 0 ) || mipTex.offsets[0] <= 0 ))
{
rgbdata_t *pic = NULL;
int wad_index = Mod_LoadTextureFromWadList( world.wadlist, world.wadcount, mipTex->name, Host_IsDedicated() ? NULL : &pic, texpath, sizeof( texpath ));
int wad_index = Mod_LoadTextureFromWadList( world.wadlist, world.wadcount, mipTex.name, Host_IsDedicated() ? NULL : &pic, texpath, sizeof( texpath ));
if( wad_index >= 0 )
{
@@ -2853,20 +2856,20 @@ static void Mod_LoadTextureData( model_t *mod, dbspmodel_t *bmod, int textureInd
return;
// WAD failed, so use internal texture (if present)
if( mipTex->offsets[0] > 0 && texture->gl_texturenum == 0 )
if( mipTex.offsets[0] > 0 && texture->gl_texturenum == 0 )
{
string texName;
const size_t size = Mod_CalculateMipTexSize( mipTex, usesCustomPalette );
const size_t size = Mod_CalculateMipTexSize( &mipTex, usesCustomPalette );
Q_snprintf( texName, sizeof( texName ), "#%s:%s.mip", loadstat.name, mipTex->name );
Q_snprintf( texName, sizeof( texName ), "#%s:%s.mip", loadstat.name, mipTex.name );
Image_SetForceFlags( texture_force_flags | IL_HOST_ENDIAN );
texture->gl_texturenum = ref.dllFuncs.GL_LoadTexture( texName, (byte *)mipTex, size, txFlags );
texture->gl_texturenum = ref.dllFuncs.GL_LoadTexture( texName, mipRaw, size, txFlags );
}
// If texture is completely missed:
if( texture->gl_texturenum == 0 )
{
Con_DPrintf( S_ERROR "Unable to find %s.mip\n", mipTex->name );
Con_DPrintf( S_ERROR "Unable to find %s.mip\n", mipTex.name );
texture->gl_texturenum = R_GetBuiltinTexture( REF_DEFAULT_TEXTURE );
}
@@ -2890,14 +2893,14 @@ static void Mod_LoadTextureData( model_t *mod, dbspmodel_t *bmod, int textureInd
{
string texName;
Q_snprintf( texName, sizeof( texName ), "#%s:%s_luma.mip", loadstat.name, mipTex->name );
Q_snprintf( texName, sizeof( texName ), "#%s:%s_luma.mip", loadstat.name, mipTex.name );
Image_SetForceFlags( texture_force_flags | IL_HOST_ENDIAN );
if( mipTex->offsets[0] > 0 )
if( mipTex.offsets[0] > 0 )
{
const size_t size = Mod_CalculateMipTexSize( mipTex, usesCustomPalette );
texture->fb_texturenum = ref.dllFuncs.GL_LoadTexture( texName, (byte *)mipTex, size, TF_MAKELUMA );
const size_t size = Mod_CalculateMipTexSize( &mipTex, usesCustomPalette );
texture->fb_texturenum = ref.dllFuncs.GL_LoadTexture( texName, mipRaw, size, TF_MAKELUMA );
}
else
{
@@ -2925,9 +2928,10 @@ static void Mod_LoadTexture( model_t *mod, dbspmodel_t *bmod, int textureIndex )
if( textureIndex < 0 || textureIndex >= mod->numtextures )
return;
mip_t *mipTex = Mod_GetMipTexForTexture( bmod, textureIndex );
mip_t mipTex;
const byte *mipRaw = Mod_GetMipTexForTexture( bmod, textureIndex, &mipTex );
if( !mipTex )
if( !mipRaw )
{
// No data for this texture.
// Create default texture (some mods require this).
@@ -2935,17 +2939,20 @@ static void Mod_LoadTexture( model_t *mod, dbspmodel_t *bmod, int textureIndex )
return;
}
if( mipTex->name[0] == '\0' )
Q_snprintf( mipTex->name, sizeof( mipTex->name ), "miptex_%i", textureIndex );
if( mipTex.name[0] == '\0' )
{
Q_snprintf( mipTex.name, sizeof( mipTex.name ), "miptex_%i", textureIndex );
memcpy((char *)mipRaw, mipTex.name, sizeof( mipTex.name ));
}
texture_t *texture = (texture_t *)Mem_Calloc( mod->mempool, sizeof( *texture ));
mod->textures[textureIndex] = texture;
// Ensure texture name is lowercase.
Q_strnlwr( mipTex->name, texture->name, sizeof( texture->name ));
Q_strnlwr( mipTex.name, texture->name, sizeof( texture->name ));
texture->width = mipTex->width;
texture->height = mipTex->height;
texture->width = mipTex.width;
texture->height = mipTex.height;
Mod_LoadTextureData( mod, bmod, textureIndex );
}

View File

@@ -155,7 +155,6 @@ void Mod_LoadCacheFile( const char *path, struct cache_user_s *cu );
void *Mod_AliasExtradata( model_t *mod );
void *Mod_StudioExtradata( model_t *mod );
model_t *Mod_FindName( const char *name, qboolean trackCRC );
model_t *Mod_LoadModel( model_t *mod, qboolean crash );
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 );

View File

@@ -292,7 +292,7 @@ Mod_LoadModel
Loads a model into the cache
==================
*/
model_t *Mod_LoadModel( model_t *mod, qboolean crash )
static model_t *Mod_LoadModel( model_t *mod, qboolean crash )
{
char tempname[MAX_QPATH];
fs_offset_t length = 0;

View File

@@ -615,41 +615,43 @@ uint MSG_ReadUBitLong( sizebuf_t *sb, int numbits )
return ret;
}
qboolean MSG_ReadBits( sizebuf_t *sb, void *pOutData, int nBits )
qboolean MSG_ReadBits( sizebuf_t *sb, void *out, size_t maxBytes, int bits )
{
byte *pOut = (byte *)pOutData;
int nBitsLeft = nBits;
byte *p = (byte *)out;
int left = bits;
if((size_t)bits > ( maxBytes << 3 ))
return false;
// get output dword-aligned.
while((( uintptr_t )pOut & 3) != 0 && nBitsLeft >= 8 )
while((( uintptr_t )p & 3) != 0 && left >= 8 )
{
*pOut = (byte)MSG_ReadUBitLong( sb, 8 );
++pOut;
nBitsLeft -= 8;
*p = (byte)MSG_ReadUBitLong( sb, 8 );
++p;
left -= 8;
}
// read dwords.
while( nBitsLeft >= 32 )
while( left >= 32 )
{
uint32_t dword = MSG_ReadUBitLong( sb, 32 );
*((uint32_t *)pOut) = LittleLong( dword );
pOut += sizeof( uint32_t );
nBitsLeft -= 32;
dword = LittleLong( dword );
memcpy( p, &dword, sizeof( dword ));
p += sizeof( dword );
left -= 32;
}
// read the remaining bytes.
while( nBitsLeft >= 8 )
while( left >= 8 )
{
*pOut = MSG_ReadUBitLong( sb, 8 );
++pOut;
nBitsLeft -= 8;
*p = MSG_ReadUBitLong( sb, 8 );
++p;
left -= 8;
}
// read the remaining bits.
if( nBitsLeft )
{
*pOut = MSG_ReadUBitLong( sb, nBitsLeft );
}
if( left )
*p = MSG_ReadUBitLong( sb, left );
return !sb->bOverflow;
}
@@ -792,9 +794,9 @@ float MSG_ReadFloat( sizebuf_t *sb )
return UintAsFloat( MSG_ReadUBitLong( sb, sizeof( float ) << 3 ));
}
qboolean MSG_ReadBytes( sizebuf_t *sb, void *pOut, int nBytes )
qboolean MSG_ReadBytes( sizebuf_t *sb, void *out, size_t maxBytes, int bytes )
{
return MSG_ReadBits( sb, pOut, nBytes << 3 );
return MSG_ReadBits( sb, out, maxBytes, bytes << 3 );
}
static char *MSG_ReadStringExt( sizebuf_t *sb, qboolean bLine )
@@ -932,7 +934,7 @@ static void Test_Buffer_Read( void )
TASSERT_EQp( sb.pData, (void *)g_testbuf );
TASSERT_EQi( sb.bOverflow, false );
MSG_ReadBytes( &sb, buf, 4 );
MSG_ReadBytes( &sb, buf, sizeof( buf ), 4 );
TASSERT( !memcmp( buf, "asdf", 4 ));
TASSERT_EQi( sb.iCurBit, 32 );
TASSERT_EQi( sb.bOverflow, false );

View File

@@ -235,7 +235,7 @@ qboolean MSG_WriteBytes( sizebuf_t *sb, const void *pBuf, int nBytes );
// Bit-read functions
int MSG_ReadOneBit( sizebuf_t *sb );
qboolean MSG_ReadBits( sizebuf_t *sb, void *pOutData, int nBits );
qboolean MSG_ReadBits( sizebuf_t *sb, void *out, size_t maxBytes, int bits );
float MSG_ReadBitAngle( sizebuf_t *sb, int numbits );
int MSG_ReadSBitLong( sizebuf_t *sb, int numbits );
uint MSG_ReadUBitLong( sizebuf_t *sb, int numbits );
@@ -257,6 +257,6 @@ void MSG_ReadVec3Coord( sizebuf_t *sb, vec3_t fa );
void MSG_ReadVec3Angles( sizebuf_t *sb, vec3_t fa );
char *MSG_ReadString( sizebuf_t *sb ) RETURNS_NONNULL;
char *MSG_ReadStringLine( sizebuf_t *sb ) RETURNS_NONNULL;
qboolean MSG_ReadBytes( sizebuf_t *sb, void *pOut, int nBytes );
qboolean MSG_ReadBytes( sizebuf_t *sb, void *out, size_t maxBytes, int bytes );
#endif//NET_BUFFER_H

View File

@@ -94,6 +94,7 @@ CVAR_DEFINE_AUTO( net_showpackets, "0", FCVAR_PRIVILEGED, "show network packets"
static CVAR_DEFINE_AUTO( net_chokeloop, "0", 0, "apply bandwidth choke to loopback packets" );
static CVAR_DEFINE_AUTO( net_showdrop, "0", 0, "show packets that are dropped" );
static CVAR_DEFINE_AUTO( net_qport, "0", FCVAR_READ_ONLY, "current quake netport" );
static CVAR_DEFINE_AUTO( net_sequence_window, "256", 0, "reject sequenced packets that jump more than this many sequences ahead (anti-spoofing; 0 disables)" );
CVAR_DEFINE_AUTO( net_send_debug, "0", FCVAR_PRIVILEGED, "enable debugging output for outgoing messages" );
CVAR_DEFINE_AUTO( net_recv_debug, "0", FCVAR_PRIVILEGED, "enable debugging output for incoming messages" );
@@ -173,6 +174,7 @@ void Netchan_Init( void )
Cvar_RegisterVariable( &net_chokeloop );
Cvar_RegisterVariable( &net_showdrop );
Cvar_RegisterVariable( &net_qport );
Cvar_RegisterVariable( &net_sequence_window );
Cvar_RegisterVariable( &net_send_debug );
Cvar_RegisterVariable( &net_recv_debug );
Cvar_FullSet( net_qport.name, buf, net_qport.flags );
@@ -239,7 +241,16 @@ void Netchan_Setup( netsrc_t sock, netchan_t *chan, netadr_t adr, int qport, voi
chan->last_received = host.realtime;
chan->connect_time = host.realtime;
chan->incoming_sequence = 0;
chan->outgoing_sequence = 1;
// the server picks a random initial outgoing sequence so a remote attacker can't guess where in the sequence space the channel is
// kept well below BIT( 30 ) — bits 30/31 are reserved for the flags
// FIXME: BIT( 27 ) taken so in the worst case we have few months of stable client<->server connection
// as netchan doesn't currently handle wrapping around
if( sock == NS_SERVER )
chan->outgoing_sequence = COM_RandomLong( 1, BIT( 27 ) - 1 );
else
chan->outgoing_sequence = 1;
chan->rate = DEFAULT_RATE;
chan->qport = qport;
chan->client = client;
@@ -248,10 +259,24 @@ void Netchan_Setup( netsrc_t sock, netchan_t *chan, netadr_t adr, int qport, voi
chan->use_bz2 = FBitSet( flags, NETCHAN_USE_BZIP2 ) ? true : false;
chan->use_lzss = FBitSet( flags, NETCHAN_USE_LZSS ) ? true : false;
chan->gs_netchan = FBitSet( flags, NETCHAN_GOLDSRC ) ? true : false;
chan->use_cookie = FBitSet( flags, NETCHAN_USE_COOKIE ) ? true : false;
chan->cookie = 0;
MSG_Init( &chan->message, "NetData", chan->message_buf, sizeof( chan->message_buf ));
}
/*
==============
Netchan_SetCookie
called on the client after parsing NET_EXT_NETCHAN_COOKIE in the connect reply
==============
*/
void Netchan_SetCookie( netchan_t *chan, uint64_t cookie )
{
chan->cookie = cookie;
}
/*
==============================
Netchan_IncomingReady
@@ -984,6 +1009,17 @@ int Netchan_CreateFileFragments( netchan_t *chan, const char *filename )
Mem_Free( uncompressed );
}
// filename string + null terminator; for gs_netchan also compressor string + null + uint32 original size
int header_size = Q_strlen( filename ) + 1;
if( chan->gs_netchan )
header_size += Q_strlen( compressor ) + 1 + 4;
if( unlikely( chunksize < 0 || chunksize < header_size + 1 ))
{
Con_Printf( S_ERROR "%s: could not fit header for \"%s\" (%d bytes) into chunk of length %d\n", NET_AdrToString( chan->remote_address ), filename, header_size, chunksize );
return 0;
}
fragbufwaiting_t *wait = (fragbufwaiting_t *)Mem_Calloc( net_mempool, sizeof( fragbufwaiting_t ));
int remaining = filesize;
int pos = 0;
@@ -992,6 +1028,9 @@ int Netchan_CreateFileFragments( netchan_t *chan, const char *filename )
{
int send = Q_min( remaining, chunksize );
if( firstfragment )
send = Q_min( header_size + remaining, chunksize );
buf = Netchan_AllocFragbuf( send );
buf->bufferid = bufferid++;
@@ -1000,7 +1039,7 @@ int Netchan_CreateFileFragments( netchan_t *chan, const char *filename )
if( firstfragment )
{
// Write filename
// write filename
MSG_WriteString( &buf->frag_message, filename );
// write compressor name and uncompressed size
@@ -1010,9 +1049,11 @@ int Netchan_CreateFileFragments( netchan_t *chan, const char *filename )
MSG_WriteLong( &buf->frag_message, (uint)originalSize );
}
// Send a bit less on first package
send -= MSG_GetNumBytesWritten( &buf->frag_message );
if( unlikely( MSG_GetNumBytesWritten( &buf->frag_message ) != header_size ))
Con_Printf( S_ERROR "%s: header size mismatch for \"%s\" (%d vs %d)\n", NET_AdrToString( chan->remote_address ), filename, header_size, MSG_GetNumBytesWritten( &buf->frag_message ));
// send a bit less on first package
send -= MSG_GetNumBytesWritten( &buf->frag_message );
firstfragment = false;
}
@@ -1074,8 +1115,6 @@ Netchan_CopyNormalFragments
*/
qboolean Netchan_CopyNormalFragments( netchan_t *chan, sizebuf_t *msg, size_t *length )
{
size_t size = 0;
if( !chan->incomingready[FRAG_NORMAL_STREAM] )
return false;
@@ -1095,56 +1134,56 @@ qboolean Netchan_CopyNormalFragments( netchan_t *chan, sizebuf_t *msg, size_t *l
// copy it in
MSG_WriteBytes( msg, MSG_GetData( &p->frag_message ), MSG_GetNumBytesWritten( &p->frag_message ));
size += MSG_GetNumBytesWritten( &p->frag_message );
Mem_Free( p );
p = n;
}
if( chan->use_bz2 && !memcmp( MSG_GetData( msg ), "BZ2", 4 ))
// consumed buffer, flush
chan->incomingbufs[FRAG_NORMAL_STREAM] = NULL;
chan->incomingready[FRAG_NORMAL_STREAM] = false;
if( MSG_Overflow( msg, 0 ))
{
Con_Printf( S_ERROR "%s: net_message_buffer overflow!\n", __func__ );
return false;
}
size_t size = MSG_GetNumBytesWritten( msg );
if( chan->use_bz2 && size >= 4 && !memcmp( MSG_GetData( msg ), "BZ2", 4 ))
{
#if !XASH_DEDICATED
byte buf[0x10000];
uint uDecompressedLen = sizeof( buf );
int bz2_err = BZ2_bzBuffToBuffDecompress( buf, &uDecompressedLen, MSG_GetData( msg ) + 4, MSG_GetNumBytesWritten( msg ) - 4, 1, 0 );
int bz2_err = BZ2_bzBuffToBuffDecompress( buf, &uDecompressedLen, MSG_GetData( msg ) + 4, size - 4, 1, 0 );
if( bz2_err == BZ_OK )
{
size = uDecompressedLen;
memcpy( msg->pData, buf, size );
}
else
if( bz2_err != BZ_OK )
{
Con_Printf( S_ERROR "%s: BZ2 decompression failed (%d)\n", __func__, bz2_err );
return false;
}
size = uDecompressedLen;
memcpy( msg->pData, buf, size );
#else
Host_Error( "%s: BZ2 compression is not supported for server\n", __func__ );
#endif
}
else if( chan->use_lzss && LZSS_IsCompressed( MSG_GetData( msg ), size ))
{
uint uDecompressedLen = LZSS_GetActualSize( MSG_GetData( msg ), size );
byte buf[NET_MAX_MESSAGE];
byte buf[NET_MAX_MESSAGE];
uint uDecompressedLen = LZSS_GetActualSize( MSG_GetData( msg ), size );
if( uDecompressedLen <= sizeof( buf ))
if( uDecompressedLen == 0 || uDecompressedLen > sizeof( buf ))
{
size = LZSS_Decompress( MSG_GetData( msg ), buf, size, sizeof( buf ));
memcpy( msg->pData, buf, size );
}
else
{
// g-cont. this should not happens
Con_Printf( S_ERROR "buffer to small to decompress message\n" );
Con_Printf( S_ERROR "LZSS fragment uncompressed size out of range: %u\n", uDecompressedLen );
return false;
}
size = LZSS_Decompress( MSG_GetData( msg ), buf, size, sizeof( buf ));
memcpy( msg->pData, buf, size );
}
chan->incomingbufs[FRAG_NORMAL_STREAM] = NULL;
// reset flag
chan->incomingready[FRAG_NORMAL_STREAM] = false;
// tell about message size
if( length ) *length = size;
@@ -1160,7 +1199,7 @@ Netchan_CopyFileFragments
qboolean Netchan_CopyFileFragments( netchan_t *chan, sizebuf_t *msg )
{
char filename[MAX_OSPATH], compressor[32];
uint uncompressedSize;
uint uncompressedSize = 0;
if( !chan->incomingready[FRAG_FILE_STREAM] )
return false;
@@ -1177,6 +1216,7 @@ qboolean Netchan_CopyFileFragments( netchan_t *chan, sizebuf_t *msg )
// copy in first chunk so we can get filename out
MSG_WriteBytes( msg, MSG_GetData( &p->frag_message ), MSG_GetNumBytesWritten( &p->frag_message ));
msg->nDataBits = msg->iCurBit; // tighten the NetMessage buffer to amount read from frag_message
MSG_Clear( msg );
Q_strncpy( filename, MSG_ReadString( msg ), sizeof( filename ));
@@ -1187,13 +1227,20 @@ qboolean Netchan_CopyFileFragments( netchan_t *chan, sizebuf_t *msg )
uncompressedSize = MSG_ReadLong( msg );
}
if( MSG_CheckOverflow( msg ))
{
Con_Printf( S_ERROR "%s: malformed file fragment header\n", __func__ );
Netchan_FlushIncoming( chan, FRAG_FILE_STREAM );
return false;
}
if( COM_StringEmptyOrNULL( filename ))
{
Con_Printf( S_ERROR "file fragment received with no filename\nFlushing input queue\n" );
Netchan_FlushIncoming( chan, FRAG_FILE_STREAM );
return false;
}
else if( filename[0] != '!' && ( COM_CheckNastyPath( filename ) || !COM_IsSafeFileToDownload( filename )))
else if( COM_CheckNastyPath( filename ) || !COM_IsSafeFileToDownload( filename ))
{
Con_Printf( S_ERROR "file fragment received with bad path, ignoring\n" );
Netchan_FlushIncoming( chan, FRAG_FILE_STREAM );
@@ -1258,6 +1305,10 @@ qboolean Netchan_CopyFileFragments( netchan_t *chan, sizebuf_t *msg )
p = n;
}
// buffers consumed, now reset incomingbufs
chan->incomingbufs[FRAG_FILE_STREAM] = NULL;
chan->incomingready[FRAG_FILE_STREAM] = false;
if( chan->gs_netchan && chan->use_bz2 && !Q_stricmp( compressor, "bz2" ))
{
#if !XASH_DEDICATED
@@ -1265,7 +1316,6 @@ qboolean Netchan_CopyFileFragments( netchan_t *chan, sizebuf_t *msg )
{
Con_Printf( S_ERROR "BZ2 fragment uncompressed size out of range: %u for %s\n", uncompressedSize, filename );
Mem_Free( buffer );
Netchan_FlushIncoming( chan, FRAG_FILE_STREAM );
return false;
}
@@ -1277,7 +1327,6 @@ qboolean Netchan_CopyFileFragments( netchan_t *chan, sizebuf_t *msg )
Con_DPrintf( S_ERROR "BZ2 decompression failed for %s\n", filename );
Mem_Free( buffer );
Mem_Free( uncompressedBuffer );
Netchan_FlushIncoming( chan, FRAG_FILE_STREAM );
return false;
}
Mem_Free( buffer );
@@ -1295,7 +1344,6 @@ qboolean Netchan_CopyFileFragments( netchan_t *chan, sizebuf_t *msg )
{
Con_Printf( S_ERROR "LZSS fragment uncompressed size out of range: %u for %s\n", uncompressedSize, filename );
Mem_Free( buffer );
Netchan_FlushIncoming( chan, FRAG_FILE_STREAM );
return false;
}
@@ -1325,14 +1373,14 @@ qboolean Netchan_CopyFileFragments( netchan_t *chan, sizebuf_t *msg )
// clear remnants
MSG_Clear( msg );
chan->incomingbufs[FRAG_FILE_STREAM] = NULL;
chan->incomingready[FRAG_FILE_STREAM] = false;
return true;
}
static qboolean Netchan_Validate( netchan_t *chan, sizebuf_t *sb, qboolean *frag_message, uint *fragid, int *frag_offset, int *frag_length )
{
int bits_read = MSG_GetNumBitsRead( sb );
int bits_total = MSG_GetMaxBits( sb );
for( int i = 0; i < MAX_STREAMS; i++ )
{
if( !frag_message[i] )
@@ -1340,8 +1388,6 @@ static qboolean Netchan_Validate( netchan_t *chan, sizebuf_t *sb, qboolean *frag
int buffer = FRAG_GETID( fragid[i] );
int count = FRAG_GETCOUNT( fragid[i] );
int offset = BitByte( frag_offset[i] );
int length = BitByte( frag_length[i] );
if( buffer < 0 || buffer > NET_MAX_BUFFER_ID )
return false;
@@ -1349,10 +1395,13 @@ static qboolean Netchan_Validate( netchan_t *chan, sizebuf_t *sb, qboolean *frag
if( count < 0 || count > NET_MAX_BUFFERS_COUNT )
return false;
if( length < 0 || length > ( FRAGMENT_MAX_SIZE << 3 ))
if( frag_offset[i] < 0 || frag_offset[i] > ( FRAGMENT_MAX_SIZE << 3 ))
return false;
if( offset < 0 || offset > ( FRAGMENT_MAX_SIZE << 3 ))
if( frag_length[i] < 0 || frag_length[i] > ( FRAGMENT_MAX_SIZE << 3 ))
return false;
if( bits_read + frag_offset[i] + frag_length[i] > bits_total )
return false;
}
@@ -1652,6 +1701,14 @@ void Netchan_TransmitBits( netchan_t *chan, int length, const byte *data )
chan->outgoing_sequence++;
// prefix the cookie so the peer can authenticate this packet as ours
// before doing anything else with it
if( chan->use_cookie )
{
MSG_WriteLong( &send, (uint)( chan->cookie & 0xFFFFFFFF ));
MSG_WriteLong( &send, (uint)( chan->cookie >> 32 ));
}
MSG_WriteLong( &send, w1 );
MSG_WriteLong( &send, w2 );
@@ -1776,15 +1833,42 @@ qboolean Netchan_Process( netchan_t *chan, sizebuf_t *msg )
// get sequence numbers
MSG_Clear( msg );
// authenticate via the per-connection cookie before parsing anything else;
// a spoofed packet from a remote attacker won't know the 64-bit cookie and
// will be rejected here without touching sequence/ack state
if( chan->use_cookie )
{
if( MSG_GetMaxBytes( msg ) < 16 )
{
Con_Reportf( S_WARN "%s: %s: truncated packet (%d bytes) with cookie expected, dropping\n", __func__, NET_AdrToString( chan->remote_address ), MSG_GetMaxBytes( msg ));
return false;
}
uint32_t cookie_lo = MSG_ReadDword( msg );
uint32_t cookie_hi = MSG_ReadDword( msg );
uint64_t cookie = ((uint64_t)cookie_hi << 32 ) | (uint64_t)cookie_lo;
if( cookie != chan->cookie )
{
Con_Reportf( S_WARN "%s: %s: cookie mismatch, dropping (possible spoof attempt)\n", __func__, NET_AdrToString( chan->remote_address ));
return false;
}
}
uint sequence = MSG_ReadLong( msg );
uint sequence_ack = MSG_ReadLong( msg );
if( chan->use_munge )
COM_UnMunge2( msg->pData + 8, ( msg->nDataBits >> 3 ) - 8, sequence & 0xFF );
if( chan->use_munge && MSG_GetMaxBytes( msg ) >= 8 )
COM_UnMunge2( msg->pData + 8, MSG_GetMaxBytes( msg ) - 8, sequence & 0xFF );
// read the qport if we are a server
// read the qport if we are a server; serves as a NAT-stable
// connection demultiplexer and rejects packets for the wrong client
if( chan->sock == NS_SERVER )
MSG_ReadShort( msg );
{
if(( MSG_ReadShort( msg ) & 0xffff ) != chan->qport )
return false;
}
uint reliable_message = sequence >> 31;
uint reliable_ack = sequence_ack >> 31;
@@ -1846,6 +1930,20 @@ qboolean Netchan_Process( netchan_t *chan, sizebuf_t *msg )
return false;
}
// reject packets that leap too far ahead of the expected sequence
// skip on the very first packet — the server starts with a random
// outgoing_sequence, so the first one legitimately jumps far ahead of 0
// NOTE: disable sequence window with cookie extension, if cookie ext proves
// to be inefficient, we can safely enable sequence window back
if( !chan->use_cookie && chan->incoming_sequence != 0 && net_sequence_window.value > 0 && sequence > chan->incoming_sequence + (uint)net_sequence_window.value )
{
Con_Printf( S_WARN "%s: %s: sequence %u jumps %u ahead of expected %i (window %i), dropping\n",
__func__, NET_AdrToString( chan->remote_address ),
sequence, sequence - chan->incoming_sequence,
chan->incoming_sequence, (int)net_sequence_window.value );
return false;
}
// dropped packets don't keep the message from being used
net_drop = sequence - ( chan->incoming_sequence + 1 );
if( net_drop > 0 && net_showdrop.value )
@@ -1909,7 +2007,7 @@ qboolean Netchan_Process( netchan_t *chan, sizebuf_t *msg )
MSG_Clear( &pbuf->frag_message );
MSG_StartReading( &temp, msg->pData, MSG_GetMaxBytes( msg ), size, -1 );
MSG_ReadBits( &temp, buffer, bits );
MSG_ReadBits( &temp, buffer, sizeof( buffer ), bits );
MSG_WriteBits( &pbuf->frag_message, buffer, bits );
}

View File

@@ -75,16 +75,16 @@ typedef struct
#pragma pack(push, 1)
typedef struct
{
int net_id;
int sequence_number;
short packet_id;
int net_id;
int sequence_number;
unsigned short packet_id;
} SPLITPACKET;
typedef struct
{
int net_id;
int sequence_number;
unsigned char packet_id;
int net_id;
int sequence_number;
unsigned char packet_id;
} SPLITPACKETGS;
#pragma pack(pop)
@@ -169,6 +169,12 @@ qboolean NET_MakeSocketNonBlocking( int socket_fd )
return true;
}
qboolean NET_MakeSocketReuseAddr( int socket_fd )
{
uint opt = 1;
return !NET_IsSocketError( setsockopt( socket_fd, SOL_SOCKET, SO_REUSEADDR, (const char *)&opt, sizeof( opt )));
}
void NET_NetadrToIP6Bytes( uint8_t *ip6, const netadr_t *adr )
{
memcpy( &ip6[0], adr->ip6_0, 2 );
@@ -770,6 +776,16 @@ NET_IsReservedAdr
Check for reserved ip's
====================
*/
static qboolean NET_IsReservedIPv4( const uint8_t ip[4] )
{
// Following checks was imported from GameNetworkingSockets library
return ( ip[0] == 10 ) // 10.x.x.x is reserved
|| ( ip[0] == 127 ) // 127.x.x.x
|| ( ip[0] == 169 && ip[1] == 254 ) // 169.254.x.x is link-local ipv4
|| ( ip[0] == 172 && ip[1] >= 16 && ip[1] <= 31 ) // 172.16.x.x - 172.31.x.x
|| ( ip[0] == 192 && ip[1] == 168 ); // 192.168.x.x
}
qboolean NET_IsReservedAdr( netadr_t a )
{
netadrtype_t type_a = NET_NetadrType( &a );
@@ -777,18 +793,8 @@ qboolean NET_IsReservedAdr( netadr_t a )
if( type_a == NA_LOOPBACK )
return true;
// Following checks was imported from GameNetworkingSockets library
if( type_a == NA_IP )
{
if(( a.ip[0] == 10 ) || // 10.x.x.x is reserved
( a.ip[0] == 127 ) || // 127.x.x.x
( a.ip[0] == 169 && a.ip[1] == 254 ) || // 169.254.x.x is link-local ipv4
( a.ip[0] == 172 && a.ip[1] >= 16 && a.ip[1] <= 31 ) || // 172.16.x.x - 172.31.x.x
( a.ip[0] == 192 && a.ip[1] >= 168 )) // 192.168.x.x
{
return true;
}
}
return NET_IsReservedIPv4( a.ip );
if( type_a == NA_IP6 )
{
@@ -796,19 +802,18 @@ qboolean NET_IsReservedAdr( netadr_t a )
NET_NetadrToIP6Bytes( ip6, &a );
// IPv4-mapped IPv6 (::ffff:0:0/96) — defer to IPv4 reservation check
static const uint8_t v4mapped_prefix[12] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF };
if( !memcmp( ip6, v4mapped_prefix, sizeof( v4mapped_prefix )))
return NET_IsReservedIPv4( &ip6[12] );
// Private addresses, fc00::/7
// Range is fc00:: to fdff:ffff:etc
if( ip6[0] >= 0xFC && ip6[1] <= 0xFD )
{
if(( ip6[0] & 0xFE ) == 0xFC )
return true;
}
// Link-local fe80::/10
// Range is fe80:: to febf::
if( ip6[0] == 0xFE && ( ip6[1] >= 0x80 && ip6[1] <= 0xBF ))
{
if( ip6[0] == 0xFE && ( ip6[1] & 0xC0 ) == 0x80 )
return true;
}
}
return false;
@@ -1231,8 +1236,8 @@ static qboolean NET_GetLong( byte *pData, size_t size, size_t *outSize, size_t s
return false;
}
int sequence_number, packet_count, packet_number, max_splits;
short packet_id;
int sequence_number, packet_count, packet_number, max_splits;
unsigned short packet_id;
if( proto == PROTO_GOLDSRC )
{
SPLITPACKETGS *pHeader = (SPLITPACKETGS *)pData;
@@ -1358,7 +1363,11 @@ static qboolean NET_QueuePacket( int net_socket, netsrc_t sock, netadr_t *from,
#if !XASH_DEDICATED
// check for split message
if( sock == NS_CLIENT && *(int *)data == NET_HEADER_SPLITPACKET )
{
if( !CL_IsFromConnectingServer( *from ))
return false;
return NET_GetLong( data, ret, length, CL_GetSplitSize( ), CL_Protocol( ));
}
#endif
// lag the packet, if needed
@@ -1606,7 +1615,7 @@ static int NET_IPSocket( const char *net_iface, int port, int family )
Con_DPrintf( S_WARN "%s: port: %d setsockopt SO_BROADCAST: %s\n", __func__, port, NET_ErrorString( ));
}
if( NET_IsSocketError( setsockopt( net_socket, SOL_SOCKET, SO_REUSEADDR, (const char *)&optval, sizeof( optval ))))
if( !NET_MakeSocketReuseAddr( net_socket ))
{
Con_DPrintf( S_WARN "%s: port: %d setsockopt SO_REUSEADDR: %s\n", __func__, port, NET_ErrorString( ));
closesocket( net_socket );
@@ -1768,8 +1777,8 @@ static void NET_DetermineLocalAddress( void )
struct sockaddr_storage address;
WSAsize_t namelen;
memset( &net_local, 0, sizeof( netadr_t ));
memset( &net6_local, 0, sizeof( netadr_t ));
memset( &net_local, 0, sizeof( net_local ));
memset( &net6_local, 0, sizeof( net6_local ));
if( !net.allow_ip && !net.allow_ip6 )
{

View File

@@ -77,6 +77,7 @@ 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 );
qboolean NET_MakeSocketReuseAddr( int socket_fd );
static inline qboolean NET_IsLocalAddress( netadr_t adr )
{
@@ -87,6 +88,7 @@ void NET_GetLocalAddress( netadr_t *ip4, netadr_t *ip6 );
#if !XASH_DEDICATED
size_t CL_GetSplitSize( void );
qboolean CL_IsFromConnectingServer( netadr_t from );
#endif
void HTTP_AddCustomServer( const char *url );
@@ -97,4 +99,7 @@ void HTTP_ResetProcessState( void );
void HTTP_Init( void );
void HTTP_Run( void );
typedef void ( *http_memory_cb_t )( const char *url, qboolean success, const byte *data, size_t size, void *userdata );
qboolean HTTP_GetToMemory( const char *url, http_memory_cb_t cb, void *userdata );
#endif//NET_WS_H

View File

@@ -167,10 +167,11 @@ typedef enum fragsize_e
typedef enum netchan_flags_e
{
NETCHAN_USE_MUNGE = BIT( 0 ),
NETCHAN_USE_BZIP2 = BIT( 1 ),
NETCHAN_GOLDSRC = BIT( 2 ),
NETCHAN_USE_LZSS = BIT( 3 ), // mutually exclusive with bzip2
NETCHAN_USE_MUNGE = BIT( 0 ),
NETCHAN_USE_BZIP2 = BIT( 1 ),
NETCHAN_GOLDSRC = BIT( 2 ),
NETCHAN_USE_LZSS = BIT( 3 ), // mutually exclusive with bzip2
NETCHAN_USE_COOKIE = BIT( 4 ), // per-connection 64-bit cookie prefixed to every sequenced packet (NET_EXT_NETCHAN_COOKIE)
} netchan_flags_t;
// Network Connection Channel
@@ -240,6 +241,8 @@ typedef struct netchan_s
qboolean use_bz2;
qboolean use_lzss;
qboolean gs_netchan;
qboolean use_cookie;
uint64_t cookie;
} netchan_t;
extern netadr_t net_from;
@@ -252,6 +255,7 @@ extern int net_drop;
void Netchan_Init( void );
void Netchan_Shutdown( void );
void Netchan_Setup( netsrc_t sock, netchan_t *chan, netadr_t adr, int qport, void *client, int (*pfnBlockSize)(void *, fragsize_t mode ), uint flags );
void Netchan_SetCookie( netchan_t *chan, uint64_t cookie );
void Netchan_CreateFileFragmentsFromBuffer( netchan_t *chan, const char *filename, byte *pbuf, int size );
qboolean Netchan_CopyNormalFragments( netchan_t *chan, sizebuf_t *msg, size_t *length );
qboolean Netchan_CopyFileFragments( netchan_t *chan, sizebuf_t *msg );

View File

@@ -100,9 +100,6 @@ GNU General Public License for more details.
#define MAX_VISIBLE_PACKET_VIS_BYTES ((MAX_VISIBLE_PACKET + 7) / 8)
// additional protocol data
#define MAX_CLIENT_BITS 5
#define MAX_CLIENTS (1<<MAX_CLIENT_BITS)// 5 bits == 32 clients ( int32 limit )
#define MAX_WEAPON_BITS 6
#define MAX_WEAPONS (1<<MAX_WEAPON_BITS)// 6 bits == 64 predictable weapons
@@ -283,7 +280,8 @@ extern const char *const svc_quake_strings[svc_lastmsg+1];
extern const char *const svc_goldsrc_strings[svc_lastmsg+1];
// FWGS extensions
#define NET_EXT_SPLITSIZE (1U<<0) // set splitsize by cl_dlmax
#define NET_EXT_SPLITSIZE (1U<<0) // set splitsize by cl_dlmax
#define NET_EXT_NETCHAN_COOKIE (1U<<1) // per-connection 64-bit netchan cookie validated on every sequenced packet
// GoldSrc protocol definitions
#define PROTOCOL_GOLDSRC_VERSION 48
@@ -332,6 +330,7 @@ extern const char *const svc_goldsrc_strings[svc_lastmsg+1];
// from server to any
#define S2A_GOLDSRC_INFO 'I'
#define S2A_GOLDSRC_LEGACY_INFO 'm'
#define S2A_GOLDSRC_RULES 'E'
#define S2A_GOLDSRC_PLAYERS 'D'

View File

@@ -95,8 +95,8 @@ void Sys_InitLog( void )
const char *mode;
if( host.change_game && host.type != HOST_DEDICATED )
mode = "a";
else mode = "w";
mode = "a+";
else mode = "w+";
if( Host_IsDedicated( ))
Q_strncpy( s_ld.title, XASH_DEDICATED_SERVER_NAME " " XASH_VERSION, sizeof( s_ld.title ));
@@ -167,7 +167,6 @@ void Sys_CloseLog( const char *finalmsg )
s_ld.logfile = NULL;
}
#if XASH_COLORIZE_CONSOLE
static qboolean Sys_WriteEscapeSequenceForColorcode( int fd, int c )
{
static const char *q3ToAnsi[ 8 ] =
@@ -185,14 +184,8 @@ static qboolean Sys_WriteEscapeSequenceForColorcode( int fd, int c )
return write( fd, esc, c == 7 ? 4 : 7 ) < 0 ? false : true;
}
#else
static qboolean Sys_WriteEscapeSequenceForColorcode( int fd, int c )
{
return true;
}
#endif
static void Sys_PrintLogfile( const int fd, const char *logtime, size_t logtime_len, const char *msg, const int colorize )
static void Sys_PrintLogfile( const int fd, const char *logtime, size_t logtime_len, const char *msg, qboolean colorize )
{
const char *p = msg;
@@ -204,6 +197,17 @@ static void Sys_PrintLogfile( const int fd, const char *logtime, size_t logtime_
}
}
if( !colorize )
{
if( write( fd, msg, Q_strlen( msg )) < 0 )
{
// don't call engine Msg, might cause recursion
fprintf( stderr, "%s: write failed: %s\n", __func__, strerror( errno ));
}
return;
}
while( p && *p )
{
p = Q_strchr( msg, '^' );
@@ -226,8 +230,7 @@ static void Sys_PrintLogfile( const int fd, const char *logtime, size_t logtime_
}
msg = p + 2;
if( colorize )
Sys_WriteEscapeSequenceForColorcode( fd, ColorIndex( p[1] ));
Sys_WriteEscapeSequenceForColorcode( fd, ColorIndex( p[1] ));
}
else
{
@@ -238,73 +241,93 @@ static void Sys_PrintLogfile( const int fd, const char *logtime, size_t logtime_
}
// flush the color
if( colorize )
Sys_WriteEscapeSequenceForColorcode( fd, 7 );
Sys_WriteEscapeSequenceForColorcode( fd, 7 );
}
static void Sys_PrintStdout( const char *logtime, size_t logtime_len, const char *msg )
static void Sys_WriteLogfile( int fd, const char *logtime, size_t logtime_len, const char *msg )
{
#if XASH_MOBILE_PLATFORM
static char buf[MAX_PRINT_MSG];
if( logtime_len != 0 )
{
if( write( fd, logtime, logtime_len ) < 0 )
{
// not critical for us
}
}
// strip color codes
COM_StripColors( msg, buf );
if( write( fd, msg, Q_strlen( msg )) < 0 )
{
// not critical for us
}
}
// platform-specific output
static void Sys_PrintStdout( const char *logtime, size_t logtime_len, const char *msg, const char *stripped )
{
#if XASH_ANDROID && !XASH_DEDICATED
__android_log_write( ANDROID_LOG_INFO, "Xash", buf );
__android_log_write( ANDROID_LOG_INFO, "Xash", stripped );
#endif // XASH_ANDROID && !XASH_DEDICATED
#if TARGET_OS_IOS
void IOS_Log( const char * );
IOS_Log( buf );
IOS_Log( stripped );
#endif // TARGET_OS_IOS
#if XASH_NSWITCH && NSWITCH_DEBUG
// just spew it to stderr normally in debug mode
fprintf( stderr, "%s %s", logtime, buf );
fprintf( stderr, "%s %s", logtime, stripped );
#endif // XASH_NSWITCH && NSWITCH_DEBUG
#if XASH_PSVITA
// spew to stderr only in developer mode
if( host_developer.value )
{
fprintf( stderr, "%s %s", logtime, buf );
fprintf( stderr, "%s %s", logtime, stripped );
}
#endif
#elif !XASH_WIN32 // Wcon does the job
Sys_PrintLogfile( STDOUT_FILENO, logtime, logtime_len, msg, XASH_COLORIZE_CONSOLE );
#if !XASH_MOBILE_PLATFORM && !XASH_WIN32 // Wcon does the job
Sys_PrintLogfile( STDOUT_FILENO, logtime, logtime_len, XASH_COLORIZE_CONSOLE ? msg : stripped, XASH_COLORIZE_CONSOLE );
Sys_FlushStdout();
#endif
XRcon_Print( stripped );
}
void Sys_PrintLog( const char *pMsg )
{
const struct tm *crt_tm = NULL;
char logtime[32] = "";
static char lastchar;
qboolean print_time = false;
size_t logtime_len = 0;
const struct tm *crt_tm = NULL;
if( !lastchar || lastchar == '\n' )
{
time_t crt_time;
if( time( &crt_time ) >= 0 )
{
crt_tm = localtime( &crt_time );
print_time = crt_tm != NULL;
}
}
if( print_time )
char logtime[32] = "";
size_t logtime_len = 0;
if( crt_tm != NULL )
{
logtime_len = strftime( logtime, sizeof( logtime ), "[%H:%M:%S] ", crt_tm ); // short time
logtime_len = Q_min( logtime_len, sizeof( logtime ) - 1 ); // just in case
}
#if !XASH_WIN32 && !XASH_COLORIZE_CONSOLE
qboolean need_strip = true; // stdout sink can't render ^N, must strip first
#else
qboolean need_strip = s_ld.logfile != NULL || XRcon_IsActive();
#endif
const char *log_msg = pMsg;
if( need_strip )
{
static char stripped[MAX_PRINT_MSG];
COM_StripColors( pMsg, stripped );
log_msg = stripped;
}
// spew to stdout
Sys_PrintStdout( logtime, logtime_len, pMsg );
Sys_PrintStdout( logtime, logtime_len, pMsg, log_msg );
size_t len = Q_strlen( pMsg );
@@ -314,7 +337,7 @@ void Sys_PrintLog( const char *pMsg )
// spew to engine.log
if( s_ld.logfile )
{
if( s_ld.log_time && print_time )
if( s_ld.log_time && crt_tm != NULL )
{
logtime_len = strftime( logtime, sizeof( logtime ), "[%Y:%m:%d|%H:%M:%S] ", crt_tm ); //full time
logtime_len = Q_min( logtime_len, sizeof( logtime ) - 1 ); // just in case
@@ -325,7 +348,7 @@ void Sys_PrintLog( const char *pMsg )
logtime_len = 0;
}
Sys_PrintLogfile( s_ld.logfileno, logtime, logtime_len, pMsg, false );
Sys_WriteLogfile( s_ld.logfileno, logtime, logtime_len, log_msg );
Sys_FlushLogfile();
}
}

643
engine/common/xrcon.c Normal file
View File

@@ -0,0 +1,643 @@
/*
xrcon.c - implementation of XRCON remote console access server
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 "net_ws.h"
#include "net_ws_private.h"
#include "net_buffer.h"
#define XRCON_TYPE_CMND HostFourCC( 'C', 'M', 'N', 'D' )
#define XRCON_TYPE_CHAN HostFourCC( 'C', 'H', 'A', 'N' )
#define XRCON_TYPE_AINF HostFourCC( 'A', 'I', 'N', 'F' )
#define XRCON_TYPE_ADON HostFourCC( 'A', 'D', 'O', 'N' )
#define XRCON_TYPE_PRNT HostFourCC( 'P', 'R', 'N', 'T' )
#define XRCON_CMND_VERSION 0x000000D4
#define XRCON_CMND_VERSION_CS2RC 0x00D40000 // CS2RemoteConsole <= 1.3.0 byte-slot bug
// type (u32) + version (u32) + length (u16) + handle (u16)
#define XRCON_HEADER_SIZE ( sizeof( uint32_t ) * 2 + sizeof( uint16_t ) * 2 )
// hardcoded size for channel name, including null terminator
#define XRCON_CHAN_NAME_SIZE 34
// unknowns[19] (u32) + padding (u8)
#define XRCON_AINF_PACKET_SIZE ( sizeof( uint32_t ) * 19 + sizeof( uint8_t ) )
// channel_id (u32) + padding (u32[5]) + RGBA (u32)
#define XRCON_PRNT_HEADER_SIZE ( sizeof( uint32_t ) * 7 )
#define XRCON_MAX_FRAME_SIZE 4096 // an arbitrary number, should be enough for everything
#define XRCON_PRINT_BUFFER_SIZE 4096
#define XRCON_MAX_PACKET_SIZE ( XRCON_HEADER_SIZE + XRCON_PRNT_HEADER_SIZE + XRCON_PRINT_BUFFER_SIZE )
#define XRCON_TX_BUFFER_SIZE ( XRCON_MAX_FRAME_SIZE * 4 )
#define XRCON_RX_BUFFER_SIZE ( XRCON_MAX_FRAME_SIZE + 64 )
static CVAR_DEFINE_AUTO( xrcon_enable, "0", FCVAR_PRIVILEGED, "enable remote console access server" );
static CVAR_DEFINE_AUTO( xrcon_address, "127.0.0.1:27000", FCVAR_PRIVILEGED, "XRCON server bind address and port" );
static CVAR_DEFINE_AUTO( xrcon_flush_interval, "0.05", FCVAR_PRIVILEGED, "seconds between flushes of pending console output to the XRCON client" );
static CVAR_DEFINE_AUTO( xrcon_retry_delay, "5.0", FCVAR_PRIVILEGED, "seconds to wait before retrying to bind the XRCON listen socket after a failure" );
typedef enum
{
XRCON_STATE_IDLE,
XRCON_STATE_LISTENING,
XRCON_STATE_CONNECTED
} xrcon_state_t;
typedef enum
{
XRCON_PARSER_WAIT_HEADER,
XRCON_PARSER_WAIT_PAYLOAD
} xrcon_parser_state_t;
typedef struct
{
uint32_t type;
uint32_t version;
uint16_t length;
uint16_t handle;
} xrcon_frame_header_t;
typedef struct
{
netadr_t bindadr;
SOCKET listen_socket;
SOCKET client_socket;
xrcon_state_t state;
uint32_t tx_pos;
uint32_t rx_pos;
uint8_t tx_buffer[XRCON_TX_BUFFER_SIZE];
uint8_t rx_buffer[XRCON_RX_BUFFER_SIZE];
char print_buffer[XRCON_PRINT_BUFFER_SIZE];
uint32_t print_pos;
double print_flush_time;
double retry_timeout;
struct
{
xrcon_parser_state_t state;
xrcon_frame_header_t frame;
} parser;
} xrcon_t;
static xrcon_t xrcon;
static void XRcon_SetState( xrcon_state_t new_state )
{
xrcon.state = new_state;
}
static void XRcon_CloseListenSocket( void )
{
if( NET_IsSocketValid( xrcon.listen_socket ))
{
closesocket( xrcon.listen_socket );
xrcon.listen_socket = INVALID_SOCKET;
}
}
static qboolean XRcon_CloseClientSocket( void )
{
if( NET_IsSocketValid( xrcon.client_socket ))
{
closesocket( xrcon.client_socket );
xrcon.client_socket = INVALID_SOCKET;
return true;
}
return false;
}
static void XRcon_DisconnectClient( void )
{
xrcon.print_pos = 0;
xrcon.tx_pos = 0;
xrcon.rx_pos = 0;
xrcon.print_buffer[0] = '\0';
xrcon.parser.state = XRCON_PARSER_WAIT_HEADER;
qboolean was_active = XRcon_CloseClientSocket();
XRcon_SetState( XRCON_STATE_LISTENING );
if( was_active )
Con_Printf( S_NOTE "%s: client disconnected\n", __func__ );
}
static qboolean XRcon_SendPacket( uint32_t type, const void *body, size_t body_len )
{
size_t total_len = XRCON_HEADER_SIZE + body_len;
uint8_t frame[XRCON_MAX_PACKET_SIZE];
if( total_len > sizeof( frame ))
{
Con_Printf( S_WARN "%s: packet too large (%zu > %zu)\n", __func__, total_len, sizeof( frame ));
return false;
}
sizebuf_t sb;
MSG_Init( &sb, __func__, frame, sizeof( frame ));
MSG_WriteBytes( &sb, &type, 4 );
MSG_WriteDword( &sb, htonl( XRCON_CMND_VERSION ));
MSG_WriteWord( &sb, htons((short)total_len ));
MSG_WriteWord( &sb, 0 ); // handle
if( body && body_len > 0 )
MSG_WriteBytes( &sb, body, body_len );
size_t frame_size = MSG_GetRealBytesWritten( &sb );
int sent = send( xrcon.client_socket, (const char *)frame, frame_size, 0 );
if( NET_IsSocketError( sent ))
{
int err = WSAGetLastError();
if( err != WSAEWOULDBLOCK && err != WSAEALREADY )
{
const char *errstr = NET_ErrorString();
XRcon_DisconnectClient();
Con_Printf( S_ERROR "%s: send error %s\n", __func__, errstr );
return false;
}
sent = 0;
}
size_t unsent = frame_size - sent;
if( unsent > 0 )
{
size_t available = sizeof( xrcon.tx_buffer ) - xrcon.tx_pos;
if( available < unsent )
{
XRcon_DisconnectClient();
Con_Printf( S_ERROR "%s: transmit buffer overflow\n", __func__ );
return false;
}
memcpy( xrcon.tx_buffer + xrcon.tx_pos, frame + sent, unsent );
xrcon.tx_pos += unsent;
}
return true;
}
static void XRcon_SendCHAN( void )
{
sizebuf_t sb;
uint8_t body[256];
MSG_Init( &sb, __func__, body, sizeof( body ));
MSG_WriteWord( &sb, htons( 1 )); // channels count
MSG_WriteDword( &sb, 0 ); // id
MSG_WriteDword( &sb, 0 ); // unknown1
MSG_WriteDword( &sb, 0 ); // unknown2
MSG_WriteDword( &sb, htonl( 5 )); // verbosity_default
MSG_WriteDword( &sb, htonl( 5 )); // verbosity_current
// RGBA = white
MSG_WriteDword( &sb, 0xFFFFFFFF );
const char *channel_name = "Console";
MSG_WriteString( &sb, channel_name );
size_t name_padding = XRCON_CHAN_NAME_SIZE - Q_strlen( channel_name ) - 1;
for( size_t i = 0; i < name_padding; i++ )
MSG_WriteByte( &sb, 0 );
XRcon_SendPacket( XRCON_TYPE_CHAN, body, MSG_GetRealBytesWritten( &sb ));
}
static void XRcon_SendAINF( void )
{
uint8_t body[XRCON_AINF_PACKET_SIZE] = { 0 };
XRcon_SendPacket( XRCON_TYPE_AINF, body, sizeof( body ));
}
static void XRcon_SendADON( const char *name )
{
sizebuf_t sb;
uint8_t body[256];
size_t len = Q_strlen( name );
MSG_Init( &sb, __func__, body, sizeof( body ));
MSG_WriteWord( &sb, htons( 0 ));
MSG_WriteWord( &sb, htons( len ));
MSG_WriteBytes( &sb, name, len );
XRcon_SendPacket( XRCON_TYPE_ADON, body, MSG_GetRealBytesWritten( &sb ));
}
static void XRcon_HandleCMND( const char *command )
{
Con_Printf( S_NOTE "XRcon command: %s\n", command );
Cbuf_AddText( command );
Cbuf_AddText( "\n" );
}
static qboolean XRcon_UpdateBindAddress( void )
{
if( NET_NetadrType( &xrcon.bindadr ) == NA_UNDEFINED )
{
if( !NET_StringToAdr( xrcon_address.string, &xrcon.bindadr ))
return false;
}
return true;
}
static void XRcon_StartListening( void )
{
int addr_family;
struct sockaddr_storage addr = { 0 };
if( !XRcon_UpdateBindAddress( ))
{
Con_Printf( S_ERROR "%s: invalid address \"%s\"\n", __func__, xrcon_address.string );
return;
}
if( NET_NetadrType( &xrcon.bindadr ) == NA_IP )
addr_family = AF_INET;
else if( NET_NetadrType( &xrcon.bindadr ) == NA_IP6 )
addr_family = AF_INET6;
else
{
Con_Printf( S_ERROR "%s: unsupported address type for %s\n", __func__, xrcon_address.string );
return;
}
xrcon.listen_socket = socket( addr_family, SOCK_STREAM, IPPROTO_TCP );
if( !NET_IsSocketValid( xrcon.listen_socket ))
{
Con_Printf( S_ERROR "%s: failed to create listen socket\n", __func__ );
return;
}
if( !NET_MakeSocketNonBlocking( xrcon.listen_socket ))
{
Con_Printf( S_ERROR "%s: failed to set non-blocking mode, error %s\n", __func__, NET_ErrorString( ));
XRcon_CloseListenSocket();
return;
}
NET_MakeSocketReuseAddr( xrcon.listen_socket );
NET_NetadrToSockadr( &xrcon.bindadr, &addr );
if( bind( xrcon.listen_socket, (struct sockaddr *)&addr, NET_SockAddrLen( &addr )) == SOCKET_ERROR )
{
Con_Printf( S_ERROR "%s: bind to %s failed, error %s\n", __func__, xrcon_address.string, NET_ErrorString( ));
XRcon_CloseListenSocket();
return;
}
if( listen( xrcon.listen_socket, 1 ) == SOCKET_ERROR )
{
Con_Printf( S_ERROR "%s: listen failed, error %s\n", __func__, NET_ErrorString( ));
XRcon_CloseListenSocket();
return;
}
Con_Printf( S_NOTE "%s: started listening on %s\n", __func__, xrcon_address.string );
XRcon_SetState( XRCON_STATE_LISTENING );
}
static void XRcon_ProcessRxData( void )
{
while( true )
{
if( xrcon.parser.state == XRCON_PARSER_WAIT_HEADER )
{
if( xrcon.rx_pos < XRCON_HEADER_SIZE )
return;
sizebuf_t sb;
uint32_t type = 0;
MSG_Init( &sb, __func__, xrcon.rx_buffer, xrcon.rx_pos );
MSG_ReadBytes( &sb, &type, sizeof( type ), 4 );
uint32_t version = ntohl( MSG_ReadDword( &sb ));
uint16_t total_len = ntohs( MSG_ReadWord( &sb ));
uint16_t handle = ntohs( MSG_ReadWord( &sb ));
if( total_len <= XRCON_HEADER_SIZE || total_len > sizeof( xrcon.rx_buffer ))
{
Con_Printf( S_WARN "%s: invalid frame size %u, disconnecting\n", __func__, total_len );
XRcon_DisconnectClient();
return;
}
if( version != XRCON_CMND_VERSION && version != XRCON_CMND_VERSION_CS2RC )
Con_Printf( S_WARN "%s: unexpected version 0x%08X\n", __func__, version );
xrcon.parser.frame.type = type;
xrcon.parser.frame.version = version;
xrcon.parser.frame.length = total_len;
xrcon.parser.frame.handle = handle;
xrcon.parser.state = XRCON_PARSER_WAIT_PAYLOAD;
size_t bytes_read = MSG_GetNumBytesRead( &sb );
memmove( xrcon.rx_buffer, xrcon.rx_buffer + bytes_read, xrcon.rx_pos - bytes_read );
xrcon.rx_pos -= bytes_read;
}
else if( xrcon.parser.state == XRCON_PARSER_WAIT_PAYLOAD )
{
size_t payload_length = xrcon.parser.frame.length - XRCON_HEADER_SIZE;
if( xrcon.rx_pos < payload_length )
return;
if( xrcon.parser.frame.type == XRCON_TYPE_CMND )
{
char cmd[XRCON_MAX_FRAME_SIZE];
size_t cmd_len = Q_min( payload_length, sizeof( cmd ) - 1 );
memcpy( cmd, xrcon.rx_buffer, cmd_len );
cmd[cmd_len] = '\0';
XRcon_HandleCMND( cmd );
}
else
{
Con_Printf( S_WARN "%s: unknown message type 0x%08x\n", __func__, xrcon.parser.frame.type );
}
xrcon.parser.state = XRCON_PARSER_WAIT_HEADER;
memmove( xrcon.rx_buffer, xrcon.rx_buffer + payload_length, xrcon.rx_pos - payload_length );
xrcon.rx_pos -= payload_length;
}
}
}
static void XRcon_HandleDataTx( void )
{
if( xrcon.tx_pos == 0 )
return;
int sent = send( xrcon.client_socket, (const char *)xrcon.tx_buffer, xrcon.tx_pos, 0 );
if( NET_IsSocketError( sent ))
{
int err = WSAGetLastError();
if( err != WSAEWOULDBLOCK && err != WSAEALREADY )
XRcon_DisconnectClient();
return;
}
if( sent > 0 )
{
memmove( xrcon.tx_buffer, xrcon.tx_buffer + sent, xrcon.tx_pos - sent );
xrcon.tx_pos -= sent;
}
}
static void XRcon_HandleDataRx( void )
{
int available = sizeof( xrcon.rx_buffer ) - xrcon.rx_pos;
if( available <= 0 )
{
Con_Printf( S_ERROR "%s: receive buffer overflow\n", __func__ );
XRcon_DisconnectClient();
return;
}
int received = recv( xrcon.client_socket, (char *)xrcon.rx_buffer + xrcon.rx_pos, available, 0 );
if( NET_IsSocketError( received ))
{
int err = WSAGetLastError();
if( err != WSAEWOULDBLOCK && err != WSAEALREADY )
{
XRcon_DisconnectClient();
}
return;
}
if( received == 0 )
{
XRcon_DisconnectClient();
return;
}
xrcon.rx_pos += received;
XRcon_ProcessRxData();
}
static void XRcon_FlushPrintBuffer( void )
{
if( xrcon.print_pos == 0 )
return;
sizebuf_t sb;
uint8_t body[XRCON_PRNT_HEADER_SIZE + XRCON_PRINT_BUFFER_SIZE];
MSG_Init( &sb, __func__, body, sizeof( body ));
MSG_WriteDword( &sb, 0 ); // channel_id = 0 (Console)
// padding 20 bytes
for( int i = 0; i < 5; i++ )
MSG_WriteDword( &sb, 0 );
// RGBA = white
MSG_WriteDword( &sb, 0xFFFFFFFF );
MSG_WriteBytes( &sb, xrcon.print_buffer, xrcon.print_pos );
MSG_WriteByte( &sb, 0 );
XRcon_SendPacket( XRCON_TYPE_PRNT, body, MSG_GetRealBytesWritten( &sb ));
xrcon.print_pos = 0;
xrcon.print_buffer[0] = '\0';
}
static void XRcon_UpdateListening( void )
{
fd_set readfds;
struct timeval tv = { 0 };
FD_ZERO( &readfds );
FD_SET( xrcon.listen_socket, &readfds );
#if XASH_WIN32
int result = select( 0, &readfds, NULL, NULL, &tv );
#else
int result = select( xrcon.listen_socket + 1, &readfds, NULL, NULL, &tv );
#endif
if( NET_IsSocketError( result ))
{
Con_Printf( S_ERROR "%s: select() failed\n", __func__ );
return;
}
if( !FD_ISSET( xrcon.listen_socket, &readfds ))
return;
struct sockaddr_storage client_addr;
socklen_t addr_len = sizeof( client_addr );
SOCKET client = accept( xrcon.listen_socket, (struct sockaddr *)&client_addr, &addr_len );
if( !NET_IsSocketValid( client ))
{
int err = WSAGetLastError();
if( err != WSAEWOULDBLOCK && err != WSAEINPROGRESS )
Con_Printf( S_ERROR "%s: accept() failed, error %s\n", __func__, NET_ErrorString( ));
return;
}
if( !NET_MakeSocketNonBlocking( client ))
{
Con_Printf( S_ERROR "%s: failed to set client non-blocking\n", __func__ );
closesocket( client );
return;
}
xrcon.client_socket = client;
xrcon.print_pos = 0;
xrcon.print_buffer[0] = '\0';
xrcon.tx_pos = 0;
xrcon.rx_pos = 0;
xrcon.parser.state = XRCON_PARSER_WAIT_HEADER;
xrcon.print_flush_time = Platform_DoubleTime() + xrcon_flush_interval.value;
netadr_t adr;
NET_SockadrToNetadr( &client_addr, &adr );
Con_Printf( S_NOTE "%s: connected client %s\n", __func__, NET_AdrToString( adr ));
XRcon_SendAINF();
XRcon_SendADON( "HLDS" );
XRcon_SendCHAN();
XRcon_SetState( XRCON_STATE_CONNECTED );
}
static void XRcon_UpdateConnected( void )
{
if( !NET_IsSocketValid( xrcon.client_socket ))
{
XRcon_DisconnectClient();
return;
}
XRcon_HandleDataTx();
XRcon_HandleDataRx();
if( xrcon.print_pos > 0 && Platform_DoubleTime() >= xrcon.print_flush_time )
{
XRcon_FlushPrintBuffer();
xrcon.print_flush_time = Platform_DoubleTime() + xrcon_flush_interval.value;
}
}
static void XRcon_UpdateIdle( void )
{
if( xrcon.retry_timeout > Platform_DoubleTime( ))
return;
XRcon_StartListening();
xrcon.retry_timeout = Platform_DoubleTime() + xrcon_retry_delay.value;
}
void XRcon_Print( const char *msg )
{
if( xrcon.state != XRCON_STATE_CONNECTED )
return;
if( !msg )
return;
while( *msg )
{
const char *p = Q_strchrnul( msg, '\n' );
size_t length = p - msg;
if( xrcon.print_pos + length < sizeof( xrcon.print_buffer ) - 1 )
{
memcpy( xrcon.print_buffer + xrcon.print_pos, msg, length );
xrcon.print_pos += length;
}
if( *p == '\0' )
return;
if( xrcon.print_pos > 0 )
{
XRcon_FlushPrintBuffer();
xrcon.print_flush_time = Platform_DoubleTime() + xrcon_flush_interval.value;
}
msg = p + 1;
}
}
static void XRcon_Terminate( void )
{
XRcon_DisconnectClient();
XRcon_CloseListenSocket();
xrcon.retry_timeout = 0;
NET_NetadrSetType( &xrcon.bindadr, NA_UNDEFINED );
}
void XRcon_Frame( void )
{
if( !xrcon_enable.value )
{
if( xrcon.state != XRCON_STATE_IDLE )
{
XRcon_Terminate();
XRcon_SetState( XRCON_STATE_IDLE );
}
return;
}
if( FBitSet( xrcon_address.flags, FCVAR_CHANGED ))
{
ClearBits( xrcon_address.flags, FCVAR_CHANGED );
XRcon_Terminate();
XRcon_SetState( XRCON_STATE_IDLE );
}
switch( xrcon.state )
{
case XRCON_STATE_IDLE:
XRcon_UpdateIdle();
break;
case XRCON_STATE_LISTENING:
XRcon_UpdateListening();
break;
case XRCON_STATE_CONNECTED:
XRcon_UpdateConnected();
break;
}
}
void XRcon_Init( void )
{
xrcon.state = XRCON_STATE_IDLE;
xrcon.listen_socket = INVALID_SOCKET;
xrcon.client_socket = INVALID_SOCKET;
xrcon.rx_pos = 0;
xrcon.tx_pos = 0;
xrcon.parser.state = XRCON_PARSER_WAIT_HEADER;
xrcon.print_pos = 0;
xrcon.print_buffer[0] = '\0';
xrcon.retry_timeout = 0;
Cvar_RegisterVariable( &xrcon_enable );
Cvar_RegisterVariable( &xrcon_address );
Cvar_RegisterVariable( &xrcon_flush_interval );
Cvar_RegisterVariable( &xrcon_retry_delay );
NET_NetadrSetType( &xrcon.bindadr, NA_UNDEFINED );
}
void XRcon_Shutdown( void )
{
XRcon_DisconnectClient();
XRcon_CloseListenSocket();
XRcon_SetState( XRCON_STATE_IDLE );
}
qboolean XRcon_IsActive( void )
{
return xrcon.state == XRCON_STATE_CONNECTED;
}

View File

@@ -21,6 +21,7 @@ GNU General Public License for more details.
#if XASH_ANDROID
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <android/log.h>
#endif
#include "library.h"
@@ -29,6 +30,7 @@ GNU General Public License for more details.
#if XASH_ANDROID
static char crashlog_path[MAX_OSPATH];
static char enginelog_path[MAX_OSPATH];
#endif
static qboolean have_libbacktrace = false;
@@ -79,6 +81,26 @@ static void Sys_Crash( int signal, siginfo_t *si, void *context )
}
}
// make a copy of engine.log in staging directory
// TODO: dump log from console buffers, if -log not enabled
if( logfd >= 0 && enginelog_path[0] && lseek( logfd, 0, SEEK_SET ) == 0 )
{
int outfd = open( enginelog_path, O_WRONLY|O_CREAT|O_TRUNC, 0644 );
if( outfd >= 0 )
{
static char buf[8192];
while( 1 )
{
ssize_t n = read( logfd, buf, sizeof( buf ));
if( n <= 0 )
break;
if( write( outfd, buf, (size_t)n ) != n )
break;
}
close( outfd );
}
}
// JNI/SDL calls aren't safe from a signal handler on Android
_exit( 128 + signal );
#else
@@ -115,7 +137,10 @@ void Sys_SetupCrashHandler( const char *argv0 )
const char *crashdir = getenv( "XASH3D_CRASH_DIR" );
if( !COM_StringEmptyOrNULL( crashdir ))
{
Q_snprintf( crashlog_path, sizeof( crashlog_path ), "%s/crash.log", crashdir );
Q_snprintf( enginelog_path, sizeof( enginelog_path ), "%s/engine.log", crashdir );
}
// unblock the engine/SDL_main thread just in case
sigset_t set;

View File

@@ -15,6 +15,7 @@ GNU General Public License for more details.
#include "platform/platform.h"
#include "xash3d_mathlib.h"
#include "atlas.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
@@ -80,6 +81,7 @@ static const vrtld_export_t aux_exports[] =
VRTLD_EXPORT( "dlopen", vrtld_dlopen ),
VRTLD_EXPORT( "dlclose", vrtld_dlclose ),
VRTLD_EXPORT( "dlsym", vrtld_dlsym ),
VRTLD_EXPORT_SYMBOL( Atlas_AllocBlock ), // HACKHACK: remove when atlas utils will be used in engine
};
const vrtld_export_t *__vrtld_exports = aux_exports;

View File

@@ -118,6 +118,15 @@ void SDLash_Init( void )
SDL_SetHint( SDL_HINT_ANDROID_BLOCK_ON_PAUSE, "0" );
SDL_SetHint( SDL_HINT_ANDROID_BLOCK_ON_PAUSE_PAUSEAUDIO, "0" );
// when launched through Steam (notably on Steam Deck) Steam Input hides the
// real controller and exposes a virtual gamepad without gyro/touchpad access
// undo the env-var filter and ignore the virtual pad instead
if( Sys_CheckParm( "-nosteaminput" ))
{
SDL_setenv( "SDL_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT", "", 1 );
SDL_setenv( "SDL_GAMECONTROLLER_IGNORE_DEVICES", "0x28DE/0x11FF", 1 );
}
if( SDL_Init( SDL_INIT_TIMER | SDL_INIT_VIDEO | SDL_INIT_EVENTS ) )
{
Sys_Warn( "SDL_Init failed: %s", SDL_GetError() );

View File

@@ -73,6 +73,7 @@ extern int SV_UPDATE_BACKUP;
#define FCL_HLTV_PROXY BIT( 8 ) // this is a proxy for a HLTV client (spectator)
#define FCL_SEND_RESOURCES BIT( 9 )
#define FCL_FORCE_UNMODIFIED BIT( 10 )
#define FCL_EXPECT_RESOURCELIST BIT( 11 ) // engine sent svc_resourcerequest, expect one clc_resourcelist in response
typedef enum
{

View File

@@ -13,6 +13,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
*/
#include <inttypes.h>
#include "common.h"
#include "const.h"
#include "server.h"
@@ -301,6 +302,7 @@ static void SV_ConnectClient( netadr_t from )
const char *s;
int extensions;
uint netchan_flags = 0;
uint64_t netchan_cookie = 0;
if( Cmd_Argc() < 5 )
{
@@ -345,6 +347,27 @@ static void SV_ConnectClient( netadr_t from )
qport = Q_atoi( Info_ValueForKey( protinfo, "qport" ));
extensions = Q_atoi( Info_ValueForKey( protinfo, "ext" ));
if( FBitSet( extensions, NET_EXT_NETCHAN_COOKIE ))
{
const char *cookie_str = Info_ValueForKey( protinfo, "cookie" );
if( Q_strlen( cookie_str ) != 16 )
{
SV_RejectConnection( from, "client advertised NET_EXT_NETCHAN_COOKIE but did not supply a cookie\n" );
return;
}
byte buf[8];
COM_HexConvert( cookie_str, 16, buf );
for( int i = 0; i < 8; i++ )
netchan_cookie = ( netchan_cookie << 8 ) | buf[i];
}
// these keys aren't useragent
Info_RemoveKey( protinfo, "cookie" );
Info_RemoveKey( protinfo, "ext" );
Info_RemoveKey( protinfo, "qport" );
s = Cmd_Argv( 4 ); // user info
if( Q_strlen( s ) > sizeof( userinfo ) || !Info_IsValid( s ))
@@ -419,7 +442,7 @@ static void SV_ConnectClient( netadr_t from )
newcl->frames = frames;
newcl->userid = g_userid++; // create unique userid
newcl->state = cs_connected; // now expect "spawn" command
newcl->extensions = FBitSet( extensions, NET_EXT_SPLITSIZE );
newcl->extensions = FBitSet( extensions, NET_EXT_SPLITSIZE | NET_EXT_NETCHAN_COOKIE );
Q_strncpy( newcl->useragent, protinfo, sizeof( newcl->useragent ));
// HACKHACK: can hear all players by default to avoid issues
@@ -429,7 +452,11 @@ static void SV_ConnectClient( netadr_t from )
// initailize netchan
if( !Host_IsLocalClient( ))
SetBits( netchan_flags, NETCHAN_USE_LZSS );
if( FBitSet( newcl->extensions, NET_EXT_NETCHAN_COOKIE ))
SetBits( netchan_flags, NETCHAN_USE_COOKIE );
Netchan_Setup( NS_SERVER, &newcl->netchan, from, qport, newcl, SV_GetFragmentSize, netchan_flags );
if( FBitSet( newcl->extensions, NET_EXT_NETCHAN_COOKIE ))
Netchan_SetCookie( &newcl->netchan, netchan_cookie );
MSG_Init( &newcl->datagram, "Datagram", newcl->datagram_buf, sizeof( newcl->datagram_buf )); // datagram buf
Q_strncpy( newcl->hashedcdkey, Info_ValueForKey( protinfo, "uuid" ), 32 );
@@ -439,6 +466,8 @@ static void SV_ConnectClient( netadr_t from )
protinfo[0] = '\0';
Info_SetValueForKeyf( protinfo, "ext", sizeof( protinfo ), "%d", newcl->extensions );
Info_SetValueForKey( protinfo, "cheats", sv_cheats.value ? "1" : "0", sizeof( protinfo ));
if( FBitSet( newcl->extensions, NET_EXT_NETCHAN_COOKIE ))
Info_SetValueForKeyf( protinfo, "cookie", sizeof( protinfo ), "%016"PRIx64, netchan_cookie );
// send the connect packet to the client
Netchan_OutOfBandPrint( NS_SERVER, from, S2C_CONNECTION" %s", protinfo );
@@ -3445,7 +3474,7 @@ static void SV_ParseResourceList( sv_client_t *cl, sizebuf_t *msg )
ClearBits( resource->ucFlags, RES_WASMISSING );
if( FBitSet( resource->ucFlags, RES_CUSTOM ))
MSG_ReadBytes( msg, resource->rgucMD5_hash, 16 );
MSG_ReadBytes( msg, resource->rgucMD5_hash, sizeof( resource->rgucMD5_hash ), 16 );
if( resource->type > t_world || resource->nDownloadSize > 1024 * 1024 * 1024 )
{
@@ -3456,15 +3485,22 @@ static void SV_ParseResourceList( sv_client_t *cl, sizebuf_t *msg )
SV_AddToResourceList( resource, &cl->resourcesneeded );
}
if( host.realtime < cl->resourcelist_next_changetime )
if( FBitSet( cl->flags, FCL_EXPECT_RESOURCELIST ))
{
Con_Reportf( "%s: ignoring resource list update from %s: too soon\n", __func__, cl->name );
SV_ClearResourceList( &cl->resourcesneeded );
SV_ClearResourceList( &cl->resourcesonhand );
return;
ClearBits( cl->flags, FCL_EXPECT_RESOURCELIST );
}
else
{
if( host.realtime < cl->resourcelist_next_changetime )
{
Con_Reportf( "%s: ignoring resource list update from %s: too soon\n", __func__, cl->name );
SV_ClearResourceList( &cl->resourcesneeded );
SV_ClearResourceList( &cl->resourcesonhand );
return;
}
cl->resourcelist_next_changetime = host.realtime + sv_upload_penalty_time.value;
cl->resourcelist_next_changetime = host.realtime + sv_upload_penalty_time.value;
}
totalsize = COM_SizeofResourceList( &cl->resourcesneeded, &ri );
@@ -3569,7 +3605,7 @@ static void SV_ParseVoiceData( sv_client_t *cl, sizebuf_t *msg )
return;
}
MSG_ReadBytes( msg, received, size );
MSG_ReadBytes( msg, received, sizeof( received ), size );
if( !sv_voiceenable.value || cl->state != cs_spawned )
return;

View File

@@ -124,8 +124,8 @@ void SV_ParseConsistencyResponse( sv_client_t *cl, sizebuf_t *msg )
byte resbuffer[32];
FORCE_TYPE ft;
MSG_ReadBytes( msg, cmins, sizeof( cmins ));
MSG_ReadBytes( msg, cmaxs, sizeof( cmaxs ));
MSG_ReadBytes( msg, cmins, sizeof( cmins ), sizeof( cmins ));
MSG_ReadBytes( msg, cmaxs, sizeof( cmaxs ), sizeof( cmaxs ));
memcpy( resbuffer, r->rguc_reserved, 32 );
ft = resbuffer[0];
@@ -543,6 +543,8 @@ void SV_SendResource( resource_t *pResource, sizebuf_t *msg )
void SV_SendResources( sv_client_t *cl, sizebuf_t *msg )
{
SetBits( cl->flags, FCL_EXPECT_RESOURCELIST );
MSG_BeginServerCmd( msg, svc_resourcerequest );
MSG_WriteLong( msg, svs.spawncount );
MSG_WriteLong( msg, 0 );

View File

@@ -787,13 +787,13 @@ static void SV_UpdateToReliableMessages( void )
if( MSG_GetNumBytesWritten( &sv.datagram ) < MSG_GetNumBytesLeft( &cl->datagram ))
MSG_WriteBits( &cl->datagram, MSG_GetData( &sv.datagram ), MSG_GetNumBitsWritten( &sv.datagram ));
else Con_DPrintf( S_WARN "Ignoring unreliable datagram for %s, would overflow\n", cl->name );
else Con_Reportf( S_WARN "Ignoring unreliable datagram for %s, would overflow\n", cl->name );
if( FBitSet( cl->flags, FCL_HLTV_PROXY ))
{
if( MSG_GetNumBytesWritten( &sv.spec_datagram ) < MSG_GetNumBytesLeft( &cl->datagram ))
MSG_WriteBits( &cl->datagram, MSG_GetData( &sv.spec_datagram ), MSG_GetNumBitsWritten( &sv.spec_datagram ));
else Con_DPrintf( S_WARN "Ignoring spectator datagram for %s, would overflow\n", cl->name );
else Con_Reportf( S_WARN "Ignoring spectator datagram for %s, would overflow\n", cl->name );
}
}

View File

@@ -4680,6 +4680,16 @@ static void GAME_EXPORT pfnGetGameDir( char *out )
}
}
static cvar_t* GAME_EXPORT SV_CvarGetPointer( const char *szVarName )
{
cvar_t *result = (cvar_t *)Cvar_FindVar( szVarName );
if( !result )
Con_DPrintf( S_WARN "%s: server tried to get non-existent cvar \"%s\"\n", __func__, szVarName );
return result;
}
// engine callbacks
static enginefuncs_t gEngfuncs =
{
@@ -4799,7 +4809,7 @@ static enginefuncs_t gEngfuncs =
pfnGetPlayerUserId,
pfnBuildSoundMsg,
pfnIsDedicatedServer,
pfnCVarGetPointer,
SV_CvarGetPointer,
pfnGetPlayerWONId,
(void*)Info_RemoveKey,
pfnGetPhysicsKeyValue,

View File

@@ -312,6 +312,12 @@ static void SV_ProcessFile( sv_client_t *cl, const char *filename )
return;
}
if( Q_strlen( filename ) < 36 )
{
Con_Printf( "%s: Malformed customization filename from %s (too short)\n", __func__, cl->name );
return;
}
COM_HexConvert( filename + 4, 32, md5 );
for( resource = cl->resourcesneeded.pNext; resource != &cl->resourcesneeded; resource = next )
@@ -371,7 +377,7 @@ SV_ReadPackets
static void SV_ReadPackets( void )
{
sv_client_t *cl;
int i, qport;
int i;
size_t curSize;
while( NET_GetPacket( NS_SERVER, &net_from, net_message_buffer, &curSize ))
@@ -385,13 +391,6 @@ static void SV_ReadPackets( void )
continue;
}
// read the qport out of the message so we can fix up
// stupid address translating routers
MSG_Clear( &net_message );
MSG_ReadLong( &net_message ); // sequence number
MSG_ReadLong( &net_message ); // sequence number
qport = (int)MSG_ReadShort( &net_message ) & 0xffff;
// check for packets from connected clients
for( i = 0, sv.current_client = svs.clients; i < svs.maxclients; i++, sv.current_client++ )
{
@@ -403,24 +402,22 @@ static void SV_ReadPackets( void )
if( !NET_CompareBaseAdr( net_from, cl->netchan.remote_address ))
continue;
if( cl->netchan.qport != qport )
if( !Netchan_Process( &cl->netchan, &net_message ))
continue;
// authenticated; safe to adopt the (possibly NAT-rewritten) source port
if( cl->netchan.remote_address.port != net_from.port )
cl->netchan.remote_address.port = net_from.port;
if( Netchan_Process( &cl->netchan, &net_message ))
{
if(( svs.maxclients == 1 && !host_limitlocal.value ) || ( cl->state != cs_spawned ))
SetBits( cl->flags, FCL_SEND_NET_MESSAGE ); // reply at end of frame
if(( svs.maxclients == 1 && !host_limitlocal.value ) || ( cl->state != cs_spawned ))
SetBits( cl->flags, FCL_SEND_NET_MESSAGE ); // reply at end of frame
// this is a valid, sequenced packet, so process it
if( cl->frames != NULL && cl->state != cs_zombie )
{
SV_ExecuteClientMessage( cl, &net_message );
svgame.globals->frametime = sv.frametime;
svgame.globals->time = sv.time;
}
// this is a valid, sequenced packet, so process it
if( cl->frames != NULL && cl->state != cs_zombie )
{
SV_ExecuteClientMessage( cl, &net_message );
svgame.globals->frametime = sv.frametime;
svgame.globals->time = sv.time;
}
// fragmentation/reassembly sending takes priority over all game messages, want this in the future?

View File

@@ -26,15 +26,6 @@ int main(int argc, char **argv)
}
'''
CURL_CHECK_FRAGMENT='''
#include <curl/curl.h>
int main(int argc, char **argv)
{
CURL *easy = curl_easy_init();
return 0;
}
'''
frameworks = ['Foundation', 'UIKit', 'QuartzCore', 'GameController', 'SystemConfiguration', 'CFNetwork', 'AVFoundation', 'CoreGraphics']
@TaskGen.extension('.m')
@@ -65,9 +56,6 @@ def options(opt):
grp.add_option('--enable-ffmpeg-dlopen', action = 'store_true', dest = 'FFMPEG_DLOPEN', default = False,
help = 'load ffmpeg libraries in runtime [default: %(default)s]')
grp.add_option('--disable-curl', action = 'store_false', dest = 'CURL', default = False,
help = 'enable curl-based HTTP downloader [default: %(default)s]')
opt.load('sdl2')
def find_sdl(conf):
@@ -162,19 +150,6 @@ def configure(conf):
conf.define('HAVE_FFMPEG', True)
conf.define_cond('XASH_FFMPEG_DLOPEN', conf.options.FFMPEG_DLOPEN)
if conf.options.CURL:
pkgconf_args = '--cflags --libs'
features = 'c cprogram'
# TODO: when dlopen curl is implemented, we might not need this check
# as we can safely bundle curl headers as curl never breaks API/ABI
conf.check_cfg(package='libcurl', uselib_store='CURL', args=pkgconf_args)
conf.check(features=features, fragment=CURL_CHECK_FRAGMENT, use='CURL', msg='Checking for curl sanity')
conf.env.CURL=True
conf.define('HAVE_CURL', True)
conf.define_cond('XASH_STATIC_LIBS', conf.env.STATIC_LINKING)
conf.define_cond('XASH_CUSTOM_SWAP', conf.options.CUSTOM_SWAP)
conf.define_cond('PSAPI_VERSION', conf.env.DEST_OS == 'win32') # will be defined as 1
@@ -186,11 +161,11 @@ def build(bld):
# public includes for renderers and utils use
bld(name = 'engine_includes', export_includes = '. common common/imagelib', use = 'filesystem_includes')
libs = ['engine_includes', 'public', 'werror', 'backtrace_custom', 'library_suffix', 'build_vcs']
libs = ['engine_includes', 'public', 'werror', 'backtrace_custom', 'library_suffix', 'build_vcs', 'mbedtls']
includes = ['server', 'client', 'client/vgui', 'common/soundlib', 'platform']
# basic build: dedicated only
source = bld.path.ant_glob(['common/*.c', 'common/imagelib/*.c', 'common/soundlib/*.c', 'server/*.c'])
source = bld.path.ant_glob(['common/*.c', 'common/imagelib/*.c', 'common/soundlib/*.c', 'server/*.c', 'common/http/*.c'])
# include platform-specific sources, this shall not fail if directory doesn't exist
source += bld.path.ant_glob('platform/%s/*.c' % bld.env.DEST_OS)
@@ -214,14 +189,10 @@ def build(bld):
if bld.get_define('XASH_STATIC_LIBS'):
source += ['platform/misc/lib_static.c']
if bld.get_define('HAVE_CURL'):
source += ['common/http/net_http_curl.c']
libs += ['CURL']
else:
source += ['common/http/net_http_xash.c']
if bld.env.DEST_OS == 'win32':
libs += ['USER32', 'SHELL32', 'GDI32', 'ADVAPI32', 'DBGHELP', 'PSAPI', 'WS2_32']
if bld.env.DEST_SIZEOF_VOID_P > 4:
libs += ['BCRYPT']
elif bld.env.DEST_OS == 'nswitch':
libs += ['SOLDER']
# HACK: link in the entirety of libstdc++ so that dynamic libs could use all of it without manual exporting

View File

@@ -222,6 +222,7 @@ const fs_api_t g_api =
FS_GetRootDirectory,
FS_MakeGameInfo,
FS_FindFile_f,
};
int EXPORT GetFSAPI( int version, fs_api_t *api, fs_globals_t **globals, fs_interface_t *engfuncs );

View File

@@ -241,6 +241,7 @@ typedef struct fs_api_t
qboolean (*GetRootDirectory)( char *path, size_t size );
void (*MakeGameInfo)( void );
void (*FindFile_f)( const char *filename );
} fs_api_t;
typedef struct fs_interface_t

View File

@@ -170,6 +170,7 @@ qboolean FS_InitStdio( qboolean caseinsensitive, const char *rootdir, const char
void FS_AllowDirectPaths( qboolean enable );
void FS_ShutdownStdio( void );
void FS_Path_f( void );
void FS_FindFile_f( const char *filename );
searchpath_t *FS_FindFile( const char *name, int *index, char *fixedname, size_t len, uint32_t flags );
qboolean FS_FindLibrary( const char *dllname, qboolean directpath, fs_dllinfo_t *dllInfo );
qboolean FS_FullPathToRelativePath( char *dst, const char *src, size_t size );

View File

@@ -24,6 +24,9 @@ GNU General Public License for more details.
#if XASH_WIN32
#include <io.h>
#endif
#if XASH_LINUX
#include <sys/sendfile.h>
#endif
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
@@ -1027,10 +1030,75 @@ FS_FileCopy
*/
qboolean FS_FileCopy( file_t *pOutput, file_t *pInput, int fileSize )
{
char *buf = Mem_Malloc( fs_mempool, FILE_COPY_SIZE );
char *buf;
int size, readSize;
qboolean done = true;
#if XASH_LINUX && !defined( XASH_REDUCE_FD )
// sendfile can't decompress, and we'd skip past userspace-buffered bytes
if( !FBitSet( pInput->flags, FILE_DEFLATED ) && pInput->ungetc == EOF && pInput->buff_ind == pInput->buff_len )
{
off_t out_pos;
// mirror FS_Write's pre-write fixup so the output fd offset matches file_t's position
if( pOutput->buff_ind != pOutput->buff_len )
lseek( pOutput->handle, pOutput->buff_ind - pOutput->buff_len, SEEK_CUR );
FS_Purge( pOutput );
off_t in_off = pInput->offset + pInput->position;
while( fileSize > 0 )
{
ssize_t sent = sendfile( pOutput->handle, pInput->handle, &in_off, fileSize );
if( sent < 0 )
{
if( errno == EINTR )
continue;
// some kernels/filesystems don't support sendfile between these fds
if( errno == EINVAL || errno == ENOSYS || errno == EOPNOTSUPP )
break;
Con_Reportf( S_ERROR "%s: sendfile failed: %s\n", __func__, strerror( errno ));
done = false;
fileSize = 0;
break;
}
if( sent == 0 )
{
Con_Reportf( S_ERROR "%s: unexpected end of input file\n", __func__ );
done = false;
fileSize = 0;
break;
}
fileSize -= sent;
}
pInput->position = in_off - pInput->offset;
out_pos = lseek( pOutput->handle, 0, SEEK_CUR );
if( out_pos < 0 )
{
Con_Reportf( S_ERROR "%s: lseek failed: %s\n", __func__, strerror( errno ));
done = false;
fileSize = 0;
}
else
{
pOutput->position = out_pos;
if( pOutput->real_length < pOutput->position )
pOutput->real_length = pOutput->position;
}
if( fileSize <= 0 )
return done;
}
#endif
buf = Mem_Malloc( fs_mempool, FILE_COPY_SIZE );
while( fileSize > 0 )
{
if( fileSize > FILE_COPY_SIZE )

View File

@@ -773,6 +773,35 @@ void FS_Path_f( void )
}
}
/*
====================
FS_FindFile_f
Print all search paths where the file was found,
ordered by priority (first one is used for FS operations)
====================
*/
void FS_FindFile_f( const char *filename )
{
int count = 0;
Con_Printf( "File " S_YELLOW "%s" S_DEFAULT " occurences:\n", filename );
for( searchpath_t *s = fs_searchpaths; s; s = s->next )
{
string fixedname;
if( s->pfnFindFile( s, filename, fixedname, sizeof( fixedname )) >= 0 )
{
string info;
count++;
s->pfnPrintInfo( s, info, sizeof( info ));
Con_Printf( " " S_CYAN "%s%s\n", info, count == 1 ? " " S_GREEN "(active)" : "" );
}
}
if( count == 0 )
Con_Printf( " " S_RED "(not found)\n" );
}
/*
====================
FS_FindFile

View File

@@ -1,39 +1,4 @@
#include "port.h"
#include "build.h"
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "filesystem.h"
#if XASH_POSIX
#include <dlfcn.h>
#define LoadLibrary( x ) dlopen( x, RTLD_NOW )
#define GetProcAddress( x, y ) dlsym( x, y )
#define FreeLibrary( x ) dlclose( x )
#elif XASH_WIN32
#include <windows.h>
#endif
void *g_hModule;
FSAPI g_pfnGetFSAPI;
fs_api_t g_fs;
fs_globals_t *g_nullglobals;
static qboolean LoadFilesystem( void )
{
g_hModule = LoadLibrary( "filesystem_stdio." OS_LIB_EXT );
if( !g_hModule )
return false;
g_pfnGetFSAPI = (void*)GetProcAddress( g_hModule, GET_FS_API );
if( !g_pfnGetFSAPI )
return false;
if( !g_pfnGetFSAPI( FS_API_VERSION, &g_fs, &g_nullglobals, NULL ))
return false;
return true;
}
#include "filesystem_test_common.h"
static qboolean CheckFileContents( const char *path, const void *buf, fs_offset_t size )
{

206
filesystem/tests/filecopy.c Normal file
View File

@@ -0,0 +1,206 @@
#include "filesystem_test_common.h"
static qboolean WriteSourceFile( const char *path, const byte *data, fs_offset_t size )
{
file_t *f = g_fs.Open( path, "wb", true );
if( !f )
{
printf( "WriteSourceFile: Open(%s) failed\n", path );
return false;
}
if( g_fs.Write( f, data, size ) != size )
{
printf( "WriteSourceFile: short write\n" );
g_fs.Close( f );
return false;
}
g_fs.Close( f );
return true;
}
static qboolean VerifyFile( const char *path, const byte *expected, fs_offset_t size )
{
fs_offset_t len;
byte *data = g_fs.LoadFile( path, &len, true );
if( !data )
{
printf( "VerifyFile: LoadFile(%s) failed\n", path );
return false;
}
if( len != size )
{
printf( "VerifyFile: size mismatch (%lld != %lld)\n", (long long)len, (long long)size );
free( data );
return false;
}
if( memcmp( data, expected, size ) != 0 )
{
printf( "VerifyFile: content mismatch\n" );
free( data );
return false;
}
free( data );
return true;
}
static qboolean DoCopy( const char *src, const char *dst, fs_offset_t size )
{
file_t *fin = g_fs.Open( src, "rb", true );
if( !fin )
{
printf( "DoCopy: Open(%s) failed\n", src );
return false;
}
file_t *fout = g_fs.Open( dst, "wb", true );
if( !fout )
{
printf( "DoCopy: Open(%s) failed\n", dst );
g_fs.Close( fin );
return false;
}
qboolean ok = g_fs.FileCopy( fout, fin, (int)size );
g_fs.Close( fout );
g_fs.Close( fin );
return ok;
}
static qboolean TestBasic( const byte *payload, fs_offset_t size )
{
if( !WriteSourceFile( "fc_src.bin", payload, size ))
return false;
if( !DoCopy( "fc_src.bin", "fc_dst.bin", size ))
{
printf( "TestBasic(%lld): FileCopy returned false\n", (long long)size );
return false;
}
if( !VerifyFile( "fc_dst.bin", payload, size ))
{
printf( "TestBasic(%lld): verify failed\n", (long long)size );
return false;
}
g_fs.Delete( "fc_src.bin" );
g_fs.Delete( "fc_dst.bin" );
return true;
}
// drains some bytes via Read first, leaving the input file_t's userspace buffer
// dirty (or its position past the start). FS_FileCopy's sendfile fast-path must
// gate this case off; the buffered fallback must still produce the right bytes.
static qboolean TestPartialReadThenCopy( const byte *payload, fs_offset_t size, fs_offset_t skip )
{
byte scratch[64];
if( skip > (fs_offset_t)sizeof( scratch ))
skip = sizeof( scratch );
if( !WriteSourceFile( "fc_src.bin", payload, size ))
return false;
file_t *fin = g_fs.Open( "fc_src.bin", "rb", true );
if( !fin )
return false;
if( g_fs.Read( fin, scratch, skip ) != skip )
{
printf( "TestPartialReadThenCopy: short read\n" );
g_fs.Close( fin );
return false;
}
if( memcmp( scratch, payload, skip ) != 0 )
{
printf( "TestPartialReadThenCopy: pre-read mismatch\n" );
g_fs.Close( fin );
return false;
}
file_t *fout = g_fs.Open( "fc_dst.bin", "wb", true );
if( !fout )
{
g_fs.Close( fin );
return false;
}
qboolean ok = g_fs.FileCopy( fout, fin, (int)( size - skip ));
g_fs.Close( fout );
g_fs.Close( fin );
if( !ok )
{
printf( "TestPartialReadThenCopy: FileCopy returned false\n" );
return false;
}
if( !VerifyFile( "fc_dst.bin", payload + skip, size - skip ))
return false;
g_fs.Delete( "fc_src.bin" );
g_fs.Delete( "fc_dst.bin" );
return true;
}
static qboolean TestFileCopy( void )
{
enum { BIG_SIZE = 2 * 1024 * 1024 + 7777 }; // > FILE_COPY_SIZE to force multi-iteration
g_fs.AddGameDirectory( "./", FS_GAMEDIR_PATH );
byte *payload = malloc( BIG_SIZE );
if( !payload )
return false;
for( fs_offset_t i = 0; i < BIG_SIZE; i++ )
payload[i] = (byte)( ( i * 2654435761u ) >> 24 );
if( !TestBasic( payload, 0 ))
goto fail;
if( !TestBasic( payload, 1 ))
goto fail;
if( !TestBasic( payload, 4095 ))
goto fail;
if( !TestBasic( payload, BIG_SIZE ))
goto fail;
if( !TestPartialReadThenCopy( payload, BIG_SIZE, 33 ))
goto fail;
free( payload );
return true;
fail:
free( payload );
g_fs.Delete( "fc_src.bin" );
g_fs.Delete( "fc_dst.bin" );
return false;
}
int main( void )
{
if( !LoadFilesystem() )
return EXIT_FAILURE;
srand( time( NULL ));
if( !TestFileCopy())
return EXIT_FAILURE;
FreeLibrary( g_hModule );
printf( "success\n" );
return EXIT_SUCCESS;
}

View File

@@ -0,0 +1,63 @@
#ifndef FILESYSTEM_TEST_COMMON_H
#define FILESYSTEM_TEST_COMMON_H
#include "port.h"
#include "build.h"
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "filesystem.h"
#if XASH_POSIX
#include <dlfcn.h>
#define LoadLibrary( x ) dlopen( x, RTLD_NOW )
#define GetProcAddress( x, y ) dlsym( x, y )
#define FreeLibrary( x ) dlclose( x )
typedef void *HMODULE;
#elif XASH_WIN32
#include <windows.h>
#endif
// each test program includes this header exactly once, so static linkage is fine
static HMODULE g_hModule;
static FSAPI g_pfnGetFSAPI;
static fs_api_t g_fs;
static fs_globals_t *g_nullglobals;
#ifdef __cplusplus
static pfnCreateInterface_t g_pfnCreateInterface;
#endif
static qboolean LoadFilesystem( void )
{
g_hModule = LoadLibrary( "filesystem_stdio." OS_LIB_EXT );
if( !g_hModule )
return false;
g_pfnGetFSAPI = (FSAPI)GetProcAddress( g_hModule, GET_FS_API );
if( !g_pfnGetFSAPI )
return false;
if( !g_pfnGetFSAPI( FS_API_VERSION, &g_fs, &g_nullglobals, NULL ))
return false;
#ifdef __cplusplus
if( !g_nullglobals )
return false;
g_pfnCreateInterface = (pfnCreateInterface_t)GetProcAddress( g_hModule, "CreateInterface" );
if( !g_pfnCreateInterface )
return false;
int temp = -1;
if( !g_pfnCreateInterface( FILESYSTEM_INTERFACE_VERSION, &temp ) || temp != 0 )
return false;
temp = -1;
if( !g_pfnCreateInterface( FS_API_CREATEINTERFACE_TAG, &temp ) || temp != 0 )
return false;
#endif
return true;
}
#endif // FILESYSTEM_TEST_COMMON_H

View File

@@ -1,62 +1,5 @@
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "port.h"
#include "build.h"
#include "filesystem_test_common.h"
#include "VFileSystem009.h"
#include "filesystem.h"
#if XASH_POSIX
#include <dlfcn.h>
#define LoadLibrary( x ) dlopen( x, RTLD_NOW )
#define GetProcAddress( x, y ) dlsym( x, y )
#define FreeLibrary( x ) dlclose( x )
typedef void *HMODULE;
#elif XASH_WIN32
#include <windows.h>
#endif
HMODULE g_hModule;
FSAPI g_pfnGetFSAPI;
pfnCreateInterface_t g_pfnCreateInterface;
fs_api_t g_fs;
fs_globals_t *g_nullglobals;
static bool LoadFilesystem()
{
int temp = -1;
g_hModule = LoadLibrary( "filesystem_stdio." OS_LIB_EXT );
if( !g_hModule )
return false;
// check our C-style interface existence
g_pfnGetFSAPI = reinterpret_cast<FSAPI>( GetProcAddress( g_hModule, GET_FS_API ));
if( !g_pfnGetFSAPI )
return false;
g_nullglobals = NULL;
if( !g_pfnGetFSAPI( FS_API_VERSION, &g_fs, &g_nullglobals, NULL ))
return false;
if( !g_nullglobals )
return false;
// check Valve-style interface existence
g_pfnCreateInterface = reinterpret_cast<pfnCreateInterface_t>( GetProcAddress( g_hModule, "CreateInterface" ));
if( !g_pfnCreateInterface )
return false;
if( !g_pfnCreateInterface( FILESYSTEM_INTERFACE_VERSION, &temp ) || temp != 0 )
return false;
temp = -1;
if( !g_pfnCreateInterface( FS_API_CREATEINTERFACE_TAG, &temp ) || temp != 0 )
return false;
return true;
}
int main()
{

View File

@@ -1,39 +1,4 @@
#include "port.h"
#include "build.h"
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "filesystem.h"
#if XASH_POSIX
#include <dlfcn.h>
#define LoadLibrary( x ) dlopen( x, RTLD_NOW )
#define GetProcAddress( x, y ) dlsym( x, y )
#define FreeLibrary( x ) dlclose( x )
#elif XASH_WIN32
#include <windows.h>
#endif
void *g_hModule;
FSAPI g_pfnGetFSAPI;
fs_api_t g_fs;
fs_globals_t *g_nullglobals;
static qboolean LoadFilesystem( void )
{
g_hModule = LoadLibrary( "filesystem_stdio." OS_LIB_EXT );
if( !g_hModule )
return false;
g_pfnGetFSAPI = (void*)GetProcAddress( g_hModule, GET_FS_API );
if( !g_pfnGetFSAPI )
return false;
if( !g_pfnGetFSAPI( FS_API_VERSION, &g_fs, &g_nullglobals, NULL ))
return false;
return true;
}
#include "filesystem_test_common.h"
static int TestNoInit( void )
{

View File

@@ -51,7 +51,8 @@ def build(bld):
tests = {
'interface' : 'tests/interface.cpp',
'caseinsensitive' : 'tests/caseinsensitive.c',
'no-init': 'tests/no-init.c'
'no-init': 'tests/no-init.c',
'filecopy': 'tests/filecopy.c'
}
for i in tests:

View File

@@ -454,23 +454,23 @@ void R_StudioCalcBones( int frame, float s, const mstudiobone_t *pbone, const ms
if( panimvalue->num.valid > j )
{
v1[i] = panimvalue[j + 1].value;
v1[i] = UnalignedShort( &( panimvalue[j + 1].value ) );
if( panimvalue->num.valid > j + 1 )
v2[i] = panimvalue[j + 2].value;
v2[i] = UnalignedShort( &( panimvalue[j + 2].value ) );
else if( panimvalue->num.total > j + 1 )
v2[i] = v1[i];
else
v2[i] = panimvalue[panimvalue->num.valid + 2].value;
v2[i] = UnalignedShort( &( panimvalue[panimvalue->num.valid + 2].value ) );
}
else
{
v1[i] = panimvalue[panimvalue->num.valid].value;
v1[i] = UnalignedShort( &( panimvalue[panimvalue->num.valid].value ) );
if( panimvalue->num.total > j + 1 )
v2[i] = v1[i];
else
v2[i] = panimvalue[panimvalue->num.valid + 2].value;
v2[i] = UnalignedShort( &( panimvalue[panimvalue->num.valid + 2].value ) );
}
v1[i] = pbone->value[i] + v1[i] * pbone->scale[i] + fadj;

View File

@@ -128,9 +128,9 @@ CONSTANTS AND HELPER MACROS
#define VectorClear(x) ((x)[0]=(x)[1]=(x)[2]=0)
#define Vector2Lerp( v1, lerp, v2, c ) ((c)[0] = (v1)[0] + (lerp) * ((v2)[0] - (v1)[0]), (c)[1] = (v1)[1] + (lerp) * ((v2)[1] - (v1)[1]))
#define VectorLerp( v1, lerp, v2, c ) ((c)[0] = (v1)[0] + (lerp) * ((v2)[0] - (v1)[0]), (c)[1] = (v1)[1] + (lerp) * ((v2)[1] - (v1)[1]), (c)[2] = (v1)[2] + (lerp) * ((v2)[2] - (v1)[2]))
#define VectorNormalize( v ) { float ilength = (float)sqrt(DotProduct(v, v));if (ilength) ilength = 1.0f / ilength;v[0] *= ilength;v[1] *= ilength;v[2] *= ilength; }
#define VectorNormalize2( v, dest ) {float ilength = (float)sqrt(DotProduct(v,v));if (ilength) ilength = 1.0f / ilength;dest[0] = v[0] * ilength;dest[1] = v[1] * ilength;dest[2] = v[2] * ilength; }
#define VectorNormalizeFast( v ) {float ilength = (float)Q_rsqrt(DotProduct(v,v)); v[0] *= ilength; v[1] *= ilength; v[2] *= ilength; }
#define VectorNormalize( v ) { float ilength = (float)sqrt(DotProduct((v), (v)));if (ilength) ilength = 1.0f / ilength;(v)[0] *= ilength;(v)[1] *= ilength;(v)[2] *= ilength; }
#define VectorNormalize2( v, dest ) {float ilength = (float)sqrt(DotProduct((v),(v)));if (ilength) ilength = 1.0f / ilength;(dest)[0] = (v)[0] * ilength;(dest)[1] = (v)[1] * ilength;(dest)[2] = (v)[2] * ilength; }
#define VectorNormalizeFast( v ) {float ilength = (float)Q_rsqrt(DotProduct((v),(v))); (v)[0] *= ilength; (v)[1] *= ilength; (v)[2] *= ilength; }
#define VectorNormalizeLength( v ) VectorNormalizeLength2((v), (v))
#define VectorNegate(x, y) ((y)[0] = -(x)[0], (y)[1] = -(x)[1], (y)[2] = -(x)[2])
#define VectorM(scale1, b1, c) ((c)[0] = (scale1) * (b1)[0],(c)[1] = (scale1) * (b1)[1],(c)[2] = (scale1) * (b1)[2])

View File

@@ -465,8 +465,35 @@ static void GL2_InitTriQuads( void )
rpglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, 0 );
}
static void GL2_InitIncrementalBuffer( int i, GLuint size )
static void GL2_FreeIncrementalBufferAttr( int i, int valid_mappings )
{
if( gl2wrap.attrbufobj[i] )
{
if( gl2wrap_config.buf_storage )
{
for( int j = 0; j < valid_mappings; j++ )
{
rpglBindBufferARB( GL_ARRAY_BUFFER_ARB, gl2wrap.attrbufobj[i][j] );
pglUnmapBufferARB( GL_ARRAY_BUFFER_ARB );
}
rpglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 );
}
pglDeleteBuffersARB( gl2wrap_config.cycle_buffers, gl2wrap.attrbufobj[i] );
Mem_Free( gl2wrap.attrbufobj[i] );
gl2wrap.attrbufobj[i] = NULL;
}
Mem_Free( gl2wrap.mappings[i] );
gl2wrap.mappings[i] = NULL;
gl2wrap.attrbuf[i] = NULL;
}
static qboolean GL2_InitIncrementalBuffer( int i, GLuint size )
{
int valid_j = 0;
gl2wrap.attrbufobj[i] = Mem_Calloc( r_temppool, gl2wrap_config.cycle_buffers * sizeof( GLuint ));
if( gl2wrap_config.buf_storage )
gl2wrap.mappings[i] = Mem_Calloc( r_temppool, gl2wrap_config.cycle_buffers * sizeof( void * ));
@@ -481,12 +508,23 @@ static void GL2_InitIncrementalBuffer( int i, GLuint size )
GL_MAP_PERSISTENT_BIT | MB( gl2wrap_config.coherent, COHERENT );
pglBufferStorage( GL_ARRAY_BUFFER_ARB, size, NULL, GL_MAP_WRITE_BIT | MB( gl2wrap_config.coherent, COHERENT ) | GL_MAP_PERSISTENT_BIT );
gl2wrap.mappings[i][j] = pglMapBufferRange( GL_ARRAY_BUFFER_ARB, 0, size, flags );
if( !gl2wrap.mappings[i][j] )
{
gEngfuncs.Con_Printf( S_ERROR "%s: pglMapBufferRange failed for attr %d, buffer %d\n", __func__, i, j );
goto err;
}
valid_j = j + 1;
}
else
pglBufferDataARB( GL_ARRAY_BUFFER_ARB, size, NULL, GL_STREAM_DRAW_ARB );
}
if( gl2wrap_config.buf_storage )
gl2wrap.attrbuf[i] = gl2wrap.mappings[i][0];
return true;
err:
GL2_FreeIncrementalBufferAttr( i, valid_j );
return false;
}
@@ -601,6 +639,7 @@ int GL2_ShimInit( void )
total = 0;
init_attrbufs:
for( int i = 0; i < GL2_ATTR_MAX; ++i )
{
GLuint size = GL2_MAX_VERTS * gl2wrap_attr_size[i] * sizeof( GLfloat );
@@ -611,7 +650,20 @@ int GL2_ShimInit( void )
if( gl2wrap_config.incremental )
{
GL2_InitIncrementalBuffer( i, size );
if( !GL2_InitIncrementalBuffer( i, size ))
{
for( int k = 0; k < i; k++ )
GL2_FreeIncrementalBufferAttr( k, gl2wrap_config.cycle_buffers );
gEngfuncs.Con_Printf( S_WARN "%s: falling back without buf_storage/incremental\n", __func__ );
gl2wrap_config.buf_storage = false;
gl2wrap_config.incremental = false;
if( !gEngfuncs.Sys_CheckParm( "-vao" ) && glConfig.context != CONTEXT_TYPE_GL_CORE ) // keep vao_mandatory for users who requested VAO
gl2wrap_config.vao_mandatory = false;
if( !gl2wrap_config.vao_mandatory )
gl2wrap_config.cycle_buffers = 1;
total = 0;
goto init_attrbufs;
}
}
else
{

View File

@@ -805,6 +805,9 @@ static void R_DrawEntitiesOnList( void )
RI.currententity = tr.draw_list->solid_entities[i];
RI.currentmodel = RI.currententity->model;
if( !RI.currentmodel && RI.currententity->player && !FBitSet( RI.rvp.flags, RF_DRAW_WORLD ))
continue;
Assert( RI.currententity != NULL );
Assert( RI.currentmodel != NULL );
@@ -837,6 +840,9 @@ static void R_DrawEntitiesOnList( void )
RI.currententity = tr.draw_list->solid_entities[i];
RI.currentmodel = RI.currententity->model;
if( !RI.currentmodel && RI.currententity->player && !FBitSet( RI.rvp.flags, RF_DRAW_WORLD ))
continue;
Assert( RI.currententity != NULL );
Assert( RI.currentmodel != NULL );
@@ -875,6 +881,9 @@ static void R_DrawEntitiesOnList( void )
if( tr.blend <= 0.0f ) continue;
if( !RI.currentmodel && RI.currententity->player && !FBitSet( RI.rvp.flags, RF_DRAW_WORLD ))
continue;
Assert( RI.currententity != NULL );
Assert( RI.currentmodel != NULL );

View File

@@ -517,6 +517,9 @@ static void R_DrawEntitiesOnList( void )
RI.currentmodel = RI.currententity->model;
// d_aflatcolor += 500;
if( !RI.currentmodel && RI.currententity->player && !FBitSet( RI.rvp.flags, RF_DRAW_WORLD ))
continue;
Assert( RI.currententity != NULL );
Assert( RI.currentmodel != NULL );
@@ -544,6 +547,9 @@ static void R_DrawEntitiesOnList( void )
RI.currententity = tr.draw_list->solid_entities[i];
RI.currentmodel = RI.currententity->model;
if( !RI.currentmodel && RI.currententity->player && !FBitSet( RI.rvp.flags, RF_DRAW_WORLD ))
continue;
Assert( RI.currententity != NULL );
Assert( RI.currentmodel != NULL );
@@ -579,6 +585,9 @@ static void R_DrawEntitiesOnList( void )
if( tr.blend <= 0.0f )
continue;
if( !RI.currentmodel && RI.currententity->player && !FBitSet( RI.rvp.flags, RF_DRAW_WORLD ))
continue;
Assert( RI.currententity != NULL );
Assert( RI.currentmodel != NULL );

View File

@@ -40,6 +40,10 @@ echo "Building libsolder..."
make -C libsolder install || die
# Remove devkitPro's outdated mbedTLS as we only target 4.x+
echo "Removing devkitPro's mbedTLS port..."
dkp-pacman -R --noconfirm switch-libssh2 switch-mbedtls || true
echo "Building engine..."
./waf configure -T release --nswitch || die_configure

View File

@@ -67,20 +67,21 @@ echo "Generating default config files..."
pushd pkgtemp/data/xash3d/valve || die
touch config.cfg
echo 'unbindall' >> config.cfg
echo 'bind A_BUTTON "+use"' >> config.cfg
echo 'bind B_BUTTON "+jump"' >> config.cfg
echo 'bind X_BUTTON "+reload"' >> config.cfg
echo 'bind Y_BUTTON "+duck"' >> config.cfg
echo 'bind L1_BUTTON "+attack2"' >> config.cfg
echo 'bind R1_BUTTON "+attack"' >> config.cfg
echo 'bind START "escape"' >> config.cfg
echo 'bind DPAD_UP "lastinv"' >> config.cfg
echo 'bind DPAD_DOWN "impulse 100"' >> config.cfg
echo 'bind DPAD_LEFT "invprev"' >> config.cfg
echo 'bind DPAD_RIGHT "invnext"' >> config.cfg
echo 'gl_vsync "1"' >> config.cfg
echo 'sv_autosave "0"' >> config.cfg
echo 'unbindall' >> config.cfg
echo 'bind A_BUTTON "+use"' >> config.cfg
echo 'bind B_BUTTON "+jump"' >> config.cfg
echo 'bind X_BUTTON "+reload"' >> config.cfg
echo 'bind Y_BUTTON "+duck"' >> config.cfg
echo 'bind L1_BUTTON "+attack2"' >> config.cfg
echo 'bind R1_BUTTON "+attack"' >> config.cfg
echo 'bind START "escape"' >> config.cfg
echo 'bind DPAD_UP "lastinv"' >> config.cfg
echo 'bind DPAD_DOWN "impulse 100"' >> config.cfg
echo 'bind DPAD_LEFT "invprev"' >> config.cfg
echo 'bind DPAD_RIGHT "invnext"' >> config.cfg
echo 'gl_vsync "1"' >> config.cfg
echo 'sv_autosave "0"' >> config.cfg
echo 'touch_config_file "touch_profiles/psvita.cfg"' >> config.cfg
touch video.cfg
echo 'fullscreen "1"' >> video.cfg

View File

@@ -13,6 +13,7 @@ echo "Downloading vitasdk..."
export VITASDK=/usr/local/vitasdk
VITAGL_SRCREV="4d3ab1053424abe3b2164a50d15c5e355e33ed99" # lock vitaGL version to avoid compilation errors
SDL_SRCREV="28a709718422915dab13b6984e6ff8c8e37447c8" # lock vitaGL fork of SDL2 to a known-good revision
install_package()
{
@@ -39,7 +40,10 @@ popd || exit 1
echo "Downloading vitaGL fork of SDL2..."
git clone https://github.com/Northfear/SDL.git --depth=1 || exit 1
git clone https://github.com/Northfear/SDL.git || exit 1
pushd SDL || exit 1
git checkout $SDL_SRCREV || exit 1
popd || exit 1
echo "Downloading vita-rtld..."

View File

@@ -96,6 +96,7 @@ SUBDIRS = [
Subproject('ref/soft', lambda x: x.env.CLIENT and x.env.SOFT),
Subproject('ref/null', lambda x: x.env.CLIENT and x.env.NULL),
Subproject('3rdparty/bzip2', lambda x: x.env.CLIENT and not x.env.HAVE_SYSTEM_BZ2),
Subproject('3rdparty/mbedtls'),
Subproject('3rdparty/opus', lambda x: x.env.CLIENT and not x.env.HAVE_SYSTEM_OPUS),
Subproject('3rdparty/libogg', lambda x: x.env.CLIENT and not x.env.HAVE_SYSTEM_OGG),
Subproject('3rdparty/vorbis', lambda x: x.env.CLIENT and (not x.env.HAVE_SYSTEM_VORBIS or not x.env.HAVE_SYSTEM_VORBISFILE)),
@@ -173,7 +174,6 @@ def options(opt):
help = 'disables rpath, duh!')
# a1ba: special option for me
grp.add_option('--debug-all-servers', action='store_true', dest='ALL_SERVERS', default=False, help='')
grp.add_option('--enable-msvcdeps', action='store_true', dest='MSVCDEPS', default=False, help='')
grp.add_option('--enable-wafcache', action='store_true', dest='WAFCACHE', default=False, help='')
@@ -445,7 +445,6 @@ def configure(conf):
conf.env.GAMEDIR = conf.options.GAMEDIR
conf.define('XASH_GAMEDIR', conf.options.GAMEDIR)
conf.define_cond('XASH_ALL_SERVERS', conf.options.ALL_SERVERS)
if conf.env.DEST_OS == 'nswitch':
conf.check_cfg(package='solder', args='--cflags --libs', uselib_store='SOLDER')
@@ -472,7 +471,7 @@ def configure(conf):
# Don't check them more than once, to save time
# Usually, they are always available
# but we need them in uselib
a = [ 'user32', 'shell32', 'gdi32', 'advapi32', 'dbghelp', 'psapi', 'ws2_32' ]
a = [ 'user32', 'shell32', 'gdi32', 'advapi32', 'dbghelp', 'psapi', 'ws2_32', 'bcrypt' ]
if conf.env.COMPILER_CC == 'msvc':
for i in a:
conf.start_msg('Checking for MSVC library')