mirror of
https://github.com/FWGS/xash3d-fwgs.git
synced 2026-08-05 11:35:05 +08:00
Compare commits
150 Commits
continuous
...
freevgui
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
45d52fe242 | ||
|
|
ecda80fb9a | ||
|
|
9f14c48787 | ||
|
|
8b15bc9a96 | ||
|
|
e6913c9ce2 | ||
|
|
b4495238d9 | ||
|
|
635b382c1d | ||
|
|
7fbd461f83 | ||
|
|
eb0ff8e631 | ||
|
|
f50e61403f | ||
|
|
2cf8c63631 | ||
|
|
e09773c912 | ||
|
|
3a15357dc8 | ||
|
|
2127735456 | ||
|
|
e0ab44213a | ||
|
|
f2166a2c9d | ||
|
|
009855c193 | ||
|
|
f0ea3a194a | ||
|
|
9f2b8954e9 | ||
|
|
2166dbde66 | ||
|
|
4c109a0c6c | ||
|
|
36c71c2610 | ||
|
|
e2d6b7ab28 | ||
|
|
8ccb9dea3f | ||
|
|
34467bfe9a | ||
|
|
b1d9eef896 | ||
|
|
c2ac632446 | ||
|
|
365c126de6 | ||
|
|
44313e624c | ||
|
|
2f776a6173 | ||
|
|
190fa1bab8 | ||
|
|
3366ad06d0 | ||
|
|
57206b3651 | ||
|
|
d9ecd03b4a | ||
|
|
2de4d0d364 | ||
|
|
cc3889146c | ||
|
|
09f5886751 | ||
|
|
fb5b4fed6e | ||
|
|
1e31fbcd51 | ||
|
|
b2a5f0db5a | ||
|
|
abd7b5ccdd | ||
|
|
8c9cd59c71 | ||
|
|
c79b29687c | ||
|
|
1eb922a0f1 | ||
|
|
f6e36994be | ||
|
|
e83ba6509e | ||
|
|
5cfb67940b | ||
|
|
e31c893852 | ||
|
|
85e5f032d8 | ||
|
|
92fd51ad99 | ||
|
|
5480055997 | ||
|
|
d634c6aad5 | ||
|
|
034efb3b31 | ||
|
|
eae45b2e81 | ||
|
|
567d279007 | ||
|
|
27f8385bb1 | ||
|
|
1cfabd5ec9 | ||
|
|
ab6672fb8a | ||
|
|
2d39a7fa24 | ||
|
|
7db12ade3a | ||
|
|
61a84f6953 | ||
|
|
75ffba64c5 | ||
|
|
c134840148 | ||
|
|
51d1bfbe08 | ||
|
|
79ff8332ac | ||
|
|
f155005bb1 | ||
|
|
a8de8cf5e6 | ||
|
|
74fcab9bfb | ||
|
|
efad71bcc9 | ||
|
|
c444ab882a | ||
|
|
8fad3bbad6 | ||
|
|
4cd6d25ee6 | ||
|
|
33e47ef5a9 | ||
|
|
d19e9606e9 | ||
|
|
fe1827f5e4 | ||
|
|
918323fcea | ||
|
|
e75256ec9d | ||
|
|
47582182e6 | ||
|
|
4a2c218cfc | ||
|
|
376817478d | ||
|
|
b96dc61e31 | ||
|
|
b997d5657b | ||
|
|
e9f5bca5c4 | ||
|
|
b36f107a2e | ||
|
|
a305ffb33f | ||
|
|
1cc1b60b10 | ||
|
|
648db4a2b3 | ||
|
|
2c0c20dfe6 | ||
|
|
17e167432a | ||
|
|
97b8ca6b38 | ||
|
|
6dabc6f2c5 | ||
|
|
e808038fee | ||
|
|
a60d0d142d | ||
|
|
bf5d88d9a4 | ||
|
|
adca77672a | ||
|
|
ef5295c5ab | ||
|
|
37da317133 | ||
|
|
c93cb49a9e | ||
|
|
4f342fb19a | ||
|
|
8ee3544507 | ||
|
|
d44e6d5db7 | ||
|
|
21cc9feca7 | ||
|
|
e87b14468d | ||
|
|
e01921dd10 | ||
|
|
e5c4e49782 | ||
|
|
d61983a5d2 | ||
|
|
c3f4eb026b | ||
|
|
fe9b8bcaca | ||
|
|
33cd20b21b | ||
|
|
78a3b958dd | ||
|
|
0e56fa10f9 | ||
|
|
b6a67b56b0 | ||
|
|
eb9de1cac8 | ||
|
|
24abd2deb2 | ||
|
|
996dcc637d | ||
|
|
8b89bdca6f | ||
|
|
3a6e3ba3ca | ||
|
|
42ab099a07 | ||
|
|
09f15042fe | ||
|
|
344712b990 | ||
|
|
2904c3e88b | ||
|
|
25ca2ecb12 | ||
|
|
f6b28e32e2 | ||
|
|
e118376735 | ||
|
|
5ef6123f03 | ||
|
|
e4600b5fe0 | ||
|
|
074bd8b2b3 | ||
|
|
fca347dd00 | ||
|
|
2a284090ef | ||
|
|
ecd74a0a7f | ||
|
|
853a67b0c5 | ||
|
|
80ec1c83f1 | ||
|
|
41681229f9 | ||
|
|
7fce99481f | ||
|
|
142dc7b87a | ||
|
|
3e24d5a347 | ||
|
|
64b1b54f5b | ||
|
|
1fed340330 | ||
|
|
64be4251c8 | ||
|
|
c5a8160c32 | ||
|
|
3c1cf81c2d | ||
|
|
c6c1163aa0 | ||
|
|
e8845fc404 | ||
|
|
d49178d5b6 | ||
|
|
e2cf580818 | ||
|
|
d0a98447c4 | ||
|
|
29a9fcf76c | ||
|
|
37d9791f00 | ||
|
|
c5fbc98d16 | ||
|
|
4c2302ec6c |
113
.clang-format
Normal file
113
.clang-format
Normal file
@@ -0,0 +1,113 @@
|
||||
Language: Cpp
|
||||
Standard: c++17
|
||||
UseTab: ForContinuationAndIndentation
|
||||
TabWidth: 4
|
||||
IndentWidth: 4
|
||||
ContinuationIndentWidth: 4
|
||||
IndentCaseLabels: false
|
||||
IndentPPDirectives: None
|
||||
NamespaceIndentation: None
|
||||
AccessModifierOffset: -4
|
||||
IndentAccessModifiers: false
|
||||
BreakBeforeBraces: Custom
|
||||
BraceWrapping:
|
||||
AfterCaseLabel: true
|
||||
AfterClass: true
|
||||
AfterControlStatement: Always
|
||||
AfterEnum: true
|
||||
AfterFunction: true
|
||||
AfterNamespace: true
|
||||
AfterStruct: true
|
||||
AfterUnion: true
|
||||
AfterExternBlock: false
|
||||
BeforeCatch: true
|
||||
BeforeElse: true
|
||||
BeforeWhile: true
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: false
|
||||
SplitEmptyRecord: false
|
||||
SplitEmptyNamespace: false
|
||||
MaxEmptyLinesToKeep: 2
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
KeepEmptyLinesAtEOF: false
|
||||
SeparateDefinitionBlocks: Always
|
||||
InsertNewlineAtEOF: true
|
||||
LineEnding: LF
|
||||
AllowAllArgumentsOnNextLine: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: true
|
||||
BinPackArguments: true
|
||||
BinPackParameters: true
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakAfterDefinitionReturnType: None
|
||||
AllowShortFunctionsOnASingleLine: None
|
||||
AllowShortBlocksOnASingleLine: Empty
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortIfStatementsOnASingleLine: Never
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AllowShortEnumsOnASingleLine: false
|
||||
BreakBeforeBinaryOperators: All
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializers: BeforeColon
|
||||
PointerAlignment: Right
|
||||
DerivePointerAlignment: false
|
||||
ReferenceAlignment: Pointer
|
||||
SpaceAroundPointerQualifiers: Default
|
||||
SpacesInParens: Custom
|
||||
SpacesInParensOptions:
|
||||
ExceptDoubleParentheses: true
|
||||
InCStyleCasts: false
|
||||
InConditionalStatements: true
|
||||
InEmptyParentheses: false
|
||||
Other: true
|
||||
# XashCollapseParens: true
|
||||
SpaceBeforeParens: Never
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterLogicalNot: false
|
||||
SpaceAfterTemplateKeyword: true
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCaseColon: false
|
||||
SpaceBeforeCpp11BracedList: false
|
||||
SpaceBeforeCtorInitializerColon: true
|
||||
SpaceBeforeInheritanceColon: true
|
||||
SpaceBeforeRangeBasedForLoopColon: true
|
||||
SpaceBeforeSquareBrackets: false
|
||||
SpaceInEmptyBlock: false
|
||||
SpacesInAngles: Never
|
||||
SpacesInContainerLiterals: false
|
||||
SpacesInSquareBrackets: false
|
||||
Cpp11BracedListStyle: false
|
||||
SpacesBeforeTrailingComments: 1
|
||||
SpacesInLineCommentPrefix:
|
||||
Minimum: 1
|
||||
Maximum: -1
|
||||
AlignAfterOpenBracket: DontAlign
|
||||
AlignArrayOfStructures: None
|
||||
AlignConsecutiveAssignments:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignConsecutiveBitFields:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignConsecutiveDeclarations:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
AlignConsecutiveMacros:
|
||||
Enabled: true
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignEscapedNewlines: Left
|
||||
AlignOperands: Align
|
||||
AlignTrailingComments:
|
||||
Kind: Always
|
||||
OverEmptyLines: 0
|
||||
ColumnLimit: 0
|
||||
ReflowComments: false
|
||||
SortIncludes: Never
|
||||
SortUsingDeclarations: false
|
||||
InsertBraces: false
|
||||
RemoveBracesLLVM: false
|
||||
FixNamespaceComments: false
|
||||
1
.github/workflows/c-cpp.yml
vendored
1
.github/workflows/c-cpp.yml
vendored
@@ -70,6 +70,7 @@ jobs:
|
||||
env:
|
||||
SDL_VERSION: 2.32.10
|
||||
FFMPEG_VERSION: 8.1
|
||||
YY_THUNKS_VERSION: v1.2.1
|
||||
GH_CPU_ARCH: ${{ matrix.targetarch }}
|
||||
GH_CPU_OS: ${{ matrix.targetos }}
|
||||
GH_CROSSCOMPILING: ${{ matrix.cross }}
|
||||
|
||||
6
.gitmodules
vendored
6
.gitmodules
vendored
@@ -43,3 +43,9 @@
|
||||
[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
|
||||
[submodule "3rdparty/freevgui"]
|
||||
path = 3rdparty/freevgui
|
||||
url = https://github.com/FWGS/freevgui.git
|
||||
|
||||
2
3rdparty/extras/wscript
vendored
2
3rdparty/extras/wscript
vendored
@@ -23,6 +23,6 @@ def build(bld):
|
||||
|
||||
bld(features='zip',
|
||||
name = 'extras.pk3',
|
||||
files = srcdir.ant_glob('**/*'),
|
||||
files = srcdir.ant_glob('**/*', excl=['scripts/**']),
|
||||
relative_to = srcdir,
|
||||
install_path = install_path)
|
||||
|
||||
2
3rdparty/extras/xash-extras
vendored
2
3rdparty/extras/xash-extras
vendored
Submodule 3rdparty/extras/xash-extras updated: 6d2457f66f...9f5b017985
1
3rdparty/freevgui
vendored
Submodule
1
3rdparty/freevgui
vendored
Submodule
Submodule 3rdparty/freevgui added at 8bf71dba02
2
3rdparty/gl4es/gl4es
vendored
2
3rdparty/gl4es/gl4es
vendored
Submodule 3rdparty/gl4es/gl4es updated: a744af14d4...81547d9867
2
3rdparty/gl4es/wscript
vendored
2
3rdparty/gl4es/wscript
vendored
@@ -16,6 +16,8 @@ def build(bld):
|
||||
cflags = []
|
||||
if bld.env.COMPILER_CC != 'msvc':
|
||||
cflags += ['-w', '-fvisibility=hidden', '-std=gnu99']
|
||||
else:
|
||||
cflags += ['-wd4098']
|
||||
bld.stlib(source = gl4es_srcdir.ant_glob(['gl/*.c', 'gl/*/*.c', 'glx/hardext.c']),
|
||||
target = 'gl4es',
|
||||
includes = ['gl4es/src', 'gl4es/src/gl', 'gl4es/src/glx', 'gl4es/include'],
|
||||
|
||||
2
3rdparty/mainui
vendored
2
3rdparty/mainui
vendored
Submodule 3rdparty/mainui updated: b1f3e6706d...8843994b3e
107
3rdparty/mbedtls/compat.c
vendored
Normal file
107
3rdparty/mbedtls/compat.c
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
compat.c - mbedTLS platform overrides for targets upstream doesn't cover
|
||||
Copyright (C) 2026 Xash3D FWGS contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
/*
|
||||
WinXP: legacy CryptGenRandom via advapi32
|
||||
Source: ttps://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptgenrandom
|
||||
|
||||
PSVita: sceKernelGetRandomNumber from psp2/kernel/rng.h (64-byte cap).
|
||||
Source: https://github.com/vitasdk/vita-headers/blob/master/include/psp2/kernel/rng.h
|
||||
|
||||
NSwitch: randomGet from libnx.
|
||||
Source: https://github.com/switchbrew/libnx/blob/master/nx/include/switch/kernel/random.h
|
||||
*/
|
||||
|
||||
#include "mbedtls/platform.h"
|
||||
#include "psa/crypto.h"
|
||||
|
||||
#if defined( MBEDTLS_PLATFORM_MS_TIME_ALT )
|
||||
|
||||
mbedtls_ms_time_t mbedtls_ms_time( void )
|
||||
{
|
||||
extern double Platform_DoubleTime( void );
|
||||
|
||||
return (mbedtls_ms_time_t)( Platform_DoubleTime() * 1000.0 );
|
||||
}
|
||||
|
||||
#endif /* MBEDTLS_PLATFORM_MS_TIME_ALT */
|
||||
|
||||
#if defined( MBEDTLS_PSA_DRIVER_GET_ENTROPY )
|
||||
#if defined( _WIN32 ) && !defined( _WIN64 )
|
||||
#include <windows.h>
|
||||
#include <wincrypt.h>
|
||||
int mbedtls_platform_get_entropy( psa_driver_get_entropy_flags_t flags, size_t *estimate_bits, unsigned char *output, size_t output_size )
|
||||
{
|
||||
if( flags != 0 )
|
||||
return PSA_ERROR_NOT_SUPPORTED;
|
||||
|
||||
HCRYPTPROV prov;
|
||||
if( !CryptAcquireContextW( &prov, NULL, NULL, PROV_RSA_FULL,
|
||||
CRYPT_VERIFYCONTEXT | CRYPT_SILENT ))
|
||||
return PSA_ERROR_INSUFFICIENT_ENTROPY;
|
||||
|
||||
if( !CryptGenRandom( prov, (DWORD)output_size, output ))
|
||||
{
|
||||
CryptReleaseContext( prov, 0 );
|
||||
return PSA_ERROR_INSUFFICIENT_ENTROPY;
|
||||
}
|
||||
|
||||
CryptReleaseContext( prov, 0 );
|
||||
*estimate_bits = 8 * output_size;
|
||||
return 0;
|
||||
}
|
||||
#elif defined( __vita__ )
|
||||
#include <psp2/kernel/rng.h>
|
||||
|
||||
int mbedtls_platform_get_entropy( psa_driver_get_entropy_flags_t flags, size_t *estimate_bits, unsigned char *output, size_t output_size )
|
||||
{
|
||||
if( flags != 0 )
|
||||
return PSA_ERROR_NOT_SUPPORTED;
|
||||
|
||||
size_t total = 0;
|
||||
while( total < output_size )
|
||||
{
|
||||
size_t chunk = output_size - total;
|
||||
|
||||
if( chunk > 64 )
|
||||
chunk = 64;
|
||||
|
||||
if( sceKernelGetRandomNumber( output + total, chunk ) != 0 )
|
||||
return PSA_ERROR_INSUFFICIENT_ENTROPY;
|
||||
|
||||
total += chunk;
|
||||
}
|
||||
|
||||
*estimate_bits = 8 * output_size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#elif defined( __SWITCH__ )
|
||||
#include <switch.h>
|
||||
|
||||
int mbedtls_platform_get_entropy( psa_driver_get_entropy_flags_t flags, size_t *estimate_bits, unsigned char *output, size_t output_size )
|
||||
{
|
||||
if( flags != 0 )
|
||||
return PSA_ERROR_NOT_SUPPORTED;
|
||||
|
||||
randomGet( output, output_size );
|
||||
*estimate_bits = 8 * output_size;
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
#error "MBEDTLS_PSA_DRIVER_GET_ENTROPY enabled but no platform impl in compat.c"
|
||||
#endif
|
||||
|
||||
#endif /* MBEDTLS_PSA_DRIVER_GET_ENTROPY */
|
||||
1
3rdparty/mbedtls/mbedtls
vendored
Submodule
1
3rdparty/mbedtls/mbedtls
vendored
Submodule
Submodule 3rdparty/mbedtls/mbedtls added at 701bb7cfde
67
3rdparty/mbedtls/wscript
vendored
Normal file
67
3rdparty/mbedtls/wscript
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
#! /usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
def options(opt):
|
||||
grp = opt.add_option_group('mbedTLS options')
|
||||
grp.add_option('--disable-mbedtls', action='store_false', dest='MBEDTLS', default=True,
|
||||
help='disable bundled mbedTLS and built-in HTTPS support [default: enabled]')
|
||||
|
||||
def configure(conf):
|
||||
conf.env.MBEDTLS = conf.options.MBEDTLS
|
||||
if not conf.env.MBEDTLS:
|
||||
return
|
||||
|
||||
if not conf.path.find_dir('mbedtls') or not conf.path.find_dir('mbedtls/library'):
|
||||
conf.fatal('Can\'t find mbedtls submodule. Run `git submodule update --init --recursive`.')
|
||||
|
||||
if not conf.path.find_dir('mbedtls/tf-psa-crypto/core'):
|
||||
conf.fatal('mbedTLS nested submodule tf-psa-crypto is missing. Run `git submodule update --init --recursive`.')
|
||||
|
||||
def build(bld):
|
||||
if not bld.env.MBEDTLS:
|
||||
return
|
||||
|
||||
sources = bld.path.ant_glob([
|
||||
'mbedtls/library/*.c',
|
||||
'mbedtls/tf-psa-crypto/core/*.c',
|
||||
'mbedtls/tf-psa-crypto/extras/*.c',
|
||||
'mbedtls/tf-psa-crypto/platform/*.c',
|
||||
'mbedtls/tf-psa-crypto/utilities/*.c',
|
||||
'mbedtls/tf-psa-crypto/drivers/builtin/src/*.c',
|
||||
])
|
||||
|
||||
sources += ['compat.c']
|
||||
|
||||
defines = [
|
||||
'MBEDTLS_USER_CONFIG_FILE="xash_mbedtls_config.h"',
|
||||
'TF_PSA_CRYPTO_USER_CONFIG_FILE="xash_psa_config.h"',
|
||||
]
|
||||
|
||||
includes = [
|
||||
'.',
|
||||
'mbedtls/include',
|
||||
'mbedtls/library',
|
||||
'mbedtls/tf-psa-crypto/include',
|
||||
'mbedtls/tf-psa-crypto/drivers/builtin/include',
|
||||
'mbedtls/tf-psa-crypto/core',
|
||||
'mbedtls/tf-psa-crypto/drivers/builtin/src',
|
||||
'mbedtls/tf-psa-crypto/utilities',
|
||||
'mbedtls/tf-psa-crypto/extras',
|
||||
'mbedtls/tf-psa-crypto/platform',
|
||||
'mbedtls/tf-psa-crypto/dispatch',
|
||||
]
|
||||
|
||||
bld.stlib(
|
||||
source = sources,
|
||||
target = 'mbedtls',
|
||||
features = 'c',
|
||||
includes = includes,
|
||||
defines = defines,
|
||||
export_defines = ['XASH_MBEDTLS'],
|
||||
export_includes = [
|
||||
'.',
|
||||
'mbedtls/include',
|
||||
'mbedtls/tf-psa-crypto/include',
|
||||
'mbedtls/tf-psa-crypto/drivers/builtin/include',
|
||||
]
|
||||
)
|
||||
36
3rdparty/mbedtls/xash_mbedtls_config.h
vendored
Normal file
36
3rdparty/mbedtls/xash_mbedtls_config.h
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
#ifndef XASH_MBEDTLS_CONFIG_H
|
||||
#define XASH_MBEDTLS_CONFIG_H
|
||||
|
||||
#undef MBEDTLS_NET_C
|
||||
#undef MBEDTLS_PKCS7_C
|
||||
#undef MBEDTLS_TIMING_C
|
||||
#undef MBEDTLS_SSL_SRV_C
|
||||
#undef MBEDTLS_SSL_PROTO_DTLS
|
||||
#undef MBEDTLS_SSL_DTLS_ANTI_REPLAY
|
||||
#undef MBEDTLS_SSL_DTLS_CONNECTION_ID
|
||||
#undef MBEDTLS_SSL_DTLS_HELLO_VERIFY
|
||||
#undef MBEDTLS_SSL_DTLS_SRTP
|
||||
#undef MBEDTLS_SSL_DTLS_CLIENT_PORT_REUSE
|
||||
#undef MBEDTLS_SSL_COOKIE_C
|
||||
#undef MBEDTLS_SSL_CACHE_C
|
||||
#undef MBEDTLS_SSL_TICKET_C
|
||||
#undef MBEDTLS_SSL_SESSION_TICKETS
|
||||
#undef MBEDTLS_SSL_RENEGOTIATION
|
||||
#undef MBEDTLS_SSL_CONTEXT_SERIALIZATION
|
||||
#undef MBEDTLS_SSL_KEYING_MATERIAL_EXPORT
|
||||
#undef MBEDTLS_SSL_MAX_FRAGMENT_LENGTH
|
||||
#undef MBEDTLS_SSL_ALL_ALERT_MESSAGES
|
||||
#undef MBEDTLS_SSL_ALPN
|
||||
#undef MBEDTLS_SSL_ENCRYPT_THEN_MAC
|
||||
#undef MBEDTLS_KEY_EXCHANGE_PSK_ENABLED
|
||||
#undef MBEDTLS_KEY_EXCHANGE_ECDHE_PSK_ENABLED
|
||||
#undef MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_PSK_ENABLED
|
||||
#undef MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_PSK_EPHEMERAL_ENABLED
|
||||
#undef MBEDTLS_X509_CREATE_C
|
||||
#undef MBEDTLS_X509_CRT_WRITE_C
|
||||
#undef MBEDTLS_X509_CSR_PARSE_C
|
||||
#undef MBEDTLS_X509_CSR_WRITE_C
|
||||
#undef MBEDTLS_X509_CRL_PARSE_C
|
||||
#undef MBEDTLS_VERSION_FEATURES
|
||||
|
||||
#endif /* XASH_MBEDTLS_CONFIG_H */
|
||||
82
3rdparty/mbedtls/xash_psa_config.h
vendored
Normal file
82
3rdparty/mbedtls/xash_psa_config.h
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
#ifndef XASH_PSA_CONFIG_H
|
||||
#define XASH_PSA_CONFIG_H
|
||||
|
||||
#if (defined( _WIN32 ) && !defined( _WIN64 )) || defined( __vita__ ) || defined( __SWITCH__ )
|
||||
/* WinXP, PSVita and NSwitch use entropy paths upstream doesn't cover.
|
||||
compat.c provides mbedtls_platform_get_entropy() for all three. */
|
||||
#undef MBEDTLS_PSA_BUILTIN_GET_ENTROPY
|
||||
#define MBEDTLS_PSA_DRIVER_GET_ENTROPY
|
||||
#endif
|
||||
|
||||
#if defined( __vita__ ) || defined( __SWITCH__ )
|
||||
/* Upstream has no Vita/NSW support; compat.c fills in */
|
||||
#define MBEDTLS_PLATFORM_MS_TIME_ALT
|
||||
#endif
|
||||
|
||||
#undef MBEDTLS_FS_IO
|
||||
#undef MBEDTLS_PSA_ITS_FILE_C
|
||||
#undef MBEDTLS_PSA_CRYPTO_STORAGE_C
|
||||
#undef MBEDTLS_SELF_TEST
|
||||
#undef MBEDTLS_HMAC_DRBG_C
|
||||
#undef MBEDTLS_LMS_C
|
||||
#undef MBEDTLS_NIST_KW_C
|
||||
#undef MBEDTLS_PKCS5_C
|
||||
|
||||
#undef PSA_WANT_ALG_CCM
|
||||
#undef PSA_WANT_ALG_CCM_STAR_NO_TAG
|
||||
#undef PSA_WANT_ALG_CBC_NO_PADDING
|
||||
#undef PSA_WANT_ALG_CBC_PKCS7
|
||||
#undef PSA_WANT_ALG_CFB
|
||||
#undef PSA_WANT_ALG_CMAC
|
||||
#undef PSA_WANT_ALG_CTR
|
||||
#undef PSA_WANT_ALG_ECB_NO_PADDING
|
||||
#undef PSA_WANT_ALG_OFB
|
||||
#undef PSA_WANT_ALG_STREAM_CIPHER
|
||||
#undef PSA_WANT_ALG_JPAKE
|
||||
#undef PSA_WANT_ALG_TLS12_ECJPAKE_TO_PMS
|
||||
#undef PSA_WANT_ALG_TLS12_PSK_TO_MS
|
||||
#undef PSA_WANT_ALG_FFDH
|
||||
#undef PSA_WANT_ALG_PBKDF2_HMAC
|
||||
#undef PSA_WANT_ALG_PBKDF2_AES_CMAC_PRF_128
|
||||
#undef PSA_WANT_KEY_TYPE_PASSWORD
|
||||
#undef PSA_WANT_KEY_TYPE_PASSWORD_HASH
|
||||
#undef PSA_WANT_ALG_SHA3_224
|
||||
#undef PSA_WANT_ALG_SHA3_256
|
||||
#undef PSA_WANT_ALG_SHA3_384
|
||||
#undef PSA_WANT_ALG_SHA3_512
|
||||
#undef PSA_WANT_ALG_SHAKE128
|
||||
#undef PSA_WANT_ALG_SHAKE256
|
||||
#undef PSA_WANT_ALG_SHA_224
|
||||
#undef PSA_WANT_ALG_SHA_1
|
||||
#undef PSA_WANT_ALG_MD5
|
||||
#undef PSA_WANT_ALG_RIPEMD160
|
||||
#undef PSA_WANT_KEY_TYPE_ARIA
|
||||
#undef PSA_WANT_KEY_TYPE_CAMELLIA
|
||||
#undef PSA_WANT_ECC_BRAINPOOL_P_R1_256
|
||||
#undef PSA_WANT_ECC_BRAINPOOL_P_R1_384
|
||||
#undef PSA_WANT_ECC_BRAINPOOL_P_R1_512
|
||||
#undef PSA_WANT_ECC_SECP_K1_256
|
||||
#undef PSA_WANT_ECC_MONTGOMERY_448
|
||||
#undef PSA_WANT_ECC_SECP_R1_521
|
||||
#undef PSA_WANT_DH_RFC7919_2048
|
||||
#undef PSA_WANT_DH_RFC7919_3072
|
||||
#undef PSA_WANT_DH_RFC7919_4096
|
||||
#undef PSA_WANT_DH_RFC7919_6144
|
||||
#undef PSA_WANT_DH_RFC7919_8192
|
||||
#undef PSA_WANT_KEY_TYPE_DH_KEY_PAIR_BASIC
|
||||
#undef PSA_WANT_KEY_TYPE_DH_KEY_PAIR_EXPORT
|
||||
#undef PSA_WANT_KEY_TYPE_DH_KEY_PAIR_GENERATE
|
||||
#undef PSA_WANT_KEY_TYPE_DH_KEY_PAIR_IMPORT
|
||||
#undef PSA_WANT_KEY_TYPE_DH_PUBLIC_KEY
|
||||
#undef PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_EXPORT
|
||||
#undef PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_IMPORT
|
||||
#undef PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_EXPORT
|
||||
#undef PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_GENERATE
|
||||
#undef PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_IMPORT
|
||||
#undef MBEDTLS_PEM_WRITE_C
|
||||
#undef MBEDTLS_PK_WRITE_C
|
||||
#undef MBEDTLS_PK_PARSE_EC_COMPRESSED
|
||||
#undef MBEDTLS_PK_PARSE_EC_EXTENDED
|
||||
#undef MBEDTLS_ASN1_WRITE_C
|
||||
|
||||
#endif /* XASH_PSA_CONFIG_H */
|
||||
2
3rdparty/vgui_support
vendored
2
3rdparty/vgui_support
vendored
Submodule 3rdparty/vgui_support updated: e021fb5e7f...aa31f43b57
59
3rdparty/yy-thunks/wscript
vendored
Normal file
59
3rdparty/yy-thunks/wscript
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
#! /usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
import hashlib
|
||||
from waflib import Task, TaskGen, Logs
|
||||
|
||||
OBJ_NAME = 'YY_Thunks_for_WinXP.obj'
|
||||
OBJ_SHA256 = '51a13c4b667c82181d357e274a50308a273be63f5f85ae958465774857721573'
|
||||
DLL_LINKFLAGS = ['-ENTRY:DllMainCRTStartupForYY_Thunks', '-alternatename:_YY_ThunksOriginalDllMainCRTStartup@12=__DllMainCRTStartup@12']
|
||||
|
||||
class copy_yy_thunks(Task.Task):
|
||||
color = 'CYAN'
|
||||
def run(self):
|
||||
return self.outputs[0].write(self.inputs[0].read('rb'), 'wb')
|
||||
|
||||
@TaskGen.feature('yy_thunks')
|
||||
def add_yy_thunks_obj(self):
|
||||
src = self.path.find_node(OBJ_NAME)
|
||||
out = self.path.find_or_declare(OBJ_NAME)
|
||||
self.compiled_tasks = [self.create_task('copy_yy_thunks', src, out)]
|
||||
|
||||
@TaskGen.feature('cshlib', 'cxxshlib')
|
||||
@TaskGen.after_method('process_use')
|
||||
def apply_yy_thunks_dll(self):
|
||||
if 'yy_thunks' not in self.to_list(getattr(self, 'use', [])):
|
||||
return
|
||||
|
||||
try:
|
||||
self.bld.get_tgen_by_name('yy_thunks')
|
||||
except Exception:
|
||||
return
|
||||
self.env.append_value('LINKFLAGS', DLL_LINKFLAGS)
|
||||
|
||||
def options(opt):
|
||||
pass
|
||||
|
||||
def configure(conf):
|
||||
if conf.env.DEST_OS != 'win32' or conf.env.COMPILER_CC != 'msvc':
|
||||
return
|
||||
|
||||
if conf.env.MSVC_TARGETS[0] not in ['amd64_x86', 'x86']:
|
||||
return
|
||||
|
||||
obj = conf.path.find_node(OBJ_NAME)
|
||||
if not obj:
|
||||
Logs.warn('YY-Thunks: %s not found, XP compatibility disabled' % OBJ_NAME)
|
||||
return
|
||||
|
||||
with open(obj.abspath(), 'rb') as f:
|
||||
digest = hashlib.sha256(f.read()).hexdigest()
|
||||
|
||||
if digest != OBJ_SHA256:
|
||||
conf.fatal('%s checksum mismatch: expected %s, got %s' % (OBJ_NAME, OBJ_SHA256, digest))
|
||||
|
||||
conf.env.YY_THUNKS = True
|
||||
|
||||
def build(bld):
|
||||
if bld.env.YY_THUNKS:
|
||||
bld(name='yy_thunks', features='yy_thunks')
|
||||
@@ -29,9 +29,7 @@ Initial Xash3D SDL2/Linux port author, Xash3D FWGS engine maintainer, creator of
|
||||
|
||||
## [$_Vladislav](https://github.com/Vladislav4KZ)
|
||||
|
||||
[YaPB Project](https://github.com/yapb) member, [Xash3D FWGS](https://github.com/FWGS/xash3d-fwgs) tester,
|
||||
editor of the [Official YaPB Documentation](https://github.com/yapb/docs) (in English and Russian), curator of the [YaPB Graph Database](https://github.com/yapb/graph) and author of [YaPB Waypoint/Graph Pack](https://gamebanana.com/mods/40087).
|
||||
[YaPB Project](https://github.com/yapb) member and [contributor](https://github.com/yapb/yapb/commits?author=Vladislav4KZ) ([Docs](https://github.com/yapb/docs), [Nav Graphs](https://github.com/yapb/graph), [RU translation](https://github.com/yapb/yapb/blob/master/cfg/addons/yapb/conf/lang/ru_lang.cfg)), Xash3D FWGS [tester](https://github.com/FWGS/xash3d-fwgs/issues?q=is%3Aissue%20state%3Aopen%20author%3AVladislav4KZ) and [contributor](https://github.com/FWGS/xash3d-fwgs/commits?author=Vladislav4KZ) (gyro-aim support, scr-parser, etc.), [CS16Client](https://github.com/Velaron/cs16-client) [contributor](https://github.com/Velaron/cs16-client/commits?author=Vladislav4KZ).
|
||||
|
||||
Also does the Russian localization of text, images (gfx/shell), as well as additional menu buttons in English used in Xash3D FWGS for various mods.
|
||||
|
||||
* [Boosty page](https://boosty.to/rasstaman1337)
|
||||
* [Boosty page](https://boosty.to/rasstaman1337)
|
||||
* [PayPal link](https://paypal.me/rasstaman1337)
|
||||
181
Documentation/extensions/xrcon-protocol.md
Normal file
181
Documentation/extensions/xrcon-protocol.md
Normal file
@@ -0,0 +1,181 @@
|
||||
# XRCON Protocol Specification
|
||||
|
||||
## Overview
|
||||
|
||||
XRCON is a TCP-based remote console protocol implemented as a server within the engine. It provides an alternative to the legacy RCON (which operates over UDP as out-of-band packets). XRCON uses a framed binary protocol over a single persistent TCP connection, accepting at most one client at a time.
|
||||
|
||||
Key characteristics:
|
||||
|
||||
- Based on TCP (stream-oriented, reliable)
|
||||
- Default port is 27000
|
||||
- Maximum only one concurrent client
|
||||
- Binary framed protocol with a 12-byte header + variable-length payload
|
||||
|
||||
|
||||
|
||||
## Configuration
|
||||
|
||||
The XRCON server is controlled by two configuration variables, both restricted to privileged users:
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `xrcon_enable` | `0` (disabled) | Master switch; when enabled, the server starts listening for connections |
|
||||
| `xrcon_address` | `127.0.0.1:27000` | Bind address and port; supports both IPv4 and IPv6. Changing this at runtime triggers a XRCON server restart (stop + rebind) |
|
||||
|
||||
By default, XRCON binds only to localhost (`127.0.0.1`), providing a degree of access control. The server will not start listening until explicitly enabled.
|
||||
|
||||
|
||||
|
||||
## Frame Format
|
||||
|
||||
All XRCON messages are encapsulated in a framed binary format. Each frame consists of a fixed 12-byte header followed by a variable-length payload.
|
||||
|
||||
### Header (12 bytes)
|
||||
|
||||
| Offset | Size | Field | Description |
|
||||
|---|---|---|---|
|
||||
| 0 | 4 | Type | 4-character message type string (null-terminated, occupies 5 bytes in the structure but the 5th byte is padding) |
|
||||
| 4 | 4 | Version | Protocol version (32-bit unsigned integer, network byte order) |
|
||||
| 8 | 2 | Length | Total frame length in bytes, including the header (16-bit unsigned integer, network byte order) |
|
||||
| 10 | 2 | Handle | Handle or sequence number (16-bit unsigned integer, network byte order; currently always zero) |
|
||||
|
||||
|
||||
### Protocol Version
|
||||
|
||||
The current protocol version is `0x000000D4` (212 decimal). The server also accepts `0x00D40000` for compatibility with certain third-party clients (e.g., CS2RemoteConsole).
|
||||
|
||||
### Maximum Frame Size
|
||||
|
||||
- Maximum payload per frame: 4096 bytes
|
||||
- Maximum total packet size (header + payload): approximately 4136 bytes
|
||||
- Receive buffer size: 4096 + 64 bytes
|
||||
- Transmit buffer size: 16384 bytes (4 × maximum frame size)
|
||||
|
||||
### Byte Order
|
||||
|
||||
Multi-byte integer fields (version, length, handle) are transmitted in **network byte order** (big-endian).
|
||||
|
||||
|
||||
|
||||
## Message Types
|
||||
|
||||
| Type | Direction | Description |
|
||||
|---|---|---|
|
||||
| `CMND` | Client → Server | Execute a console command |
|
||||
| `PRNT` | Server → Client | Console output / print message |
|
||||
| `CHAN` | Server → Client | Channel list (channel metadata) |
|
||||
| `AINF` | Server → Client | Application / server info |
|
||||
| `ADON` | Server → Client | Additional info / server name |
|
||||
|
||||
### CMND — Client Command
|
||||
|
||||
**Direction**: Client → Server
|
||||
|
||||
Sent by the client to execute a console command on the server.
|
||||
|
||||
| Section | Size | Description |
|
||||
|---|---|---|
|
||||
| Header | 12 bytes | Type = `"CMND"`, version, length, handle |
|
||||
| Payload | Variable | Raw command string (null-terminated) |
|
||||
|
||||
The command string is injected into the engine's command buffer for execution. There is **no authentication** — any connected client may execute arbitrary commands.
|
||||
|
||||
### PRNT — Server Print
|
||||
|
||||
**Direction**: Server → Client
|
||||
|
||||
Sent by the server to stream console output to the connected client.
|
||||
|
||||
| Section | Size | Description |
|
||||
|---|---|---|
|
||||
| Header | 12 bytes | Type = `"PRNT"`, version, length, handle |
|
||||
| Channel ID | 4 bytes | Channel identifier (unsigned 32-bit integer; always 0 for "Console") |
|
||||
| Padding | 20 bytes | Reserved (5 × 32-bit unsigned integers, all zero) |
|
||||
| Red | 1 byte | Red color component (always 255) |
|
||||
| Green | 1 byte | Green color component (always 255) |
|
||||
| Blue | 1 byte | Blue color component (always 255) |
|
||||
| Alpha | 1 byte | Alpha / opacity component (always 255) |
|
||||
| Text | Variable | The console output text (up to 4096 bytes) |
|
||||
|
||||
|
||||
### CHAN — Channel List
|
||||
|
||||
**Direction**: Server → Client
|
||||
|
||||
Sent by the server during the initial handshake (and potentially later) to describe the available console output channels.
|
||||
|
||||
| Section | Size | Description |
|
||||
|---|---|---|
|
||||
| Header | 12 bytes | Type = `"CHAN"`, version, length, handle |
|
||||
| Channel Count | 2 bytes | Number of channel records (unsigned 16-bit integer; always 1) |
|
||||
|
||||
Each channel record (58 bytes):
|
||||
|
||||
| Field | Size | Description |
|
||||
|---|---|---|
|
||||
| Channel ID | 4 bytes | Unique channel identifier (unsigned 32-bit integer; always 0) |
|
||||
| Unknown 1 | 4 bytes | Reserved (unsigned 32-bit integer; always 0) |
|
||||
| Unknown 2 | 4 bytes | Reserved (unsigned 32-bit integer; always 0) |
|
||||
| Default Verbosity | 4 bytes | Default verbosity level (unsigned 32-bit integer; always 5) |
|
||||
| Current Verbosity | 4 bytes | Current verbosity level (unsigned 32-bit integer; always 5) |
|
||||
| Red | 1 byte | Red color component for the channel (always 255) |
|
||||
| Green | 1 byte | Green color component (always 255) |
|
||||
| Blue | 1 byte | Blue color component (always 255) |
|
||||
| Alpha | 1 byte | Alpha component (always 255) |
|
||||
| Name | 34 bytes | Channel name string, null-padded (always `"Console"`) |
|
||||
|
||||
Total payload size for this frame: 2 + 58 = 60 bytes.
|
||||
|
||||
### AINF — Application Info
|
||||
|
||||
**Direction**: Server → Client
|
||||
|
||||
Sent by the server immediately after a client connects, before `ADON` and `CHAN`.
|
||||
|
||||
| Section | Size | Description |
|
||||
|---|---|---|
|
||||
| Header | 12 bytes | Type = `"AINF"`, version, length, handle |
|
||||
| Payload | 77 bytes | All zeros (placeholder / reserved for structured application information) |
|
||||
|
||||
This packet currently serves as a placeholder and contains no meaningful data. It may be extended in future versions to carry structured metadata about the server application.
|
||||
|
||||
### ADON — Additional Info
|
||||
|
||||
**Direction**: Server → Client
|
||||
|
||||
Sent by the server immediately after `AINF` during the connection handshake.
|
||||
|
||||
| Section | Size | Description |
|
||||
|---|---|---|
|
||||
| Header | 12 bytes | Type = `"ADON"`, version, length, handle |
|
||||
| Unknown | 2 bytes | Reserved (unsigned 16-bit integer; always 0) |
|
||||
| Name Length | 2 bytes | Length of the name string (unsigned 16-bit integer) |
|
||||
| Name | Variable | Server name / identifier string (e.g., `"HLDS"`) |
|
||||
|
||||
This packet carries the server application name, which is a short string identifying the server type (e.g., `"HLDS"` for Half-Life Dedicated Server).
|
||||
|
||||
|
||||
|
||||
## Security
|
||||
|
||||
XRCON has **no built-in authentication or encryption**. There is no password validation or transport-layer security. Any client that can establish a TCP connection to the XRCON port has full command execution access to the engine console.
|
||||
|
||||
The only security measures are:
|
||||
|
||||
1. **Default localhost binding**: The server binds to `127.0.0.1` by default, accepting connections only from the local machine.
|
||||
2. **Disabled by default**: The `xrcon_enable` variable defaults to `0`, so the server does not listen unless explicitly enabled.
|
||||
3. **Privileged variables**: Both configuration variables are restricted to privileged users and cannot be modified through unprivileged console commands or external access.
|
||||
|
||||
|
||||
|
||||
## Comparison with Legacy RCON
|
||||
|
||||
| Feature | XRCON | Legacy RCON |
|
||||
|---|---|---|
|
||||
| Transport | TCP (stream) | UDP (datagram, out-of-band) |
|
||||
| Port | Configurable, default 27000 | Game port (default 27015) |
|
||||
| Framing | Binary frame header | Plain text over UDP |
|
||||
| Authentication | None | Password-based |
|
||||
| Max clients | 1 | Multiple |
|
||||
| Interaction format | Full console access | Request-response only for submitted command |
|
||||
| Supported environments | Client and server | Server only |
|
||||
@@ -38,6 +38,9 @@ Mirrored on github - https://github.com/solidi/hl-mods/tree/master/cir
|
||||
## Counter-Life
|
||||
Available on ModDB - https://www.moddb.com/mods/counter-life/downloads/cl-version-1-source-code
|
||||
|
||||
## Crack-Life Anniversary Campaign
|
||||
Available on ModDB - https://www.moddb.com/mods/crack-life/downloads/crack-life-anniversary-campaign-sdk
|
||||
|
||||
## Cthulhu
|
||||
Uploaded to github by Oleg Cherkasky - https://github.com/gunrunners-paradise/Cthulhu-HLmod-SDK
|
||||
|
||||
@@ -85,12 +88,21 @@ Available on ModDB - https://www.moddb.com/mods/half-life-echoes
|
||||
## Half-Life: Expanded Arsenal
|
||||
Available on ModDB - https://www.moddb.com/mods/half-life-expanded-arsenal
|
||||
|
||||
## Half-Life: Featureful
|
||||
Branch **featureful** in halflife-featureful - https://github.com/FreeSlave/halflife-featureful
|
||||
|
||||
## Half-Life: Field Intensity
|
||||
Branch **field_intensity_1.7** in halflife-featureful - https://github.com/FreeSlave/halflife-featureful/tree/field_intensity_1.7
|
||||
|
||||
## Half-Life: Gravgun mod
|
||||
Branch **gravgun** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/gravgun
|
||||
|
||||
## Half-Life: Induction
|
||||
Version 1.4: Branch **induction** in halflife-featureful - https://github.com/FreeSlave/halflife-featureful/tree/induction
|
||||
|
||||
## Half-Life: Insecure
|
||||
Version 1.5 is available on ModDB - https://www.moddb.com/mods/half-life-insecure/downloads/half-life-insecure-map-tools-and-source-code-ver-15
|
||||
|
||||
## Half-Life: Invasion
|
||||
Official github repository - https://github.com/jlecorre/hlinvasion
|
||||
|
||||
@@ -109,6 +121,9 @@ Official github repository - https://github.com/Fograin/hl-subsmod-ex
|
||||
## Half-Life: Rally
|
||||
Official gitlab repository - https://gitlab.com/hlrally/src
|
||||
|
||||
## Half-Life: Reprocessing
|
||||
Official github repository - https://github.com/EgorYak/ReprocessingSourceCode
|
||||
|
||||
## Half-Life: RXT
|
||||
Available on ModDB - https://www.moddb.com/mods/half-life-rxt/downloads/rxt-source-code-v57
|
||||
|
||||
@@ -147,6 +162,9 @@ Mirrored on github (Includes maps and models source files) - https://github.com/
|
||||
## Ingram Chillin' Mod
|
||||
Official SourceForge repository - https://sourceforge.net/projects/icm-hl/
|
||||
|
||||
## Intense Force
|
||||
Branch **intense_force** in halflife-featureful - https://github.com/FreeSlave/halflife-featureful/tree/intense_force
|
||||
|
||||
## Master Sword
|
||||
Official github repository of Master Sword Classic - https://github.com/BerntA/MasterSwordClassic
|
||||
|
||||
@@ -189,6 +207,9 @@ Available in Valve's Half-Life repository - https://github.com/ValveSoftware/hal
|
||||
## Spirit of Half-Life
|
||||
[Logic&Trick's](https://github.com/LogicAndTrick) mirror - https://files.logic-and-trick.com/#/Half-Life/Mods/Spirit%20of%20Half-Life
|
||||
|
||||
## The Last Bullet
|
||||
Official github repository - https://github.com/GT-Project-Cat/The-Last-Bullet-Updated-Code
|
||||
|
||||
## The Wastes
|
||||
Version 1.5: mirrored on code.idtech.space - https://code.idtech.space/vera/halflife-thewastes-sdk
|
||||
|
||||
@@ -212,6 +233,19 @@ Download page on official site - http://www.thothie.com/ww/
|
||||
## Wrong Door
|
||||
Available on ModDB - https://www.moddb.com/mods/wrong-door/downloads/wrong-door-sdk
|
||||
|
||||
## XDM
|
||||
*This mod is opensource but access to source code is always rebus*
|
||||
|
||||
How to get sources of version 3.0.4.3:
|
||||
|
||||
Download SFX-archive from ModDB - https://www.moddb.com/mods/xdm/downloads/xdm3043
|
||||
|
||||
Extract it.
|
||||
|
||||
Open client.dll in HEX-editor and patch header - change `Win!` to `Rar!`
|
||||
|
||||
Extract it using unrar - password `iamtheone`
|
||||
|
||||
## XashXT
|
||||
Mirrored on github - https://github.com/a1batross/XashXT_original
|
||||
|
||||
@@ -301,7 +335,7 @@ Unkle Mike's recreation - https://hlfx.ru/forum/showthread.php?s=&threadid=5253
|
||||
Reverse-engineered code: branch **bshift** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/bshift
|
||||
|
||||
## Half-Life: Induction
|
||||
Branch **induction** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/induction
|
||||
Version 1.0-1.2: Branch **induction_1.2** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/induction_1.2
|
||||
|
||||
## Half-Life: Opposing Force
|
||||
Recreation by lostgamer aka nillerusr - https://github.com/LostGamerHL/hlsdk-xash3d
|
||||
@@ -375,6 +409,9 @@ Branch **aghl** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/
|
||||
## Bubblemod
|
||||
Branch **bubblemod** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/bubblemod
|
||||
|
||||
## Counter-Life
|
||||
Branch **counter-life** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/counter-life
|
||||
|
||||
## Deathmatch Classic
|
||||
Deathmatch Classic: Adrenaline Gamer Edition - https://github.com/martinwebrant/agmod/tree/master/src/dmc
|
||||
|
||||
@@ -388,6 +425,9 @@ Branch **delta_particles** in hlsdk-portable - https://github.com/FWGS/hlsdk-por
|
||||
## Half-Life: Echoes
|
||||
Branch **echoes** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/echoes
|
||||
|
||||
## Half-Life: Insecure
|
||||
Branch **insecure** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/insecure
|
||||
|
||||
## Half-Life: Invasion
|
||||
Port to HLSDK 2.4 by malortie - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-invasion
|
||||
|
||||
@@ -423,4 +463,3 @@ Branch **sohl1.2** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tr
|
||||
|
||||
## Zombie-X
|
||||
Branch **zombie-x** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/zombie-x
|
||||
|
||||
|
||||
@@ -12,14 +12,18 @@ All connectionless packets have `\xff\xff\xff\xff` (32-bit integer set to all on
|
||||
|
||||
- [Any](#any)
|
||||
- [Any to Any](#any-to-any)
|
||||
- [`A2A_NETINFO`](#a2a_netinfo)
|
||||
- [`A2A_INFO`](#a2a_info)
|
||||
- [`A2A_PING` and `A2A_GOLDSRC_PING`](#a2a_ping-and-a2a_goldsrc_ping)
|
||||
- [`A2A_ACK` and `A2A_GOLDSRC_ACK`](#a2a_ack-and-a2a_goldsrc_ack)
|
||||
- [Deprecated queries](#deprecated-queries)
|
||||
- [`A2A_NETINFO`](#a2a_netinfo)
|
||||
- [`A2A_INFO`](#a2a_info)
|
||||
- [Any to Client](#any-to-client)
|
||||
- [`A2C_PRINT` and `A2C_GOLDSRC_PRINT`](#a2c_print-and-a2c_goldsrc_print)
|
||||
- [Any to Server and Server to Any](#any-to-server-and-server-to-any)
|
||||
- [`A2S_GOLDSRC_INFO`, `A2S_GOLDSRC_RULES` and `A2S_GOLDSRC_PLAYERS` and their responses](#a2s_goldsrc_info-a2s_goldsrc_rules-and-a2s_goldsrc_players-and-their-responses)
|
||||
- [GoldSrc server queries](#goldsrc-server-queries)
|
||||
- [`A2S_GOLDSRC_INFO`](#a2s_goldsrc_info)
|
||||
- [`A2S_GOLDSRC_PLAYERS`](#a2s_goldsrc_players)
|
||||
- [`A2S_GOLDSRC_RULES`](#a2s_goldsrc_rules)
|
||||
- [Any to Master](#any-to-master)
|
||||
- [`S2M_SCAN_REQUEST`](#s2m_scan_request)
|
||||
- [Master to Any](#master-to-any)
|
||||
@@ -50,7 +54,23 @@ All connectionless packets have `\xff\xff\xff\xff` (32-bit integer set to all on
|
||||
|
||||
### Any to Any
|
||||
|
||||
#### `A2A_NETINFO`
|
||||
#### `A2A_PING` and `A2A_GOLDSRC_PING`
|
||||
|
||||
Simple ping message.
|
||||
* Request, in ASCII: `ping` or `i` in GoldSrc format
|
||||
* Response is always `A2A_ACK` or `A2A_GOLDSRC_ACK`
|
||||
|
||||
#### `A2A_ACK` and `A2A_GOLDSRC_ACK`
|
||||
|
||||
Simple ack message.
|
||||
* Request, in ASCII: `ack` or `j` in GoldSrc format
|
||||
* Response: none
|
||||
|
||||
#### Deprecated queries
|
||||
|
||||
These text-based Xash protocol queries are deprecated in favor of [GoldSrc server queries](#goldsrc-server-queries), which Xash3D FWGS servers implement as well. New implementations must not send them. The server still responds to `A2A_INFO` for compatibility with older clients, `A2A_NETINFO` is not handled anymore.
|
||||
|
||||
##### `A2A_NETINFO`
|
||||
|
||||
Used to implement Half-Life's NetAPI.
|
||||
* Request message, in ASCII: `netinfo <version> <context> <request_id>`
|
||||
@@ -81,7 +101,7 @@ Possible requests and response formats:
|
||||
* On any other server will respond with `neterror` set to `undefined`
|
||||
* If protocol version doesn't match, server responds with `neterror` set to `protocol`
|
||||
|
||||
#### `A2A_INFO`
|
||||
##### `A2A_INFO`
|
||||
|
||||
Used to request server's details.
|
||||
* Request message, in ASCII: `info <version>`
|
||||
@@ -101,18 +121,6 @@ Used to request server's details.
|
||||
- `password` set to 1 if server is protected with password otherwise 0
|
||||
- `host` is server's name
|
||||
|
||||
#### `A2A_PING` and `A2A_GOLDSRC_PING`
|
||||
|
||||
Simple ping message.
|
||||
* Request, in ASCII: `ping` or `i` in GoldSrc format
|
||||
* Response is always `A2A_ACK` or `A2A_GOLDSRC_ACK`
|
||||
|
||||
#### `A2A_ACK` and `A2A_GOLDSRC_ACK`
|
||||
|
||||
Simple ack message.
|
||||
* Request, in ASCII: `ack` or `j` in GoldSrc format
|
||||
* Response: none
|
||||
|
||||
### Any to Client
|
||||
|
||||
#### `A2C_PRINT` and `A2C_GOLDSRC_PRINT`
|
||||
@@ -124,9 +132,62 @@ Simple print message.
|
||||
|
||||
### Any to Server and Server to Any
|
||||
|
||||
#### `A2S_GOLDSRC_INFO`, `A2S_GOLDSRC_RULES` and `A2S_GOLDSRC_PLAYERS` and their responses
|
||||
#### GoldSrc server queries
|
||||
|
||||
These match Source Engine Query messages used in GoldSrc and Source engine and implemented for compatibility with existing tools. Read documentation for them on VDC: https://developer.valvesoftware.com/wiki/Server_queries
|
||||
These match Source Engine Query messages used in GoldSrc and Source engine and implemented for compatibility with existing server browsers and monitoring tools. The client uses them to query GoldSrc servers in the server browser, and as text-based Xash protocol queries are [deprecated](#deprecated-queries), it will use them to query Xash servers as well. Read documentation for them on VDC: https://developer.valvesoftware.com/wiki/Server_queries
|
||||
|
||||
Unlike other connectionless messages these are binary: integers are little-endian, floats are 32-bit IEEE 754 and strings are null-terminated.
|
||||
|
||||
`A2S_GOLDSRC_PLAYERS` and `A2S_GOLDSRC_RULES` requests carry a 32-bit challenge, `A2S_GOLDSRC_INFO` may have one appended after the query string. When no challenge is known yet, `\xff\xff\xff\xff` is sent in it's place. The server may answer with a challenge instead of the data: byte `A` (`0x41`) followed by a 32-bit challenge value, 9 bytes total with the header. The request is then repeated with the received challenge. Don't confuse this message with textual `S2C_GOLDSRC_CHALLENGE` used during connection, they only share the first byte.
|
||||
|
||||
Responses that don't fit into a single packet (usually rules) come from GoldSrc servers split into fragments. Such packets start with `\xfe\xff\xff\xff` instead of `\xff\xff\xff\xff`, followed by a 32-bit sequence number, same in all fragments of one response, and a byte with total fragment count in the lower 4 bits and this fragment's index in the upper 4 bits. Fragment payloads concatenated in index order form a normal `\xff\xff\xff\xff` message. Xash servers never split query responses and send them as single datagrams.
|
||||
|
||||
#### `A2S_GOLDSRC_INFO`
|
||||
|
||||
Used to request server's details.
|
||||
|
||||
* Request: `TSource Engine Query\0`, optionally followed by a 32-bit challenge. Xash servers ignore it.
|
||||
* Response: byte `I` (`S2A_GOLDSRC_INFO`), followed by:
|
||||
- byte: protocol version, 48 on GoldSrc, 49 on Xash3D FWGS
|
||||
- string: server's name
|
||||
- string: server's current level
|
||||
- string: game directory name
|
||||
- string: game description
|
||||
- int16: Steam AppID, always 0 on Xash servers
|
||||
- byte: total player count, bots included
|
||||
- byte: max players limit
|
||||
- byte: bot count
|
||||
- byte: server type, `d` for dedicated or `l` for listen server
|
||||
- byte: server's operating system, `w` for Windows, `l` for Linux, `m` for macOS
|
||||
- byte: set to 1 if server is protected with password otherwise 0
|
||||
- byte: set to 1 if server is VAC secured, Xash servers put the `secure` value from gameinfo here
|
||||
- string: server's version
|
||||
|
||||
Same as the Source engine response format but without the extra data flag and its fields.
|
||||
|
||||
Legacy GoldSrc servers respond with the obsolete `m` format (`S2A_GOLDSRC_LEGACY_INFO`) instead, see VDC for its layout. Xash servers never send it but the client understands it and assumes protocol 48.
|
||||
|
||||
#### `A2S_GOLDSRC_PLAYERS`
|
||||
|
||||
Used to request server's player list.
|
||||
|
||||
* Request: byte `U` (`0x55`), then a 32-bit challenge.
|
||||
* Response: byte `D` (`S2A_GOLDSRC_PLAYERS`), followed by a byte with total player count, then for each player:
|
||||
- byte: player's index. Xash servers send sequential indexes starting from 0, GoldSrc servers always send 0 here.
|
||||
- string: player's name
|
||||
- int32: player's kill count
|
||||
- float: time in seconds the player is connected, -1.0 for bots on Xash servers
|
||||
|
||||
Xash server doesn't respond at all if the player list is empty, the server is protected with password or exposing the player list is disabled with `sv_expose_player_list` cvar.
|
||||
|
||||
#### `A2S_GOLDSRC_RULES`
|
||||
|
||||
Used to request server's game rules.
|
||||
|
||||
* Request: byte `V` (`0x56`), then a 32-bit challenge.
|
||||
* Response: byte `E` (`S2A_GOLDSRC_RULES`), followed by an int16 with total rule count, then rule name and rule value strings for each.
|
||||
|
||||
Only server cvars (`FCVAR_SERVER`) are sent. Values of protected cvars (`FCVAR_PROTECTED`) are replaced with `1` if the value is set otherwise `0`. If there are no cvars to expose, server doesn't respond at all.
|
||||
|
||||
### Any to Master
|
||||
|
||||
@@ -154,7 +215,7 @@ These match Source Engine Query messages used in GoldSrc and Source engine and i
|
||||
- `reserved` is single reserved 8-bit byte
|
||||
- Following is list of IP addresses in binary form: 6-bytes for IPv4 address + port and 18-bytes for IPv6 address + port
|
||||
- If port is 0, it means the end of list, otherwise it's the last IP used for pagination (not implemented in Xash3D)
|
||||
* Response: client doesn't make response to master server but might request `A2S_INFO` from servers
|
||||
* Response: client doesn't make response to master server but might request `A2S_GOLDSRC_INFO` from servers
|
||||
|
||||
## Master and game server
|
||||
|
||||
|
||||
72
Documentation/protocol/http-server-list.md
Normal file
72
Documentation/protocol/http-server-list.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# Static HTTP server list
|
||||
|
||||
Replacement for the UDP `S2M_SCAN_REQUEST` / `M2A_SERVERSLIST` exchange
|
||||
described in [02-connectionless.md](02-connectionless.md). Read-only HTTP,
|
||||
no NAT punching, no filtering or pagination in the request.
|
||||
|
||||
## URL
|
||||
|
||||
`xashcomm.lst` carries a base URL per source:
|
||||
|
||||
```
|
||||
masterstatic http://master.example.org/server-list
|
||||
```
|
||||
|
||||
The engine appends `/v1/servers/<gamedir>` and `GET`s the result. For
|
||||
`gamedir = valve`:
|
||||
|
||||
```
|
||||
GET http://master.example.org/server-list/v1/servers/valve
|
||||
```
|
||||
|
||||
Trailing slashes on the base URL are stripped. Multiple `masterstatic`
|
||||
lines are allowed; results are merged.
|
||||
|
||||
The request is bare: no body, no auth, no cookies, no compressed encodings.
|
||||
Only the standard `User-Agent` is set. `POST` / `PUT` / `DELETE` are not
|
||||
used; servers register out of band.
|
||||
|
||||
## Response
|
||||
|
||||
UTF-8 text, parsed line by line. Each line is tokenized with
|
||||
`COM_ParseFileSafe` (whitespace separates, `//` and `#` start line
|
||||
comments, `"..."` quotes a token). Blank lines and comment-only lines
|
||||
are ignored. One directive per line:
|
||||
|
||||
* `ip <address>` — Xash3D server (protocol 49).
|
||||
* `gs <address>` — GoldSrc server (protocol 48).
|
||||
|
||||
`<address>` is parsed by `NET_StringToAdr` (`1.2.3.4:27015`,
|
||||
`[2001:db8::1]:27015`, hostnames). Port defaults to `27015`. Lines
|
||||
starting with an unknown directive are skipped entirely, so new keywords
|
||||
with any number of operands can be added without breaking older clients.
|
||||
|
||||
`Content-Type` is not inspected, `text/plain; charset=utf-8` expected.
|
||||
|
||||
### Example
|
||||
|
||||
```
|
||||
# diffusion servers
|
||||
ip 192.0.2.10:27015
|
||||
ip [2001:db8::1]:27015
|
||||
gs 198.51.100.5:27015
|
||||
```
|
||||
|
||||
A file with zero records is valid and represents an empty list.
|
||||
|
||||
### Versioning
|
||||
|
||||
The `/v1/` segment is fixed in this revision. A future protocol revision
|
||||
adds a sibling `/v2/...` resource without breaking older clients.
|
||||
|
||||
## Client behaviour
|
||||
|
||||
Each `ip` / `gs` record from the response triggers a probe to the listed
|
||||
address, identical to a server discovered through the UDP master. DNS
|
||||
errors, non-200 responses, and malformed bodies are reported to the
|
||||
console.
|
||||
|
||||
## Server behaviour
|
||||
|
||||
Any static HTTP server works. A typical setup is a periodic job that
|
||||
probes a set of known addresses and writes `v1/servers/<gamedir>`.
|
||||
@@ -892,6 +892,8 @@ Currently, fresh Xash3D FWGS builds for Android, PS Vita and Nintendo Switch use
|
||||
## List of games and mods with custom gamedll
|
||||
For mods from this category - first of all place mod folder beside **valve** folder.
|
||||
|
||||
NOTE: Precompiled libraries for some mods from this category may be already available for your platform in [hlsdk-mega-build](https://github.com/FWGS/hlsdk-mega-build) and this mods may be launch on Android out-of-the-box.
|
||||
|
||||
1. **[Absolute Redemption](https://www.moddb.com/mods/absolute-redemption)**
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **redemption** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
@@ -950,7 +952,11 @@ This mod already supports x86 Linux.
|
||||
|
||||
Support for other platforms may be available later.
|
||||
|
||||
14. **[Counter Strike 1.6](https://store.steampowered.com/app/10/CounterStrike/)**
|
||||
14. **[Counter-Life](https://www.moddb.com/mods/counter-life)**
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **counter-life** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
15. **[Counter Strike 1.6](https://store.steampowered.com/app/10/CounterStrike/)**
|
||||
|
||||
On x86 you may be need to compile client part.
|
||||
|
||||
@@ -962,57 +968,65 @@ Client: https://github.com/Velaron/cs16-client/
|
||||
|
||||
Server: https://github.com/rehlds/ReGameDLL_CS
|
||||
|
||||
15. **[Crack-Life](https://www.moddb.com/mods/crack-life/downloads/crack-life)**
|
||||
16. **[Crack-Life](https://www.moddb.com/mods/crack-life/downloads/crack-life)**
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **cracklife** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
16. **[Crack-Life: Campaign Mode](https://www.moddb.com/mods/crack-life/downloads/crack-life-campaign-mode)**
|
||||
17. **[Crack-Life: Campaign Mode](https://www.moddb.com/mods/crack-life/downloads/crack-life-campaign-mode)**
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **clcampaign** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
17. **[Deathmatch Classic](https://store.steampowered.com/app/40/Deathmatch_Classic/)**
|
||||
18. **[Deathmatch Classic](https://store.steampowered.com/app/40/Deathmatch_Classic/)**
|
||||
|
||||
Steam version already supports x86 Linux.
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **dmc** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
18. **[Delta Particles](https://www.moddb.com/mods/half-life-delta)**
|
||||
19. **[Delta Particles](https://www.moddb.com/mods/half-life-delta)**
|
||||
|
||||
This mod already supports x86 Linux.
|
||||
|
||||
Support for other platforms may be available later.
|
||||
To run this mod on specific platforms you may be need to compile libraries from **delta_particles** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
19. **[Escape from the Darkness](https://www.moddb.com/mods/escape-from-the-darkness)**
|
||||
20. **[Escape from the Darkness](https://www.moddb.com/mods/escape-from-the-darkness)**
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **eftd** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
20. **[Gang Wars](https://www.moddb.com/mods/gangwars)**
|
||||
21. **[Gang Wars](https://www.moddb.com/mods/gangwars)**
|
||||
|
||||
This mod already supports x86 Linux.
|
||||
|
||||
Support for other platforms may be available later.
|
||||
|
||||
21. **[Half-Life: Blue Shift](https://store.steampowered.com/app/130/HalfLife_Blue_Shift/)**
|
||||
22. **[Half-Life: Blue Shift](https://store.steampowered.com/app/130/HalfLife_Blue_Shift/)**
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **bshift** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
22. **[Half-Life: Decay](https://www.moddb.com/mods/half-life-decay)**
|
||||
23. **[Half-Life: Decay](https://www.moddb.com/mods/half-life-decay)**
|
||||
|
||||
Currently, you can compile mod libraries for x86 Linux from **decay-pc** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
Support for other platforms may be available later.
|
||||
|
||||
23. **[Half-Life: Echoes](https://www.moddb.com/mods/half-life-echoes)**
|
||||
24. **[Half-Life: Echoes](https://www.moddb.com/mods/half-life-echoes)**
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **echoes** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
24. **[Half-Life: Field Intensity](https://www.moddb.com/mods/field-intensity)**
|
||||
25. [Half-Life: Featureful](https://freeslave.github.io/halflife-featureful/)
|
||||
|
||||
*It's modding SDK*
|
||||
|
||||
This mod already supports x86 Linux.
|
||||
|
||||
Support for other platforms may be available later.
|
||||
To run this mod on specific platforms you may be need to compile libraries from **featureful** branch of [halflife-featureful](https://github.com/FreeSlave/halflife-featureful).
|
||||
|
||||
25. **Half-Life: Gravgun** (unfinished) by *mittorn*, *a1batross* and *Solexid*
|
||||
26. **[Half-Life: Field Intensity](https://www.moddb.com/mods/field-intensity)**
|
||||
|
||||
This mod already supports x86 Linux.
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **field_intensity_1.7** branch of [halflife-featureful](https://github.com/FreeSlave/halflife-featureful).
|
||||
|
||||
27. **Half-Life: Gravgun** (unfinished) by *mittorn*, *a1batross* and *Solexid*
|
||||
|
||||
*Support temporary abandoned*
|
||||
|
||||
@@ -1022,163 +1036,183 @@ Currently, MSVC is not supported, use MinGW if you need build for Windows.
|
||||
|
||||
Mod files: [1](http://mittorn.fwgs.ru/coop-entpatches/), [2](http://mittorn.fwgs.ru/ggm/), [3](https://github.com/nillerusr/gravgun-extras).
|
||||
|
||||
26. **[Half-Life: Induction 1.2](https://www.moddb.com/mods/half-life-induction)**
|
||||
28. **[Half-Life: Induction 1.2](https://www.moddb.com/mods/half-life-induction)**
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **induction_1.2** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
27. **[Half-Life: Invasion](https://www.moddb.com/mods/half-life-invasion)**
|
||||
29. **[Half-Life: Induction 1.4](https://www.moddb.com/mods/half-life-induction)**
|
||||
|
||||
This mod supports x86 Linux.
|
||||
This mod already supports x86 Linux.
|
||||
|
||||
Support for other platforms may be available later.
|
||||
To run this mod on specific platforms you may be need to compile libraries from **induction** branch of [halflife-featureful](https://github.com/FreeSlave/halflife-featureful).
|
||||
|
||||
28. **[Half-Life: Intense Force](https://www.moddb.com/downloads/intense-force)**
|
||||
30. **[Half-Life: Insecure](https://www.moddb.com/mods/half-life-insecure)**
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **insecure** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
31. **[Half-Life: Intense Force](https://www.moddb.com/downloads/intense-force)**
|
||||
|
||||
This mod already supports x86 Linux.
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **intense_force** branch of [halflife-featureful](https://github.com/FreeSlave/halflife-featureful).
|
||||
|
||||
29. **[Half-Life: Opposing Force](https://store.steampowered.com/app/50/HalfLife_Opposing_Force/)**
|
||||
32. **[Half-Life: Invasion](https://www.moddb.com/mods/half-life-invasion)**
|
||||
|
||||
This mod supports x86 Linux.
|
||||
|
||||
Support for other platforms may be available later.
|
||||
|
||||
33. **[Half-Life: Opposing Force](https://store.steampowered.com/app/50/HalfLife_Opposing_Force/)**
|
||||
|
||||
Steam version already supports x86 Linux.
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **opfor** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
30. **[Half-Life: Quest mod](https://csm.dev/threads/half-life-the-quest-mod-isxodniki.38030/)**
|
||||
34. **[Half-Life: Quest mod](https://csm.dev/threads/half-life-the-quest-mod-isxodniki.38030/)**
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **sci** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
31. **[Half-Life: TopDown](https://www.moddb.com/mods/half-life-top-downs)**
|
||||
35. **[Half-Life: TopDown](https://www.moddb.com/mods/half-life-top-downs)**
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **topdown** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
32. **[Half-Life: Urbicide](https://www.moddb.com/mods/half-life-urbicide)**
|
||||
36. **[Half-Life: Urbicide](https://www.moddb.com/mods/half-life-urbicide)**
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **hl_urbicide** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
33. **[Half-Life: Visitors](https://www.moddb.com/mods/half-life-visitors)**
|
||||
37. **[Half-Life: Visitors](https://www.moddb.com/mods/half-life-visitors)**
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **visitors** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
34. **[Half-Rats: Parasomnia](https://www.moddb.com/mods/half-rats-parasomnia/downloads/half-rats-parasomnia-v10b-steamlinux)**
|
||||
38. **[Half-Rats: Parasomnia](https://www.moddb.com/mods/half-rats-parasomnia/downloads/half-rats-parasomnia-v10b-steamlinux)**
|
||||
|
||||
This mod already supports x86 Linux.
|
||||
|
||||
Support for other platforms may be available later.
|
||||
|
||||
35. **[Half-Screwed: Death and Rebirth](https://www.moddb.com/mods/half-screwed)**
|
||||
39. **[Half-Screwed: Death and Rebirth](https://www.moddb.com/mods/half-screwed)**
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **half-screwed** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
36. **[Half-Secret](https://www.moddb.com/mods/half-secret)**
|
||||
40. **[Half-Secret](https://www.moddb.com/mods/half-secret)**
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **half-secret** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
37. **[Natural Selection](https://www.moddb.com/mods/natural-selection)**
|
||||
41. **[Natural Selection](https://www.moddb.com/mods/natural-selection)**
|
||||
|
||||
This mod already supports x86 Linux.
|
||||
|
||||
Support for other platforms may be available later.
|
||||
|
||||
38. **[Night at the Office](https://www.moddb.com/mods/night-at-the-office)**
|
||||
42. **[Night at the Office](https://www.moddb.com/mods/night-at-the-office)**
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **noffice** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
39. **[Paranoia](https://www.moddb.com/mods/paranoia)**
|
||||
43. **[Paranoia](https://www.moddb.com/mods/paranoia)**
|
||||
|
||||
*Support temporary abandoned*
|
||||
|
||||
[Source code](https://github.com/FWGS/paranoia_toolkit).
|
||||
|
||||
40. **[Poke646](https://poke646.com/)**
|
||||
44. **[Poke646](https://poke646.com/)**
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **poke646** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
41. **[Poke646: Vendetta](https://poke646.com/)**
|
||||
45. **[Poke646: Vendetta](https://poke646.com/)**
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **poke646_vendetta** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
42. [PrimeXT](https://snmetamorph.github.io/PrimeXT/)
|
||||
46. [PrimeXT](https://snmetamorph.github.io/PrimeXT/)
|
||||
|
||||
*It's modding SDK*
|
||||
|
||||
[Source code](https://github.com/SNMetamorph/PrimeXT)
|
||||
|
||||
43. **[Quake Remake](https://www.moddb.com/games/quake-remake)**
|
||||
47. **[Quake Remake](https://www.moddb.com/games/quake-remake)**
|
||||
|
||||
*Support was fully abandoned due Quake Wrapper release.*
|
||||
|
||||
[Actual source code](https://github.com/FWGS/quakeremake)
|
||||
|
||||
44. **[Rebellion](https://www.moddb.com/mods/rebellion1)**
|
||||
48. **[Rebellion](https://www.moddb.com/mods/rebellion1)**
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **rebellion** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
45. **[Residual Life](https://www.moddb.com/mods/hl-residual-life)**
|
||||
49. **[Residual Life](https://www.moddb.com/mods/hl-residual-life)**
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **residual_point** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
46. **[Residual Point](https://www.moddb.com/mods/hl-residual-point)**
|
||||
50. **[Residual Point](https://www.moddb.com/mods/hl-residual-point)**
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **residual_point** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
47. **[Ricochet](https://store.steampowered.com/app/60/Ricochet/)**
|
||||
51. **[Ricochet](https://store.steampowered.com/app/60/Ricochet/)**
|
||||
|
||||
This mod already supports x86 Linux.
|
||||
|
||||
Support for other platforms may be available later.
|
||||
|
||||
48. [Sewer](https://www.moddb.com/games/half-life/addons/sewer-beta)
|
||||
52. [Sewer](https://www.moddb.com/games/half-life/addons/sewer-beta)
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **sewer_beta** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
49. [Spirit of Half-Life 1.2](https://www.moddb.com/mods/spirit-of-half-life)
|
||||
53. [Spirit of Half-Life 1.2](https://www.moddb.com/mods/spirit-of-half-life)
|
||||
|
||||
*It's modding SDK*
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **sohl1.2** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
50. **[Swiss Cheese Halloween](https://www.moddb.com/mods/half-life-halloween-mod)**
|
||||
54. **[Swiss Cheese Halloween](https://www.moddb.com/mods/half-life-halloween-mod)**
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **halloween** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
51. **[Team Fortress Classic](https://store.steampowered.com/app/20/Team_Fortress_Classic/)**
|
||||
55. **[Team Fortress Classic](https://store.steampowered.com/app/20/Team_Fortress_Classic/)**
|
||||
|
||||
This mod already supports x86 Linux.
|
||||
|
||||
Currently, only source code of [client part](https://github.com/Velaron/tf15-client) is available.
|
||||
|
||||
52. **[The Gate](https://www.moddb.com/mods/the-gate)**
|
||||
56. **[The Gate](https://www.moddb.com/mods/the-gate)**
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **thegate** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
53. **[They Hunger: Trilogy](https://www.moddb.com/mods/they-hunger)**
|
||||
57. **[The Last Bullet](https://www.moddb.com/mods/the-last-bullet-update)**
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **fwgs** branch of [The-Last-Bullet-Updated-Code](https://github.com/GT-Project-Cat/The-Last-Bullet-Updated-Code).
|
||||
|
||||
58. **[They Hunger: Trilogy](https://www.moddb.com/mods/they-hunger)**
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **theyhunger** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
54. **[ThreeWave CTF](https://www.moddb.com/games/deathmatch-classic/downloads/threewave-ctf)**
|
||||
59. **[ThreeWave CTF](https://www.moddb.com/games/deathmatch-classic/downloads/threewave-ctf)**
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **dmc** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
55. **[Times of Troubles](https://www.moddb.com/mods/times-of-troubles)**
|
||||
60. **[Times of Troubles](https://www.moddb.com/mods/times-of-troubles)**
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **tot** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
56. [XashXT](https://csm.dev/threads/xashxt-0-65-rev-4-release-stable.38319/)
|
||||
61. [XashXT](https://csm.dev/threads/xashxt-0-65-rev-4-release-stable.38319/)
|
||||
|
||||
*It's modding SDK*
|
||||
|
||||
*Support was fully abandoned due PrimeXT development*
|
||||
|
||||
[Source code](https://github.com/FWGS/XashXT-FWGS/)
|
||||
|
||||
57. **[X-Half-Life DeathMatch](https://www.moddb.com/mods/xdm)**
|
||||
62. **[X-Half-Life DeathMatch](https://www.moddb.com/mods/xdm)**
|
||||
|
||||
This mod already supports x86 Linux.
|
||||
|
||||
Support for other platforms may be available later.
|
||||
|
||||
58. **[Xen-Warrior](https://www.moddb.com/mods/xen-warrior)**
|
||||
63. **[Xen-Warrior](https://www.moddb.com/mods/xen-warrior)**
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **sohl1.2** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
Don't forget to enable XENWARRIOR build option!!!
|
||||
|
||||
59. [Zombie-X](https://www.moddb.com/mods/zombie-x-10-final)
|
||||
64. [Zombie-X](https://www.moddb.com/mods/zombie-x-10-final)
|
||||
|
||||
To run this mod on specific platforms you may be need to compile libraries from **zombie-x** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
> [!CAUTION]
|
||||
> **Only download Xash3D FWGS from official sources.** Third-party builds, "modded launchers", "optimized" repacks and random mirrors are frequently bundled with malware, miners, spyware and credential stealers. We cannot vouch for anything we did not build. Get official binaries only from the [releases page](https://github.com/FWGS/xash3d-fwgs/releases/tag/continuous).
|
||||
|
||||
# Xash3D FWGS Engine <img align="right" width="128" height="128" src="https://github.com/FWGS/xash3d-fwgs/raw/master/game_launch/icon-xash-material.png" alt="Xash3D FWGS icon" />
|
||||
[](https://github.com/FWGS/xash3d-fwgs/actions/workflows/c-cpp.yml) [](https://cirrus-ci.com/github/FWGS/xash3d-fwgs) \
|
||||
[](http://xash.su/discord/) [](https://t.me/flyingwithgauss) \
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
typedef struct usercmd_s
|
||||
{
|
||||
int16_t lerp_msec; // added in HL
|
||||
int8_t msec; // added in QW
|
||||
uint8_t msec; // added in QW
|
||||
uint8_t pad1;
|
||||
vec3_t viewangles;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -50,7 +50,7 @@ const char *CL_MsgInfo( int cmd )
|
||||
// get engine message name
|
||||
const char *svc_string = NULL;
|
||||
|
||||
switch( cls.legacymode )
|
||||
switch( cls.net_protocol )
|
||||
{
|
||||
case PROTO_CURRENT:
|
||||
svc_string = svc_strings[cmd];
|
||||
@@ -146,7 +146,7 @@ static void CL_WriteErrorMessage( int current_count, sizebuf_t *msg )
|
||||
|
||||
FS_Write( fp, &cls.starting_count, sizeof( int ));
|
||||
FS_Write( fp, ¤t_count, sizeof( int ));
|
||||
FS_Write( fp, &cls.legacymode, sizeof( cls.legacymode ));
|
||||
FS_Write( fp, &cls.net_protocol, sizeof( cls.net_protocol ));
|
||||
FS_Write( fp, MSG_GetData( msg ), MSG_GetMaxBytes( msg ));
|
||||
FS_Close( fp );
|
||||
|
||||
@@ -207,7 +207,7 @@ void CL_ReplayBufferDat_f( void )
|
||||
FS_Read( f, ¤t_count, sizeof( current_count ));
|
||||
FS_Read( f, &protocol, sizeof( protocol ));
|
||||
|
||||
cls.legacymode = protocol;
|
||||
cls.net_protocol = protocol;
|
||||
|
||||
len = FS_Read( f, buffer, sizeof( buffer ));
|
||||
FS_Close( f );
|
||||
|
||||
@@ -412,7 +412,7 @@ static void CL_WriteDemoHeader( const char *name )
|
||||
|
||||
demo.header.id = IDEMOHEADER;
|
||||
demo.header.dem_protocol = DEMO_PROTOCOL;
|
||||
demo.header.net_protocol = CL_GetDemoNetProtocol( cls.legacymode );
|
||||
demo.header.net_protocol = CL_GetDemoNetProtocol( cls.net_protocol );
|
||||
demo.header.host_fps = host_maxfps.value ? bound( MIN_FPS, host_maxfps.value, maxfps ) : maxfps;
|
||||
Q_strncpy( demo.header.mapname, clgame.mapname, sizeof( demo.header.mapname ));
|
||||
Q_strncpy( demo.header.comment, clgame.maptitle, sizeof( demo.header.comment ));
|
||||
@@ -723,7 +723,7 @@ static void CL_DemoStartPlayback( int mode )
|
||||
|
||||
demo.starttime = CL_GetDemoPlaybackClock(); // for determining whether to read another message
|
||||
|
||||
CL_SetupNetchanForProtocol( cls.legacymode );
|
||||
CL_SetupNetchanForProtocol( cls.net_protocol );
|
||||
|
||||
memset( demo.cmds, 0, sizeof( demo.cmds ));
|
||||
demo.angle_position = 1;
|
||||
@@ -1555,7 +1555,7 @@ void CL_PlayDemo_f( void )
|
||||
|
||||
if( neg ) cls.forcetrack = -cls.forcetrack;
|
||||
CL_DemoStartPlayback( DEMO_QUAKE1 );
|
||||
cls.legacymode = PROTO_QUAKE;
|
||||
cls.net_protocol = PROTO_QUAKE;
|
||||
return; // quake demo is started
|
||||
}
|
||||
|
||||
@@ -1591,7 +1591,7 @@ void CL_PlayDemo_f( void )
|
||||
CL_DemoStartPlayback( DEMO_XASH3D );
|
||||
|
||||
// must be after DemoStartPlayback, as CL_Disconnect_f resets the protocol
|
||||
cls.legacymode = CL_GetProtocolFromDemo( demo.header.net_protocol );
|
||||
cls.net_protocol = CL_GetProtocolFromDemo( demo.header.net_protocol );
|
||||
|
||||
// g-cont. is this need?
|
||||
Q_strncpy( cls.servername, demoname, sizeof( cls.servername ));
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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] );
|
||||
|
||||
|
||||
@@ -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 );
|
||||
|
||||
@@ -554,6 +554,7 @@ void V_PostRender( void )
|
||||
SCR_DrawNetGraph();
|
||||
SCR_DrawUserCmd();
|
||||
Joy_DrawDebug();
|
||||
IN_GyroDrawDebug();
|
||||
SV_DrawOrthoTriangles();
|
||||
CL_DrawDemoRecording();
|
||||
CL_DrawHUD( CL_CHANGELEVEL );
|
||||
|
||||
@@ -445,6 +445,7 @@ typedef struct
|
||||
double timeout;
|
||||
double timesend; // time when request was sended
|
||||
int flags; // FNETAPI_MULTIPLE_RESPONSE etc
|
||||
int challenge; // GoldSrc query challenge, -1 until received
|
||||
} net_request_t;
|
||||
|
||||
// new versions of client dlls have a single export with all callbacks
|
||||
@@ -579,6 +580,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,10 +646,9 @@ 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;
|
||||
connprotocol_t net_protocol;
|
||||
int extensions;
|
||||
|
||||
netadr_t serveradr;
|
||||
@@ -792,6 +793,8 @@ 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 );
|
||||
qboolean CL_NetRequestSend( net_request_t *nr );
|
||||
|
||||
//
|
||||
// cl_demo.c
|
||||
@@ -1154,7 +1157,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 );
|
||||
|
||||
@@ -3364,44 +3364,60 @@ NetAPI_SendRequest
|
||||
*/
|
||||
static void GAME_EXPORT NetAPI_SendRequest( int context, int request, int flags, double timeout, netadr_t *remote_address, net_api_response_func_t response )
|
||||
{
|
||||
net_request_t *nr = NULL;
|
||||
int i;
|
||||
|
||||
if( !response )
|
||||
{
|
||||
Con_DPrintf( S_ERROR "%s: no callbcak specified for request with context %i!\n", __func__, context );
|
||||
Con_DPrintf( S_ERROR "%s: no callback specified for request with context %i!\n", __func__, context );
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME: call pfnFunc and tell client.dll about errors?
|
||||
|
||||
if( NET_NetadrType( remote_address ) == NA_IPX || NET_NetadrType( remote_address ) == NA_BROADCAST_IPX )
|
||||
return; // IPX no longer support
|
||||
|
||||
if( request == NETAPI_REQUEST_SERVERLIST )
|
||||
switch( request )
|
||||
{
|
||||
case NETAPI_REQUEST_SERVERLIST:
|
||||
return; // no support for server list requests
|
||||
case NETAPI_REQUEST_PING:
|
||||
case NETAPI_REQUEST_RULES:
|
||||
case NETAPI_REQUEST_PLAYERS:
|
||||
case NETAPI_REQUEST_DETAILS:
|
||||
break;
|
||||
default:
|
||||
Con_Printf( S_ERROR "%s: unknown request type %d, context %i\n", __func__, request, context );
|
||||
return;
|
||||
}
|
||||
|
||||
// find a free request
|
||||
for( i = 0; i < MAX_REQUESTS; i++ )
|
||||
net_request_t *nr = NULL, *oldest_nr = NULL;
|
||||
int i;
|
||||
double max_timeout = 0;
|
||||
for( i = 0; i < ARRAYSIZE( clgame.net_requests ); i++ )
|
||||
{
|
||||
nr = &clgame.net_requests[i];
|
||||
if( !nr->pfnFunc ) break;
|
||||
}
|
||||
|
||||
if( i == MAX_REQUESTS )
|
||||
{
|
||||
double max_timeout = 0;
|
||||
|
||||
// no free requests? use oldest
|
||||
for( i = 0, nr = NULL; i < MAX_REQUESTS; i++ )
|
||||
if(( host.realtime - nr->timesend ) > max_timeout )
|
||||
{
|
||||
if(( host.realtime - clgame.net_requests[i].timesend ) > max_timeout )
|
||||
{
|
||||
max_timeout = host.realtime - clgame.net_requests[i].timesend;
|
||||
nr = &clgame.net_requests[i];
|
||||
}
|
||||
max_timeout = host.realtime - nr->timesend;
|
||||
oldest_nr = nr;
|
||||
}
|
||||
|
||||
if( !nr->pfnFunc )
|
||||
break;
|
||||
}
|
||||
|
||||
Assert( nr != NULL );
|
||||
if( i == ARRAYSIZE( clgame.net_requests ))
|
||||
{
|
||||
// no free requests? use oldest
|
||||
nr = oldest_nr;
|
||||
}
|
||||
|
||||
if( !nr )
|
||||
{
|
||||
Con_Printf( S_ERROR "%s: no free requests\n", __func__ );
|
||||
return;
|
||||
}
|
||||
|
||||
// clear slot
|
||||
memset( nr, 0, sizeof( *nr ));
|
||||
@@ -3414,9 +3430,10 @@ static void GAME_EXPORT NetAPI_SendRequest( int context, int request, int flags,
|
||||
nr->resp.type = request;
|
||||
nr->resp.remote_address = *remote_address;
|
||||
nr->flags = flags;
|
||||
nr->challenge = -1;
|
||||
|
||||
// local servers request
|
||||
Netchan_OutOfBandPrint( NS_CLIENT, nr->resp.remote_address, A2A_NETINFO" %i %i %i", PROTOCOL_VERSION, context, request );
|
||||
if( !CL_NetRequestSend( nr ))
|
||||
Con_Printf( S_ERROR "%s: failed to send net request for type %d with context %i\n", __func__, request, context );
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -3428,7 +3445,7 @@ NetAPI_CancelRequest
|
||||
static void GAME_EXPORT NetAPI_CancelRequest( int context )
|
||||
{
|
||||
// find a specified request
|
||||
for( int i = 0; i < MAX_REQUESTS; i++ )
|
||||
for( int i = 0; i < ARRAYSIZE( clgame.net_requests ); i++ )
|
||||
{
|
||||
net_request_t *nr = &clgame.net_requests[i];
|
||||
|
||||
@@ -3456,7 +3473,7 @@ NetAPI_CancelAllRequests
|
||||
void GAME_EXPORT NetAPI_CancelAllRequests( void )
|
||||
{
|
||||
// tell the user about cancel
|
||||
for( int i = 0; i < MAX_REQUESTS; i++ )
|
||||
for( int i = 0; i < ARRAYSIZE( clgame.net_requests ); i++ )
|
||||
{
|
||||
net_request_t *nr = &clgame.net_requests[i];
|
||||
if( !nr->pfnFunc ) continue; // not used
|
||||
@@ -3568,6 +3585,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 +3817,7 @@ static cl_enginefunc_t gEngfuncs =
|
||||
pfnHookEvent,
|
||||
Con_Visible,
|
||||
pfnGetGameDirectory,
|
||||
pfnCVarGetPointer,
|
||||
CL_CvarGetPointer,
|
||||
Key_LookupBinding,
|
||||
pfnGetLevelName,
|
||||
pfnGetScreenFade,
|
||||
@@ -3924,7 +3951,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,
|
||||
|
||||
@@ -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 );
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
|
||||
@@ -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 )
|
||||
@@ -470,7 +469,7 @@ void CL_BatchResourceRequest( qboolean initialize )
|
||||
{
|
||||
if( done_downloading && CL_PrecacheResources( ))
|
||||
{
|
||||
CL_RegisterResources( &msg, cls.legacymode );
|
||||
CL_RegisterResources( &msg, cls.net_protocol );
|
||||
}
|
||||
|
||||
Netchan_CreateFragments( &cls.netchan, &msg );
|
||||
@@ -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 );
|
||||
@@ -1187,8 +1186,8 @@ void CL_ParseBaseline( sizebuf_t *msg, connprotocol_t proto )
|
||||
int type = MSG_ReadUBitLong( msg, 2 );
|
||||
int delta_type;
|
||||
|
||||
if( player ) delta_type = DT_ENTITY_STATE_PLAYER_T;
|
||||
else if( type != ENTITY_NORMAL ) delta_type = DT_CUSTOM_ENTITY_STATE_T;
|
||||
if( FBitSet( type, ENTITY_BEAM )) delta_type = DT_CUSTOM_ENTITY_STATE_T;
|
||||
else if( player ) delta_type = DT_ENTITY_STATE_PLAYER_T;
|
||||
else delta_type = DT_ENTITY_STATE_T;
|
||||
|
||||
Delta_ReadGSFields( msg, delta_type, &ent->prevstate, &ent->baseline, 1.0f );
|
||||
@@ -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 )
|
||||
{
|
||||
|
||||
@@ -465,7 +465,7 @@ static void CL_ParseSoundPacketGS( sizebuf_t *msg )
|
||||
float attn;
|
||||
if( FBitSet( flags, SND_ATTENUATION ))
|
||||
attn = (float)MSG_ReadByte( msg ) / 64.0f;
|
||||
else attn = ATTN_NONE;
|
||||
else attn = 1.0f;
|
||||
|
||||
int chan = MSG_ReadUBitLong( msg, 3 );
|
||||
int entnum = MSG_ReadUBitLong( msg, MAX_GOLDSRC_ENTITY_BITS );
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -274,7 +274,7 @@ void FS_FreeStream( stream_t *stream )
|
||||
stream->format->freefunc( stream );
|
||||
}
|
||||
|
||||
#if XASH_ENGINE_TESTS
|
||||
#if XASH_LLVM_LIBFUZZER
|
||||
#define IMPLEMENT_SOUNDLIB_FUZZ_TARGET( export, target ) \
|
||||
int EXPORT export( const uint8_t *Data, size_t Size ); \
|
||||
int EXPORT export( const uint8_t *Data, size_t Size ) \
|
||||
@@ -294,4 +294,4 @@ int EXPORT export( const uint8_t *Data, size_t Size ) \
|
||||
|
||||
IMPLEMENT_SOUNDLIB_FUZZ_TARGET( Fuzz_Sound_LoadMPG, Sound_LoadMPG )
|
||||
IMPLEMENT_SOUNDLIB_FUZZ_TARGET( Fuzz_Sound_LoadWAV, Sound_LoadWAV )
|
||||
#endif
|
||||
#endif // XASH_LLVM_LIBFUZZER
|
||||
|
||||
@@ -185,7 +185,7 @@ static qboolean Sound_ParseID3Tag( const byte *buffer, fs_offset_t filesize )
|
||||
return true;
|
||||
}
|
||||
|
||||
#if XASH_ENGINE_TESTS
|
||||
#if XASH_LLVM_LIBFUZZER
|
||||
int EXPORT Fuzz_Sound_ParseID3Tag( const uint8_t *Data, size_t Size );
|
||||
int EXPORT Fuzz_Sound_ParseID3Tag( const uint8_t *Data, size_t Size )
|
||||
{
|
||||
@@ -193,7 +193,7 @@ int EXPORT Fuzz_Sound_ParseID3Tag( const uint8_t *Data, size_t Size )
|
||||
Sound_ParseID3Tag( Data, Size );
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
#endif // XASH_LLVM_LIBFUZZER
|
||||
|
||||
/*
|
||||
=================================================================
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 )
|
||||
|
||||
@@ -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" );
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
274
engine/common/http/net_http_tls.c
Normal file
274
engine/common/http/net_http_tls.c
Normal 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
|
||||
53
engine/common/http/net_http_tls.h
Normal file
53
engine/common/http/net_http_tls.h
Normal 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
|
||||
@@ -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 );
|
||||
}
|
||||
|
||||
@@ -701,6 +701,10 @@ void Test_RunImagelib( void )
|
||||
|
||||
Z_Free( rgb.buffer );
|
||||
}
|
||||
#endif // XASH_ENGINE_TESTS
|
||||
|
||||
#if XASH_LLVM_LIBFUZZER
|
||||
#include "sprite.h"
|
||||
|
||||
#define IMPLEMENT_IMAGELIB_FUZZ_TARGET( export, target ) \
|
||||
int EXPORT export( const uint8_t *Data, size_t Size ); \
|
||||
@@ -724,4 +728,29 @@ IMPLEMENT_IMAGELIB_FUZZ_TARGET( Fuzz_Image_LoadPNG, Image_LoadPNG )
|
||||
IMPLEMENT_IMAGELIB_FUZZ_TARGET( Fuzz_Image_LoadDDS, Image_LoadDDS )
|
||||
IMPLEMENT_IMAGELIB_FUZZ_TARGET( Fuzz_Image_LoadTGA, Image_LoadTGA )
|
||||
|
||||
#endif /* XASH_ENGINE_TESTS */
|
||||
int EXPORT Fuzz_Image_LoadSPR( const uint8_t *Data, size_t Size );
|
||||
int EXPORT Fuzz_Image_LoadSPR( const uint8_t *Data, size_t Size )
|
||||
{
|
||||
rgbdata_t *rgb;
|
||||
|
||||
// the real caller passes only the pixel data size, the frame header is extra
|
||||
if( Size < sizeof( dspriteframe_t ))
|
||||
return 0;
|
||||
|
||||
host.type = HOST_NORMAL;
|
||||
Memory_Init();
|
||||
Image_Init();
|
||||
|
||||
image.hint = IL_HINT_Q1; // installs the Quake palette so the decoder runs
|
||||
|
||||
if( Image_LoadSPR( "#internal.spr", Data, Size - sizeof( dspriteframe_t )))
|
||||
{
|
||||
rgb = ImagePack( "#internal.spr" );
|
||||
FS_FreeImage( rgb );
|
||||
}
|
||||
|
||||
Image_Shutdown();
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif // XASH_LLVM_LIBFUZZER
|
||||
|
||||
@@ -262,10 +262,10 @@ qboolean Image_LoadSPR( const char *name, const byte *buffer, fs_offset_t filesi
|
||||
image.width = pin.width;
|
||||
image.height = pin.height;
|
||||
|
||||
if( filesize < image.width * image.height )
|
||||
if( filesize < (fs_offset_t)image.width * image.height )
|
||||
return false;
|
||||
|
||||
if( filesize == ( image.width * image.height * 4 ))
|
||||
if( filesize == (fs_offset_t)image.width * image.height * 4 )
|
||||
truecolor = true;
|
||||
|
||||
// sorry, can't validate palette rendermode
|
||||
|
||||
@@ -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,116 @@ 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( const byte *body, size_t size )
|
||||
{
|
||||
char line[1024];
|
||||
int offset = 0;
|
||||
int count = 0;
|
||||
|
||||
while( Q_memfgets( (byte *)body, size, &offset, line, sizeof( line )) != NULL )
|
||||
{
|
||||
char token[MAX_TOKEN];
|
||||
char *pfile = line;
|
||||
qboolean gs;
|
||||
netadr_t adr = { 0 };
|
||||
|
||||
pfile = COM_ParseFileSafe( pfile, token, sizeof( token ), PFILE_HASH_AS_COMMENT, NULL, NULL );
|
||||
if( !pfile || token[0] == '\0' )
|
||||
continue;
|
||||
|
||||
if( !Q_strcmp( token, "ip" ))
|
||||
gs = false;
|
||||
else if( !Q_strcmp( token, "gs" ))
|
||||
gs = true;
|
||||
else
|
||||
continue;
|
||||
|
||||
pfile = COM_ParseFileSafe( pfile, token, sizeof( token ), PFILE_HASH_AS_COMMENT, NULL, NULL );
|
||||
if( !pfile )
|
||||
continue;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
NET_Config( true, false ); // allow remote sends
|
||||
|
||||
int count = NET_ParseMasterStaticBody( data, size );
|
||||
|
||||
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 +336,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 +355,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 +441,7 @@ static master_t *NET_GetMasterFromAdr( netadr_t adr )
|
||||
/*
|
||||
========================
|
||||
NET_GetMaster
|
||||
|
||||
========================
|
||||
*/
|
||||
qboolean NET_GetMaster( netadr_t from, uint *challenge, double *last_heartbeat )
|
||||
@@ -365,7 +509,7 @@ static master_t *NET_AddMaster( const char *addr )
|
||||
|
||||
static void NET_AddMaster_f( void )
|
||||
{
|
||||
if( Cmd_Argc() != 2 )
|
||||
if( Cmd_Argc() != 2 && Cmd_Argc() != 3 )
|
||||
{
|
||||
Msg( S_USAGE "addmaster <address> [gs]\n");
|
||||
return;
|
||||
@@ -380,6 +524,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 +589,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 +626,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 +680,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 +737,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 +758,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();
|
||||
}
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
|
||||
@@ -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 );
|
||||
|
||||
@@ -62,14 +62,19 @@ static byte *Mod_SwapSpriteFrame( byte *p, byte *end, int bytes )
|
||||
p += sizeof( frame );
|
||||
|
||||
// skip pixel data
|
||||
if( p + frame.width * frame.height * bytes > end )
|
||||
if( frame.width < 0 || frame.height < 0 )
|
||||
return NULL;
|
||||
|
||||
p += frame.width * frame.height * bytes;
|
||||
uint64_t pixels = (uint64_t)frame.width * frame.height * bytes;
|
||||
|
||||
if( pixels > (size_t)( end - p ))
|
||||
return NULL;
|
||||
|
||||
p += pixels;
|
||||
return p;
|
||||
}
|
||||
|
||||
static byte *Mod_SwapSpriteGroup( byte *p, byte *end, int bytes )
|
||||
static byte *Mod_SwapSpriteGroup( byte *p, byte *end, int bytes, int min_frames )
|
||||
{
|
||||
dspritegroup_t group;
|
||||
|
||||
@@ -84,7 +89,7 @@ static byte *Mod_SwapSpriteGroup( byte *p, byte *end, int bytes )
|
||||
// swap intervals
|
||||
int numframes = group.numframes;
|
||||
|
||||
if( p + numframes * sizeof( dspriteinterval_t ) > end )
|
||||
if( numframes < min_frames || (size_t)numframes > (size_t)( end - p ) / sizeof( dspriteinterval_t ))
|
||||
return NULL;
|
||||
|
||||
for( int i = 0; i < numframes; i++ )
|
||||
@@ -144,18 +149,26 @@ static qboolean Mod_SwapSprite( void *buffer, size_t buffersize, int *out_versio
|
||||
|
||||
short numi = LittleShort( *(short *)p );
|
||||
*(short *)p = numi;
|
||||
p += sizeof( short ) + numi * 3;
|
||||
p += sizeof( short );
|
||||
|
||||
if( numi < 0 || p + numi * 3 > end )
|
||||
return false;
|
||||
|
||||
p += numi * 3;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
if( numframes < 0 )
|
||||
return false;
|
||||
|
||||
*out_version = version;
|
||||
int bytes = ( version == SPRITE_VERSION_32 ) ? 4 : 1;
|
||||
|
||||
// swap all frames
|
||||
for( int i = 0; i < numframes && p && p < end; i++ )
|
||||
for( int i = 0; i < numframes; i++ )
|
||||
{
|
||||
dframetype_t frametype;
|
||||
|
||||
@@ -173,12 +186,18 @@ static qboolean Mod_SwapSprite( void *buffer, size_t buffersize, int *out_versio
|
||||
p = Mod_SwapSpriteFrame( p, end, bytes );
|
||||
break;
|
||||
case FRAME_GROUP:
|
||||
p = Mod_SwapSpriteGroup( p, end, bytes, 1 );
|
||||
break;
|
||||
case FRAME_ANGLED:
|
||||
p = Mod_SwapSpriteGroup( p, end, bytes );
|
||||
// angled groups are indexed by 0..7 at render time
|
||||
p = Mod_SwapSpriteGroup( p, end, bytes, 8 );
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
if( !p )
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -229,17 +248,23 @@ void Mod_LoadSpriteModel( model_t *mod, void *buffer, size_t buffersize, qboolea
|
||||
psprite->texFormat = SPR_ADDITIVE;
|
||||
psprite->numframes = mod->numframes = pinq1->numframes;
|
||||
psprite->facecull = SPR_CULL_FRONT;
|
||||
psprite->radius = pinq1->boundingradius;
|
||||
float radius = floorf( pinq1->boundingradius );
|
||||
if( radius >= (float)INT_MAX )
|
||||
psprite->radius = INT_MAX;
|
||||
else if( radius < 0.0f || IS_NAN( radius ))
|
||||
psprite->radius = 0;
|
||||
else
|
||||
psprite->radius = (int)radius;
|
||||
psprite->synctype = pinq1->synctype;
|
||||
|
||||
// LadyHavoc: hack to allow sprites to be non-fullbright
|
||||
if( Q_strchr( mod->name, '!' ))
|
||||
psprite->texFormat = SPR_ALPHTEST;
|
||||
|
||||
mod->mins[0] = mod->mins[1] = -pinq1->bounds[0] * 0.5f;
|
||||
mod->maxs[0] = mod->maxs[1] = pinq1->bounds[0] * 0.5f;
|
||||
mod->mins[2] = -pinq1->bounds[1] * 0.5f;
|
||||
mod->mins[0] = mod->mins[1] = -mod->maxs[0];
|
||||
mod->maxs[2] = pinq1->bounds[1] * 0.5f;
|
||||
mod->mins[2] = -mod->maxs[2];
|
||||
}
|
||||
else // if( version == SPRITE_VERSION_HL )
|
||||
{
|
||||
@@ -263,10 +288,10 @@ void Mod_LoadSpriteModel( model_t *mod, void *buffer, size_t buffersize, qboolea
|
||||
psprite->radius = pinhl->boundingradius;
|
||||
psprite->synctype = pinhl->synctype;
|
||||
|
||||
mod->mins[0] = mod->mins[1] = -pinhl->bounds[0] * 0.5f;
|
||||
mod->maxs[0] = mod->maxs[1] = pinhl->bounds[0] * 0.5f;
|
||||
mod->mins[2] = -pinhl->bounds[1] * 0.5f;
|
||||
mod->mins[0] = mod->mins[1] = -mod->maxs[0];
|
||||
mod->maxs[2] = pinhl->bounds[1] * 0.5f;
|
||||
mod->mins[2] = -mod->maxs[2];
|
||||
}
|
||||
|
||||
if( loaded )
|
||||
@@ -283,3 +308,34 @@ void Mod_LoadSpriteModel( model_t *mod, void *buffer, size_t buffersize, qboolea
|
||||
Mod_SpriteLoadTextures( mod, buffer );
|
||||
#endif
|
||||
}
|
||||
|
||||
#if XASH_LLVM_LIBFUZZER
|
||||
int EXPORT Fuzz_Mod_LoadSpriteModel( const uint8_t *Data, size_t Size );
|
||||
int EXPORT Fuzz_Mod_LoadSpriteModel( const uint8_t *Data, size_t Size )
|
||||
{
|
||||
model_t mod = { .name = "#internal.spr", .needload = NL_NEEDS_LOADED };
|
||||
qboolean loaded = false;
|
||||
byte *buf;
|
||||
|
||||
if( Size == 0 )
|
||||
return 0;
|
||||
|
||||
Memory_Init();
|
||||
|
||||
// dedicated mode exercises the parser/validator without needing a renderer
|
||||
host.type = HOST_DEDICATED;
|
||||
host.mempool = Mem_AllocPool( "fuzzing pool" );
|
||||
|
||||
// the loader byteswaps the buffer in place, so hand it a writable copy
|
||||
buf = Mem_Malloc( host.mempool, Size );
|
||||
memcpy( buf, Data, Size );
|
||||
|
||||
Mod_LoadSpriteModel( &mod, buf, Size, &loaded );
|
||||
|
||||
if( mod.mempool )
|
||||
Mem_FreePool( &mod.mempool );
|
||||
Mem_FreePool( &host.mempool );
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif // XASH_LLVM_LIBFUZZER
|
||||
|
||||
@@ -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;
|
||||
@@ -653,7 +653,7 @@ void Mod_NeedCRC( const char *name, qboolean needCRC )
|
||||
else ClearBits( p->flags, FCRC_SHOULD_CHECKSUM );
|
||||
}
|
||||
|
||||
#if XASH_ENGINE_TESTS
|
||||
#if XASH_LLVM_LIBFUZZER
|
||||
|
||||
static const uint8_t *fuzz_data;
|
||||
static size_t fuzz_size;
|
||||
@@ -689,4 +689,4 @@ int EXPORT Fuzz_Mod_LoadModel( const uint8_t *Data, size_t Size )
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif // XASH_ENGINE_TESTS
|
||||
#endif // XASH_LLVM_LIBFUZZER
|
||||
|
||||
@@ -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 );
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
|
||||
|
||||
@@ -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,16 @@ 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 )
|
||||
return NET_GetLong( data, ret, length, CL_GetSplitSize( ), CL_Protocol( ));
|
||||
{
|
||||
if( CL_IsFromConnectingServer( *from ))
|
||||
return NET_GetLong( data, ret, length, CL_GetSplitSize( ), CL_Protocol( ));
|
||||
|
||||
// GoldSrc servers (e.g. ReUnion) can split large query responses
|
||||
if( CL_HasActiveNetRequest( *from ))
|
||||
return NET_GetLong( data, ret, length, 1400, PROTO_GOLDSRC );
|
||||
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
// lag the packet, if needed
|
||||
@@ -1606,7 +1620,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 +1782,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 )
|
||||
{
|
||||
|
||||
@@ -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,8 @@ void NET_GetLocalAddress( netadr_t *ip4, netadr_t *ip6 );
|
||||
|
||||
#if !XASH_DEDICATED
|
||||
size_t CL_GetSplitSize( void );
|
||||
qboolean CL_IsFromConnectingServer( netadr_t from );
|
||||
qboolean CL_HasActiveNetRequest( netadr_t from );
|
||||
#endif
|
||||
|
||||
void HTTP_AddCustomServer( const char *url );
|
||||
@@ -97,4 +100,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
|
||||
|
||||
@@ -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 );
|
||||
|
||||
@@ -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
|
||||
@@ -321,7 +319,6 @@ extern const char *const svc_goldsrc_strings[svc_lastmsg+1];
|
||||
#define A2A_PING "ping" // reply with A2A_ACK
|
||||
#define A2A_ACK "ack" // no-op
|
||||
#define A2A_INFO "info" // different format for client and server, see code
|
||||
#define A2A_NETINFO "netinfo" // different format for client and server, see code
|
||||
#define A2A_GOLDSRC_PING "i" // reply with A2A_GOLDSRC_ACK
|
||||
#define A2A_GOLDSRC_ACK "j" // no-op
|
||||
|
||||
@@ -332,6 +329,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'
|
||||
|
||||
|
||||
@@ -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
643
engine/common/xrcon.c
Normal 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;
|
||||
}
|
||||
@@ -13,41 +13,22 @@
|
||||
GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include "SDL_syswm.h"
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#include <sys/stat.h>
|
||||
#include "dlfcn.h"
|
||||
|
||||
#define XASHLIB "@rpath/libxash.dylib"
|
||||
|
||||
|
||||
@interface XashPromptAlertViewDelegate : NSObject <UIAlertViewDelegate>
|
||||
|
||||
@property (nonatomic, assign) int *button;
|
||||
|
||||
@end
|
||||
|
||||
@implementation XashPromptAlertViewDelegate
|
||||
|
||||
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
|
||||
*_button = buttonIndex;
|
||||
}
|
||||
@end
|
||||
|
||||
|
||||
|
||||
int szArgc;
|
||||
char **szArgv;
|
||||
char *g_szLibrarySuffix;
|
||||
float g_iOSVer;
|
||||
bool isdark;
|
||||
|
||||
const char *IOS_GetDocsDir(void)
|
||||
//UI vars
|
||||
UIAlertController *alert;
|
||||
UIViewController *controller;
|
||||
bool finishedDialog = false;
|
||||
|
||||
const char *IOS_GetDocsDir( void )
|
||||
{
|
||||
if( g_iOSVer >= 8.0 )
|
||||
{
|
||||
static const char *dir = NULL;
|
||||
|
||||
if( dir )
|
||||
@@ -61,23 +42,9 @@ const char *IOS_GetDocsDir(void)
|
||||
NSLog(@"IOS_GetDocsDir: %s", dir);
|
||||
|
||||
return dir;
|
||||
}
|
||||
else
|
||||
{
|
||||
static char dir[1024];
|
||||
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
|
||||
NSString *basePath = paths.firstObject;
|
||||
[[NSFileManager defaultManager] createDirectoryAtPath:basePath withIntermediateDirectories:YES attributes:nil error:nil];
|
||||
strcpy(dir,[basePath UTF8String]);
|
||||
mkdir(dir,777);
|
||||
|
||||
NSLog(@"IOS_GetDocsDir: %s", dir);
|
||||
|
||||
return dir;
|
||||
}
|
||||
}
|
||||
|
||||
const char *IOS_GetExecDir(void)
|
||||
const char *IOS_GetExecDir( void )
|
||||
{
|
||||
static const char *dir = NULL;
|
||||
|
||||
@@ -87,7 +54,7 @@ const char *IOS_GetExecDir(void)
|
||||
NSString *executableDirctory = [[NSBundle mainBundle] bundlePath];
|
||||
|
||||
dir = [executableDirctory fileSystemRepresentation];
|
||||
NSLog(@"IOS_GetDocsDir: %s", dir);
|
||||
NSLog(@"IOS_GetExecDir: %s", dir);
|
||||
|
||||
return dir;
|
||||
}
|
||||
@@ -99,43 +66,44 @@ typedef struct settings_s
|
||||
{
|
||||
unsigned char magic;
|
||||
char args[1024];
|
||||
unsigned int port;
|
||||
char suffix[32];
|
||||
unsigned int ftpserver;
|
||||
} settings_t;
|
||||
@interface ButtonHandler :NSObject
|
||||
@property (nonatomic, assign) int *button1;
|
||||
@end
|
||||
@implementation ButtonHandler
|
||||
|
||||
settings_t settings;
|
||||
FILE *settingsfile;
|
||||
char settingspath[256];
|
||||
|
||||
-(void) buttonClicked:(UIButton*)sender
|
||||
static void SaveSettings( void )
|
||||
{
|
||||
*_button1 = 0;
|
||||
if( (settingsfile = fopen( settingspath, "wb" )) )
|
||||
{
|
||||
strlcpy( settings.args, [alert.textFields.firstObject.text UTF8String], 1024 );
|
||||
settings.magic = 111;
|
||||
|
||||
fwrite( &settings, sizeof(settings), 1, settingsfile );
|
||||
fclose( settingsfile );
|
||||
}
|
||||
}
|
||||
@end
|
||||
void IOS_PrepareView(void)
|
||||
|
||||
static void IOS_PrepareView( void )
|
||||
{
|
||||
ButtonHandler *handler = [[ButtonHandler alloc] init];
|
||||
UIWindow *window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
|
||||
UIViewController *controller = [[UIViewController alloc] init];
|
||||
[[controller view] setBackgroundColor:[UIColor grayColor]];
|
||||
controller = [[UIViewController alloc] init];
|
||||
[controller.view setBackgroundColor:[UIColor grayColor]];
|
||||
[window setRootViewController:controller];
|
||||
[window makeKeyAndVisible];
|
||||
if([[controller traitCollection] userInterfaceStyle] == UIUserInterfaceStyleDark && g_iOSVer >= 13.0) isdark = true; else isdark = false;
|
||||
#if 0
|
||||
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(10, 10, 100, 20)];
|
||||
int button1 = -1;
|
||||
handler.button1 = &button1;
|
||||
alert = [UIAlertController alertControllerWithTitle:@"Xash3D FWGS"
|
||||
message:nil
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
|
||||
[button addTarget:handler action:@selector(buttonClicked:) forControlEvents:UIControlEventValueChanged];
|
||||
@autoreleasepool {
|
||||
while( button1 == -1 ) {
|
||||
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
|
||||
}
|
||||
}
|
||||
[[controller view] addSubview:button];
|
||||
#endif
|
||||
UIAlertAction* startAction = [UIAlertAction actionWithTitle:@"Start" style:UIAlertActionStyleDefault
|
||||
handler:^(UIAlertAction *action) { finishedDialog = true; }];
|
||||
UIAlertAction* exitAction = [UIAlertAction actionWithTitle:@"Exit" style:UIAlertActionStyleDefault
|
||||
handler:^(UIAlertAction *action) {
|
||||
SaveSettings();
|
||||
NSLog(@"Exit Selected\n");
|
||||
exit(0); }];
|
||||
[alert addAction:startAction];
|
||||
[alert addAction:exitAction];
|
||||
}
|
||||
|
||||
void IOS_LaunchDialog( void )
|
||||
@@ -148,91 +116,48 @@ void IOS_LaunchDialog( void )
|
||||
[[AVAudioSession sharedInstance] requestRecordPermission:^(BOOL granted){}];
|
||||
|
||||
IOS_PrepareView();
|
||||
int button = -1, bExit, bStart;
|
||||
UIAlertView * alert = [[UIAlertView alloc] init];
|
||||
bExit = [alert addButtonWithTitle:@"Exit"];
|
||||
bStart = [alert addButtonWithTitle:@"Start"];
|
||||
XashPromptAlertViewDelegate *delegate = [[XashPromptAlertViewDelegate alloc] init];
|
||||
delegate.button = &button;
|
||||
|
||||
alert.delegate = delegate;
|
||||
|
||||
const char *docsDir = IOS_GetDocsDir();
|
||||
|
||||
//set working directory to documents so logs can be generated there
|
||||
NSString *workingDir = [NSString stringWithUTF8String:IOS_GetDocsDir()];
|
||||
[[NSFileManager defaultManager] changeCurrentDirectoryPath:workingDir];
|
||||
[[NSFileManager defaultManager]
|
||||
changeCurrentDirectoryPath:[NSString stringWithUTF8String:IOS_GetDocsDir()]];
|
||||
|
||||
FILE *settingsfile;
|
||||
char settingspath[256];
|
||||
snprintf(settingspath, sizeof(settingspath), "%s/settings.bin", docsDir );
|
||||
snprintf( settingspath, sizeof( settingspath ), "%s/settings.bin", docsDir );
|
||||
settingspath[255] = 0;
|
||||
settings_t settings;
|
||||
|
||||
[alert setTransform:CGAffineTransformMakeTranslation(0,109)];
|
||||
|
||||
UIScrollView *scroll = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, 300, 200)];
|
||||
|
||||
UILabel *argstitle = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 300, 30)];
|
||||
[argstitle setText:@"Command-line arguments:"];
|
||||
|
||||
UITextField *args = [[UITextField alloc] initWithFrame:CGRectMake(0, 30, 300, 30)];
|
||||
[args setBackgroundColor:[[UIColor alloc] initWithRed:1 green:1 blue:1 alpha:1]];
|
||||
if(isdark) [args setBackgroundColor:[[UIColor alloc] initWithRed:0 green:0 blue:0 alpha:1]];
|
||||
|
||||
|
||||
UITextField *suffix = [[UITextField alloc] initWithFrame:CGRectMake(140, 90, 160, 30 )];
|
||||
[suffix setBackgroundColor:[[UIColor alloc] initWithRed:1 green:1 blue:1 alpha:1]];
|
||||
if(isdark) [suffix setBackgroundColor:[[UIColor alloc] initWithRed:0 green:0 blue:0 alpha:1]];
|
||||
|
||||
UILabel *suffixtitle = [[UILabel alloc] initWithFrame:CGRectMake(0, 90, 140, 30)];
|
||||
[suffixtitle setText:@"Library suffix"];
|
||||
|
||||
[scroll addSubview:argstitle];
|
||||
[scroll addSubview:args];
|
||||
[scroll addSubview:suffix];
|
||||
[scroll addSubview:suffixtitle];
|
||||
|
||||
|
||||
settingsfile = fopen( settingspath, "rb" );
|
||||
if( settingsfile && ( fread(&settings, sizeof( settings ), 1, settingsfile ) == 1 ) && ( settings.magic == SETTINGS_MAGIC ) )
|
||||
bool ret = false;
|
||||
if ( settingsfile )
|
||||
{
|
||||
settings.args[1023] = 0;
|
||||
settings.suffix[31] = 0;
|
||||
[args setText:@(settings.args)];
|
||||
[suffix setText:@(settings.suffix)];
|
||||
}
|
||||
else
|
||||
{
|
||||
[args setText:@"-dev 2 -log"];
|
||||
ret = fread( &settings, sizeof( settings ), 1, settingsfile ) == 1;
|
||||
}
|
||||
|
||||
scroll.contentSize=CGSizeMake(250, 200);
|
||||
[alert setValue:scroll forKey:@"accessoryView"];
|
||||
|
||||
[alert show];
|
||||
[alert addTextFieldWithConfigurationHandler:^(UITextField *textField) {
|
||||
[textField setPlaceholder:@"Launch Options"];
|
||||
if (ret && ( settings.magic == SETTINGS_MAGIC ))
|
||||
{
|
||||
[textField setText:@(settings.args)];
|
||||
}
|
||||
else
|
||||
{
|
||||
[textField setText:@"-dev 2 -log"];
|
||||
}
|
||||
[textField setAutocapitalizationType:UITextAutocapitalizationTypeNone];
|
||||
}];
|
||||
|
||||
[controller presentViewController:alert animated:YES completion:nil];
|
||||
|
||||
@autoreleasepool {
|
||||
while( button == -1 ) {
|
||||
while( !finishedDialog ) {
|
||||
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
|
||||
}
|
||||
}
|
||||
|
||||
if( (settingsfile = fopen( settingspath, "wb" )) )
|
||||
{
|
||||
strlcpy(settings.args, [args.text UTF8String], 1024);
|
||||
strlcpy(settings.suffix, [suffix.text UTF8String], 32 );
|
||||
settings.magic = 111;
|
||||
|
||||
fwrite(&settings, sizeof(settings), 1, settingsfile);
|
||||
fclose(settingsfile);
|
||||
}
|
||||
if( button == bExit )
|
||||
{
|
||||
printf("Exit selected\n");
|
||||
exit(0);
|
||||
}
|
||||
NSArray *argv = [alert.textFields.firstObject.text componentsSeparatedByString:@" "];
|
||||
|
||||
NSArray *argv = [ args.text componentsSeparatedByString:@" " ];
|
||||
SaveSettings();
|
||||
|
||||
int count = [argv count];
|
||||
szArgv = calloc( count + 2, sizeof( char* ) );
|
||||
@@ -244,16 +169,6 @@ void IOS_LaunchDialog( void )
|
||||
szArgc = count + 1;
|
||||
szArgv[count + 1] = 0;
|
||||
|
||||
if( [suffix.text length] )
|
||||
g_szLibrarySuffix = strdup([suffix.text UTF8String]);
|
||||
|
||||
alert.delegate = nil;
|
||||
|
||||
[args release];
|
||||
[argstitle release];
|
||||
[suffix release];
|
||||
[suffixtitle release];
|
||||
|
||||
[alert release];
|
||||
}
|
||||
|
||||
@@ -266,7 +181,7 @@ char *IOS_GetUDID( void )
|
||||
return udid;
|
||||
}
|
||||
|
||||
void IOS_Log(const char *text)
|
||||
void IOS_Log( const char *text )
|
||||
{
|
||||
NSLog(@"Xash: %@", [NSString stringWithUTF8String:text]);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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() );
|
||||
|
||||
@@ -28,4 +28,12 @@ GNU General Public License for more details.
|
||||
void SDLash_InitCursors( void );
|
||||
void SDLash_FreeCursors( void );
|
||||
|
||||
//
|
||||
// sensor_sdl3.c
|
||||
//
|
||||
void SDLash_InitSensors( void );
|
||||
void SDLash_ShutdownSensors( void );
|
||||
qboolean SDLash_GyroIsAvailable( void );
|
||||
void SDLash_SensorUpdate( SDL_SensorEvent sensor );
|
||||
|
||||
#endif // PLATFORM_SDL3_H
|
||||
|
||||
98
engine/platform/sdl3/sensor_sdl3.c
Normal file
98
engine/platform/sdl3/sensor_sdl3.c
Normal file
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
sensor_sdl3.c - SDL3 sensor handling
|
||||
Copyright (C) 2026 Xash3D FWGS contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
#include "input.h"
|
||||
#include "platform_sdl3.h"
|
||||
|
||||
static SDL_SensorID g_system_gyro_id = -1;
|
||||
static SDL_Sensor *g_system_gyro;
|
||||
|
||||
/*
|
||||
==============
|
||||
SDLash_InitSensors
|
||||
|
||||
==============
|
||||
*/
|
||||
void SDLash_InitSensors( void )
|
||||
{
|
||||
if( SDL_InitSubSystem( SDL_INIT_SENSOR ) == 0 )
|
||||
{
|
||||
int num_sensors;
|
||||
SDL_SensorID *sensors = SDL_GetSensors( &num_sensors );
|
||||
|
||||
for( int i = 0; i < num_sensors; i++ )
|
||||
{
|
||||
if( SDL_GetSensorTypeForID( i ) == SDL_SENSOR_GYRO )
|
||||
{
|
||||
g_system_gyro = SDL_OpenSensor( i );
|
||||
if( g_system_gyro )
|
||||
{
|
||||
g_system_gyro_id = sensors[i];
|
||||
Con_Printf( "SDL: Opened built-in gyroscope: %s\n", SDL_GetSensorName( g_system_gyro ));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
SDL_free( sensors );
|
||||
}
|
||||
else
|
||||
{
|
||||
Con_Reportf( S_ERROR "Failed to init SDL Sensor subsystem: %s\n", SDL_GetError());
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
==============
|
||||
SDLash_ShutdownSensors
|
||||
|
||||
==============
|
||||
*/
|
||||
void SDLash_ShutdownSensors( void )
|
||||
{
|
||||
if( g_system_gyro )
|
||||
{
|
||||
SDL_CloseSensor( g_system_gyro );
|
||||
g_system_gyro = NULL;
|
||||
g_system_gyro_id = -1;
|
||||
}
|
||||
|
||||
SDL_QuitSubSystem( SDL_INIT_SENSOR );
|
||||
}
|
||||
|
||||
/*
|
||||
==============
|
||||
SDLash_GyroIsAvailable
|
||||
|
||||
==============
|
||||
*/
|
||||
qboolean SDLash_GyroIsAvailable( void )
|
||||
{
|
||||
return( g_system_gyro != NULL );
|
||||
}
|
||||
|
||||
/*
|
||||
==============
|
||||
SDLash_SensorUpdate
|
||||
|
||||
==============
|
||||
*/
|
||||
void SDLash_SensorUpdate( SDL_SensorEvent sensor )
|
||||
{
|
||||
if( sensor.which == g_system_gyro_id )
|
||||
{
|
||||
IN_GyroEvent( sensor.data );
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,7 @@ void Platform_Sleep( int msec )
|
||||
{
|
||||
SDL_Delay( msec );
|
||||
}
|
||||
|
||||
#endif // XASH_TIMER == TIMER_SDL
|
||||
|
||||
#if XASH_MESSAGEBOX == MSGBOX_SDL
|
||||
@@ -45,6 +46,7 @@ void Platform_MessageBox( const char *title, const char *message, qboolean paren
|
||||
{
|
||||
SDL_ShowSimpleMessageBox( SDL_MESSAGEBOX_ERROR, title, message, parentMainWindow ? host.hWnd : NULL );
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void SDLash_NanoSleep( int nsec )
|
||||
@@ -90,10 +92,8 @@ static void SDLCALL SDLash_LogOutputFunction( void *userdata, int category, SDL_
|
||||
}
|
||||
}
|
||||
|
||||
void SDLash_Init( const char *basedir )
|
||||
void SDLash_Init( void )
|
||||
{
|
||||
(void)basedir;
|
||||
|
||||
// TODO: initial state, to be filled from gameinfo!
|
||||
SDL_SetAppMetadata( XASH_ENGINE_NAME, XASH_VERSION, "su.xash.engine" );
|
||||
SDL_SetAppMetadataProperty( SDL_PROP_APP_METADATA_TYPE_STRING, "game" );
|
||||
|
||||
@@ -19,6 +19,7 @@ GNU General Public License for more details.
|
||||
#include "ref_common.h"
|
||||
#include "xash3d_mathlib.h"
|
||||
#include "common.h"
|
||||
#include "wrect.h"
|
||||
#include <SDL3/SDL_system.h>
|
||||
|
||||
static struct
|
||||
@@ -92,8 +93,8 @@ static SDL_Window *VID_CreateWindowWithSafeGL( const char *title, SDL_Rect *rect
|
||||
static void VID_SaveWindowSize( SDL_Window *hWnd, int width, int height )
|
||||
{
|
||||
qboolean maximized = FBitSet( SDL_GetWindowFlags( hWnd ), SDL_WINDOW_MAXIMIZED );
|
||||
int render_w;
|
||||
int render_h;
|
||||
int render_w;
|
||||
int render_h;
|
||||
|
||||
if( !SDL_GetWindowSizeInPixels( hWnd, &render_w, &render_h ))
|
||||
{
|
||||
@@ -110,7 +111,7 @@ static void VID_SaveWindowSize( SDL_Window *hWnd, int width, int height )
|
||||
static qboolean VID_CreateWindow( const int input_width, const int input_height, window_mode_t window_mode )
|
||||
{
|
||||
SDL_Rect rect = { SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, input_width, input_height };
|
||||
Uint32 flags = SDL_WINDOW_RESIZABLE;
|
||||
Uint32 flags = SDL_WINDOW_RESIZABLE;
|
||||
|
||||
if( !glw_state.software )
|
||||
SetBits( flags, SDL_WINDOW_OPENGL );
|
||||
@@ -118,7 +119,7 @@ static qboolean VID_CreateWindow( const int input_width, const int input_height,
|
||||
// probe true fullscreen first, if it fails, try borderless next
|
||||
if( window_mode == WINDOW_MODE_FULLSCREEN )
|
||||
{
|
||||
SDL_DisplayID display = SDL_GetPrimaryDisplay();
|
||||
SDL_DisplayID display = SDL_GetPrimaryDisplay();
|
||||
|
||||
SDL_DisplayMode dm;
|
||||
if( SDL_GetClosestFullscreenDisplayMode( display, rect.w, rect.h, 0.0f, true, &dm ))
|
||||
@@ -203,9 +204,44 @@ static qboolean VID_CreateWindow( const int input_width, const int input_height,
|
||||
return true;
|
||||
}
|
||||
|
||||
void VID_Info_f( void )
|
||||
{
|
||||
Uint32 flags = SDL_GetWindowFlags( host.hWnd );
|
||||
int width, height;
|
||||
int render_width, render_height;
|
||||
int x, y;
|
||||
|
||||
SDL_GetWindowSize( host.hWnd, &width, &height );
|
||||
SDL_GetWindowSizeInPixels( host.hWnd, &render_width, &render_height );
|
||||
SDL_GetWindowPosition( host.hWnd, &x, &y );
|
||||
|
||||
Con_Printf( "Video: " S_GREEN "SDL3" S_DEFAULT "\n" );
|
||||
Con_Printf( "Video driver: " S_GREEN "%s" S_DEFAULT "\n", SDL_GetCurrentVideoDriver( ));
|
||||
Con_Printf( "Window size: " S_GREEN "%dx%d" S_DEFAULT " (" S_YELLOW "real %dx%d" S_DEFAULT ")\n", width, height, render_width, render_height );
|
||||
Con_Printf( "Window position: " S_GREEN "%dx%d" S_DEFAULT "\n", x, y );
|
||||
Con_Printf( "Window mode: %s" S_DEFAULT "\n",
|
||||
FBitSet( flags, SDL_WINDOW_FULLSCREEN ) ? S_YELLOW "fullscreen"
|
||||
: S_CYAN "windowed" );
|
||||
Con_Printf( "Window bordered: %s" S_DEFAULT "\n", FBitSet( flags, SDL_WINDOW_BORDERLESS ) ? S_RED "false" : S_GREEN "true" );
|
||||
Con_Printf( "Window resizable: %s" S_DEFAULT "\n", FBitSet( flags, SDL_WINDOW_RESIZABLE ) ? S_GREEN "true" : S_RED "false" );
|
||||
Con_Printf( "Window maximized: %s" S_DEFAULT "\n", FBitSet( flags, SDL_WINDOW_MAXIMIZED ) ? S_GREEN "true" : S_RED "false" );
|
||||
|
||||
int display_index = SDL_GetDisplayForWindow( host.hWnd );
|
||||
if( display_index >= 0 )
|
||||
Con_Printf( "Window display index: " S_GREEN "%d" S_DEFAULT "\n", display_index );
|
||||
else
|
||||
Con_Printf( "Window display index: " S_RED "fail: " S_DEFAULT "%s\n", SDL_GetError( ));
|
||||
|
||||
const SDL_DisplayMode *dm = SDL_GetWindowFullscreenMode( host.hWnd );
|
||||
if( dm )
|
||||
Con_Printf( "Window display mode: " S_GREEN "%dx%d@%f" S_DEFAULT "\n", dm->w, dm->h, dm->refresh_rate );
|
||||
else
|
||||
Con_Printf( "Window display mode: " S_RED "fail: " S_DEFAULT "%s\n", SDL_GetError( ));
|
||||
}
|
||||
|
||||
static void R_InitVideoModes( void )
|
||||
{
|
||||
SDL_DisplayID display = SDL_GetDisplayForWindow( host.hWnd );
|
||||
SDL_DisplayID display = SDL_GetDisplayForWindow( host.hWnd );
|
||||
int count;
|
||||
SDL_DisplayMode **modes = SDL_GetFullscreenDisplayModes( display, &count );
|
||||
|
||||
@@ -257,6 +293,32 @@ void Platform_Minimize_f( void )
|
||||
SDL_MinimizeWindow( host.hWnd );
|
||||
}
|
||||
|
||||
platform_orientation_t Platform_GetDisplayOrientation( void )
|
||||
{
|
||||
if( host.hWnd )
|
||||
{
|
||||
int display_index = SDL_GetDisplayForWindow( host.hWnd );
|
||||
if( display_index >= 0 )
|
||||
{
|
||||
switch( SDL_GetCurrentDisplayOrientation( display_index ))
|
||||
{
|
||||
case SDL_ORIENTATION_LANDSCAPE:
|
||||
return ORIENTATION_LANDSCAPE;
|
||||
case SDL_ORIENTATION_LANDSCAPE_FLIPPED:
|
||||
return ORIENTATION_LANDSCAPE_FLIPPED;
|
||||
case SDL_ORIENTATION_PORTRAIT:
|
||||
return ORIENTATION_PORTRAIT;
|
||||
case SDL_ORIENTATION_PORTRAIT_FLIPPED:
|
||||
return ORIENTATION_PORTRAIT_FLIPPED;
|
||||
default:
|
||||
return ORIENTATION_UNKNOWN;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ORIENTATION_UNKNOWN;
|
||||
}
|
||||
|
||||
void GL_UpdateSwapInterval( void )
|
||||
{
|
||||
if( FBitSet( gl_vsync.flags, FCVAR_CHANGED ))
|
||||
@@ -307,6 +369,7 @@ qboolean R_Init_Video( ref_graphic_apis_t type )
|
||||
switch( type )
|
||||
{
|
||||
case REF_SOFTWARE:
|
||||
case REF_D3D:
|
||||
break;
|
||||
case REF_GL:
|
||||
ref.dllFuncs.GL_InitExtensions();
|
||||
@@ -341,26 +404,26 @@ int GL_SetAttribute( int attr, int val )
|
||||
{
|
||||
switch( attr )
|
||||
{
|
||||
#define MAP_REF_API_ATTRIBUTE_TO_SDL( name ) case REF_##name: return SDL_GL_SetAttribute( SDL_##name, val );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_RED_SIZE );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_GREEN_SIZE );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_BLUE_SIZE );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_ALPHA_SIZE );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_DOUBLEBUFFER );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_DEPTH_SIZE );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_STENCIL_SIZE );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_MULTISAMPLEBUFFERS );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_MULTISAMPLESAMPLES );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_ACCELERATED_VISUAL );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_MAJOR_VERSION );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_MINOR_VERSION );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_FLAGS );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_SHARE_WITH_CURRENT_CONTEXT );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_FRAMEBUFFER_SRGB_CAPABLE );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_PROFILE_MASK );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_RELEASE_BEHAVIOR );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_RESET_NOTIFICATION );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_NO_ERROR );
|
||||
#define MAP_REF_API_ATTRIBUTE_TO_SDL( name ) case REF_ ## name: return SDL_GL_SetAttribute( SDL_ ## name, val );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_RED_SIZE );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_GREEN_SIZE );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_BLUE_SIZE );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_ALPHA_SIZE );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_DOUBLEBUFFER );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_DEPTH_SIZE );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_STENCIL_SIZE );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_MULTISAMPLEBUFFERS );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_MULTISAMPLESAMPLES );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_ACCELERATED_VISUAL );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_MAJOR_VERSION );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_MINOR_VERSION );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_FLAGS );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_SHARE_WITH_CURRENT_CONTEXT );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_FRAMEBUFFER_SRGB_CAPABLE );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_PROFILE_MASK );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_RELEASE_BEHAVIOR );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_RESET_NOTIFICATION );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_NO_ERROR );
|
||||
#undef MAP_REF_API_ATTRIBUTE_TO_SDL
|
||||
}
|
||||
|
||||
@@ -371,26 +434,26 @@ int GL_GetAttribute( int attr, int *val )
|
||||
{
|
||||
switch( attr )
|
||||
{
|
||||
#define MAP_REF_API_ATTRIBUTE_TO_SDL( name ) case REF_##name: return SDL_GL_GetAttribute( SDL_##name, val );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_RED_SIZE );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_GREEN_SIZE );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_BLUE_SIZE );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_ALPHA_SIZE );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_DOUBLEBUFFER );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_DEPTH_SIZE );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_STENCIL_SIZE );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_MULTISAMPLEBUFFERS );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_MULTISAMPLESAMPLES );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_ACCELERATED_VISUAL );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_MAJOR_VERSION );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_MINOR_VERSION );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_FLAGS );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_SHARE_WITH_CURRENT_CONTEXT );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_FRAMEBUFFER_SRGB_CAPABLE );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_PROFILE_MASK );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_RELEASE_BEHAVIOR );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_RESET_NOTIFICATION );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_NO_ERROR );
|
||||
#define MAP_REF_API_ATTRIBUTE_TO_SDL( name ) case REF_ ## name: return SDL_GL_GetAttribute( SDL_ ## name, val );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_RED_SIZE );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_GREEN_SIZE );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_BLUE_SIZE );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_ALPHA_SIZE );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_DOUBLEBUFFER );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_DEPTH_SIZE );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_STENCIL_SIZE );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_MULTISAMPLEBUFFERS );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_MULTISAMPLESAMPLES );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_ACCELERATED_VISUAL );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_MAJOR_VERSION );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_MINOR_VERSION );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_FLAGS );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_SHARE_WITH_CURRENT_CONTEXT );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_FRAMEBUFFER_SRGB_CAPABLE );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_PROFILE_MASK );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_RELEASE_BEHAVIOR );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_RESET_NOTIFICATION );
|
||||
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_NO_ERROR );
|
||||
#undef MAP_REF_API_ATTRIBUTE_TO_SDL
|
||||
}
|
||||
|
||||
@@ -550,6 +613,9 @@ qboolean VID_SetMode( void )
|
||||
case rserr_invalid_mode:
|
||||
Sys_Warn( "%s: invalid mode, engine will run in %dx%d", __func__, vid_state.prev_width, vid_state.prev_height );
|
||||
break;
|
||||
default:
|
||||
Sys_Warn( "%s: unknown error, engine will revert to %dx%d", __func__, vid_state.prev_width, vid_state.prev_height );
|
||||
break;
|
||||
}
|
||||
|
||||
// revert to last successful mode
|
||||
@@ -563,8 +629,169 @@ qboolean VID_SetMode( void )
|
||||
return true;
|
||||
}
|
||||
|
||||
static qboolean VID_GetDisplayBounds( SDL_DisplayID display_id, SDL_Window *hWnd, SDL_Rect *rect )
|
||||
{
|
||||
if( SDL_GetDisplayUsableBounds( display_id, rect ) != 0 )
|
||||
{
|
||||
memset( rect, 0, sizeof( *rect ));
|
||||
return false;
|
||||
}
|
||||
|
||||
wrect_t wrc = { 0 };
|
||||
if( hWnd )
|
||||
{
|
||||
SDL_GetWindowBordersSize( hWnd, &wrc.top, &wrc.left, &wrc.bottom, &wrc.right );
|
||||
}
|
||||
else
|
||||
{
|
||||
#if XASH_WIN32
|
||||
wrc.left = GetSystemMetrics( SM_CYSIZEFRAME );
|
||||
wrc.right = wrc.bottom = wrc.left;
|
||||
wrc.top = GetSystemMetrics( SM_CYSMCAPTION ) + wrc.left;
|
||||
#endif // XASH_WIN32
|
||||
}
|
||||
|
||||
rect->x += wrc.left + wrc.right;
|
||||
rect->y += wrc.top + wrc.bottom;
|
||||
rect->w -= ( wrc.left + wrc.right ) * 2;
|
||||
rect->h -= ( wrc.top + wrc.bottom ) * 2;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static rserr_t VID_SetScreenResolution( int width, int height, window_mode_t window_mode, window_mode_t prev_window_mode )
|
||||
{
|
||||
const SDL_DisplayID display_id = SDL_GetDisplayForWindow( host.hWnd );
|
||||
int out_width, out_height;
|
||||
|
||||
switch( window_mode )
|
||||
{
|
||||
case WINDOW_MODE_BORDERLESS:
|
||||
{
|
||||
if( !SDL_SetWindowFullscreen( host.hWnd, false ))
|
||||
{
|
||||
Con_PrintSDLError( "SDL_SetWindowFullscreen" );
|
||||
|
||||
// there is no "invalid mode" for borderless fullscreen as there is
|
||||
// no video mode change to begin with
|
||||
return rserr_invalid_fullscreen;
|
||||
}
|
||||
if( !SDL_SetWindowBordered( host.hWnd, false ))
|
||||
{
|
||||
Con_PrintSDLError( "SDL_SetWindowBordered" );
|
||||
|
||||
return rserr_invalid_fullscreen;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WINDOW_MODE_FULLSCREEN:
|
||||
{
|
||||
const SDL_DisplayMode want = { .w = width, .h = height };
|
||||
SDL_DisplayMode **modes;
|
||||
const SDL_DisplayMode *got = 0;
|
||||
int mode_count;
|
||||
|
||||
// return "invalid mode" if we are switching between video modes in fullscreen mode
|
||||
// or "invalid fullscreen" if we are switching from windowed to fullscreen
|
||||
const rserr_t appropriate_err = prev_window_mode == WINDOW_MODE_WINDOWED ? rserr_invalid_fullscreen : rserr_invalid_mode;
|
||||
|
||||
modes = SDL_GetFullscreenDisplayModes( display_id, &mode_count );
|
||||
for( int i = 0; i < mode_count; i++ )
|
||||
{
|
||||
if( modes[i]->w == want.w && modes[i]->h == want.h )
|
||||
{
|
||||
got = modes[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if( !got )
|
||||
{
|
||||
// Fallback to native
|
||||
got = SDL_GetDesktopDisplayMode( display_id );
|
||||
}
|
||||
|
||||
if( !SDL_SetWindowFullscreenMode( host.hWnd, got ))
|
||||
{
|
||||
Con_PrintSDLError( "SDL_SetWindowFullscreenMode" );
|
||||
SDL_free( modes );
|
||||
return appropriate_err;
|
||||
}
|
||||
|
||||
if( !SDL_SetWindowFullscreen( host.hWnd, true ))
|
||||
{
|
||||
Con_PrintSDLError( "SDL_SetWindowFullscreen" );
|
||||
SDL_free( modes );
|
||||
return appropriate_err;
|
||||
}
|
||||
|
||||
SDL_free( modes );
|
||||
break;
|
||||
}
|
||||
case WINDOW_MODE_WINDOWED:
|
||||
if( !SDL_SetWindowFullscreen( host.hWnd, false ))
|
||||
{
|
||||
Con_PrintSDLError( "SDL_SetWindowFullscreen" );
|
||||
|
||||
// TODO: appropriate error type when going back from fullscreen to windowed?
|
||||
return rserr_unknown;
|
||||
}
|
||||
|
||||
SDL_SetWindowResizable( host.hWnd, true );
|
||||
SDL_SetWindowBordered( host.hWnd, true );
|
||||
|
||||
if( !FBitSet( SDL_GetWindowFlags( host.hWnd ), SDL_WINDOW_MAXIMIZED ))
|
||||
{
|
||||
const SDL_DisplayMode *dm;
|
||||
qboolean center_window = false;
|
||||
|
||||
if( !( dm = SDL_GetDesktopDisplayMode( display_id )) && width >= dm->w && height >= dm->h )
|
||||
{
|
||||
SDL_SetWindowSize( host.hWnd, dm->w, dm->h );
|
||||
// Con_Printf( "%s: activating fake fullscreen mode\n", __func__ );
|
||||
center_window = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
SDL_SetWindowSize( host.hWnd, width, height );
|
||||
|
||||
SDL_Rect r;
|
||||
if( VID_GetDisplayBounds( display_id, host.hWnd, &r ) >= 0 )
|
||||
{
|
||||
int x, y;
|
||||
SDL_GetWindowPosition( host.hWnd, &x, &y );
|
||||
|
||||
if( x <= r.x || y <= r.y )
|
||||
center_window = true;
|
||||
}
|
||||
}
|
||||
|
||||
if( center_window )
|
||||
SDL_SetWindowPosition( host.hWnd, SDL_WINDOWPOS_CENTERED_DISPLAY( display_id ), SDL_WINDOWPOS_CENTERED_DISPLAY( display_id ));
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
Con_Printf( S_WARN "%s: got invalid mode %d", __func__, window_mode );
|
||||
}
|
||||
|
||||
SDL_GetWindowSize( host.hWnd, &out_width, &out_height );
|
||||
|
||||
Con_Printf( "%s: Setting video mode to %dx%d %s\n", __func__, out_width, out_height,
|
||||
window_mode == WINDOW_MODE_BORDERLESS ? "borderless"
|
||||
: window_mode == WINDOW_MODE_FULLSCREEN ? "fullscreen" : "windowed" );
|
||||
|
||||
VID_SaveWindowSize( host.hWnd, out_width, out_height );
|
||||
|
||||
// set icon that could've been lost after changing modes
|
||||
VID_SetWindowIcon( host.hWnd );
|
||||
|
||||
return rserr_ok;
|
||||
}
|
||||
|
||||
rserr_t R_ChangeDisplaySettings( int width, int height, window_mode_t window_mode )
|
||||
{
|
||||
rserr_t err;
|
||||
|
||||
if( !host.hWnd )
|
||||
{
|
||||
// create window if not exists
|
||||
@@ -573,12 +800,13 @@ rserr_t R_ChangeDisplaySettings( int width, int height, window_mode_t window_mod
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: change current window mode
|
||||
return rserr_invalid_fullscreen;
|
||||
err = VID_SetScreenResolution( width, height, window_mode, refState.window_mode );
|
||||
if( err != rserr_ok )
|
||||
return err;
|
||||
}
|
||||
|
||||
refState.desktopBitsPixel = 24; // TODO: figure this out
|
||||
refState.window_mode = window_mode;
|
||||
refState.desktopBitsPixel = 24;
|
||||
|
||||
return rserr_ok;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
@@ -541,6 +542,8 @@ void SV_UpdateServerInfo( void );
|
||||
void SV_EndRedirect( host_redirect_t *rd );
|
||||
void SV_RejectConnection( netadr_t from, const char *fmt, ... ) FORMAT_CHECK( 2 );
|
||||
void SV_GetPlayerCount( int *clients, int *bots );
|
||||
int SV_CreateChallenge( netadr_t from, qboolean *error );
|
||||
qboolean SV_ValidateChallenge( netadr_t from, int challenge );
|
||||
|
||||
static inline qboolean SV_HavePassword( void )
|
||||
{
|
||||
@@ -700,6 +703,6 @@ int SV_LightForEntity( edict_t *pEdict );
|
||||
//
|
||||
// sv_query.c
|
||||
//
|
||||
void SV_SourceQuery_HandleConnnectionlessPacket( const char *c, netadr_t from );
|
||||
void SV_SourceQuery_HandleConnnectionlessPacket( const char *c, netadr_t from, sizebuf_t *msg );
|
||||
|
||||
#endif//SERVER_H
|
||||
|
||||
@@ -13,11 +13,11 @@ 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"
|
||||
#include "net_encode.h"
|
||||
#include "net_api.h"
|
||||
|
||||
// challenges are valid for two consecutive windows of this size (max lifetime ~10s).
|
||||
#define CHALLENGE_WINDOW_SECONDS 5
|
||||
@@ -109,11 +109,43 @@ static int SV_GetChallenge( netadr_t from, uint32_t time_window, qboolean *error
|
||||
return digest[0] | digest[1] << 8 | digest[2] << 16 | digest[3] << 24;
|
||||
}
|
||||
|
||||
static void SV_SendChallenge( netadr_t from, qboolean skip_bandwidth_test )
|
||||
/*
|
||||
=================
|
||||
SV_CreateChallenge
|
||||
=================
|
||||
*/
|
||||
int SV_CreateChallenge( netadr_t from, qboolean *error )
|
||||
{
|
||||
uint32_t time_window = (uint32_t)( host.realtime / CHALLENGE_WINDOW_SECONDS );
|
||||
|
||||
return SV_GetChallenge( from, time_window, error );
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
SV_ValidateChallenge
|
||||
=================
|
||||
*/
|
||||
qboolean SV_ValidateChallenge( netadr_t from, int challenge )
|
||||
{
|
||||
qboolean error = false;
|
||||
uint32_t time_window = (uint32_t)( host.realtime / CHALLENGE_WINDOW_SECONDS );
|
||||
int challenge = SV_GetChallenge( from, time_window, &error );
|
||||
|
||||
// accept the current window and the previous one so challenges issued just
|
||||
// before a window boundary remain valid for the full expected lifetime
|
||||
if( SV_GetChallenge( from, time_window, &error ) == challenge && !error )
|
||||
return true;
|
||||
|
||||
if( SV_GetChallenge( from, time_window - 1, &error ) == challenge && !error )
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void SV_SendChallenge( netadr_t from, qboolean skip_bandwidth_test )
|
||||
{
|
||||
qboolean error = false;
|
||||
int challenge = SV_CreateChallenge( from, &error );
|
||||
|
||||
if( error )
|
||||
return;
|
||||
@@ -213,15 +245,7 @@ Make sure connecting client is not spoofing
|
||||
*/
|
||||
static int SV_CheckChallenge( netadr_t from, int challenge )
|
||||
{
|
||||
qboolean error = false;
|
||||
uint32_t time_window = (uint32_t)( host.realtime / CHALLENGE_WINDOW_SECONDS );
|
||||
|
||||
// accept the current window and the previous one so challenges issued just
|
||||
// before a window boundary remain valid for the full expected lifetime
|
||||
if( SV_GetChallenge( from, time_window, &error ) == challenge && !error )
|
||||
return true;
|
||||
|
||||
if( SV_GetChallenge( from, time_window - 1, &error ) == challenge && !error )
|
||||
if( SV_ValidateChallenge( from, challenge ))
|
||||
return true;
|
||||
|
||||
SV_RejectConnection( from, "no challenge for your address\n" );
|
||||
@@ -301,6 +325,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 +370,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 +465,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 +475,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 +489,9 @@ 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 ));
|
||||
Info_SetValueForKeyf( protinfo, "challenge", sizeof( protinfo ), "%d", challenge );
|
||||
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 );
|
||||
@@ -922,111 +975,6 @@ static void SV_ConnectNatClient( netadr_t from )
|
||||
SV_Info( to, PROTOCOL_VERSION );
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
SV_BuildNetAnswer
|
||||
|
||||
Responds with long info for local and broadcast requests
|
||||
================
|
||||
*/
|
||||
static void SV_BuildNetAnswer( netadr_t from )
|
||||
{
|
||||
const cvar_t *cv;
|
||||
char string[4096];
|
||||
int version;
|
||||
int context;
|
||||
int type;
|
||||
int count = 0;
|
||||
int i;
|
||||
|
||||
// ignore in single player
|
||||
if( svs.maxclients == 1 || !svs.initialized )
|
||||
return;
|
||||
|
||||
version = Q_atoi( Cmd_Argv( 1 ));
|
||||
context = Q_atoi( Cmd_Argv( 2 ));
|
||||
type = Q_atoi( Cmd_Argv( 3 ));
|
||||
|
||||
string[0] = 0;
|
||||
|
||||
if( version != PROTOCOL_VERSION )
|
||||
{
|
||||
// send error unsupported protocol
|
||||
Info_SetValueForKey( string, "neterror", "protocol", sizeof( string ));
|
||||
Netchan_OutOfBandPrint( NS_SERVER, from, A2A_NETINFO" %i %i %s\n", context, type, string );
|
||||
return;
|
||||
}
|
||||
|
||||
switch( type )
|
||||
{
|
||||
case NETAPI_REQUEST_PING:
|
||||
break;
|
||||
case NETAPI_REQUEST_RULES:
|
||||
for( cv = Cvar_GetList( ); cv; cv = cv->next )
|
||||
{
|
||||
if( !FBitSet( cv->flags, FCVAR_SERVER ))
|
||||
continue;
|
||||
|
||||
if( FBitSet( cv->flags, FCVAR_PROTECTED ))
|
||||
{
|
||||
if( !COM_StringEmpty( cv->string ) && Q_stricmp( cv->string, "none" ))
|
||||
Info_SetValueForKey( string, cv->name, "1", sizeof( string ));
|
||||
else Info_SetValueForKey( string, cv->name, "0", sizeof( string ));
|
||||
}
|
||||
else Info_SetValueForKey( string, cv->name, cv->string, sizeof( string ));
|
||||
|
||||
count++;
|
||||
}
|
||||
|
||||
Info_SetValueForKeyf( string, "rules", sizeof( string ), "%i", count );
|
||||
break;
|
||||
case NETAPI_REQUEST_PLAYERS:
|
||||
if( !sv_expose_player_list.value || SV_HavePassword( ))
|
||||
{
|
||||
Info_SetValueForKey( string, "neterror", "forbidden", sizeof( string ));
|
||||
}
|
||||
else
|
||||
{
|
||||
for( i = 0; i < svs.maxclients; i++ )
|
||||
{
|
||||
const sv_client_t *cl = &svs.clients[i];
|
||||
|
||||
if( cl->state < cs_connected )
|
||||
continue;
|
||||
|
||||
Info_SetValueForKey( string, va( "p%iname", count ), cl->name, sizeof( string ));
|
||||
Info_SetValueForKeyf( string, va( "p%ifrags", count ), sizeof( string ), "%i", (int)cl->edict->v.frags );
|
||||
Info_SetValueForKeyf( string, va( "p%itime", count ), sizeof( string ), "%f", host.realtime - cl->connection_started );
|
||||
|
||||
count++;
|
||||
}
|
||||
|
||||
Info_SetValueForKeyf( string, "players", sizeof( string ), "%i", count );
|
||||
}
|
||||
break;
|
||||
case NETAPI_REQUEST_DETAILS:
|
||||
for( i = 0; i < svs.maxclients; i++ )
|
||||
{
|
||||
if( svs.clients[i].state >= cs_connected )
|
||||
count++;
|
||||
}
|
||||
|
||||
// should match SV_SourceQuery_Details
|
||||
Info_SetValueForKey( string, "hostname", hostname.string, sizeof( string ));
|
||||
Info_SetValueForKey( string, "gamedir", GI->gamefolder, sizeof( string ));
|
||||
Info_SetValueForKeyf( string, "current", sizeof( string ), "%i", count );
|
||||
Info_SetValueForKeyf( string, "max", sizeof( string ), "%i", svs.maxclients );
|
||||
Info_SetValueForKey( string, "map", sv.name, sizeof( string ));
|
||||
break;
|
||||
default:
|
||||
// send error undefined request type
|
||||
Info_SetValueForKey( string, "neterror", "undefined", sizeof( string ));
|
||||
break;
|
||||
}
|
||||
|
||||
Netchan_OutOfBandPrint( NS_SERVER, from, A2A_NETINFO" %i %i %s\n", context, type, string );
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
Rcon_Validate
|
||||
@@ -3214,11 +3162,7 @@ void SV_ConnectionlessPacket( netadr_t from, sizebuf_t *msg )
|
||||
// `pcmd` points only to the first word from the query string.
|
||||
if( !Q_strcmp( args, A2S_GOLDSRC_INFO ) || pcmd[0] == A2S_GOLDSRC_PLAYERS || pcmd[0] == A2S_GOLDSRC_RULES )
|
||||
{
|
||||
SV_SourceQuery_HandleConnnectionlessPacket( args, from );
|
||||
}
|
||||
else if( !Q_strcmp( pcmd, A2A_NETINFO ))
|
||||
{
|
||||
SV_BuildNetAnswer( from );
|
||||
SV_SourceQuery_HandleConnnectionlessPacket( args, from, msg );
|
||||
}
|
||||
else if( !Q_strcmp( pcmd, A2A_INFO ))
|
||||
{
|
||||
@@ -3445,7 +3389,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 +3400,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 +3520,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;
|
||||
|
||||
@@ -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 );
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -520,8 +520,7 @@ static void SV_SetupPMove( playermove_t *pmove, sv_client_t *cl, usercmd_t *ucmd
|
||||
vec3_t absmin, absmax;
|
||||
edict_t *clent = cl->edict;
|
||||
|
||||
svgame.globals->frametime = (ucmd->msec * 0.001f);
|
||||
|
||||
pmove->frametime = ucmd->msec * 0.001f;
|
||||
pmove->player_index = NUM_FOR_EDICT( clent ) - 1;
|
||||
pmove->multiplayer = (svs.maxclients > 1) ? true : false;
|
||||
pmove->time = (float)(cl->timebase * 1000.0);
|
||||
|
||||
@@ -165,12 +165,49 @@ static void SV_SourceQuery_Players( netadr_t from )
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_SourceQuery_CheckChallenge
|
||||
|
||||
Validates the challenge that follows the query type byte.
|
||||
On missing, mismatched or explicitly requested (-1) challenge,
|
||||
replies with a new challenge for this address.
|
||||
==================
|
||||
*/
|
||||
static qboolean SV_SourceQuery_CheckChallenge( netadr_t from, sizebuf_t *msg )
|
||||
{
|
||||
qboolean error = false;
|
||||
|
||||
MSG_SeekToBit( msg, ( sizeof( uint32_t ) + sizeof( uint8_t )) << 3, SEEK_SET );
|
||||
int challenge = MSG_ReadLong( msg );
|
||||
|
||||
if( !MSG_CheckOverflow( msg ) && challenge != -1 && SV_ValidateChallenge( from, challenge ))
|
||||
return true;
|
||||
|
||||
challenge = SV_CreateChallenge( from, &error );
|
||||
if( error )
|
||||
return false;
|
||||
|
||||
sizebuf_t buf;
|
||||
char answer[16];
|
||||
|
||||
MSG_Init( &buf, "QueryChallenge", answer, sizeof( answer ));
|
||||
|
||||
MSG_WriteDword( &buf, 0xFFFFFFFFU );
|
||||
MSG_WriteByte( &buf, S2C_GOLDSRC_CHALLENGE[0] );
|
||||
MSG_WriteLong( &buf, challenge );
|
||||
|
||||
NET_SendPacket( NS_SERVER, MSG_GetNumBytesWritten( &buf ), MSG_GetData( &buf ), from );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
SV_SourceQuery_HandleConnnectionlessPacket
|
||||
==================
|
||||
*/
|
||||
void SV_SourceQuery_HandleConnnectionlessPacket( const char *c, netadr_t from )
|
||||
void SV_SourceQuery_HandleConnnectionlessPacket( const char *c, netadr_t from, sizebuf_t *msg )
|
||||
{
|
||||
if( !Q_strcmp( c, A2S_GOLDSRC_INFO ))
|
||||
{
|
||||
@@ -179,10 +216,12 @@ void SV_SourceQuery_HandleConnnectionlessPacket( const char *c, netadr_t from )
|
||||
else switch( c[0] )
|
||||
{
|
||||
case A2S_GOLDSRC_RULES:
|
||||
SV_SourceQuery_Rules( from );
|
||||
if( SV_SourceQuery_CheckChallenge( from, msg ))
|
||||
SV_SourceQuery_Rules( from );
|
||||
break;
|
||||
case A2S_GOLDSRC_PLAYERS:
|
||||
SV_SourceQuery_Players( from );
|
||||
if( SV_SourceQuery_CheckChallenge( from, msg ))
|
||||
SV_SourceQuery_Players( from );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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')
|
||||
@@ -56,18 +47,12 @@ def options(opt):
|
||||
grp.add_option('--enable-static-binary', action = 'store_true', dest = 'STATIC', default = False,
|
||||
help = 'build static binary(not recommended, --single-binary required) [default: %(default)s]')
|
||||
|
||||
grp.add_option('--enable-engine-fuzz', action = 'store_true', dest = 'ENGINE_FUZZ', default = False,
|
||||
help = 'add LLVM libFuzzer [default: %(default)s]' )
|
||||
|
||||
grp.add_option('--enable-ffmpeg', action = 'store_true', dest = 'FFMPEG', default = False,
|
||||
help = 'enable ffmpeg based movie playback [default: %(default)s')
|
||||
|
||||
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):
|
||||
@@ -101,9 +86,10 @@ def configure(conf):
|
||||
elif conf.env.DEST_OS == 'dos':
|
||||
conf.options.STATIC = True
|
||||
|
||||
if conf.options.ENGINE_FUZZ:
|
||||
if conf.options.ENABLE_FUZZER:
|
||||
conf.define('XASH_LLVM_LIBFUZZER', 1)
|
||||
conf.env.append_unique('CFLAGS', '-fsanitize=fuzzer-no-link')
|
||||
conf.env.append_unique('LINKFLAGS', '-fsanitize=fuzzer')
|
||||
conf.env.append_unique('LINKFLAGS', '-fsanitize=fuzzer-no-link')
|
||||
|
||||
# Client-only dependencies
|
||||
if conf.env.CLIENT:
|
||||
@@ -162,19 +148,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 +159,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', 'yy_thunks']
|
||||
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 +187,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
|
||||
|
||||
@@ -28,7 +28,7 @@ GNU General Public License for more details.
|
||||
// GoldSrc Directories and ID
|
||||
// GAME gamedir
|
||||
// GAMECONFIG gamedir (rodir integration?)
|
||||
// GAMEDOWNLOAD gamedir_downloads (gamedir/downloads for us)
|
||||
// GAMEDOWNLOAD gamedir_downloads
|
||||
// GAME_FALLBACK liblist.gam's fallback_dir
|
||||
// ROOT and BASE rootdir
|
||||
// PLATFORM platform
|
||||
@@ -41,14 +41,22 @@ GNU General Public License for more details.
|
||||
char * const var = static_cast<char *>( alloca( var ## _size )); \
|
||||
CopyAndFixSlashes( var, ( str ), var ## _size )
|
||||
|
||||
static inline bool IsIdGamedir( const char *id )
|
||||
static bool IsIdGamedir( const char *id )
|
||||
{
|
||||
return !Q_strcmp( id, "GAME" ) ||
|
||||
!Q_strcmp( id, "GAMECONFIG" ) ||
|
||||
!Q_strcmp( id, "GAMEDOWNLOAD" );
|
||||
}
|
||||
|
||||
static inline const char *IdToDir( char *dir, size_t size, const char *id )
|
||||
static searchpath_t *IdToWritableSearchpath( const char *id )
|
||||
{
|
||||
if( !Q_strcmp( id, "GAME" ) || !Q_strcmp( id, "GAMECONFIG" ))
|
||||
return fs_writepath;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char *IdToDir( char *dir, size_t size, const char *id )
|
||||
{
|
||||
if( !Q_strcmp( id, "GAME" ))
|
||||
return GI->gamefolder;
|
||||
@@ -72,7 +80,7 @@ static inline const char *IdToDir( char *dir, size_t size, const char *id )
|
||||
return fs_rootdir; // give at least root directory
|
||||
}
|
||||
|
||||
static inline void CopyAndFixSlashes( char *p, const char *in, size_t size )
|
||||
static void CopyAndFixSlashes( char *p, const char *in, size_t size )
|
||||
{
|
||||
Q_strncpy( p, in, size );
|
||||
COM_FixSlashes( p );
|
||||
@@ -147,10 +155,9 @@ public:
|
||||
|
||||
void RemoveFile( const char *path, const char *id ) override
|
||||
{
|
||||
char dir[MAX_VA_STRING], fullpath[MAX_VA_STRING];
|
||||
|
||||
Q_snprintf( fullpath, sizeof( fullpath ), "%s/%s", IdToDir( dir, sizeof( dir ), id ), path );
|
||||
FS_Delete( fullpath ); // FS_Delete is aware of slashes
|
||||
// FIXME: handle writable downloads path
|
||||
if( IdToWritableSearchpath( id ))
|
||||
FS_Delete( path ); // FS_Delete is aware of slashes
|
||||
}
|
||||
|
||||
void CreateDirHierarchy( const char *path, const char *id ) override
|
||||
|
||||
@@ -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 );
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 );
|
||||
|
||||
@@ -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 )
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
206
filesystem/tests/filecopy.c
Normal 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;
|
||||
}
|
||||
63
filesystem/tests/filesystem_test_common.h
Normal file
63
filesystem/tests/filesystem_test_common.h
Normal 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
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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 )
|
||||
{
|
||||
|
||||
@@ -33,7 +33,7 @@ def configure(conf):
|
||||
def build(bld):
|
||||
bld(name = 'filesystem_includes', export_includes = '.')
|
||||
|
||||
libs = [ 'filesystem_includes', 'sdk_includes', 'werror' ]
|
||||
libs = [ 'filesystem_includes', 'sdk_includes', 'werror', 'yy_thunks' ]
|
||||
|
||||
# on PSVita do not link any libraries that are already in the main executable, but add the includes target
|
||||
if bld.env.DEST_OS != 'psvita':
|
||||
@@ -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:
|
||||
|
||||
@@ -32,7 +32,7 @@ def build(bld):
|
||||
|
||||
bld.program(source = source,
|
||||
target = 'xash3d', # hl.exe
|
||||
use = 'sdk_includes DL USER32 SHELL32 werror',
|
||||
use = 'sdk_includes DL USER32 SHELL32 werror yy_thunks',
|
||||
rpath = bld.env.DEFAULT_RPATH,
|
||||
install_path = bld.env.BINDIR,
|
||||
subsystem = bld.env.MSVC_SUBSYSTEM
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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])
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user