Compare commits

...

55 Commits

Author SHA1 Message Date
Alibek Omarov
46add4784b ref: gl: extract studio draw mesh branch into it's own function, refactoring 2026-08-05 04:17:40 +05:00
Alibek Omarov
fe62e61aa4 3rdparty: nanogl: update, again 2026-08-05 01:00:12 +05:00
Alibek Omarov
de74ce9767 ref: add r_studio_builtin_renderer cvar to use engine's StudioModelRenderer. It gives no benefit other than for debugging purposes 2026-08-05 00:15:23 +05:00
Alibek Omarov
8c89579c35 3rdparty: nanogl: update 2026-08-04 21:58:54 +05:00
Alibek Omarov
18dddaf1a5 3rdparty: nanogl: update 2026-08-03 23:44:48 +05:00
Alibek Omarov
d3bcdc6c06 engine: imagelib: Image_SaveWAD refactoring 2026-08-03 21:55:03 +05:00
Andrey Akhmichin
c0a40f6e03 Documentation: opensource-mods.md: add Zombie Panic. 2026-08-03 18:43:59 +08:00
Andrey Akhmichin
e3e459bb67 Documentation: supported-mod-list.md: add Half-Life: Sum. 2026-08-02 18:12:30 +05:00
Andrey Akhmichin
badda6c585 Documentation: opensource-mods.md: add Half-Life: Sum. 2026-08-02 18:05:46 +05:00
Andrey Akhmichin
db68b7c6e7 Documentation: supported-mod-list.md: update. 2026-08-01 19:20:22 +05:00
Andrey Akhmichin
841b2bcf5a Documentation: opensource-mods.md: update. 2026-08-01 19:19:50 +05:00
Alibek Omarov
a5fb74eb8f engine: use XASH_OSX macro 2026-08-01 16:12:04 +05:00
Alibek Omarov
31d44f85a7 engine: implement reading hardware serial ID on Mac, implement reading MAC addresses on *BSDs, refactoring 2026-08-01 16:12:04 +05:00
Alibek Omarov
24f8525fad engine: server: refactoring. 2026-08-01 16:12:04 +05:00
SLB8910
04fbe1a356 CONTRIBUTING.md: fixed horrible grammer
made contributing.md actually readable
2026-07-30 20:55:09 +05:00
Andrey Akhmichin
ecda80fb9a Documentation: supported-mod-list.md: update. 2026-07-29 02:43:00 +05:00
Владислав Сухов
9f14c48787 3rdparty: mainui: update 2026-07-27 18:47:53 +04:00
Владислав Сухов
8b15bc9a96 3rdparty: mainui: update 2026-07-26 18:08:54 +05:00
Andrey Akhmichin
e6913c9ce2 Documentation: opensource-mods.md: update. 2026-07-26 08:24:34 +05:00
lewa_j
b4495238d9 3rdparty: gl4es: update from upstream, with msvc build fix 2026-07-25 17:33:13 +03:00
lewa_j
635b382c1d gl4es: msvc: suppress warning spam from CHECK_SHADER: warning C4098: 'void' function returning a value
Same flag is used in gl4es's CMakeLists
2026-07-25 15:26:25 +03:00
Terence Noone
7fbd461f83 engine: Format code
Ran uncrustify on each file
2026-07-24 14:23:39 +05:00
Terence Noone
eb0ff8e631 engine: Implement SDL3 resizing/mode switching
Adds the ability to switch to fullscreen, windowed, and borderless, as
well as changing fullscreen mode.
2026-07-24 14:23:39 +05:00
Terence Noone
f50e61403f engine: Fix SDL3 support
Gets SDL3 building again on macOS. The game is able to boot to the main
menu and play with hlsdk-portable and HL assets. Some bugs when changing
video modes.
2026-07-24 14:23:39 +05:00
Alibek Omarov
2cf8c63631 engine: server: fix a typo, we should set pmove frametime before calling server pmove, instead of server global pmove, like GoldSrc
Thanks to @dkenw for drawing attention to this issue.
2026-07-22 17:36:32 +05:00
Alibek Omarov
e09773c912 engine: server: add challenge to A2S_GOLDSRC_RULES and A2S_GOLDSRC_PLAYERS 2026-07-22 10:03:59 +05:00
Alibek Omarov
3a15357dc8 engine: server: drop A2A_NETINFO 2026-07-22 10:03:59 +05:00
Sergey Degtyar
2127735456 engine: implement GoldSrc A2S query for NetAPI
As Xash also supports GoldSrc like queries, it's netinfo is dropped.
2026-07-22 10:03:59 +05:00
Alibek Omarov
e0ab44213a Documentation: protocol: connectionless: add GoldSrc A2S queries 2026-07-22 10:03:59 +05:00
Buggem's Fedora PC
f2166a2c9d ref: soft: Try to stop decal->pnext loops from recursing infinitely
Doesn't fix the root cause of the problem, which is the mysterious setting of decal->pnext to decal which doesn't happen in GL, but does act as a blockade and a temporary fix
2026-07-21 15:16:32 +05:00
Alibek Omarov
009855c193 scripts: waifulib: clang_format: don't abort configure when clang-format is too old, just disable the format command 2026-07-19 07:37:01 +05:00
ksagameng
f0ea3a194a platform: ios: Remake launch dialog using UIAlertController 2026-07-15 15:16:14 +05:00
Alibek Omarov
9f2b8954e9 utils: add new tool xash-clang-format, a wrapper around clang-format that adds our formatting extensions 2026-07-01 22:53:52 +05:00
Alibek Omarov
2166dbde66 3rdparty: mainui: update 2026-06-30 23:25:50 +05:00
Alibek Omarov
4c109a0c6c engine: propagate challenge to client connect message 2026-06-30 23:24:16 +05:00
Alibek Omarov
36c71c2610 engine: fixes found by fuzzing sprite loader: mostly float-to-int conversions and overflows 2026-06-30 22:12:01 +05:00
Alibek Omarov
e2d6b7ab28 ref: gl: do not deadname LadyHavoc 2026-06-30 22:12:01 +05:00
Alibek Omarov
8ccb9dea3f engine: add sprite model loading fuzzing, decouple fuzzing from tests 2026-06-30 22:12:01 +05:00
Alibek Omarov
34467bfe9a ref: gl: make R_AddDynamicLights only iterate through actually lit box 2026-06-30 22:12:01 +05:00
Владислав Сухов
b1d9eef896 Documentation: donate: refine contribution info and add PayPal link 2026-06-24 23:48:39 +05:00
Alibek Omarov
c2ac632446 engine: client: s/cls.legacymode/cls.net_protocol/g 2026-06-22 18:42:56 +05:00
Alibek Omarov
365c126de6 engine: client: fix uninitlaized variable, treat legacy info response servers as 48 even though it's incorrect 2026-06-22 18:34:55 +05:00
Alibek Omarov
44313e624c engine: client: fix parsing of legacy goldsrc A2S_INFO response format 2026-06-22 17:39:42 +05:00
Alibek Omarov
2f776a6173 engine: add overflow checks to sprite loader 2026-06-22 17:39:42 +05:00
Владислав Сухов
190fa1bab8 3rdparty: mainui: update 2026-06-22 09:54:57 +05:00
Alibek Omarov
3366ad06d0 wscript: add yy-thunks library to help support the holy cow of windows users 2026-06-21 11:16:03 +05:00
Alibek Omarov
57206b3651 client: parse: fix svc_spawnbaseline entity type delta table determine order 2026-06-21 10:34:33 +05:00
Alibek Omarov
d9ecd03b4a client: parse: fix default attenuation value for goldsrc sound packet 2026-06-21 10:12:56 +05:00
Alibek Omarov
2de4d0d364 common: fix usercmd_t::msec type in q_client.h 2026-06-21 09:45:50 +05:00
auewe
cc3889146c 3rdparty: mainui: update 2026-06-20 08:14:04 +05:00
Alibek Omarov
09f5886751 readme: add note about third party builds 2026-06-17 20:09:15 +05:00
Alibek Omarov
fb5b4fed6e filesystem: VFileSystem009: fix DeleteFile 2026-06-16 15:13:30 +05:00
Alibek Omarov
1e31fbcd51 ref: shut up DecalShoot about decals must hit mod_brush
This is an annoying message that's caused by the game code. Decals, without
custom renderer, cannot be rendered on non-brush entities, and everybody knows
that anyway.
2026-06-14 07:22:30 +05:00
Alibek Omarov
b2a5f0db5a engine: masterlist: fix addmaster command not allowing third optional argument 2026-06-09 21:47:08 +05:00
Alibek Omarov
abd7b5ccdd engine: masterlist: change parsing static server list from by token to by line 2026-06-08 22:17:21 +05:00
84 changed files with 3622 additions and 985 deletions

113
.clang-format Normal file
View 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

View File

@@ -70,6 +70,7 @@ jobs:
env: env:
SDL_VERSION: 2.32.10 SDL_VERSION: 2.32.10
FFMPEG_VERSION: 8.1 FFMPEG_VERSION: 8.1
YY_THUNKS_VERSION: v1.2.1
GH_CPU_ARCH: ${{ matrix.targetarch }} GH_CPU_ARCH: ${{ matrix.targetarch }}
GH_CPU_OS: ${{ matrix.targetos }} GH_CPU_OS: ${{ matrix.targetos }}
GH_CROSSCOMPILING: ${{ matrix.cross }} GH_CROSSCOMPILING: ${{ matrix.cross }}

View File

@@ -16,6 +16,8 @@ def build(bld):
cflags = [] cflags = []
if bld.env.COMPILER_CC != 'msvc': if bld.env.COMPILER_CC != 'msvc':
cflags += ['-w', '-fvisibility=hidden', '-std=gnu99'] cflags += ['-w', '-fvisibility=hidden', '-std=gnu99']
else:
cflags += ['-wd4098']
bld.stlib(source = gl4es_srcdir.ant_glob(['gl/*.c', 'gl/*/*.c', 'glx/hardext.c']), bld.stlib(source = gl4es_srcdir.ant_glob(['gl/*.c', 'gl/*/*.c', 'glx/hardext.c']),
target = 'gl4es', target = 'gl4es',
includes = ['gl4es/src', 'gl4es/src/gl', 'gl4es/src/glx', 'gl4es/include'], includes = ['gl4es/src', 'gl4es/src/gl', 'gl4es/src/glx', 'gl4es/include'],

59
3rdparty/yy-thunks/wscript vendored Normal file
View 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')

View File

@@ -1,53 +1,55 @@
## If you are reporting bugs ## If you are reporting bugs
1. Check you are using latest version. You can build latest Xash3D FWGS for yourself, look to README.md. 1. Check that you are using the latest version. To build the latest Xash3D FWGS, look at README.md.
2. Check open issues is your bug is already reported and closed issues if it reported and fixed. Don't send bug if it's already reported. 2. Check the open issues to make sure your bug is not already reported and closed issues if it reported and fixed. Don't send a bug if it's already been reported.
3. Re-run engine with `-dev 2 -log` arguments, reproduce bug and post engine.log which can be found in your working directory. 3. Re-run engine with `-dev 2 -log` arguments, then reproduce the bug and post `engine.log` which can be found in your working directory.
3. Describe steps to reproduce bug. 3. Describe the steps to reproduce the bug.
4. Describe which OS and architecture you are using. 4. Describe which OS and architecture you are using.
6. Attach screenshot if it will help clarify the situation. 6. Attach a screenshot if it will help clarify the situation.
## If you are contributing code ## If you are contributing code
### Which branch? ### Which branch?
* We recommend using `master` branch. * We recommend using the `master` branch.
### Third-party libraries ### Third-party libraries
* Philosophy of any Xash Project by Uncle Mike: don't be bloated. We follow it too. * The philosophy of any Xash Project by Uncle Mike: don't be bloated. We follow it too.
* Adding new library is allowed only if there is a REAL reason to use it. It's will be nice, if you will leave a possibility to remove new dependency at build-time. * Adding new libraries are allowed only if there is a REAL reason to use it. It will be nice, if you leave a possibility to remove the new dependency at build-time.
* Adding new dependencies for Waf Build System is not welcomed. * Adding new dependencies for the Waf Build System is not welcomed.
### Portability level ### Portability level
* Xash3D have it's own crt library. It's recommended to use it. It most cases it's just a wrappers around standart C library. * Xash3D has it's own crt library. It's recommended to use it. In most cases it's just a wrapper around the standard C library.
* If your feature need platform-specific code, move it to `engine/platform` and try to implement to every supported OS and every supported compiler or at least leave a stubs. * If your feature needs platform-specific code, move it to `engine/platform` and try to implement every supported OS and every supported compiler or at least leave a stub.
* You must put it under appopriate macro. It's a rule: Xash3D FWGS must compile everywhere. For list of platforms we support, refer to public/build.h file. * You must put it under the appropriate macro. It's a rule: Xash3D FWGS must compile everywhere. For list of platforms we support, refer to `public/build.h`.
### Code style ### Code style
* This project uses mixed Quake's and HLSDK's C/C++ code style convention. * This project uses a mix of Quake's and HLSDK's C/C++ code style convention.
* In short: * In short:
* Use spaces in parenthesis, but not when two parentheses are consecutive. * Use spaces in parenthesis, but not when two parentheses are consecutive.
* Only tabs for indentation. * Use only tabs for indentation.
* Any brace must have it's own line. * Any brace must have it's own line.
* Short blocks, if statements and loops on single line are allowed. * Use short blocks, if statements and loops on single line are allowed.
* Prefer generic utilities from libpublic over rolling your own. * Prefer generic utilities from libpublic over rolling your own.
* Avoid magic numbers. * Avoid magic numbers.
* While macros are powerful, it's better to avoid overusing them. * While macros are powerful, it's better to avoid overusing them.
* If you unsure, try to mimic code style from anywhere else of engine source code. * If you are unsure, try to mimic the code style from anywhere else in the engine source code.
* **ANY** commit message should start from declaring a tags, in format: * **ANY** commit message should start from declaring tags, in the format:
`tag: added some bugs` `tag: added some bugs`
`tag: subtag: fixed some features` `tag: subtag: fixed some features`
Tags can be any: subsystem, simple feature name or even just a filename, without extension. The tags can be a:
Just keep them always same, it helps keep history clean and commit messages short. * subsystem
* simple feature name or even just a filename, without the extension.
Just always keep them the same because it helps keep the commit history clean and messages short.
## LLM-based tools usage. ## LLM-based tools usage.
While we wouldn't recommend using any LLM-based (also misleadingly called AI) tools, we understand that they are here to stay. While we wouldn't recommend using any LLM-based (also misleadingly called AI) tools, we understand that they are here to stay.
Whether you're reporting bug or contributing the code, you take complete authorship and responsibility over provided content and the same rules will apply to you as for everybody else, so validate the bug report or the patch before sending it. Whether you're reporting a bug or contributing to the code, you will take complete authorship and responsibility over the provided content, and the same rules will apply to you as for everybody else, so validate the bug report or the patch it before sending it.

View File

@@ -29,9 +29,7 @@ Initial Xash3D SDL2/Linux port author, Xash3D FWGS engine maintainer, creator of
## [$_Vladislav](https://github.com/Vladislav4KZ) ## [$_Vladislav](https://github.com/Vladislav4KZ)
[YaPB Project](https://github.com/yapb) member, [Xash3D FWGS](https://github.com/FWGS/xash3d-fwgs) tester, [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).
editor of the [Official YaPB Documentation](https://github.com/yapb/docs) (in English and Russian), curator of the [YaPB Graph Database](https://github.com/yapb/graph) and author of [YaPB Waypoint/Graph Pack](https://gamebanana.com/mods/40087).
Also does the Russian localization of text, images (gfx/shell), as well as additional menu buttons in English used in Xash3D FWGS for various mods.
* [Boosty page](https://boosty.to/rasstaman1337) * [Boosty page](https://boosty.to/rasstaman1337)
* [PayPal link](https://paypal.me/rasstaman1337)

View File

@@ -38,6 +38,9 @@ Mirrored on github - https://github.com/solidi/hl-mods/tree/master/cir
## Counter-Life ## Counter-Life
Available on ModDB - https://www.moddb.com/mods/counter-life/downloads/cl-version-1-source-code 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 ## Cthulhu
Uploaded to github by Oleg Cherkasky - https://github.com/gunrunners-paradise/Cthulhu-HLmod-SDK Uploaded to github by Oleg Cherkasky - https://github.com/gunrunners-paradise/Cthulhu-HLmod-SDK
@@ -62,11 +65,14 @@ Available on GamerLab - http://gamer-lab.com/eng/code_mods_goldsrc/Half-Life_2D_
## Gang Wars ## Gang Wars
Mirrored on github - https://github.com/nekonomicon/gw1.45src Mirrored on github - https://github.com/nekonomicon/gw1.45src
## Go-mod ## Go-Mod
Versions 2.0 and 3.0, available in mod archives on ModDB - https://www.moddb.com/mods/go-mod/downloads Versions 2.0 and 3.0, available in mod archives on ModDB - https://www.moddb.com/mods/go-mod/downloads
Version 3.0, mirrored on github - https://github.com/nekonomicon/Go-mod30 Version 3.0, mirrored on github - https://github.com/nekonomicon/Go-mod30
## Go-Mod: Reborn
Official github repository - https://github.com/LambdaLuke87/Go-Mod-Reborn-Lite-Build
## GT mod ## GT mod
Available on GamerLab - http://gamer-lab.com/eng/code_mods_goldsrc/GT_mod_(Polnie_ishodniki) Available on GamerLab - http://gamer-lab.com/eng/code_mods_goldsrc/GT_mod_(Polnie_ishodniki)
@@ -85,12 +91,21 @@ Available on ModDB - https://www.moddb.com/mods/half-life-echoes
## Half-Life: Expanded Arsenal ## Half-Life: Expanded Arsenal
Available on ModDB - https://www.moddb.com/mods/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 ## Half-Life: Field Intensity
Branch **field_intensity_1.7** in halflife-featureful - https://github.com/FreeSlave/halflife-featureful/tree/field_intensity_1.7 Branch **field_intensity_1.7** in halflife-featureful - https://github.com/FreeSlave/halflife-featureful/tree/field_intensity_1.7
## Half-Life: Gravgun mod ## Half-Life: Gravgun mod
Branch **gravgun** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/gravgun 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 ## Half-Life: Invasion
Official github repository - https://github.com/jlecorre/hlinvasion Official github repository - https://github.com/jlecorre/hlinvasion
@@ -100,6 +115,13 @@ Mirrored on github - https://github.com/solidi/hl-mods/tree/master/pong
## Half-Life: Quest Mode ## Half-Life: Quest Mode
Available on cs-mapping.com.ua - https://old.cs-mapping.com.ua/forum/showthread.php?t=38030 Available on cs-mapping.com.ua - https://old.cs-mapping.com.ua/forum/showthread.php?t=38030
## Half-Life: Sum
Original Version: Branch **sum_orig** in halflife-featureful - https://github.com/FreeSlave/halflife-featureful/tree/sum_orig
Version 2022: Branch **sum_2022** in halflife-featureful - https://github.com/FreeSlave/halflife-featureful/tree/sum_2022
Current Version: Branch **sum** in halflife-featureful - https://github.com/FreeSlave/halflife-featureful/tree/sum
## Half-Life: Top-Down ## Half-Life: Top-Down
Official gitlab repository - https://gitlab.com/Sockman/hltopdown Official gitlab repository - https://gitlab.com/Sockman/hltopdown
@@ -109,6 +131,9 @@ Official github repository - https://github.com/Fograin/hl-subsmod-ex
## Half-Life: Rally ## Half-Life: Rally
Official gitlab repository - https://gitlab.com/hlrally/src Official gitlab repository - https://gitlab.com/hlrally/src
## Half-Life: Reprocessing
Official github repository - https://github.com/EgorYak/ReprocessingSourceCode
## Half-Life: RXT ## Half-Life: RXT
Available on ModDB - https://www.moddb.com/mods/half-life-rxt/downloads/rxt-source-code-v57 Available on ModDB - https://www.moddb.com/mods/half-life-rxt/downloads/rxt-source-code-v57
@@ -147,6 +172,9 @@ Mirrored on github (Includes maps and models source files) - https://github.com/
## Ingram Chillin' Mod ## Ingram Chillin' Mod
Official SourceForge repository - https://sourceforge.net/projects/icm-hl/ 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 ## Master Sword
Official github repository of Master Sword Classic - https://github.com/BerntA/MasterSwordClassic Official github repository of Master Sword Classic - https://github.com/BerntA/MasterSwordClassic
@@ -189,6 +217,9 @@ Available in Valve's Half-Life repository - https://github.com/ValveSoftware/hal
## Spirit of Half-Life ## 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 [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 ## The Wastes
Version 1.5: mirrored on code.idtech.space - https://code.idtech.space/vera/halflife-thewastes-sdk Version 1.5: mirrored on code.idtech.space - https://code.idtech.space/vera/halflife-thewastes-sdk
@@ -212,6 +243,19 @@ Download page on official site - http://www.thothie.com/ww/
## Wrong Door ## Wrong Door
Available on ModDB - https://www.moddb.com/mods/wrong-door/downloads/wrong-door-sdk 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 ## XashXT
Mirrored on github - https://github.com/a1batross/XashXT_original Mirrored on github - https://github.com/a1batross/XashXT_original
@@ -220,6 +264,12 @@ Mirrored on github - https://github.com/a1batross/XashXT_original
[Logic&Trick's](https://github.com/LogicAndTrick) mirror - https://files.logic-and-trick.com/#/Half-Life/Mods/Spirit%20of%20Half-Life [Logic&Trick's](https://github.com/LogicAndTrick) mirror - https://files.logic-and-trick.com/#/Half-Life/Mods/Spirit%20of%20Half-Life
## Yet another PS2 Half-Life port
Official github repository - https://github.com/supadupaplex/ya-ps2hl-dll
## Zombie Panic!
Official github repository - https://github.com/Monochrome-Inc/ZombiePanic-HL
## Zombie-X ## Zombie-X
Available in mod archive on ModDB - https://www.moddb.com/mods/zombie-x-10-final/downloads/zombie-x-10-dle-beta6-last-version Available in mod archive on ModDB - https://www.moddb.com/mods/zombie-x-10-final/downloads/zombie-x-10-dle-beta6-last-version
@@ -301,7 +351,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 Reverse-engineered code: branch **bshift** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/bshift
## Half-Life: Induction ## 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 ## Half-Life: Opposing Force
Recreation by lostgamer aka nillerusr - https://github.com/LostGamerHL/hlsdk-xash3d Recreation by lostgamer aka nillerusr - https://github.com/LostGamerHL/hlsdk-xash3d
@@ -375,6 +425,9 @@ Branch **aghl** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/
## Bubblemod ## Bubblemod
Branch **bubblemod** 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
Deathmatch Classic: Adrenaline Gamer Edition - https://github.com/martinwebrant/agmod/tree/master/src/dmc Deathmatch Classic: Adrenaline Gamer Edition - https://github.com/martinwebrant/agmod/tree/master/src/dmc
@@ -388,6 +441,9 @@ Branch **delta_particles** in hlsdk-portable - https://github.com/FWGS/hlsdk-por
## Half-Life: Echoes ## Half-Life: Echoes
Branch **echoes** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/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 ## Half-Life: Invasion
Port to HLSDK 2.4 by malortie - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-invasion Port to HLSDK 2.4 by malortie - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-invasion
@@ -421,6 +477,8 @@ Branch **dmc** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/d
## Xen-Warrior ## Xen-Warrior
Branch **sohl1.2** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/sohl1.2 Branch **sohl1.2** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/sohl1.2
## Yet another PS2 Half-Life port
Branch **ps2hl** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/ps2hl
## Zombie-X ## Zombie-X
Branch **zombie-x** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/zombie-x Branch **zombie-x** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/zombie-x

View File

@@ -12,14 +12,18 @@ All connectionless packets have `\xff\xff\xff\xff` (32-bit integer set to all on
- [Any](#any) - [Any](#any)
- [Any to Any](#any-to-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_PING` and `A2A_GOLDSRC_PING`](#a2a_ping-and-a2a_goldsrc_ping)
- [`A2A_ACK` and `A2A_GOLDSRC_ACK`](#a2a_ack-and-a2a_goldsrc_ack) - [`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) - [Any to Client](#any-to-client)
- [`A2C_PRINT` and `A2C_GOLDSRC_PRINT`](#a2c_print-and-a2c_goldsrc_print) - [`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) - [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) - [Any to Master](#any-to-master)
- [`S2M_SCAN_REQUEST`](#s2m_scan_request) - [`S2M_SCAN_REQUEST`](#s2m_scan_request)
- [Master to Any](#master-to-any) - [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 ### 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. Used to implement Half-Life's NetAPI.
* Request message, in ASCII: `netinfo <version> <context> <request_id>` * 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` * On any other server will respond with `neterror` set to `undefined`
* If protocol version doesn't match, server responds with `neterror` set to `protocol` * If protocol version doesn't match, server responds with `neterror` set to `protocol`
#### `A2A_INFO` ##### `A2A_INFO`
Used to request server's details. Used to request server's details.
* Request message, in ASCII: `info <version>` * 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 - `password` set to 1 if server is protected with password otherwise 0
- `host` is server's name - `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 ### Any to Client
#### `A2C_PRINT` and `A2C_GOLDSRC_PRINT` #### `A2C_PRINT` and `A2C_GOLDSRC_PRINT`
@@ -124,9 +132,62 @@ Simple print message.
### 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 #### 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 ### 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 - `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 - 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) - 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 ## Master and game server

View File

@@ -28,17 +28,18 @@ used; servers register out of band.
## Response ## Response
UTF-8 text, tokenized with `COM_ParseFileSafe` (whitespace separates, UTF-8 text, parsed line by line. Each line is tokenized with
`//` and `#` start line comments, `"..."` quotes a token). One directive `COM_ParseFileSafe` (whitespace separates, `//` and `#` start line
per record: comments, `"..."` quotes a token). Blank lines and comment-only lines
are ignored. One directive per line:
* `ip <address>` — Xash3D server (protocol 49). * `ip <address>` — Xash3D server (protocol 49).
* `gs <address>` — GoldSrc server (protocol 48). * `gs <address>` — GoldSrc server (protocol 48).
`<address>` is parsed by `NET_StringToAdr` (`1.2.3.4:27015`, `<address>` is parsed by `NET_StringToAdr` (`1.2.3.4:27015`,
`[2001:db8::1]:27015`, hostnames). Port defaults to `27015`. Unknown `[2001:db8::1]:27015`, hostnames). Port defaults to `27015`. Lines
directives are skipped together with one operand so new keywords can be starting with an unknown directive are skipped entirely, so new keywords
added without breaking older clients. with any number of operands can be added without breaking older clients.
`Content-Type` is not inspected, `text/plain; charset=utf-8` expected. `Content-Type` is not inspected, `text/plain; charset=utf-8` expected.

View File

@@ -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 ## List of games and mods with custom gamedll
For mods from this category - first of all place mod folder beside **valve** folder. 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)** 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). 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. 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. 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 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). 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). 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. 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). 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. 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). 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. This mod already supports x86 Linux.
Support for other platforms may be available later. 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). 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). 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. 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). 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. 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* *Support temporary abandoned*
@@ -1022,163 +1036,197 @@ 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). 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). 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. 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). 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. 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). 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). 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: Sum](https://www.moddb.com/mods/half-life-sum)**
This mod already supports x86 Linux.
To run this mod on specific platforms you may be need to compile libraries from **sum** branch of [halflife-featureful](https://github.com/FreeSlave/halflife-featureful).
36. **[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). 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)** 37. **[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). 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)** 38. **[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). 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)** 39. **[Half-Rats: Parasomnia](https://www.moddb.com/mods/half-rats-parasomnia/downloads/half-rats-parasomnia-v10b-steamlinux)**
This mod already supports x86 Linux. This mod already supports x86 Linux.
Support for other platforms may be available later. Support for other platforms may be available later.
35. **[Half-Screwed: Death and Rebirth](https://www.moddb.com/mods/half-screwed)** 40. **[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). 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)** 41. **[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). 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)** 42. **[Mortal Kombat](https://www.moddb.com/mods/hl-mortal-kombat)**
This mod already supports Android and x86 Linux.
43. **[Natural Selection](https://www.moddb.com/mods/natural-selection)**
This mod already supports x86 Linux. This mod already supports x86 Linux.
Support for other platforms may be available later. Support for other platforms may be available later.
38. **[Night at the Office](https://www.moddb.com/mods/night-at-the-office)** 44. **[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). 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)** 45. **[Paranoia](https://www.moddb.com/mods/paranoia)**
*Support temporary abandoned* *Support temporary abandoned*
[Source code](https://github.com/FWGS/paranoia_toolkit). [Source code](https://github.com/FWGS/paranoia_toolkit).
40. **[Poke646](https://poke646.com/)** 46. **[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). 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/)** 47. **[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). 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/) 48. [PrimeXT](https://snmetamorph.github.io/PrimeXT/)
*It's modding SDK*
[Source code](https://github.com/SNMetamorph/PrimeXT) [Source code](https://github.com/SNMetamorph/PrimeXT)
43. **[Quake Remake](https://www.moddb.com/games/quake-remake)** 49. **[Quake Remake](https://www.moddb.com/games/quake-remake)**
*Support was fully abandoned due Quake Wrapper release.* *Support was fully abandoned due Quake Wrapper release.*
[Actual source code](https://github.com/FWGS/quakeremake) [Actual source code](https://github.com/FWGS/quakeremake)
44. **[Rebellion](https://www.moddb.com/mods/rebellion1)** 50. **[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). 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)** 51. **[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). 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)** 52. **[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). 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/)** 53. **[Ricochet](https://store.steampowered.com/app/60/Ricochet/)**
This mod already supports x86 Linux. This mod already supports x86 Linux.
Support for other platforms may be available later. Support for other platforms may be available later.
48. [Sewer](https://www.moddb.com/games/half-life/addons/sewer-beta) 54. [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). 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) 55. [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). 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)** 56. **[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). 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/)** 57. **[Team Fortress Classic](https://store.steampowered.com/app/20/Team_Fortress_Classic/)**
This mod already supports x86 Linux. This mod already supports x86 Linux.
Currently, only source code of [client part](https://github.com/Velaron/tf15-client) is available. 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)** 58. **[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). 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)** 59. **[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).
60. **[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). 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)** 61. **[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). 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)** 62. **[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). 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/) 63. [XashXT](https://csm.dev/threads/xashxt-0-65-rev-4-release-stable.38319/)
*It's modding SDK*
*Support was fully abandoned due PrimeXT development* *Support was fully abandoned due PrimeXT development*
[Source code](https://github.com/FWGS/XashXT-FWGS/) [Source code](https://github.com/FWGS/XashXT-FWGS/)
57. **[X-Half-Life DeathMatch](https://www.moddb.com/mods/xdm)** 64. **[X-Half-Life DeathMatch](https://www.moddb.com/mods/xdm)**
This mod already supports x86 Linux. This mod already supports x86 Linux.
Support for other platforms may be available later. Support for other platforms may be available later.
58. **[Xen-Warrior](https://www.moddb.com/mods/xen-warrior)** 65. **[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). 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!!! Don't forget to enable XENWARRIOR build option!!!
59. [Zombie-X](https://www.moddb.com/mods/zombie-x-10-final) 66. **[Yet another PS2 Half-Life port](https://www.moddb.com/mods/yet-another-ps2-half-life-pc-port)**
To run this mod on specific platforms you may be need to compile libraries from **ps2hl** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
67. [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). To run this mod on specific platforms you may be need to compile libraries from **zombie-x** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).

View File

@@ -1,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" /> # Xash3D FWGS Engine <img align="right" width="128" height="128" src="https://github.com/FWGS/xash3d-fwgs/raw/master/game_launch/icon-xash-material.png" alt="Xash3D FWGS icon" />
[![GitHub Actions Status](https://github.com/FWGS/xash3d-fwgs/actions/workflows/c-cpp.yml/badge.svg)](https://github.com/FWGS/xash3d-fwgs/actions/workflows/c-cpp.yml) [![FreeBSD Build Status](https://img.shields.io/cirrus/github/FWGS/xash3d-fwgs?label=freebsd%20build)](https://cirrus-ci.com/github/FWGS/xash3d-fwgs) \ [![GitHub Actions Status](https://github.com/FWGS/xash3d-fwgs/actions/workflows/c-cpp.yml/badge.svg)](https://github.com/FWGS/xash3d-fwgs/actions/workflows/c-cpp.yml) [![FreeBSD Build Status](https://img.shields.io/cirrus/github/FWGS/xash3d-fwgs?label=freebsd%20build)](https://cirrus-ci.com/github/FWGS/xash3d-fwgs) \
[![Discord Server](https://img.shields.io/discord/355697768582610945?logo=Discord&label=International%20Discord%20chat)](http://xash.su/discord/) [![Russian speakers Telegram Chat](https://img.shields.io/badge/Russian_speakers_Telegram_chat-gray?logo=Telegram)](https://t.me/flyingwithgauss) \ [![Discord Server](https://img.shields.io/discord/355697768582610945?logo=Discord&label=International%20Discord%20chat)](http://xash.su/discord/) [![Russian speakers Telegram Chat](https://img.shields.io/badge/Russian_speakers_Telegram_chat-gray?logo=Telegram)](https://t.me/flyingwithgauss) \

View File

@@ -30,7 +30,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
typedef struct usercmd_s typedef struct usercmd_s
{ {
int16_t lerp_msec; // added in HL int16_t lerp_msec; // added in HL
int8_t msec; // added in QW uint8_t msec; // added in QW
uint8_t pad1; uint8_t pad1;
vec3_t viewangles; vec3_t viewangles;

View File

@@ -50,7 +50,7 @@ const char *CL_MsgInfo( int cmd )
// get engine message name // get engine message name
const char *svc_string = NULL; const char *svc_string = NULL;
switch( cls.legacymode ) switch( cls.net_protocol )
{ {
case PROTO_CURRENT: case PROTO_CURRENT:
svc_string = svc_strings[cmd]; 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, &cls.starting_count, sizeof( int ));
FS_Write( fp, &current_count, sizeof( int )); FS_Write( fp, &current_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_Write( fp, MSG_GetData( msg ), MSG_GetMaxBytes( msg ));
FS_Close( fp ); FS_Close( fp );
@@ -207,7 +207,7 @@ void CL_ReplayBufferDat_f( void )
FS_Read( f, &current_count, sizeof( current_count )); FS_Read( f, &current_count, sizeof( current_count ));
FS_Read( f, &protocol, sizeof( protocol )); FS_Read( f, &protocol, sizeof( protocol ));
cls.legacymode = protocol; cls.net_protocol = protocol;
len = FS_Read( f, buffer, sizeof( buffer )); len = FS_Read( f, buffer, sizeof( buffer ));
FS_Close( f ); FS_Close( f );

View File

@@ -412,7 +412,7 @@ static void CL_WriteDemoHeader( const char *name )
demo.header.id = IDEMOHEADER; demo.header.id = IDEMOHEADER;
demo.header.dem_protocol = DEMO_PROTOCOL; 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; 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.mapname, clgame.mapname, sizeof( demo.header.mapname ));
Q_strncpy( demo.header.comment, clgame.maptitle, sizeof( demo.header.comment )); 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 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 )); memset( demo.cmds, 0, sizeof( demo.cmds ));
demo.angle_position = 1; demo.angle_position = 1;
@@ -1555,7 +1555,7 @@ void CL_PlayDemo_f( void )
if( neg ) cls.forcetrack = -cls.forcetrack; if( neg ) cls.forcetrack = -cls.forcetrack;
CL_DemoStartPlayback( DEMO_QUAKE1 ); CL_DemoStartPlayback( DEMO_QUAKE1 );
cls.legacymode = PROTO_QUAKE; cls.net_protocol = PROTO_QUAKE;
return; // quake demo is started return; // quake demo is started
} }
@@ -1591,7 +1591,7 @@ void CL_PlayDemo_f( void )
CL_DemoStartPlayback( DEMO_XASH3D ); CL_DemoStartPlayback( DEMO_XASH3D );
// must be after DemoStartPlayback, as CL_Disconnect_f resets the protocol // 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? // g-cont. is this need?
Q_strncpy( cls.servername, demoname, sizeof( cls.servername )); Q_strncpy( cls.servername, demoname, sizeof( cls.servername ));

View File

@@ -42,6 +42,7 @@ static CVAR_DEFINE_AUTO( cl_logoext, "bmp", FCVAR_ARCHIVE, "temporary cvar to te
static CVAR_DEFINE( cl_logoupdate, "@cl_logoupdate", "0", 0, "set by menu to trigger clan logo update" ); static CVAR_DEFINE( cl_logoupdate, "@cl_logoupdate", "0", 0, "set by menu to trigger clan logo update" );
CVAR_DEFINE_AUTO( cl_logomaxdim, "96", FCVAR_ARCHIVE, "maximum decal dimension" ); CVAR_DEFINE_AUTO( cl_logomaxdim, "96", FCVAR_ARCHIVE, "maximum decal dimension" );
static CVAR_DEFINE_AUTO( cl_test_bandwidth, "1", FCVAR_ARCHIVE, "test network bandwith before connection" ); static CVAR_DEFINE_AUTO( cl_test_bandwidth, "1", FCVAR_ARCHIVE, "test network bandwith before connection" );
static CVAR_DEFINE_AUTO( cl_require_challenge_echo, "-1", FCVAR_ARCHIVE, "reject connect packets that don't echo challenge, protects against spoofed servers but breaks connection to old servers (-1 = engine default)" );
CVAR_DEFINE( cl_draw_particles, "r_drawparticles", "1", FCVAR_CHEAT, "render particles" ); CVAR_DEFINE( cl_draw_particles, "r_drawparticles", "1", FCVAR_CHEAT, "render particles" );
CVAR_DEFINE( cl_draw_tracers, "r_drawtracers", "1", FCVAR_CHEAT, "render tracers" ); CVAR_DEFINE( cl_draw_tracers, "r_drawtracers", "1", FCVAR_CHEAT, "render tracers" );
@@ -171,7 +172,7 @@ int CL_IsDevOverviewMode( void )
connprotocol_t CL_Protocol( void ) connprotocol_t CL_Protocol( void )
{ {
return cls.legacymode; return cls.net_protocol;
} }
void CL_SetCheatState( qboolean multiplayer, qboolean allow_cheats ) void CL_SetCheatState( qboolean multiplayer, qboolean allow_cheats )
@@ -235,7 +236,7 @@ static void CL_CreateResourceList( void )
Cvar_DirectSet( &cl_logoext, "bmp" ); Cvar_DirectSet( &cl_logoext, "bmp" );
Q_snprintf( szFileName, sizeof( szFileName ), "logos/remapped.%s", cl_logoext.string ); Q_snprintf( szFileName, sizeof( szFileName ), "logos/remapped.%s", cl_logoext.string );
if( cls.legacymode == PROTO_GOLDSRC ) if( cls.net_protocol == PROTO_GOLDSRC )
{ {
CL_ConvertImageToWAD3( szFileName ); CL_ConvertImageToWAD3( szFileName );
Q_strncpy( szFileName, "tempdecal.wad", sizeof( szFileName )); Q_strncpy( szFileName, "tempdecal.wad", sizeof( szFileName ));
@@ -335,7 +336,7 @@ static void CL_CheckClientState( void )
Cvar_SetValue( "scr_loading", 0.0f ); // reset progress bar Cvar_SetValue( "scr_loading", 0.0f ); // reset progress bar
Netchan_ReportFlow( &cls.netchan ); Netchan_ReportFlow( &cls.netchan );
if( cls.legacymode == PROTO_GOLDSRC ) if( cls.net_protocol == PROTO_GOLDSRC )
{ {
CL_ServerCommand(true, "specmode 4\n"); CL_ServerCommand(true, "specmode 4\n");
CL_ServerCommand(true, "specmode 4\n"); CL_ServerCommand(true, "specmode 4\n");
@@ -868,7 +869,7 @@ static void CL_WritePacket( void )
byte data[MAX_CMD_BUFFER] = { 0 }; byte data[MAX_CMD_BUFFER] = { 0 };
runcmd_t *pcmd; runcmd_t *pcmd;
int numbackup, maxbackup, maxcmds; int numbackup, maxbackup, maxcmds;
const connprotocol_t proto = cls.legacymode; const connprotocol_t proto = cls.net_protocol;
// FIXME: on Xash protocol we don't send move commands until ca_active // FIXME: on Xash protocol we don't send move commands until ca_active
// to prevent outgoing_command outrun incoming_acknowledged // to prevent outgoing_command outrun incoming_acknowledged
@@ -1374,7 +1375,7 @@ static void CL_CheckForResend( void )
cls.state = ca_connecting; cls.state = ca_connecting;
Q_strncpy( cls.servername, "localhost", sizeof( cls.servername )); Q_strncpy( cls.servername, "localhost", sizeof( cls.servername ));
NET_NetadrSetType( &cls.serveradr, NA_LOOPBACK ); NET_NetadrSetType( &cls.serveradr, NA_LOOPBACK );
cls.legacymode = PROTO_CURRENT; cls.net_protocol = PROTO_CURRENT;
// we don't need a challenge on the localhost // we don't need a challenge on the localhost
CL_SendConnectPacket( PROTO_CURRENT, 0 ); CL_SendConnectPacket( PROTO_CURRENT, 0 );
@@ -1449,7 +1450,7 @@ static void CL_CheckForResend( void )
// but tell us that test is allowed // but tell us that test is allowed
// in this case, just send connect packet and hope for the best // in this case, just send connect packet and hope for the best
if( cls.bandwidth_test.passed || cls.bandwidth_test.failed ) if( cls.bandwidth_test.passed || cls.bandwidth_test.failed )
CL_SendConnectPacket( cls.legacymode, cls.bandwidth_test.challenge ); CL_SendConnectPacket( cls.net_protocol, cls.bandwidth_test.challenge );
else else
CL_SendBandwidthTest( adr, false ); CL_SendBandwidthTest( adr, false );
} }
@@ -1512,7 +1513,7 @@ static void CL_Connect_f( void )
Key_SetKeyDest( key_console ); Key_SetKeyDest( key_console );
cls.state = ca_connecting; cls.state = ca_connecting;
cls.legacymode = proto; cls.net_protocol = proto;
Q_strncpy( cls.servername, server, sizeof( cls.servername )); Q_strncpy( cls.servername, server, sizeof( cls.servername ));
cls.connect_time = MAX_HEARTBEAT; // CL_CheckForResend() will fire immediately cls.connect_time = MAX_HEARTBEAT; // CL_CheckForResend() will fire immediately
cls.max_fragment_size = FRAGMENT_MAX_SIZE; // guess a we can establish connection with maximum fragment size cls.max_fragment_size = FRAGMENT_MAX_SIZE; // guess a we can establish connection with maximum fragment size
@@ -1717,7 +1718,7 @@ static void CL_Reconnect( qboolean setup_netchan )
{ {
if( setup_netchan ) if( setup_netchan )
{ {
CL_SetupNetchanForProtocol( cls.legacymode ); CL_SetupNetchanForProtocol( cls.net_protocol );
} }
else else
{ {
@@ -1762,7 +1763,7 @@ void CL_Disconnect( void )
CL_Stop_f(); CL_Stop_f();
// send a disconnect message to the server // send a disconnect message to the server
CL_SendDisconnectMessage( cls.legacymode ); CL_SendDisconnectMessage( cls.net_protocol );
SteamBroker_TerminateGameConnection(); SteamBroker_TerminateGameConnection();
CL_ClearState (); CL_ClearState ();
@@ -1780,7 +1781,7 @@ void CL_Disconnect( void )
cls.connect_retry = 0; cls.connect_retry = 0;
memset( &cls.bandwidth_test, 0, sizeof( cls.bandwidth_test )); memset( &cls.bandwidth_test, 0, sizeof( cls.bandwidth_test ));
cls.signon = 0; cls.signon = 0;
cls.legacymode = PROTO_CURRENT; cls.net_protocol = PROTO_CURRENT;
// back to menu in non-developer mode // back to menu in non-developer mode
if( host_developer.value || cls.key_dest == key_menu ) if( host_developer.value || cls.key_dest == key_menu )
@@ -1808,7 +1809,7 @@ void CL_Crashed( void )
CL_Stop_f(); // stop any demos CL_Stop_f(); // stop any demos
// send a disconnect message to the server // send a disconnect message to the server
CL_SendDisconnectMessage( cls.legacymode ); CL_SendDisconnectMessage( cls.net_protocol );
Host_WriteOpenGLConfig(); Host_WriteOpenGLConfig();
Host_WriteConfig(); // write config Host_WriteConfig(); // write config
@@ -1914,7 +1915,7 @@ static void CL_Reconnect_f( void )
if( !COM_StringEmptyOrNULL( cls.servername )) if( !COM_StringEmptyOrNULL( cls.servername ))
{ {
connprotocol_t proto = cls.legacymode; connprotocol_t proto = cls.net_protocol;
if( cls.state >= ca_connected ) if( cls.state >= ca_connected )
CL_Disconnect(); CL_Disconnect();
@@ -1923,7 +1924,7 @@ static void CL_Reconnect_f( void )
cls.demonum = cls.movienum = -1; // not in the demo loop now cls.demonum = cls.movienum = -1; // not in the demo loop now
cls.state = ca_connecting; cls.state = ca_connecting;
cls.signon = 0; cls.signon = 0;
cls.legacymode = proto; // don't change protocol cls.net_protocol = proto; // don't change protocol
Con_Printf( "reconnecting...\n" ); Con_Printf( "reconnecting...\n" );
} }
@@ -2072,23 +2073,61 @@ static void CL_ParseStatusMessage( netadr_t from, sizebuf_t *msg )
UI_AddServerToList( from, infostring ); UI_AddServerToList( from, infostring );
} }
static net_request_t *CL_NetRequestFind( netadr_t from, int type )
{
for( int i = 0; i < ARRAYSIZE( clgame.net_requests ); i++ )
{
net_request_t *nr = &clgame.net_requests[i];
if( !nr->pfnFunc || nr->resp.type != type )
continue;
if( NET_CompareAdr( nr->resp.remote_address, from ))
return nr;
// broadcast requests accept a response from anyone
if( NET_NetadrType( &nr->resp.remote_address ) == NA_BROADCAST )
return nr;
}
return NULL;
}
static void CL_NetRequestComplete( net_request_t *nr, netadr_t from, const char *response )
{
netadr_t request_adr = nr->resp.remote_address;
nr->resp.response = (void *)response;
nr->resp.remote_address = from;
nr->resp.error = NET_SUCCESS;
nr->resp.ping = host.realtime - nr->timesend;
nr->pfnFunc( &nr->resp );
if( FBitSet( nr->flags, FNETAPI_MULTIPLE_RESPONSE ))
nr->resp.remote_address = request_adr;
else memset( nr, 0, sizeof( *nr )); // done
}
static void CL_ParseGoldSrcStatusMessage( netadr_t from, sizebuf_t *msg, qboolean legacy_format ) static void CL_ParseGoldSrcStatusMessage( netadr_t from, sizebuf_t *msg, qboolean legacy_format )
{ {
static char s[512+8]; static char s[512+8];
int p, numcl, maxcl, password, remaining, bots; int p, numcl, maxcl, password, bots;
string host, map, gamedir, version; string host, map, gamedir, version;
char *replace;
// set to beginning but skip header // set to beginning but skip header
MSG_SeekToBit( msg, (sizeof( uint32_t ) + sizeof( uint8_t )) << 3, SEEK_SET ); MSG_SeekToBit( msg, (sizeof( uint32_t ) + sizeof( uint8_t )) << 3, SEEK_SET );
if( legacy_format ) if( legacy_format )
{ {
string address;
int mod; int mod;
p = MSG_ReadByte( msg ); // FIXME: this is invalid, `m` servers might be geniune 47 proto servers
Q_strncpy( address, MSG_ReadString( msg ), sizeof( address )); // but many servers that reply with legacy format are 47/48
// so at least let the user to connect them
p = PROTOCOL_GOLDSRC_VERSION;
MSG_ReadString( msg ); // address
Q_strncpy( host, MSG_ReadString( msg ), sizeof( host )); Q_strncpy( host, MSG_ReadString( msg ), sizeof( host ));
Q_strncpy( map, MSG_ReadString( msg ), sizeof( map )); Q_strncpy( map, MSG_ReadString( msg ), sizeof( map ));
Q_strncpy( gamedir, MSG_ReadString( msg ), sizeof( gamedir )); Q_strncpy( gamedir, MSG_ReadString( msg ), sizeof( gamedir ));
@@ -2159,7 +2198,7 @@ static void CL_ParseGoldSrcStatusMessage( netadr_t from, sizebuf_t *msg, qboolea
// write host last so we can try to cut off too long hostnames // write host last so we can try to cut off too long hostnames
// TODO: value size limit for infostrings // TODO: value size limit for infostrings
remaining = sizeof( s ) - Q_strlen( s ) - sizeof( "\\host\\" ) - 1; int remaining = sizeof( s ) - Q_strlen( s ) - sizeof( "\\host\\" ) - 1;
if( remaining < 0 ) if( remaining < 0 )
{ {
// should never happen? // should never happen?
@@ -2167,103 +2206,205 @@ static void CL_ParseGoldSrcStatusMessage( netadr_t from, sizebuf_t *msg, qboolea
return; return;
} }
char *replace;
while(( replace = Q_strpbrk( host, "\\\"" ))) while(( replace = Q_strpbrk( host, "\\\"" )))
{
*replace = ' '; // find a better replacement? *replace = ' '; // find a better replacement?
}
Info_SetValueForKey( s, "host", host, sizeof( s )); Info_SetValueForKey( s, "host", host, sizeof( s ));
// tell mainui about server
UI_AddServerToList( from, s ); UI_AddServerToList( from, s );
// now process NetAPI requst
net_request_t *nr = CL_NetRequestFind( from, NETAPI_REQUEST_DETAILS );
if( !nr )
return;
s[0] = 0;
Info_SetValueForKey( s, "gamedir", gamedir, sizeof( s ));
Info_SetValueForKeyf( s, "current", sizeof( s ), "%i", numcl );
Info_SetValueForKeyf( s, "max", sizeof( s ), "%i", maxcl );
Info_SetValueForKey( s, "map", map, sizeof( s ));
Info_SetValueForKey( s, "hostname", host, sizeof( s ));
CL_NetRequestComplete( nr, from, s );
} }
/* /*
================= =================
CL_ParseNETInfoMessage CL_NetRequestSend
Handle a reply from a netinfo
================= =================
*/ */
static void CL_ParseNETInfoMessage( netadr_t from, const char *s ) qboolean CL_NetRequestSend( net_request_t *nr )
{ {
net_request_t *nr = NULL; byte buf[64];
static char infostring[MAX_PRINT_MSG]; sizebuf_t msg;
int i, context, type;
int errorBits = 0;
const char *val;
context = Q_atoi( Cmd_Argv( 1 )); if( nr->resp.type == NETAPI_REQUEST_PING )
type = Q_atoi( Cmd_Argv( 2 ));
// find request with specified context and type
for( i = 0; i < MAX_REQUESTS; i++ )
{ {
if( clgame.net_requests[i].resp.context == context && clgame.net_requests[i].resp.type == type ) Netchan_OutOfBandPrint( NS_CLIENT, nr->resp.remote_address, A2A_GOLDSRC_PING );
{ return true;
nr = &clgame.net_requests[i];
break;
}
} }
// not found, ignore MSG_Init( &msg, "NetRequest", buf, sizeof( buf ));
if( nr == NULL )
return;
// find the payload switch( nr->resp.type )
s = Q_strchr( s, ' ' ); // skip netinfo
if( !s )
return;
s = Q_strchr( s + 1, ' ' ); // skip challenge
if( !s )
return;
s = Q_strchr( s + 1, ' ' ); // skip type
if( s )
s++; // skip final whitespace
else if( type != NETAPI_REQUEST_PING ) // ping have no payload, and that's ok
return;
if( s )
{ {
if( s[0] == '\\' ) case NETAPI_REQUEST_DETAILS:
{ MSG_WriteString( &msg, A2S_GOLDSRC_INFO );
// check for errors break;
val = Info_ValueForKey( s, "neterror" ); case NETAPI_REQUEST_PLAYERS:
MSG_WriteByte( &msg, A2S_GOLDSRC_PLAYERS );
if( !Q_stricmp( val, "protocol" )) break;
SetBits( errorBits, NET_ERROR_PROTO_UNSUPPORTED ); case NETAPI_REQUEST_RULES:
else if( !Q_stricmp( val, "undefined" )) MSG_WriteByte( &msg, A2S_GOLDSRC_RULES );
SetBits( errorBits, NET_ERROR_UNDEFINED ); break;
else if( !Q_stricmp( val, "forbidden" )) default:
SetBits( errorBits, NET_ERROR_FORBIDDEN ); return false;
CL_FixupColorStringsForInfoString( s, infostring, sizeof( infostring ));
}
else
{
Q_strncpy( infostring, s, sizeof( infostring ));
}
}
else
{
infostring[0] = 0;
} }
// setup the answer MSG_WriteLong( &msg, nr->challenge );
nr->resp.response = infostring;
nr->resp.remote_address = from;
nr->resp.error = NET_SUCCESS;
nr->resp.ping = host.realtime - nr->timesend;
if( nr->timeout <= host.realtime ) Netchan_OutOfBand( NS_CLIENT, nr->resp.remote_address, MSG_GetNumBytesWritten( &msg ), MSG_GetData( &msg ));
SetBits( nr->resp.error, NET_ERROR_TIMEOUT );
SetBits( nr->resp.error, errorBits ); // misc error bits
nr->pfnFunc( &nr->resp ); return true;
}
if( !FBitSet( nr->flags, FNETAPI_MULTIPLE_RESPONSE )) /*
memset( nr, 0, sizeof( *nr )); // done =================
CL_HasActiveNetRequest
=================
*/
qboolean CL_HasActiveNetRequest( netadr_t from )
{
for( int i = 0; i < ARRAYSIZE( clgame.net_requests ); i++ )
{
const net_request_t *nr = &clgame.net_requests[i];
if( !nr->pfnFunc )
continue;
if( nr->resp.type != NETAPI_REQUEST_RULES && nr->resp.type != NETAPI_REQUEST_PLAYERS )
continue;
if( NET_CompareAdr( nr->resp.remote_address, from ))
return true;
}
return false;
}
/*
=================
CL_NetRequestChallenge
=================
*/
static void CL_NetRequestChallenge( netadr_t from, sizebuf_t *msg )
{
MSG_SeekToBit( msg, ( sizeof( uint32_t ) + sizeof( uint8_t )) << 3, SEEK_SET );
int challenge = MSG_ReadLong( msg );
// give the challenge to the first request without one,
// the server sends a challenge per received query
for( int i = 0; i < ARRAYSIZE( clgame.net_requests ); i++ )
{
net_request_t *nr = &clgame.net_requests[i];
if( !nr->pfnFunc || nr->resp.type == NETAPI_REQUEST_PING )
continue;
if( nr->challenge != -1 )
continue;
if( !NET_CompareAdr( nr->resp.remote_address, from ))
continue;
nr->challenge = challenge;
CL_NetRequestSend( nr );
break;
}
}
/*
=================
CL_NetRequestPing
=================
*/
static void CL_NetRequestPing( netadr_t from )
{
net_request_t *nr = CL_NetRequestFind( from, NETAPI_REQUEST_PING );
if( !nr )
return;
CL_NetRequestComplete( nr, from, "" );
}
/*
=================
CL_NetRequestPlayers
=================
*/
static void CL_NetRequestPlayers( netadr_t from, sizebuf_t *msg )
{
static char s[MAX_PRINT_MSG];
net_request_t *nr = CL_NetRequestFind( from, NETAPI_REQUEST_PLAYERS );
if( !nr )
return;
MSG_SeekToBit( msg, ( sizeof( uint32_t ) + sizeof( uint8_t )) << 3, SEEK_SET );
int count = MSG_ReadByte( msg );
s[0] = 0;
for( int i = 0; i < count && !MSG_CheckOverflow( msg ); i++ )
{
MSG_ReadByte( msg ); // index, GoldSrc servers always send 0 here
Info_SetValueForKey( s, va( "p%iname", i ), MSG_ReadString( msg ), sizeof( s ));
Info_SetValueForKeyf( s, va( "p%ifrags", i ), sizeof( s ), "%i", MSG_ReadLong( msg ));
Info_SetValueForKeyf( s, va( "p%itime", i ), sizeof( s ), "%f", MSG_ReadFloat( msg ));
}
Info_SetValueForKeyf( s, "players", sizeof( s ), "%i", count );
if( MSG_CheckOverflow( msg ))
return;
CL_NetRequestComplete( nr, from, s );
}
/*
=================
CL_NetRequestRules
=================
*/
static void CL_NetRequestRules( netadr_t from, sizebuf_t *msg )
{
static char s[MAX_PRINT_MSG];
net_request_t *nr = CL_NetRequestFind( from, NETAPI_REQUEST_RULES );
if( !nr )
return;
MSG_SeekToBit( msg, ( sizeof( uint32_t ) + sizeof( uint8_t )) << 3, SEEK_SET );
int count = MSG_ReadShort( msg );
s[0] = 0;
for( int i = 0; i < count && !MSG_CheckOverflow( msg ); i++ )
{
string key;
Q_strncpy( key, MSG_ReadString( msg ), sizeof( key ));
Info_SetValueForKey( s, key, MSG_ReadString( msg ), sizeof( s ));
}
Info_SetValueForKeyf( s, "rules", sizeof( s ), "%i", count );
if( MSG_CheckOverflow( msg ))
return;
CL_NetRequestComplete( nr, from, s );
} }
/* /*
@@ -2275,14 +2416,13 @@ check for timeouts
*/ */
static void CL_ProcessNetRequests( void ) static void CL_ProcessNetRequests( void )
{ {
net_request_t *nr;
int i;
// find a request with specified context // find a request with specified context
for( i = 0; i < MAX_REQUESTS; i++ ) for( int i = 0; i < ARRAYSIZE( clgame.net_requests ); i++ )
{ {
nr = &clgame.net_requests[i]; net_request_t *nr = &clgame.net_requests[i];
if( !nr->pfnFunc ) continue; // not used
if( !nr->pfnFunc )
continue; // not used
if( nr->timeout <= host.realtime ) if( nr->timeout <= host.realtime )
{ {
@@ -2443,6 +2583,29 @@ static void CL_ClientConnect( connprotocol_t proto, const char *c, netadr_t from
return; return;
} }
const char *challenge_str = Info_ValueForKey( Cmd_Argv( 1 ), "challenge" );
if( COM_StringEmpty( challenge_str ))
{
qboolean require_challenge;
if( cl_require_challenge_echo.value < 0 )
require_challenge = false; // FIXME: flip once most servers migrate to newer engine version
else
require_challenge = !!cl_require_challenge_echo.value;
if( require_challenge )
{
Con_Printf( S_WARN "%s: %s did not echo challenge, ignoring (possible spoof)\n", __func__, NET_AdrToString( from ));
return;
}
}
else if( Q_atoi( challenge_str ) != cls.bandwidth_test.challenge )
{
Con_Printf( S_WARN "%s: challenge mismatch from %s, ignoring (possible spoof)\n", __func__, NET_AdrToString( from ));
return;
}
if( cls.netchan_pending_cookie != 0 ) if( cls.netchan_pending_cookie != 0 )
{ {
int server_extensions = Q_atoi( Info_ValueForKey( Cmd_Argv( 1 ), "ext" )); int server_extensions = Q_atoi( Info_ValueForKey( Cmd_Argv( 1 ), "ext" ));
@@ -2497,8 +2660,16 @@ static void CL_Print( const char *c, const char *args, netadr_t from, sizebuf_t
Con_Printf( "%s%c", s, s[Q_strlen( s ) - 1] != '\n' ? '\n' : '\0' ); Con_Printf( "%s%c", s, s[Q_strlen( s ) - 1] != '\n' ? '\n' : '\0' );
} }
static void CL_Challenge( const char *c, netadr_t from ) static void CL_Challenge( const char *c, netadr_t from, sizebuf_t *msg )
{ {
// connection challenge is text and always comes as literal A00000000,
// query challenge is binary: challenge type byte and the challenge value
if( c[0] == S2C_GOLDSRC_CHALLENGE[0] && Q_strcmp( c, S2C_GOLDSRC_CHALLENGE ))
{
CL_NetRequestChallenge( from, msg );
return;
}
if( cls.state != ca_connecting ) if( cls.state != ca_connecting )
return; return;
@@ -2508,7 +2679,7 @@ static void CL_Challenge( const char *c, netadr_t from )
// try to autodetect protocol by challenge response // try to autodetect protocol by challenge response
if( !Q_strcmp( c, S2C_GOLDSRC_CHALLENGE )) if( !Q_strcmp( c, S2C_GOLDSRC_CHALLENGE ))
{ {
cls.legacymode = PROTO_GOLDSRC; cls.net_protocol = PROTO_GOLDSRC;
cls.steam_auth = Q_atoi( Cmd_Argv( 2 )) == 3; cls.steam_auth = Q_atoi( Cmd_Argv( 2 )) == 3;
@@ -2524,14 +2695,14 @@ static void CL_Challenge( const char *c, netadr_t from )
cls.bandwidth_test.challenge = Q_atoi( Cmd_Argv( 1 )); cls.bandwidth_test.challenge = Q_atoi( Cmd_Argv( 1 ));
if( cls.legacymode == PROTO_CURRENT && cl_test_bandwidth.value && !cls.bandwidth_test.passed ) if( cls.net_protocol == PROTO_CURRENT && cl_test_bandwidth.value && !cls.bandwidth_test.passed )
{ {
// when connecting to old server or server that has bandwidth test disabled // when connecting to old server or server that has bandwidth test disabled
// it might be more preferrable to have some sane fragment size // it might be more preferrable to have some sane fragment size
if( !Q_atoi( Cmd_Argv( 2 ))) if( !Q_atoi( Cmd_Argv( 2 )))
{ {
Cvar_SetValue( "cl_dlmax", FRAGMENT_DEFAULT_SIZE ); Cvar_SetValue( "cl_dlmax", FRAGMENT_DEFAULT_SIZE );
CL_SendConnectPacket( cls.legacymode, cls.bandwidth_test.challenge ); CL_SendConnectPacket( cls.net_protocol, cls.bandwidth_test.challenge );
} }
else else
{ {
@@ -2541,7 +2712,7 @@ static void CL_Challenge( const char *c, netadr_t from )
else else
{ {
// challenge from the server we are connecting to // challenge from the server we are connecting to
CL_SendConnectPacket( cls.legacymode, cls.bandwidth_test.challenge ); CL_SendConnectPacket( cls.net_protocol, cls.bandwidth_test.challenge );
} }
} }
@@ -2686,7 +2857,7 @@ static void CL_ConnectionlessPacket( netadr_t from, sizebuf_t *msg )
// server connection // server connection
if( !Q_strcmp( c, S2C_GOLDSRC_CONNECTION ) || !Q_strcmp( c, S2C_CONNECTION )) if( !Q_strcmp( c, S2C_GOLDSRC_CONNECTION ) || !Q_strcmp( c, S2C_CONNECTION ))
{ {
CL_ClientConnect( cls.legacymode, c, from ); CL_ClientConnect( cls.net_protocol, c, from );
} }
else if( !Q_strcmp( c, A2A_INFO )) else if( !Q_strcmp( c, A2A_INFO ))
{ {
@@ -2700,9 +2871,13 @@ static void CL_ConnectionlessPacket( netadr_t from, sizebuf_t *msg )
{ {
CL_ParseGoldSrcStatusMessage( from, msg, true ); CL_ParseGoldSrcStatusMessage( from, msg, true );
} }
else if( !Q_strcmp( c, A2A_NETINFO )) else if( c[0] == S2A_GOLDSRC_PLAYERS )
{ {
CL_ParseNETInfoMessage( from, args ); // server responding to a status broadcast CL_NetRequestPlayers( from, msg );
}
else if( c[0] == S2A_GOLDSRC_RULES )
{
CL_NetRequestRules( from, msg );
} }
else if( c[0] == A2C_GOLDSRC_PRINT || !Q_strcmp( c, A2C_PRINT )) else if( c[0] == A2C_GOLDSRC_PRINT || !Q_strcmp( c, A2C_PRINT ))
{ {
@@ -2722,11 +2897,11 @@ static void CL_ConnectionlessPacket( netadr_t from, sizebuf_t *msg )
} }
else if( !Q_strcmp( c, A2A_ACK ) || !Q_strcmp( c, A2A_GOLDSRC_ACK )) else if( !Q_strcmp( c, A2A_ACK ) || !Q_strcmp( c, A2A_GOLDSRC_ACK ))
{ {
// no-op CL_NetRequestPing( from ); // server responding to a NetAPI ping
} }
else if( !Q_strcmp( c, S2C_CHALLENGE ) || !Q_strcmp( c, S2C_GOLDSRC_CHALLENGE )) else if( !Q_strcmp( c, S2C_CHALLENGE ) || c[0] == S2C_GOLDSRC_CHALLENGE[0] )
{ {
CL_Challenge( c, from ); CL_Challenge( c, from, msg );
} }
else if( !Q_strcmp( c, S2C_REJECT ) || c[0] == S2C_GOLDSRC_REJECT || c[0] == S2C_GOLDSRC_REJECT_BADPASSWORD ) else if( !Q_strcmp( c, S2C_REJECT ) || c[0] == S2C_GOLDSRC_REJECT || c[0] == S2C_GOLDSRC_REJECT_BADPASSWORD )
{ {
@@ -2806,7 +2981,7 @@ static void CL_ReadNetMessage( void )
size_t curSize; size_t curSize;
void (*parsefn)( sizebuf_t *msg ); void (*parsefn)( sizebuf_t *msg );
switch( cls.legacymode ) switch( cls.net_protocol )
{ {
case PROTO_QUAKE: case PROTO_QUAKE:
parsefn = CL_ParseQuakeMessage; parsefn = CL_ParseQuakeMessage;
@@ -3102,7 +3277,7 @@ void CL_ProcessFile( qboolean successfully_received, const char *filename )
MSG_Init( &msg, "Resource Registration", msg_buf, sizeof( msg_buf )); MSG_Init( &msg, "Resource Registration", msg_buf, sizeof( msg_buf ));
if( CL_PrecacheResources( )) if( CL_PrecacheResources( ))
CL_RegisterResources( &msg, cls.legacymode ); CL_RegisterResources( &msg, cls.net_protocol );
if( MSG_GetNumBytesWritten( &msg ) > 0 ) if( MSG_GetNumBytesWritten( &msg ) > 0 )
{ {
@@ -3162,7 +3337,7 @@ tell server about changed userinfo
*/ */
void CL_UpdateInfo( const char *key, const char *value ) void CL_UpdateInfo( const char *key, const char *value )
{ {
switch( cls.legacymode ) switch( cls.net_protocol )
{ {
case PROTO_GOLDSRC: case PROTO_GOLDSRC:
if( cl_advertise_engine_in_name.value && !Q_stricmp( key, "name" ) && Q_strnicmp( value, "[Xash3D]", 8 )) if( cl_advertise_engine_in_name.value && !Q_stricmp( key, "name" ) && Q_strnicmp( value, "[Xash3D]", 8 ))
@@ -3495,6 +3670,7 @@ static void CL_InitLocal( void )
Cvar_RegisterVariable( &cl_logoupdate ); Cvar_RegisterVariable( &cl_logoupdate );
Cvar_RegisterVariable( &cl_logomaxdim ); Cvar_RegisterVariable( &cl_logomaxdim );
Cvar_RegisterVariable( &cl_test_bandwidth ); Cvar_RegisterVariable( &cl_test_bandwidth );
Cvar_RegisterVariable( &cl_require_challenge_echo );
Voice_RegisterCvars(); Voice_RegisterCvars();
VGui_RegisterCvars(); VGui_RegisterCvars();

View File

@@ -445,6 +445,7 @@ typedef struct
double timeout; double timeout;
double timesend; // time when request was sended double timesend; // time when request was sended
int flags; // FNETAPI_MULTIPLE_RESPONSE etc int flags; // FNETAPI_MULTIPLE_RESPONSE etc
int challenge; // GoldSrc query challenge, -1 until received
} net_request_t; } net_request_t;
// new versions of client dlls have a single export with all callbacks // new versions of client dlls have a single export with all callbacks
@@ -647,7 +648,7 @@ typedef struct
string internetservers_customfilter; string internetservers_customfilter;
// multiprotocol support // multiprotocol support
connprotocol_t legacymode; connprotocol_t net_protocol;
int extensions; int extensions;
netadr_t serveradr; netadr_t serveradr;
@@ -793,6 +794,7 @@ void CL_ClearState( void );
void CL_SetCheatState( qboolean multiplayer, qboolean allow_cheats ); void CL_SetCheatState( qboolean multiplayer, qboolean allow_cheats );
void CL_SendGoldSrcConnectPacket( netadr_t adr, int challenge, const void *ticket, size_t ticketlen ); void CL_SendGoldSrcConnectPacket( netadr_t adr, int challenge, const void *ticket, size_t ticketlen );
void CL_NotifyServerListResponse( void ); void CL_NotifyServerListResponse( void );
qboolean CL_NetRequestSend( net_request_t *nr );
// //
// cl_demo.c // cl_demo.c

View File

@@ -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 ) 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 ) 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; return;
} }
// FIXME: call pfnFunc and tell client.dll about errors?
if( NET_NetadrType( remote_address ) == NA_IPX || NET_NetadrType( remote_address ) == NA_BROADCAST_IPX ) if( NET_NetadrType( remote_address ) == NA_IPX || NET_NetadrType( remote_address ) == NA_BROADCAST_IPX )
return; // IPX no longer support return; // IPX no longer support
if( request == NETAPI_REQUEST_SERVERLIST ) switch( request )
{
case NETAPI_REQUEST_SERVERLIST:
return; // no support for server list requests 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 // 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]; nr = &clgame.net_requests[i];
if( !nr->pfnFunc ) break;
}
if( i == MAX_REQUESTS ) if(( host.realtime - nr->timesend ) > max_timeout )
{
double max_timeout = 0;
// no free requests? use oldest
for( i = 0, nr = NULL; i < MAX_REQUESTS; i++ )
{ {
if(( host.realtime - clgame.net_requests[i].timesend ) > max_timeout ) max_timeout = host.realtime - nr->timesend;
{ oldest_nr = nr;
max_timeout = host.realtime - clgame.net_requests[i].timesend;
nr = &clgame.net_requests[i];
}
} }
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 // clear slot
memset( nr, 0, sizeof( *nr )); 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.type = request;
nr->resp.remote_address = *remote_address; nr->resp.remote_address = *remote_address;
nr->flags = flags; nr->flags = flags;
nr->challenge = -1;
// local servers request if( !CL_NetRequestSend( nr ))
Netchan_OutOfBandPrint( NS_CLIENT, nr->resp.remote_address, A2A_NETINFO" %i %i %i", PROTOCOL_VERSION, context, request ); 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 ) static void GAME_EXPORT NetAPI_CancelRequest( int context )
{ {
// find a specified request // 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]; net_request_t *nr = &clgame.net_requests[i];
@@ -3456,7 +3473,7 @@ NetAPI_CancelAllRequests
void GAME_EXPORT NetAPI_CancelAllRequests( void ) void GAME_EXPORT NetAPI_CancelAllRequests( void )
{ {
// tell the user about cancel // 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]; net_request_t *nr = &clgame.net_requests[i];
if( !nr->pfnFunc ) continue; // not used if( !nr->pfnFunc ) continue; // not used

View File

@@ -15,14 +15,13 @@ GNU General Public License for more details.
#include "build.h" #include "build.h"
#include <inttypes.h> #include <inttypes.h>
#if XASH_LINUX
#include <fcntl.h> #include <fcntl.h>
#if !XASH_WIN32
#include <dirent.h> #include <dirent.h>
#else
#include <io.h>
#endif #endif
#include "common.h" #include "common.h"
#include "client.h" #include "client.h"
#include "platform/platform.h"
/* /*
========================================================== ==========================================================
@@ -97,8 +96,6 @@ IDENTIFICATION
#define MAXBITS_GEN 30 #define MAXBITS_GEN 30
#define MAXBITS_CHECK MAXBITS_GEN + 6 #define MAXBITS_CHECK MAXBITS_GEN + 6
static qboolean ID_ProcessFile( bloomfilter_t *value, const char *path );
static void ID_BloomFilter_f( void ) static void ID_BloomFilter_f( void )
{ {
bloomfilter_t value = 0; bloomfilter_t value = 0;
@@ -203,84 +200,6 @@ static qboolean ID_ProcessCPUInfo( bloomfilter_t *value )
return true; return true;
} }
static qboolean ID_ValidateNetDevice( const char *dev )
{
const char *prefix = "/sys/class/net";
// These devices are fake, their mac address is generated each boot, while assign_type is 0
if( !Q_strnicmp( dev, "ccmni", sizeof( "ccmni" ) ) ||
!Q_strnicmp( dev, "ifb", sizeof( "ifb" ) ) )
return false;
byte *pfile = FS_LoadDirectFile( va( "%s/%s/addr_assign_type", prefix, dev ), NULL );
// if NULL, it may be old kernel
if( pfile )
{
int assignType = Q_atoi( (char*)pfile );
Mem_Free( pfile );
// check is MAC address is constant
if( assignType != 0 )
return false;
}
return true;
}
static int ID_ProcessNetDevices( bloomfilter_t *value )
{
const char *prefix = "/sys/class/net";
DIR *dir;
struct dirent *entry;
int count = 0;
if( !( dir = opendir( prefix ) ) )
return 0;
while( ( entry = readdir( dir ) ) && BloomFilter_Weight( *value ) < MAXBITS_GEN )
{
if( !Q_strcmp( entry->d_name, "." ) || !Q_strcmp( entry->d_name, ".." ) )
continue;
if( !ID_ValidateNetDevice( entry->d_name ) )
continue;
count += ID_ProcessFile( value, va( "%s/%s/address", prefix, entry->d_name ) );
}
closedir( dir );
return count;
}
static int ID_CheckNetDevices( bloomfilter_t value )
{
const char *prefix = "/sys/class/net";
DIR *dir;
struct dirent *entry;
int count = 0;
bloomfilter_t filter = 0;
if( !( dir = opendir( prefix ) ) )
return 0;
while( ( entry = readdir( dir ) ) )
{
if( !Q_strcmp( entry->d_name, "." ) || !Q_strcmp( entry->d_name, ".." ) )
continue;
if( !ID_ValidateNetDevice( entry->d_name ) )
continue;
if( ID_ProcessFile( &filter, va( "%s/%s/address", prefix, entry->d_name ) ) )
count += ( value & filter ) == filter, filter = 0;
}
closedir( dir );
return count;
}
static void ID_TestCPUInfo_f( void ) static void ID_TestCPUInfo_f( void )
{ {
bloomfilter_t value = 0; bloomfilter_t value = 0;
@@ -291,8 +210,6 @@ static void ID_TestCPUInfo_f( void )
Msg( "Could not get serial\n" ); Msg( "Could not get serial\n" );
} }
#endif
static qboolean ID_ProcessFile( bloomfilter_t *value, const char *path ) static qboolean ID_ProcessFile( bloomfilter_t *value, const char *path )
{ {
int fd = open( path, O_RDONLY ); int fd = open( path, O_RDONLY );
@@ -322,7 +239,6 @@ static qboolean ID_ProcessFile( bloomfilter_t *value, const char *path )
return true; return true;
} }
#if !XASH_WIN32
static int ID_ProcessFiles( bloomfilter_t *value, const char *prefix, const char *postfix ) static int ID_ProcessFiles( bloomfilter_t *value, const char *prefix, const char *postfix )
{ {
DIR *dir; DIR *dir;
@@ -365,7 +281,94 @@ static int ID_CheckFiles( bloomfilter_t value, const char *prefix, const char *p
closedir( dir ); closedir( dir );
return count; return count;
} }
#else #endif // XASH_LINUX
#if XASH_POSIX
#define MAX_NETDEVICES 16
static qboolean ID_IsReservedMAC( uint64_t mac )
{
byte first = ( mac >> 40 ) & 0xff;
// no address at all
if( mac == 0 )
return true;
// group and local bits of the first octet, see https://www.rfc-editor.org/rfc/rfc9542#section-2.1.1
if( FBitSet( first, 0x01 ) || FBitSet( first, 0x02 ))
return true;
// IANA OUI, see https://www.iana.org/assignments/ethernet-numbers/ethernet-numbers.xhtml and https://www.rfc-editor.org/rfc/rfc9568#section-7.3
if(( mac >> 24 ) == 0x00005e )
return true;
return false;
}
static void ID_FormatMAC( char *out, size_t size, uint64_t mac )
{
// format exactly like /sys/class/net/ */address contents, so ids generated by older versions stay valid
Q_snprintf( out, size, "%02x:%02x:%02x:%02x:%02x:%02x\n",
(uint)(( mac >> 40 ) & 0xffu ),
(uint)(( mac >> 32 ) & 0xffu ),
(uint)(( mac >> 24 ) & 0xffu ),
(uint)(( mac >> 16 ) & 0xffu ),
(uint)(( mac >> 8 ) & 0xffu ),
(uint)( mac & 0xffu ));
}
static int ID_ProcessNetDevices( bloomfilter_t *value )
{
uint64_t macs[MAX_NETDEVICES];
int total = Posix_GetNetDeviceAddresses( macs, MAX_NETDEVICES );
int count = 0;
for( int i = 0; i < total && BloomFilter_Weight( *value ) < MAXBITS_GEN; i++ )
{
char buf[32];
if( ID_IsReservedMAC( macs[i] ))
continue;
ID_FormatMAC( buf, sizeof( buf ), macs[i] );
if( !ID_VerifyHEX( buf ))
continue;
*value |= BloomFilter_ProcessStr( buf );
count++;
}
return count;
}
static int ID_CheckNetDevices( bloomfilter_t value )
{
uint64_t macs[MAX_NETDEVICES];
int total = Posix_GetNetDeviceAddresses( macs, MAX_NETDEVICES );
int count = 0;
for( int i = 0; i < total; i++ )
{
char buf[32];
if( ID_IsReservedMAC( macs[i] ))
continue;
ID_FormatMAC( buf, sizeof( buf ), macs[i] );
if( !ID_VerifyHEX( buf ))
continue;
bloomfilter_t filter = BloomFilter_ProcessStr( buf );
count += ( value & filter ) == filter;
}
return count;
}
#endif // XASH_POSIX
#if XASH_WIN32
static int ID_GetKeyData( HKEY hRootKey, char *subKey, char *value, LPBYTE data, DWORD cbData ) static int ID_GetKeyData( HKEY hRootKey, char *subKey, char *value, LPBYTE data, DWORD cbData )
{ {
HKEY hKey; HKEY hKey;
@@ -539,6 +542,17 @@ static bloomfilter_t ID_GenerateRawId( void )
count += ID_ProcessCPUInfo( &value ); count += ID_ProcessCPUInfo( &value );
count += ID_ProcessFiles( &value, "/sys/block", "device/cid" ); count += ID_ProcessFiles( &value, "/sys/block", "device/cid" );
#endif
#if XASH_OSX
char buf[64];
if( Apple_GetSerialNumber( buf, sizeof( buf )))
{
value |= BloomFilter_ProcessStr( buf );
count++;
}
#endif
#if XASH_POSIX
count += ID_ProcessNetDevices( &value ); count += ID_ProcessNetDevices( &value );
#endif #endif
#if XASH_WIN32 #if XASH_WIN32
@@ -587,12 +601,26 @@ static uint ID_CheckRawId( bloomfilter_t filter )
} }
#endif // !XASH_ANDROID #endif // !XASH_ANDROID
count += ID_CheckNetDevices( filter );
count += ID_CheckFiles( filter, "/sys/block", "device/cid" ); count += ID_CheckFiles( filter, "/sys/block", "device/cid" );
if( ID_ProcessCPUInfo( &value ) ) if( ID_ProcessCPUInfo( &value ) )
count += (filter & value) == value; count += (filter & value) == value;
#endif #endif
#if XASH_OSX
char buf[64];
if( Apple_GetSerialNumber( buf, sizeof( buf )))
{
value = BloomFilter_ProcessStr( buf );
count += (filter & value) == value;
value = 0;
}
#endif
#if XASH_POSIX
count += ID_CheckNetDevices( filter );
#endif
#if XASH_WIN32 #if XASH_WIN32
count += ID_CheckWMIC( filter, L"wmic path win32_physicalmedia get SerialNumber" ); count += ID_CheckWMIC( filter, L"wmic path win32_physicalmedia get SerialNumber" );
count += ID_CheckWMIC( filter, L"wmic bios get serialnumber" ); count += ID_CheckWMIC( filter, L"wmic bios get serialnumber" );

View File

@@ -469,7 +469,7 @@ void CL_BatchResourceRequest( qboolean initialize )
{ {
if( done_downloading && CL_PrecacheResources( )) if( done_downloading && CL_PrecacheResources( ))
{ {
CL_RegisterResources( &msg, cls.legacymode ); CL_RegisterResources( &msg, cls.net_protocol );
} }
Netchan_CreateFragments( &cls.netchan, &msg ); Netchan_CreateFragments( &cls.netchan, &msg );
@@ -1186,8 +1186,8 @@ void CL_ParseBaseline( sizebuf_t *msg, connprotocol_t proto )
int type = MSG_ReadUBitLong( msg, 2 ); int type = MSG_ReadUBitLong( msg, 2 );
int delta_type; int delta_type;
if( player ) delta_type = DT_ENTITY_STATE_PLAYER_T; if( FBitSet( type, ENTITY_BEAM )) delta_type = DT_CUSTOM_ENTITY_STATE_T;
else if( type != ENTITY_NORMAL ) delta_type = DT_CUSTOM_ENTITY_STATE_T; else if( player ) delta_type = DT_ENTITY_STATE_PLAYER_T;
else delta_type = DT_ENTITY_STATE_T; else delta_type = DT_ENTITY_STATE_T;
Delta_ReadGSFields( msg, delta_type, &ent->prevstate, &ent->baseline, 1.0f ); Delta_ReadGSFields( msg, delta_type, &ent->prevstate, &ent->baseline, 1.0f );

View File

@@ -465,7 +465,7 @@ static void CL_ParseSoundPacketGS( sizebuf_t *msg )
float attn; float attn;
if( FBitSet( flags, SND_ATTENUATION )) if( FBitSet( flags, SND_ATTENUATION ))
attn = (float)MSG_ReadByte( msg ) / 64.0f; attn = (float)MSG_ReadByte( msg ) / 64.0f;
else attn = ATTN_NONE; else attn = 1.0f;
int chan = MSG_ReadUBitLong( msg, 3 ); int chan = MSG_ReadUBitLong( msg, 3 );
int entnum = MSG_ReadUBitLong( msg, MAX_GOLDSRC_ENTITY_BITS ); int entnum = MSG_ReadUBitLong( msg, MAX_GOLDSRC_ENTITY_BITS );

View File

@@ -274,7 +274,7 @@ void FS_FreeStream( stream_t *stream )
stream->format->freefunc( stream ); stream->format->freefunc( stream );
} }
#if XASH_ENGINE_TESTS #if XASH_LLVM_LIBFUZZER
#define IMPLEMENT_SOUNDLIB_FUZZ_TARGET( export, target ) \ #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 ); \
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_LoadMPG, Sound_LoadMPG )
IMPLEMENT_SOUNDLIB_FUZZ_TARGET( Fuzz_Sound_LoadWAV, Sound_LoadWAV ) IMPLEMENT_SOUNDLIB_FUZZ_TARGET( Fuzz_Sound_LoadWAV, Sound_LoadWAV )
#endif #endif // XASH_LLVM_LIBFUZZER

View File

@@ -185,7 +185,7 @@ static qboolean Sound_ParseID3Tag( const byte *buffer, fs_offset_t filesize )
return true; 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 );
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 ); Sound_ParseID3Tag( Data, Size );
return 0; return 0;
} }
#endif #endif // XASH_LLVM_LIBFUZZER
/* /*
================================================================= =================================================================

View File

@@ -701,6 +701,10 @@ void Test_RunImagelib( void )
Z_Free( rgb.buffer ); Z_Free( rgb.buffer );
} }
#endif // XASH_ENGINE_TESTS
#if XASH_LLVM_LIBFUZZER
#include "sprite.h"
#define IMPLEMENT_IMAGELIB_FUZZ_TARGET( export, target ) \ #define IMPLEMENT_IMAGELIB_FUZZ_TARGET( export, target ) \
int EXPORT export( const uint8_t *Data, size_t Size ); \ 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_LoadDDS, Image_LoadDDS )
IMPLEMENT_IMAGELIB_FUZZ_TARGET( Fuzz_Image_LoadTGA, Image_LoadTGA ) 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

View File

@@ -262,10 +262,10 @@ qboolean Image_LoadSPR( const char *name, const byte *buffer, fs_offset_t filesi
image.width = pin.width; image.width = pin.width;
image.height = pin.height; image.height = pin.height;
if( filesize < image.width * image.height ) if( filesize < (fs_offset_t)image.width * image.height )
return false; return false;
if( filesize == ( image.width * image.height * 4 )) if( filesize == (fs_offset_t)image.width * image.height * 4 )
truecolor = true; truecolor = true;
// sorry, can't validate palette rendermode // sorry, can't validate palette rendermode
@@ -731,69 +731,77 @@ Image_SaveWAD
*/ */
qboolean Image_SaveWAD( const char *name, rgbdata_t *pix ) qboolean Image_SaveWAD( const char *name, rgbdata_t *pix )
{ {
byte *mip1_data = NULL, *mip2_data = NULL, *mip3_data = NULL;
byte grad_palette[256 * 3];
file_t *f;
dwadinfo_t header =
{
.ident = IDWAD3HEADER,
.numlumps = 1,
};
mip_t miptex;
long infotableofs;
dlumpinfo_t lump;
fs_offset_t pad;
qboolean result = false;
int lump_type = ( pix->flags & IMAGE_GRADIENT_DECAL ) ? TYP_PALETTE : TYP_MIPTEX;
short palette_size = 256;
int infotableofs32 = 0;
if( !pix || !pix->buffer ) if( !pix || !pix->buffer )
return false; return false;
const byte *palette = pix->palette ? pix->palette : (const byte *)image.palette; const int lump_type = ( pix->flags & IMAGE_GRADIENT_DECAL ) ? TYP_PALETTE : TYP_MIPTEX;
int m0size = pix->width * pix->height; int m0size = pix->width * pix->height;
int m1size = m0size / 4; int m1size = m0size / 4;
int m2size = m0size / 16; int m2size = m0size / 16;
int m3size = m0size / 64; int m3size = m0size / 64;
mip1_data = (byte *)Mem_Malloc( host.imagepool, m1size ); byte *mip1_data = Mem_Malloc( host.imagepool, m1size );
mip2_data = (byte *)Mem_Malloc( host.imagepool, m2size ); byte *mip2_data = Mem_Malloc( host.imagepool, m2size );
mip3_data = (byte *)Mem_Malloc( host.imagepool, m3size ); byte *mip3_data = Mem_Malloc( host.imagepool, m3size );
if( !mip1_data || !mip2_data || !mip3_data ) if( !mip1_data || !mip2_data || !mip3_data ) // Mem_Malloc never returns NULL, but keep this check anyway
goto cleanup; {
Mem_Free( mip1_data );
Mem_Free( mip2_data );
Mem_Free( mip3_data );
return false;
}
Image_GenerateMipmaps( pix->buffer, pix->width, pix->height, mip1_data, mip2_data, mip3_data ); Image_GenerateMipmaps( pix->buffer, pix->width, pix->height, mip1_data, mip2_data, mip3_data );
memset( &miptex, 0, sizeof( mip_t )); file_t *f = FS_Open( name, "wb", false );
Q_strncpy( miptex.name, "{LOGO", sizeof( miptex.name ));
miptex.width = pix->width;
miptex.height = pix->height;
miptex.offsets[0] = sizeof( mip_t );
miptex.offsets[1] = miptex.offsets[0] + m0size;
miptex.offsets[2] = miptex.offsets[1] + m1size;
miptex.offsets[3] = miptex.offsets[2] + m2size;
f = FS_Open( name, "wb", false );
if( !f ) if( !f )
goto cleanup; {
Mem_Free( mip1_data );
Mem_Free( mip2_data );
Mem_Free( mip3_data );
return false;
}
le_struct_swap( dwadinfo_swap, &header ); {
FS_Write( f, &header, sizeof( header )); dwadinfo_t header =
{
.ident = IDWAD3HEADER,
.numlumps = 1,
};
le_struct_swap( dwadinfo_swap, &header );
FS_Write( f, &header, sizeof( header ));
}
mip_t miptex = {
.name = "{LOGO",
.width = pix->width,
.height = pix->height,
.offsets[0] = sizeof( mip_t ),
.offsets[1] = sizeof( mip_t ) + m0size,
.offsets[2] = sizeof( mip_t ) + m0size + m1size,
.offsets[3] = sizeof( mip_t ) + m0size + m1size + m2size,
};
le_struct_swap( mip_swap, &miptex ); le_struct_swap( mip_swap, &miptex );
FS_Write( f, &miptex, sizeof( mip_t )); FS_Write( f, &miptex, sizeof( mip_t ));
le_struct_swap( mip_swap, &miptex ); le_struct_swap( mip_swap, &miptex );
FS_Write( f, pix->buffer, m0size ); FS_Write( f, pix->buffer, m0size );
FS_Write( f, mip1_data, m1size ); FS_Write( f, mip1_data, m1size );
FS_Write( f, mip2_data, m2size ); FS_Write( f, mip2_data, m2size );
FS_Write( f, mip3_data, m3size ); FS_Write( f, mip3_data, m3size );
palette_size = LittleShort( palette_size );
FS_Write( f, &palette_size, sizeof( short )); {
short palette_size = LittleShort( 256 );
FS_Write( f, &palette_size, sizeof( short ));
}
const byte *palette = pix->palette ? pix->palette : (const byte *)image.palette;
if( lump_type == TYP_PALETTE ) if( lump_type == TYP_PALETTE )
{ {
const byte *frontColorPtr = palette + 255 * 3; const byte *frontColorPtr = palette + 255 * 3;
byte grad_palette[256 * 3];
for( int i = 0; i < 256; ++i ) for( int i = 0; i < 256; ++i )
{ {
float t = i / 255.0f; float t = i / 255.0f;
@@ -809,34 +817,37 @@ qboolean Image_SaveWAD( const char *name, rgbdata_t *pix )
} }
// padding up to a multiple of 4 // padding up to a multiple of 4
pad = (( FS_Tell( f ) + 3 ) & ~3 ) - FS_Tell( f ); fs_offset_t pad = (( FS_Tell( f ) + 3 ) & ~3 ) - FS_Tell( f );
for( int i = 0; i < pad; ++i ) for( int i = 0; i < pad; ++i )
FS_Write( f, (const void *)&(char){0}, 1 ); {
byte x = 0;
FS_Write( f, &x, 1 );
}
infotableofs = FS_Tell( f ); fs_offset_t infotableofs = FS_Tell( f );
memset( &lump, 0, sizeof( lump ));
lump.filepos = sizeof( dwadinfo_t ); {
lump.disksize = (int)( miptex.offsets[3] + m3size + sizeof( short ) + 256 * 3 ); dlumpinfo_t lump =
lump.size = lump.disksize; {
lump.type = (char)lump_type; .filepos = sizeof( dwadinfo_t ),
lump.attribs = 0; .disksize = (int)( miptex.offsets[3] + m3size + sizeof( short ) + 256 * 3 ),
Q_strncpy( lump.name, "tempdecal", sizeof( lump.name )); .size = (int)( miptex.offsets[3] + m3size + sizeof( short ) + 256 * 3 ),
le_struct_swap( dlumpinfo_swap, &lump ); .type = (char)lump_type,
FS_Write( f, &lump, sizeof( lump )); .attribs = 0,
.name = "tempdecal",
};
le_struct_swap( dlumpinfo_swap, &lump );
FS_Write( f, &lump, sizeof( lump ));
}
FS_Seek( f, offsetof( dwadinfo_t, infotableofs ), SEEK_SET ); FS_Seek( f, offsetof( dwadinfo_t, infotableofs ), SEEK_SET );
infotableofs32 = LittleLong((int)infotableofs ); int infotableofs32 = LittleLong((int)infotableofs );
FS_Write( f, &infotableofs32, sizeof( int )); FS_Write( f, &infotableofs32, sizeof( int ));
FS_Close( f ); FS_Close( f );
result = true;
cleanup: Mem_Free( mip1_data );
if( mip1_data ) Mem_Free( mip2_data );
Mem_Free( mip1_data ); Mem_Free( mip3_data );
if( mip2_data ) return true;
Mem_Free( mip2_data );
if( mip3_data )
Mem_Free( mip3_data );
return result;
} }

View File

@@ -225,33 +225,33 @@ void NET_QueryServerByAddress( netadr_t adr, connprotocol_t proto )
Netchan_OutOfBandPrint( NS_CLIENT, adr, A2A_INFO " %i", PROTOCOL_VERSION ); Netchan_OutOfBandPrint( NS_CLIENT, adr, A2A_INFO " %i", PROTOCOL_VERSION );
} }
static int NET_ParseMasterStaticBody( char *body ) static int NET_ParseMasterStaticBody( const byte *body, size_t size )
{ {
char token[MAX_TOKEN]; char line[1024];
char *pfile = body; int offset = 0;
int count = 0; int count = 0;
while(( pfile = COM_ParseFileSafe( pfile, token, sizeof( token ), PFILE_HASH_AS_COMMENT, NULL, NULL ))) while( Q_memfgets( (byte *)body, size, &offset, line, sizeof( line )) != NULL )
{ {
char token[MAX_TOKEN];
char *pfile = line;
qboolean gs; 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" )) if( !Q_strcmp( token, "ip" ))
gs = false; gs = false;
else if( !Q_strcmp( token, "gs" )) else if( !Q_strcmp( token, "gs" ))
gs = true; gs = true;
else else
{
pfile = COM_ParseFileSafe( pfile, token, sizeof( token ), PFILE_HASH_AS_COMMENT, NULL, NULL );
if( !pfile )
break;
continue; continue;
}
pfile = COM_ParseFileSafe( pfile, token, sizeof( token ), PFILE_HASH_AS_COMMENT, NULL, NULL ); pfile = COM_ParseFileSafe( pfile, token, sizeof( token ), PFILE_HASH_AS_COMMENT, NULL, NULL );
if( !pfile ) if( !pfile )
break; continue;
netadr_t adr = { 0 };
if( !NET_StringToAdr( token, &adr )) if( !NET_StringToAdr( token, &adr ))
{ {
@@ -288,16 +288,9 @@ static void NET_MasterStaticResponse( const char *url, qboolean success, const b
return; return;
} }
// HTTP buffer isn't NUL-terminated; COM_ParseFileSafe needs one.
char *body = Mem_Malloc( host.mempool, size + 1 );
memcpy( body, data, size );
body[size] = 0;
NET_Config( true, false ); // allow remote sends NET_Config( true, false ); // allow remote sends
int count = NET_ParseMasterStaticBody( body ); int count = NET_ParseMasterStaticBody( data, size );
Mem_Free( body );
Con_Reportf( "masterstatic: %s yielded %d server(s)\n", url, count ); Con_Reportf( "masterstatic: %s yielded %d server(s)\n", url, count );
@@ -516,7 +509,7 @@ static master_t *NET_AddMaster( const char *addr )
static void NET_AddMaster_f( void ) static void NET_AddMaster_f( void )
{ {
if( Cmd_Argc() != 2 ) if( Cmd_Argc() != 2 && Cmd_Argc() != 3 )
{ {
Msg( S_USAGE "addmaster <address> [gs]\n"); Msg( S_USAGE "addmaster <address> [gs]\n");
return; return;

View File

@@ -62,14 +62,19 @@ static byte *Mod_SwapSpriteFrame( byte *p, byte *end, int bytes )
p += sizeof( frame ); p += sizeof( frame );
// skip pixel data // skip pixel data
if( p + frame.width * frame.height * bytes > end ) if( frame.width < 0 || frame.height < 0 )
return NULL; 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; 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; dspritegroup_t group;
@@ -84,7 +89,7 @@ static byte *Mod_SwapSpriteGroup( byte *p, byte *end, int bytes )
// swap intervals // swap intervals
int numframes = group.numframes; 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; return NULL;
for( int i = 0; i < numframes; i++ ) 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 numi = LittleShort( *(short *)p );
*(short *)p = numi; *(short *)p = numi;
p += sizeof( short ) + numi * 3; p += sizeof( short );
if( numi < 0 || p + numi * 3 > end )
return false;
p += numi * 3;
break; break;
} }
default: default:
return false; return false;
} }
if( numframes < 0 )
return false;
*out_version = version; *out_version = version;
int bytes = ( version == SPRITE_VERSION_32 ) ? 4 : 1; int bytes = ( version == SPRITE_VERSION_32 ) ? 4 : 1;
// swap all frames // swap all frames
for( int i = 0; i < numframes && p && p < end; i++ ) for( int i = 0; i < numframes; i++ )
{ {
dframetype_t frametype; 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 ); p = Mod_SwapSpriteFrame( p, end, bytes );
break; break;
case FRAME_GROUP: case FRAME_GROUP:
p = Mod_SwapSpriteGroup( p, end, bytes, 1 );
break;
case FRAME_ANGLED: 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; break;
default: default:
return false; return false;
} }
if( !p )
return false;
} }
return true; return true;
@@ -229,17 +248,23 @@ void Mod_LoadSpriteModel( model_t *mod, void *buffer, size_t buffersize, qboolea
psprite->texFormat = SPR_ADDITIVE; psprite->texFormat = SPR_ADDITIVE;
psprite->numframes = mod->numframes = pinq1->numframes; psprite->numframes = mod->numframes = pinq1->numframes;
psprite->facecull = SPR_CULL_FRONT; 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; psprite->synctype = pinq1->synctype;
// LadyHavoc: hack to allow sprites to be non-fullbright // LadyHavoc: hack to allow sprites to be non-fullbright
if( Q_strchr( mod->name, '!' )) if( Q_strchr( mod->name, '!' ))
psprite->texFormat = SPR_ALPHTEST; 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->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->maxs[2] = pinq1->bounds[1] * 0.5f;
mod->mins[2] = -mod->maxs[2];
} }
else // if( version == SPRITE_VERSION_HL ) 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->radius = pinhl->boundingradius;
psprite->synctype = pinhl->synctype; 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->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->maxs[2] = pinhl->bounds[1] * 0.5f;
mod->mins[2] = -mod->maxs[2];
} }
if( loaded ) if( loaded )
@@ -283,3 +308,34 @@ void Mod_LoadSpriteModel( model_t *mod, void *buffer, size_t buffersize, qboolea
Mod_SpriteLoadTextures( mod, buffer ); Mod_SpriteLoadTextures( mod, buffer );
#endif #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

View File

@@ -653,7 +653,7 @@ void Mod_NeedCRC( const char *name, qboolean needCRC )
else ClearBits( p->flags, FCRC_SHOULD_CHECKSUM ); else ClearBits( p->flags, FCRC_SHOULD_CHECKSUM );
} }
#if XASH_ENGINE_TESTS #if XASH_LLVM_LIBFUZZER
static const uint8_t *fuzz_data; static const uint8_t *fuzz_data;
static size_t fuzz_size; static size_t fuzz_size;
@@ -689,4 +689,4 @@ int EXPORT Fuzz_Mod_LoadModel( const uint8_t *Data, size_t Size )
return 0; return 0;
} }
#endif // XASH_ENGINE_TESTS #endif // XASH_LLVM_LIBFUZZER

View File

@@ -1364,9 +1364,14 @@ static qboolean NET_QueuePacket( int net_socket, netsrc_t sock, netadr_t *from,
// check for split message // check for split message
if( sock == NS_CLIENT && *(int *)data == NET_HEADER_SPLITPACKET ) if( sock == NS_CLIENT && *(int *)data == NET_HEADER_SPLITPACKET )
{ {
if( !CL_IsFromConnectingServer( *from )) if( CL_IsFromConnectingServer( *from ))
return false; return NET_GetLong( data, ret, length, CL_GetSplitSize( ), CL_Protocol( ));
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 #endif

View File

@@ -89,6 +89,7 @@ void NET_GetLocalAddress( netadr_t *ip4, netadr_t *ip6 );
#if !XASH_DEDICATED #if !XASH_DEDICATED
size_t CL_GetSplitSize( void ); size_t CL_GetSplitSize( void );
qboolean CL_IsFromConnectingServer( netadr_t from ); qboolean CL_IsFromConnectingServer( netadr_t from );
qboolean CL_HasActiveNetRequest( netadr_t from );
#endif #endif
void HTTP_AddCustomServer( const char *url ); void HTTP_AddCustomServer( const char *url );

View File

@@ -319,7 +319,6 @@ extern const char *const svc_goldsrc_strings[svc_lastmsg+1];
#define A2A_PING "ping" // reply with A2A_ACK #define A2A_PING "ping" // reply with A2A_ACK
#define A2A_ACK "ack" // no-op #define A2A_ACK "ack" // no-op
#define A2A_INFO "info" // different format for client and server, see code #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_PING "i" // reply with A2A_GOLDSRC_ACK
#define A2A_GOLDSRC_ACK "j" // no-op #define A2A_GOLDSRC_ACK "j" // no-op

View File

@@ -0,0 +1,50 @@
/*
id_apple.c - macOS hardware serial id source
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 "platform/platform.h"
#include <AvailabilityMacros.h>
#include <CoreFoundation/CoreFoundation.h>
#include <IOKit/IOKitLib.h>
// kIOMasterPortDefault was renamed to kIOMainPortDefault in macOS 12 SDK
#if MAC_OS_X_VERSION_MIN_REQUIRED < 120000
#define kIOMainPortDefault kIOMasterPortDefault
#endif
static qboolean Apple_GetPlatformExpertProperty( CFStringRef key, char *out, size_t size )
{
io_service_t service = IOServiceGetMatchingService( kIOMainPortDefault, IOServiceMatching( "IOPlatformExpertDevice" ));
if( !service )
return false;
qboolean ret = false;
CFTypeRef prop = IORegistryEntryCreateCFProperty( service, key, kCFAllocatorDefault, 0 );
if( prop )
{
if( CFGetTypeID( prop ) == CFStringGetTypeID() && CFStringGetCString( (CFStringRef)prop, out, size, kCFStringEncodingUTF8 ))
ret = true;
CFRelease( prop );
}
IOObjectRelease( service );
return ret;
}
qboolean Apple_GetSerialNumber( char *out, size_t size )
{
return Apple_GetPlatformExpertProperty( CFSTR( "IOPlatformSerialNumber" ), out, size );
}

View File

@@ -13,41 +13,22 @@
GNU General Public License for more details. GNU General Public License for more details.
*/ */
#include "SDL_syswm.h"
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h> #import <AVFoundation/AVFoundation.h>
#include <sys/stat.h>
#include "dlfcn.h"
#define XASHLIB "@rpath/libxash.dylib" #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; int szArgc;
char **szArgv; char **szArgv;
char *g_szLibrarySuffix;
float g_iOSVer; 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; static const char *dir = NULL;
if( dir ) if( dir )
@@ -61,23 +42,9 @@ const char *IOS_GetDocsDir(void)
NSLog(@"IOS_GetDocsDir: %s", dir); NSLog(@"IOS_GetDocsDir: %s", dir);
return 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; static const char *dir = NULL;
@@ -87,7 +54,7 @@ const char *IOS_GetExecDir(void)
NSString *executableDirctory = [[NSBundle mainBundle] bundlePath]; NSString *executableDirctory = [[NSBundle mainBundle] bundlePath];
dir = [executableDirctory fileSystemRepresentation]; dir = [executableDirctory fileSystemRepresentation];
NSLog(@"IOS_GetDocsDir: %s", dir); NSLog(@"IOS_GetExecDir: %s", dir);
return dir; return dir;
} }
@@ -99,43 +66,44 @@ typedef struct settings_s
{ {
unsigned char magic; unsigned char magic;
char args[1024]; char args[1024];
unsigned int port;
char suffix[32];
unsigned int ftpserver;
} settings_t; } 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]]; UIWindow *window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
UIViewController *controller = [[UIViewController alloc] init]; controller = [[UIViewController alloc] init];
[[controller view] setBackgroundColor:[UIColor grayColor]]; [controller.view setBackgroundColor:[UIColor grayColor]];
[window setRootViewController:controller]; [window setRootViewController:controller];
[window makeKeyAndVisible]; [window makeKeyAndVisible];
if([[controller traitCollection] userInterfaceStyle] == UIUserInterfaceStyleDark && g_iOSVer >= 13.0) isdark = true; else isdark = false; alert = [UIAlertController alertControllerWithTitle:@"Xash3D FWGS"
#if 0 message:nil
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(10, 10, 100, 20)]; preferredStyle:UIAlertControllerStyleAlert];
int button1 = -1;
handler.button1 = &button1;
[button addTarget:handler action:@selector(buttonClicked:) forControlEvents:UIControlEventValueChanged]; UIAlertAction* startAction = [UIAlertAction actionWithTitle:@"Start" style:UIAlertActionStyleDefault
@autoreleasepool { handler:^(UIAlertAction *action) { finishedDialog = true; }];
while( button1 == -1 ) { UIAlertAction* exitAction = [UIAlertAction actionWithTitle:@"Exit" style:UIAlertActionStyleDefault
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; handler:^(UIAlertAction *action) {
} SaveSettings();
} NSLog(@"Exit Selected\n");
[[controller view] addSubview:button]; exit(0); }];
#endif [alert addAction:startAction];
[alert addAction:exitAction];
} }
void IOS_LaunchDialog( void ) void IOS_LaunchDialog( void )
@@ -148,91 +116,48 @@ void IOS_LaunchDialog( void )
[[AVAudioSession sharedInstance] requestRecordPermission:^(BOOL granted){}]; [[AVAudioSession sharedInstance] requestRecordPermission:^(BOOL granted){}];
IOS_PrepareView(); 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(); const char *docsDir = IOS_GetDocsDir();
//set working directory to documents so logs can be generated there //set working directory to documents so logs can be generated there
NSString *workingDir = [NSString stringWithUTF8String:IOS_GetDocsDir()]; [[NSFileManager defaultManager]
[[NSFileManager defaultManager] changeCurrentDirectoryPath:workingDir]; changeCurrentDirectoryPath:[NSString stringWithUTF8String:IOS_GetDocsDir()]];
FILE *settingsfile; snprintf( settingspath, sizeof( settingspath ), "%s/settings.bin", docsDir );
char settingspath[256];
snprintf(settingspath, sizeof(settingspath), "%s/settings.bin", docsDir );
settingspath[255] = 0; 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" ); 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; ret = fread( &settings, sizeof( settings ), 1, settingsfile ) == 1;
settings.suffix[31] = 0;
[args setText:@(settings.args)];
[suffix setText:@(settings.suffix)];
}
else
{
[args setText:@"-dev 2 -log"];
} }
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 { @autoreleasepool {
while( button == -1 ) { while( !finishedDialog ) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
} }
} }
if( (settingsfile = fopen( settingspath, "wb" )) ) NSArray *argv = [alert.textFields.firstObject.text componentsSeparatedByString:@" "];
{
strlcpy(settings.args, [args.text UTF8String], 1024);
strlcpy(settings.suffix, [suffix.text UTF8String], 32 );
settings.magic = 111;
fwrite(&settings, sizeof(settings), 1, settingsfile); SaveSettings();
fclose(settingsfile);
}
if( button == bExit )
{
printf("Exit selected\n");
exit(0);
}
NSArray *argv = [ args.text componentsSeparatedByString:@" " ];
int count = [argv count]; int count = [argv count];
szArgv = calloc( count + 2, sizeof( char* ) ); szArgv = calloc( count + 2, sizeof( char* ) );
@@ -244,16 +169,6 @@ void IOS_LaunchDialog( void )
szArgc = count + 1; szArgc = count + 1;
szArgv[count + 1] = 0; 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]; [alert release];
} }
@@ -266,7 +181,7 @@ char *IOS_GetUDID( void )
return udid; return udid;
} }
void IOS_Log(const char *text) void IOS_Log( const char *text )
{ {
NSLog(@"Xash: %@", [NSString stringWithUTF8String:text]); NSLog(@"Xash: %@", [NSString stringWithUTF8String:text]);
} }

View File

@@ -69,6 +69,12 @@ void IOS_LaunchDialog( void );
void Posix_Daemonize( void ); void Posix_Daemonize( void );
void Posix_SetupSigtermHandling( void ); void Posix_SetupSigtermHandling( void );
char *Posix_Input( void ); char *Posix_Input( void );
// returns the number of stable network device MAC addresses, each packed into low 48 bits
int Posix_GetNetDeviceAddresses( uint64_t *addresses, int max );
#endif
#if XASH_OSX
qboolean Apple_GetSerialNumber( char *out, size_t size );
#endif #endif
#if XASH_SDL #if XASH_SDL

View File

@@ -0,0 +1,141 @@
/*
id_posix.c - network device enumeration for unique id generation
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 "platform/platform.h"
#if XASH_LINUX
#include <dirent.h>
#include <fcntl.h>
#include <unistd.h>
static qboolean Posix_ValidateNetDevice( const char *dev )
{
const char *prefix = "/sys/class/net";
// These devices are fake, their mac address is generated each boot, while assign_type is 0
if( !Q_strnicmp( dev, "ccmni", sizeof( "ccmni" ) - 1 ) || !Q_strnicmp( dev, "ifb", sizeof( "ifb" ) - 1 ))
return false;
byte *pfile = FS_LoadDirectFile( va( "%s/%s/addr_assign_type", prefix, dev ), NULL );
// if NULL, it may be old kernel
if( pfile )
{
int assignType = Q_atoi( (char*)pfile );
Mem_Free( pfile );
// check is MAC address is constant
if( assignType != 0 )
return false;
}
return true;
}
int Posix_GetNetDeviceAddresses( uint64_t *addresses, int max )
{
const char *prefix = "/sys/class/net";
DIR *dir = opendir( prefix );
struct dirent *entry;
int count = 0;
if( !dir )
return 0;
while(( entry = readdir( dir )) && count < max )
{
if( !Q_strcmp( entry->d_name, "." ) || !Q_strcmp( entry->d_name, ".." ))
continue;
if( !Posix_ValidateNetDevice( entry->d_name ))
continue;
int fd = open( va( "%s/%s/address", prefix, entry->d_name ), O_RDONLY );
if( fd < 0 )
continue;
char buffer[64];
int ret = read( fd, buffer, sizeof( buffer ) - 1 );
close( fd );
if( ret <= 0 )
continue;
buffer[ret] = 0;
uint mac[6];
char term = 0;
if( sscanf( buffer, "%02x:%02x:%02x:%02x:%02x:%02x%c", &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5], &term ) < 6 )
continue;
// skip malformed or long address devices, we only need MAC addresses and they're always 48-bit
if( term != '\n' && term != '\0' )
continue;
addresses[count] = ((uint64_t)mac[0] << 40) | ((uint64_t)mac[1] << 32) | ((uint64_t)mac[2] << 24) | ((uint64_t)mac[3] << 16) | ((uint64_t)mac[4] << 8) | (uint64_t)mac[5];
count++;
}
closedir( dir );
return count;
}
#elif XASH_APPLE || XASH_FREEBSD || XASH_NETBSD || XASH_OPENBSD
#include <sys/types.h>
#include <sys/socket.h>
#include <ifaddrs.h>
#include <net/if.h>
#include <net/if_dl.h>
int Posix_GetNetDeviceAddresses( uint64_t *addresses, int max )
{
struct ifaddrs *ifaddr;
int count = 0;
if( getifaddrs( &ifaddr ) < 0 )
return 0;
for( struct ifaddrs *ifa = ifaddr; ifa != NULL && count < max; ifa = ifa->ifa_next )
{
if( !ifa->ifa_addr || ifa->ifa_addr->sa_family != AF_LINK )
continue;
if( FBitSet( ifa->ifa_flags, IFF_LOOPBACK | IFF_POINTOPOINT ))
continue;
const struct sockaddr_dl *sdl = (const struct sockaddr_dl *)ifa->ifa_addr;
if( sdl->sdl_alen != 6 )
continue;
const byte *mac = (const byte *)LLADDR( sdl );
addresses[count] = ((uint64_t)mac[0] << 40) | ((uint64_t)mac[1] << 32) | ((uint64_t)mac[2] << 24) | ((uint64_t)mac[3] << 16) | ((uint64_t)mac[4] << 8) | (uint64_t)mac[5];
count++;
}
freeifaddrs( ifaddr );
return count;
}
#else
int Posix_GetNetDeviceAddresses( uint64_t *addresses, int max )
{
Con_Reportf( S_WARN "%s: implement me!\n", __func__ );
return 0;
}
#endif

View File

@@ -28,4 +28,12 @@ GNU General Public License for more details.
void SDLash_InitCursors( void ); void SDLash_InitCursors( void );
void SDLash_FreeCursors( 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 #endif // PLATFORM_SDL3_H

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

View File

@@ -38,6 +38,7 @@ void Platform_Sleep( int msec )
{ {
SDL_Delay( msec ); SDL_Delay( msec );
} }
#endif // XASH_TIMER == TIMER_SDL #endif // XASH_TIMER == TIMER_SDL
#if XASH_MESSAGEBOX == MSGBOX_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 ); SDL_ShowSimpleMessageBox( SDL_MESSAGEBOX_ERROR, title, message, parentMainWindow ? host.hWnd : NULL );
} }
#endif #endif
void SDLash_NanoSleep( int nsec ) 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! // TODO: initial state, to be filled from gameinfo!
SDL_SetAppMetadata( XASH_ENGINE_NAME, XASH_VERSION, "su.xash.engine" ); SDL_SetAppMetadata( XASH_ENGINE_NAME, XASH_VERSION, "su.xash.engine" );
SDL_SetAppMetadataProperty( SDL_PROP_APP_METADATA_TYPE_STRING, "game" ); SDL_SetAppMetadataProperty( SDL_PROP_APP_METADATA_TYPE_STRING, "game" );

View File

@@ -19,6 +19,7 @@ GNU General Public License for more details.
#include "ref_common.h" #include "ref_common.h"
#include "xash3d_mathlib.h" #include "xash3d_mathlib.h"
#include "common.h" #include "common.h"
#include "wrect.h"
#include <SDL3/SDL_system.h> #include <SDL3/SDL_system.h>
static struct 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 ) static void VID_SaveWindowSize( SDL_Window *hWnd, int width, int height )
{ {
qboolean maximized = FBitSet( SDL_GetWindowFlags( hWnd ), SDL_WINDOW_MAXIMIZED ); qboolean maximized = FBitSet( SDL_GetWindowFlags( hWnd ), SDL_WINDOW_MAXIMIZED );
int render_w; int render_w;
int render_h; int render_h;
if( !SDL_GetWindowSizeInPixels( hWnd, &render_w, &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 ) 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 }; 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 ) if( !glw_state.software )
SetBits( flags, SDL_WINDOW_OPENGL ); 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 // probe true fullscreen first, if it fails, try borderless next
if( window_mode == WINDOW_MODE_FULLSCREEN ) if( window_mode == WINDOW_MODE_FULLSCREEN )
{ {
SDL_DisplayID display = SDL_GetPrimaryDisplay(); SDL_DisplayID display = SDL_GetPrimaryDisplay();
SDL_DisplayMode dm; SDL_DisplayMode dm;
if( SDL_GetClosestFullscreenDisplayMode( display, rect.w, rect.h, 0.0f, true, &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; 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 ) static void R_InitVideoModes( void )
{ {
SDL_DisplayID display = SDL_GetDisplayForWindow( host.hWnd ); SDL_DisplayID display = SDL_GetDisplayForWindow( host.hWnd );
int count; int count;
SDL_DisplayMode **modes = SDL_GetFullscreenDisplayModes( display, &count ); SDL_DisplayMode **modes = SDL_GetFullscreenDisplayModes( display, &count );
@@ -257,6 +293,32 @@ void Platform_Minimize_f( void )
SDL_MinimizeWindow( host.hWnd ); 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 ) void GL_UpdateSwapInterval( void )
{ {
if( FBitSet( gl_vsync.flags, FCVAR_CHANGED )) if( FBitSet( gl_vsync.flags, FCVAR_CHANGED ))
@@ -307,6 +369,7 @@ qboolean R_Init_Video( ref_graphic_apis_t type )
switch( type ) switch( type )
{ {
case REF_SOFTWARE: case REF_SOFTWARE:
case REF_D3D:
break; break;
case REF_GL: case REF_GL:
ref.dllFuncs.GL_InitExtensions(); ref.dllFuncs.GL_InitExtensions();
@@ -341,26 +404,26 @@ int GL_SetAttribute( int attr, int val )
{ {
switch( attr ) switch( attr )
{ {
#define MAP_REF_API_ATTRIBUTE_TO_SDL( name ) case REF_##name: return SDL_GL_SetAttribute( SDL_##name, val ); #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_RED_SIZE );
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_GREEN_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_BLUE_SIZE );
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_ALPHA_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_DOUBLEBUFFER );
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_DEPTH_SIZE ); 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_STENCIL_SIZE );
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_MULTISAMPLEBUFFERS ); MAP_REF_API_ATTRIBUTE_TO_SDL( GL_MULTISAMPLEBUFFERS );
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_MULTISAMPLESAMPLES ); MAP_REF_API_ATTRIBUTE_TO_SDL( GL_MULTISAMPLESAMPLES );
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_ACCELERATED_VISUAL ); 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_MAJOR_VERSION );
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_MINOR_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_CONTEXT_FLAGS );
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_SHARE_WITH_CURRENT_CONTEXT ); 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_FRAMEBUFFER_SRGB_CAPABLE );
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_PROFILE_MASK ); 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_RELEASE_BEHAVIOR );
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_RESET_NOTIFICATION ); MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_RESET_NOTIFICATION );
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_NO_ERROR ); MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_NO_ERROR );
#undef MAP_REF_API_ATTRIBUTE_TO_SDL #undef MAP_REF_API_ATTRIBUTE_TO_SDL
} }
@@ -371,26 +434,26 @@ int GL_GetAttribute( int attr, int *val )
{ {
switch( attr ) switch( attr )
{ {
#define MAP_REF_API_ATTRIBUTE_TO_SDL( name ) case REF_##name: return SDL_GL_GetAttribute( SDL_##name, val ); #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_RED_SIZE );
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_GREEN_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_BLUE_SIZE );
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_ALPHA_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_DOUBLEBUFFER );
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_DEPTH_SIZE ); 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_STENCIL_SIZE );
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_MULTISAMPLEBUFFERS ); MAP_REF_API_ATTRIBUTE_TO_SDL( GL_MULTISAMPLEBUFFERS );
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_MULTISAMPLESAMPLES ); MAP_REF_API_ATTRIBUTE_TO_SDL( GL_MULTISAMPLESAMPLES );
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_ACCELERATED_VISUAL ); 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_MAJOR_VERSION );
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_MINOR_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_CONTEXT_FLAGS );
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_SHARE_WITH_CURRENT_CONTEXT ); 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_FRAMEBUFFER_SRGB_CAPABLE );
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_PROFILE_MASK ); 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_RELEASE_BEHAVIOR );
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_RESET_NOTIFICATION ); MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_RESET_NOTIFICATION );
MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_NO_ERROR ); MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_NO_ERROR );
#undef MAP_REF_API_ATTRIBUTE_TO_SDL #undef MAP_REF_API_ATTRIBUTE_TO_SDL
} }
@@ -550,6 +613,9 @@ qboolean VID_SetMode( void )
case rserr_invalid_mode: case rserr_invalid_mode:
Sys_Warn( "%s: invalid mode, engine will run in %dx%d", __func__, vid_state.prev_width, vid_state.prev_height ); Sys_Warn( "%s: invalid mode, engine will run in %dx%d", __func__, vid_state.prev_width, vid_state.prev_height );
break; 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 // revert to last successful mode
@@ -563,8 +629,169 @@ qboolean VID_SetMode( void )
return true; 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 R_ChangeDisplaySettings( int width, int height, window_mode_t window_mode )
{ {
rserr_t err;
if( !host.hWnd ) if( !host.hWnd )
{ {
// create window if not exists // create window if not exists
@@ -573,12 +800,13 @@ rserr_t R_ChangeDisplaySettings( int width, int height, window_mode_t window_mod
} }
else else
{ {
// TODO: change current window mode err = VID_SetScreenResolution( width, height, window_mode, refState.window_mode );
return rserr_invalid_fullscreen; if( err != rserr_ok )
return err;
} }
refState.desktopBitsPixel = 24; // TODO: figure this out
refState.window_mode = window_mode; refState.window_mode = window_mode;
refState.desktopBitsPixel = 24;
return rserr_ok; return rserr_ok;
} }

View File

@@ -542,6 +542,8 @@ void SV_UpdateServerInfo( void );
void SV_EndRedirect( host_redirect_t *rd ); void SV_EndRedirect( host_redirect_t *rd );
void SV_RejectConnection( netadr_t from, const char *fmt, ... ) FORMAT_CHECK( 2 ); void SV_RejectConnection( netadr_t from, const char *fmt, ... ) FORMAT_CHECK( 2 );
void SV_GetPlayerCount( int *clients, int *bots ); 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 ) static inline qboolean SV_HavePassword( void )
{ {
@@ -701,6 +703,25 @@ int SV_LightForEntity( edict_t *pEdict );
// //
// sv_query.c // 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 );
static inline qboolean SV_CheckGroupOp( int op, int groupinfo, int mask )
{
if( op == GROUP_OP_AND && !FBitSet( groupinfo, mask ))
return false;
if( op == GROUP_OP_NAND && FBitSet( groupinfo, mask ))
return false;
return true;
}
static inline qboolean SV_CheckGroupTrace( const edict_t *e1, const edict_t *e2 )
{
if( e1->v.groupinfo && e2->v.groupinfo )
return SV_CheckGroupOp( svs.groupop, e1->v.groupinfo, e2->v.groupinfo );
return true;
}
#endif//SERVER_H #endif//SERVER_H

View File

@@ -18,7 +18,6 @@ GNU General Public License for more details.
#include "const.h" #include "const.h"
#include "server.h" #include "server.h"
#include "net_encode.h" #include "net_encode.h"
#include "net_api.h"
// challenges are valid for two consecutive windows of this size (max lifetime ~10s). // challenges are valid for two consecutive windows of this size (max lifetime ~10s).
#define CHALLENGE_WINDOW_SECONDS 5 #define CHALLENGE_WINDOW_SECONDS 5
@@ -110,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; 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; qboolean error = false;
uint32_t time_window = (uint32_t)( host.realtime / CHALLENGE_WINDOW_SECONDS ); 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 ) if( error )
return; return;
@@ -214,15 +245,7 @@ Make sure connecting client is not spoofing
*/ */
static int SV_CheckChallenge( netadr_t from, int challenge ) static int SV_CheckChallenge( netadr_t from, int challenge )
{ {
qboolean error = false; if( SV_ValidateChallenge( from, challenge ))
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 )
return true; return true;
SV_RejectConnection( from, "no challenge for your address\n" ); SV_RejectConnection( from, "no challenge for your address\n" );
@@ -466,6 +489,7 @@ static void SV_ConnectClient( netadr_t from )
protinfo[0] = '\0'; protinfo[0] = '\0';
Info_SetValueForKeyf( protinfo, "ext", sizeof( protinfo ), "%d", newcl->extensions ); Info_SetValueForKeyf( protinfo, "ext", sizeof( protinfo ), "%d", newcl->extensions );
Info_SetValueForKey( protinfo, "cheats", sv_cheats.value ? "1" : "0", sizeof( protinfo )); 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 )) if( FBitSet( newcl->extensions, NET_EXT_NETCHAN_COOKIE ))
Info_SetValueForKeyf( protinfo, "cookie", sizeof( protinfo ), "%016"PRIx64, netchan_cookie ); Info_SetValueForKeyf( protinfo, "cookie", sizeof( protinfo ), "%016"PRIx64, netchan_cookie );
@@ -951,111 +975,6 @@ static void SV_ConnectNatClient( netadr_t from )
SV_Info( to, PROTOCOL_VERSION ); 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 Rcon_Validate
@@ -3243,11 +3162,7 @@ void SV_ConnectionlessPacket( netadr_t from, sizebuf_t *msg )
// `pcmd` points only to the first word from the query string. // `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 ) if( !Q_strcmp( args, A2S_GOLDSRC_INFO ) || pcmd[0] == A2S_GOLDSRC_PLAYERS || pcmd[0] == A2S_GOLDSRC_RULES )
{ {
SV_SourceQuery_HandleConnnectionlessPacket( args, from ); SV_SourceQuery_HandleConnnectionlessPacket( args, from, msg );
}
else if( !Q_strcmp( pcmd, A2A_NETINFO ))
{
SV_BuildNetAnswer( from );
} }
else if( !Q_strcmp( pcmd, A2A_INFO )) else if( !Q_strcmp( pcmd, A2A_INFO ))
{ {

View File

@@ -435,14 +435,8 @@ static int SV_Multicast( int dest, const vec3_t origin, const edict_t *ent, qboo
if( filter && cl == sv.current_client && FBitSet( sv.current_client->flags, FCL_PREDICT_MOVEMENT )) if( filter && cl == sv.current_client && FBitSet( sv.current_client->flags, FCL_PREDICT_MOVEMENT ))
continue; continue;
if( SV_IsValidEdict( ent ) && ent->v.groupinfo && cl->edict->v.groupinfo ) if( SV_IsValidEdict( ent ) && !SV_CheckGroupTrace( ent, cl->edict ))
{ continue;
if( svs.groupop == GROUP_OP_AND && !FBitSet( cl->edict->v.groupinfo, ent->v.groupinfo ))
continue;
if( svs.groupop == GROUP_OP_NAND && FBitSet( cl->edict->v.groupinfo, ent->v.groupinfo ))
continue;
}
if( !SV_CheckClientVisiblity( cl, mask )) if( !SV_CheckClientVisiblity( cl, mask ))
continue; continue;
@@ -4123,14 +4117,8 @@ void GAME_EXPORT SV_PlaybackEventFull( int flags, const edict_t *pInvoker, word
if( cl->state != cs_spawned || !cl->edict || FBitSet( cl->flags, FCL_FAKECLIENT )) if( cl->state != cs_spawned || !cl->edict || FBitSet( cl->flags, FCL_FAKECLIENT ))
continue; continue;
if( SV_IsValidEdict( pInvoker ) && pInvoker->v.groupinfo && cl->edict->v.groupinfo ) if( SV_IsValidEdict( pInvoker ) && !SV_CheckGroupTrace( pInvoker, cl->edict ))
{ continue;
if( svs.groupop == GROUP_OP_AND && !FBitSet( cl->edict->v.groupinfo, pInvoker->v.groupinfo ))
continue;
if( svs.groupop == GROUP_OP_NAND && FBitSet( cl->edict->v.groupinfo, pInvoker->v.groupinfo ))
continue;
}
if( SV_IsValidEdict( pInvoker )) if( SV_IsValidEdict( pInvoker ))
{ {

View File

@@ -60,6 +60,47 @@ Utility functions
=============================================================================== ===============================================================================
*/ */
/*
================
SV_CheckVelocity
================
*/
void SV_CheckVelocity( edict_t *ent )
{
float wishspd;
float maxspd;
// bound velocity
for( int i = 0; i < 3; i++ )
{
if( IS_NAN( ent->v.velocity[i] ))
{
if( sv_check_errors.value )
Con_Printf( "Got a NaN velocity on %s\n", SV_GetString( ent->v.classname ));
ent->v.velocity[i] = 0.0f;
}
if( IS_NAN( ent->v.origin[i] ))
{
if( sv_check_errors.value )
Con_Printf( "Got a NaN origin on %s\n", SV_GetString( ent->v.classname ));
ent->v.origin[i] = 0.0f;
}
}
wishspd = DotProduct( ent->v.velocity, ent->v.velocity );
maxspd = sv_maxvelocity.value * sv_maxvelocity.value * 1.73f; // half-diagonal
if( wishspd > maxspd )
{
wishspd = sqrt( wishspd );
if( sv_check_errors.value )
Con_Printf( "Got a velocity too high on %s ( %.2f > %.2f )\n", SV_GetString( ent->v.classname ), wishspd, sqrt( maxspd ));
wishspd = sv_maxvelocity.value / wishspd;
VectorScale( ent->v.velocity, wishspd, ent->v.velocity );
}
}
/* /*
================ ================
SV_CheckAllEnts SV_CheckAllEnts
@@ -110,47 +151,6 @@ static void SV_CheckAllEnts( void )
} }
} }
/*
================
SV_CheckVelocity
================
*/
void SV_CheckVelocity( edict_t *ent )
{
float wishspd;
float maxspd;
// bound velocity
for( int i = 0; i < 3; i++ )
{
if( IS_NAN( ent->v.velocity[i] ))
{
if( sv_check_errors.value )
Con_Printf( "Got a NaN velocity on %s\n", SV_GetString( ent->v.classname ));
ent->v.velocity[i] = 0.0f;
}
if( IS_NAN( ent->v.origin[i] ))
{
if( sv_check_errors.value )
Con_Printf( "Got a NaN origin on %s\n", SV_GetString( ent->v.classname ));
ent->v.origin[i] = 0.0f;
}
}
wishspd = DotProduct( ent->v.velocity, ent->v.velocity );
maxspd = sv_maxvelocity.value * sv_maxvelocity.value * 1.73f; // half-diagonal
if( wishspd > maxspd )
{
wishspd = sqrt( wishspd );
if( sv_check_errors.value )
Con_Printf( "Got a velocity too high on %s ( %.2f > %.2f )\n", SV_GetString( ent->v.classname ), wishspd, sqrt( maxspd ));
wishspd = sv_maxvelocity.value / wishspd;
VectorScale( ent->v.velocity, wishspd, ent->v.velocity );
}
}
/* /*
================ ================
SV_UpdateBaseVelocity SV_UpdateBaseVelocity
@@ -158,26 +158,20 @@ SV_UpdateBaseVelocity
*/ */
void SV_UpdateBaseVelocity( edict_t *ent ) void SV_UpdateBaseVelocity( edict_t *ent )
{ {
if( ent->v.flags & FL_ONGROUND ) if( !FBitSet( ent->v.flags, FL_ONGROUND ))
{ return;
edict_t *groundentity = ent->v.groundentity;
if( SV_IsValidEdict( groundentity )) const edict_t *groundentity = ent->v.groundentity;
{
// On conveyor belt that's moving?
if( groundentity->v.flags & FL_CONVEYOR )
{
vec3_t new_basevel;
VectorScale( groundentity->v.movedir, groundentity->v.speed, new_basevel ); if( !SV_IsValidEdict( groundentity ) || !FBitSet( groundentity->v.flags, FL_CONVEYOR ))
if( ent->v.flags & FL_BASEVELOCITY ) return;
VectorAdd( new_basevel, ent->v.basevelocity, new_basevel );
ent->v.flags |= FL_BASEVELOCITY; if( FBitSet( ent->v.flags, FL_BASEVELOCITY ))
VectorCopy( new_basevel, ent->v.basevelocity ); VectorMA( ent->v.basevelocity, groundentity->v.speed, groundentity->v.movedir, ent->v.basevelocity );
} else
} VectorScale( groundentity->v.movedir, groundentity->v.speed, ent->v.basevelocity );
}
ent->v.flags |= FL_BASEVELOCITY;
} }
/* /*
@@ -189,18 +183,15 @@ returns true if the entity is in solid currently
*/ */
static qboolean SV_TestEntityPosition( edict_t *ent, edict_t *blocker ) static qboolean SV_TestEntityPosition( edict_t *ent, edict_t *blocker )
{ {
qboolean monsterClip = FBitSet( ent->v.flags, FL_MONSTERCLIP ) ? true : false;
trace_t trace;
if( FBitSet( ent->v.flags, FL_CLIENT|FL_FAKECLIENT )) if( FBitSet( ent->v.flags, FL_CLIENT|FL_FAKECLIENT ))
{ {
// to avoid falling through tracktrain update client mins\maxs here // to avoid falling through tracktrain update client mins\maxs here
if( FBitSet( ent->v.flags, FL_DUCKING )) int hull = FBitSet( ent->v.flags, FL_DUCKING ) ? 1 : 0;
SV_SetMinMaxSize( ent, host.player_mins[1], host.player_maxs[1], true ); SV_SetMinMaxSize( ent, host.player_mins[hull], host.player_maxs[hull], true );
else SV_SetMinMaxSize( ent, host.player_mins[0], host.player_maxs[0], true );
} }
trace = SV_Move( ent->v.origin, ent->v.mins, ent->v.maxs, ent->v.origin, MOVE_NORMAL, ent, monsterClip ); qboolean monsterClip = FBitSet( ent->v.flags, FL_MONSTERCLIP ) ? true : false;
trace_t trace = SV_Move( ent->v.origin, ent->v.mins, ent->v.maxs, ent->v.origin, MOVE_NORMAL, ent, monsterClip );
if( SV_IsValidEdict( blocker ) && SV_IsValidEdict( trace.ent )) if( SV_IsValidEdict( blocker ) && SV_IsValidEdict( trace.ent ))
{ {
@@ -212,6 +203,25 @@ static qboolean SV_TestEntityPosition( edict_t *ent, edict_t *blocker )
return trace.startsolid; return trace.startsolid;
} }
static qboolean SV_TryThink( edict_t *ent, double frametime, double time )
{
float thinktime = ent->v.nextthink;
if( thinktime <= 0.0f || thinktime > ( time + frametime ))
return false;
// don't let things stay in the past.
// it is possible to start that way
// by a trigger with a local time.
if( thinktime < time )
thinktime = time;
ent->v.nextthink = 0.0f;
svgame.globals->time = thinktime;
svgame.dllFuncs.pfnThink( ent );
return true;
}
/* /*
============= =============
SV_RunThink SV_RunThink
@@ -224,21 +234,10 @@ Returns false if the entity removed itself.
*/ */
static qboolean SV_RunThink( edict_t *ent ) static qboolean SV_RunThink( edict_t *ent )
{ {
float thinktime;
if( !FBitSet( ent->v.flags, FL_KILLME )) if( !FBitSet( ent->v.flags, FL_KILLME ))
{ {
thinktime = ent->v.nextthink; if( !SV_TryThink( ent, sv.frametime, sv.time ))
if( thinktime <= 0.0f || thinktime > (sv.time + sv.frametime))
return true; return true;
if( thinktime < sv.time )
thinktime = sv.time; // don't let things stay in the past.
// it is possible to start that way
// by a trigger with a local time.
ent->v.nextthink = 0.0f;
svgame.globals->time = thinktime;
svgame.dllFuncs.pfnThink( ent );
} }
if( FBitSet( ent->v.flags, FL_KILLME )) if( FBitSet( ent->v.flags, FL_KILLME ))
@@ -259,25 +258,13 @@ Returns false if the entity removed itself.
*/ */
qboolean SV_PlayerRunThink( edict_t *ent, float frametime, double time ) qboolean SV_PlayerRunThink( edict_t *ent, float frametime, double time )
{ {
float thinktime;
if( svgame.physFuncs.SV_PlayerThink ) if( svgame.physFuncs.SV_PlayerThink )
return svgame.physFuncs.SV_PlayerThink( ent, frametime, time ); return svgame.physFuncs.SV_PlayerThink( ent, frametime, time );
if( !FBitSet( ent->v.flags, FL_KILLME|FL_DORMANT )) if( !FBitSet( ent->v.flags, FL_KILLME|FL_DORMANT ))
{ {
thinktime = ent->v.nextthink; if( !SV_TryThink( ent, frametime, time ))
if( thinktime <= 0.0f || thinktime > (time + frametime))
return true; return true;
if( thinktime < time )
thinktime = time; // don't let things stay in the past.
// it is possible to start that way
// by a trigger with a local time.
ent->v.nextthink = 0.0f;
svgame.globals->time = thinktime;
svgame.dllFuncs.pfnThink( ent );
} }
if( FBitSet( ent->v.flags, FL_KILLME )) if( FBitSet( ent->v.flags, FL_KILLME ))
@@ -297,17 +284,11 @@ void SV_Impact( edict_t *e1, edict_t *e2, trace_t *trace )
{ {
svgame.globals->time = sv.time; svgame.globals->time = sv.time;
if(( e1->v.flags|e2->v.flags ) & FL_KILLME ) if( FBitSet( e1->v.flags|e2->v.flags, FL_KILLME ))
return; return;
if( e1->v.groupinfo && e2->v.groupinfo ) if( !SV_CheckGroupTrace( e1, e2 ))
{ return;
if( svs.groupop == GROUP_OP_AND && !FBitSet( e1->v.groupinfo, e2->v.groupinfo ))
return;
if( svs.groupop == GROUP_OP_NAND && FBitSet( e1->v.groupinfo, e2->v.groupinfo ))
return;
}
if( e1->v.solid != SOLID_NOT ) if( e1->v.solid != SOLID_NOT )
{ {

View File

@@ -205,10 +205,7 @@ static void SV_AddLinksToPmove( areanode_t *node, const vec3_t pmove_mins, const
if( check->v.groupinfo != 0 ) if( check->v.groupinfo != 0 )
{ {
if( svs.groupop == GROUP_OP_AND && !FBitSet( check->v.groupinfo, pl->v.groupinfo )) if( !SV_CheckGroupOp( svs.groupop, check->v.groupinfo, pl->v.groupinfo ))
continue;
if( svs.groupop == GROUP_OP_NAND && FBitSet( check->v.groupinfo, pl->v.groupinfo ))
continue; continue;
} }
@@ -520,8 +517,7 @@ static void SV_SetupPMove( playermove_t *pmove, sv_client_t *cl, usercmd_t *ucmd
vec3_t absmin, absmax; vec3_t absmin, absmax;
edict_t *clent = cl->edict; 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->player_index = NUM_FOR_EDICT( clent ) - 1;
pmove->multiplayer = (svs.maxclients > 1) ? true : false; pmove->multiplayer = (svs.maxclients > 1) ? true : false;
pmove->time = (float)(cl->timebase * 1000.0); pmove->time = (float)(cl->timebase * 1000.0);
@@ -978,9 +974,9 @@ void SV_RunCmd( sv_client_t *cl, usercmd_t *ucmd, int random_seed )
// touch other objects // touch other objects
for( int i = 0; i < svgame.pmove->numtouch; i++ ) for( int i = 0; i < svgame.pmove->numtouch; i++ )
{ {
pmtrace_t *pmtrace = &svgame.pmove->touchindex[i]; pmtrace_t *pmtrace = &svgame.pmove->touchindex[i];
edict_t *touch = SV_EdictNum( svgame.pmove->physents[pmtrace->ent].info ); edict_t *touch = SV_EdictNum( svgame.pmove->physents[pmtrace->ent].info );
trace_t trace; trace_t trace;
VectorCopy( pmtrace->deltavelocity, clent->v.velocity ); VectorCopy( pmtrace->deltavelocity, clent->v.velocity );
PM_ConvertTrace( &trace, pmtrace, touch ); PM_ConvertTrace( &trace, pmtrace, touch );

View File

@@ -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 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 )) 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] ) else switch( c[0] )
{ {
case A2S_GOLDSRC_RULES: case A2S_GOLDSRC_RULES:
SV_SourceQuery_Rules( from ); if( SV_SourceQuery_CheckChallenge( from, msg ))
SV_SourceQuery_Rules( from );
break; break;
case A2S_GOLDSRC_PLAYERS: case A2S_GOLDSRC_PLAYERS:
SV_SourceQuery_Players( from ); if( SV_SourceQuery_CheckChallenge( from, msg ))
SV_SourceQuery_Players( from );
break; break;
} }
} }

View File

@@ -522,14 +522,8 @@ static void SV_TouchLinks( edict_t *ent, areanode_t *node )
if( touch == ent || touch->v.solid != SOLID_TRIGGER ) // disabled ? if( touch == ent || touch->v.solid != SOLID_TRIGGER ) // disabled ?
continue; continue;
if( touch->v.groupinfo && ent->v.groupinfo ) if( !SV_CheckGroupTrace( touch, ent ))
{ continue;
if( svs.groupop == GROUP_OP_AND && !FBitSet( touch->v.groupinfo, ent->v.groupinfo ))
continue;
if( svs.groupop == GROUP_OP_NAND && FBitSet( touch->v.groupinfo, ent->v.groupinfo ))
continue;
}
if( !BoundsIntersect( ent->v.absmin, ent->v.absmax, touch->v.absmin, touch->v.absmax )) if( !BoundsIntersect( ent->v.absmin, ent->v.absmax, touch->v.absmin, touch->v.absmax ))
continue; continue;
@@ -725,10 +719,7 @@ static void SV_WaterLinks( const vec3_t origin, int *pCont, areanode_t *node )
if( touch->v.groupinfo ) if( touch->v.groupinfo )
{ {
if( svs.groupop == GROUP_OP_AND && !FBitSet( touch->v.groupinfo, svs.groupmask )) if( !SV_CheckGroupOp( svs.groupop, touch->v.groupinfo, svs.groupmask ))
continue;
if( svs.groupop == GROUP_OP_NAND && FBitSet( touch->v.groupinfo, svs.groupmask ))
continue; continue;
} }
@@ -1101,14 +1092,8 @@ static qboolean SV_ClipToEntity( edict_t *touch, moveclip_t *clip )
trace_t trace; trace_t trace;
model_t *mod; model_t *mod;
if( touch->v.groupinfo && SV_IsValidEdict( clip->passedict ) && clip->passedict->v.groupinfo != 0 ) if( SV_IsValidEdict( clip->passedict ) && !SV_CheckGroupTrace( touch, clip->passedict ))
{ return true;
if( svs.groupop == GROUP_OP_AND && !FBitSet( touch->v.groupinfo, clip->passedict->v.groupinfo ))
return true;
if( svs.groupop == GROUP_OP_NAND && FBitSet( touch->v.groupinfo, clip->passedict->v.groupinfo ))
return true;
}
if( touch == clip->passedict || touch->v.solid == SOLID_NOT ) if( touch == clip->passedict || touch->v.solid == SOLID_NOT )
return true; return true;

View File

@@ -26,7 +26,12 @@ int main(int argc, char **argv)
} }
''' '''
frameworks = ['Foundation', 'UIKit', 'QuartzCore', 'GameController', 'SystemConfiguration', 'CFNetwork', 'AVFoundation', 'CoreGraphics'] def get_frameworks(env):
if env.IOS:
return ['Foundation', 'UIKit', 'QuartzCore', 'GameController', 'SystemConfiguration', 'CFNetwork', 'AVFoundation', 'CoreGraphics']
if env.DEST_OS == 'darwin':
return ['IOKit', 'CoreFoundation']
return []
@TaskGen.extension('.m') @TaskGen.extension('.m')
def m_hook(self, node): def m_hook(self, node):
@@ -47,9 +52,6 @@ def options(opt):
grp.add_option('--enable-static-binary', action = 'store_true', dest = 'STATIC', default = False, 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]') 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, grp.add_option('--enable-ffmpeg', action = 'store_true', dest = 'FFMPEG', default = False,
help = 'enable ffmpeg based movie playback [default: %(default)s') help = 'enable ffmpeg based movie playback [default: %(default)s')
@@ -89,9 +91,10 @@ def configure(conf):
elif conf.env.DEST_OS == 'dos': elif conf.env.DEST_OS == 'dos':
conf.options.STATIC = True 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('CFLAGS', '-fsanitize=fuzzer-no-link')
conf.env.append_unique('LINKFLAGS', '-fsanitize=fuzzer') conf.env.append_unique('LINKFLAGS', '-fsanitize=fuzzer-no-link')
# Client-only dependencies # Client-only dependencies
if conf.env.CLIENT: if conf.env.CLIENT:
@@ -126,9 +129,8 @@ def configure(conf):
if not conf.env.DEST_OS in ['win32', 'android']: if not conf.env.DEST_OS in ['win32', 'android']:
conf.check_pthreads(mode='c') conf.check_pthreads(mode='c')
if conf.env.IOS: for i in get_frameworks(conf.env):
for i in frameworks : conf.check(features='c cprogram', framework=i, uselib_store=i, msg='Checking for %s framework' % i)
conf.check(features='c cprogram', framework=i, uselib_store=i, msg='Checking for %s framework' % i)
conf.define('ENGINE_DLL', 1) conf.define('ENGINE_DLL', 1)
@@ -161,7 +163,7 @@ def build(bld):
# public includes for renderers and utils use # public includes for renderers and utils use
bld(name = 'engine_includes', export_includes = '. common common/imagelib', use = 'filesystem_includes') bld(name = 'engine_includes', export_includes = '. common common/imagelib', use = 'filesystem_includes')
libs = ['engine_includes', 'public', 'werror', 'backtrace_custom', 'library_suffix', 'build_vcs', 'mbedtls'] libs = ['engine_includes', 'public', 'werror', 'backtrace_custom', 'library_suffix', 'build_vcs', 'mbedtls', 'yy_thunks']
includes = ['server', 'client', 'client/vgui', 'common/soundlib', 'platform'] includes = ['server', 'client', 'client/vgui', 'common/soundlib', 'platform']
# basic build: dedicated only # basic build: dedicated only
@@ -181,6 +183,8 @@ def build(bld):
source += bld.path.ant_glob('platform/ios/*.c') source += bld.path.ant_glob('platform/ios/*.c')
source += bld.path.ant_glob('platform/ios/*.m') source += bld.path.ant_glob('platform/ios/*.m')
includes += bld.path.ant_glob('platform/ios') includes += bld.path.ant_glob('platform/ios')
elif bld.env.DEST_OS == 'darwin':
source += bld.path.ant_glob('platform/apple/*.c')
# include sources for optional features # include sources for optional features
if bld.get_define('XASH_CUSTOM_SWAP'): if bld.get_define('XASH_CUSTOM_SWAP'):
@@ -208,6 +212,7 @@ def build(bld):
] ]
else: # POSIX else: # POSIX
libs += ['M', 'RT', 'PTHREAD', 'ASOUND', 'HAIKU', 'MAGX', 'LOG', 'SOCKET'] libs += ['M', 'RT', 'PTHREAD', 'ASOUND', 'HAIKU', 'MAGX', 'LOG', 'SOCKET']
libs += get_frameworks(bld.env)
if not bld.env.STATIC: if not bld.env.STATIC:
libs += ['DL'] libs += ['DL']
@@ -276,7 +281,6 @@ def build(bld):
install_path = bld.env.LIBDIR install_path = bld.env.LIBDIR
if bld.env.IOS: if bld.env.IOS:
libs += frameworks
defines += ['XASH_SDLMAIN=1'] defines += ['XASH_SDLMAIN=1']
if bld.env.DEST_OS in ['nswitch', 'psvita', 'emscripten']: if bld.env.DEST_OS in ['nswitch', 'psvita', 'emscripten']:

View File

@@ -28,7 +28,7 @@ GNU General Public License for more details.
// GoldSrc Directories and ID // GoldSrc Directories and ID
// GAME gamedir // GAME gamedir
// GAMECONFIG gamedir (rodir integration?) // GAMECONFIG gamedir (rodir integration?)
// GAMEDOWNLOAD gamedir_downloads (gamedir/downloads for us) // GAMEDOWNLOAD gamedir_downloads
// GAME_FALLBACK liblist.gam's fallback_dir // GAME_FALLBACK liblist.gam's fallback_dir
// ROOT and BASE rootdir // ROOT and BASE rootdir
// PLATFORM platform // PLATFORM platform
@@ -41,14 +41,22 @@ GNU General Public License for more details.
char * const var = static_cast<char *>( alloca( var ## _size )); \ char * const var = static_cast<char *>( alloca( var ## _size )); \
CopyAndFixSlashes( var, ( str ), 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" ) || return !Q_strcmp( id, "GAME" ) ||
!Q_strcmp( id, "GAMECONFIG" ) || !Q_strcmp( id, "GAMECONFIG" ) ||
!Q_strcmp( id, "GAMEDOWNLOAD" ); !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" )) if( !Q_strcmp( id, "GAME" ))
return GI->gamefolder; 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 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 ); Q_strncpy( p, in, size );
COM_FixSlashes( p ); COM_FixSlashes( p );
@@ -147,10 +155,9 @@ public:
void RemoveFile( const char *path, const char *id ) override void RemoveFile( const char *path, const char *id ) override
{ {
char dir[MAX_VA_STRING], fullpath[MAX_VA_STRING]; // FIXME: handle writable downloads path
if( IdToWritableSearchpath( id ))
Q_snprintf( fullpath, sizeof( fullpath ), "%s/%s", IdToDir( dir, sizeof( dir ), id ), path ); FS_Delete( path ); // FS_Delete is aware of slashes
FS_Delete( fullpath ); // FS_Delete is aware of slashes
} }
void CreateDirHierarchy( const char *path, const char *id ) override void CreateDirHierarchy( const char *path, const char *id ) override

View File

@@ -33,7 +33,7 @@ def configure(conf):
def build(bld): def build(bld):
bld(name = 'filesystem_includes', export_includes = '.') 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 # 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': if bld.env.DEST_OS != 'psvita':

View File

@@ -32,7 +32,7 @@ def build(bld):
bld.program(source = source, bld.program(source = source,
target = 'xash3d', # hl.exe 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, rpath = bld.env.DEFAULT_RPATH,
install_path = bld.env.BINDIR, install_path = bld.env.BINDIR,
subsystem = bld.env.MSVC_SUBSYSTEM subsystem = bld.env.MSVC_SUBSYSTEM

View File

@@ -721,7 +721,7 @@ void R_DecalShoot( int textureIndex, int entityIndex, int modelIndex, vec3_t pos
if( model->type != mod_brush ) if( model->type != mod_brush )
{ {
gEngfuncs.Con_Printf( S_ERROR "Decals must hit mod_brush!\n" ); gEngfuncs.Con_Reportf( S_ERROR "Decals must hit mod_brush!\n" );
return; return;
} }

View File

@@ -736,6 +736,7 @@ extern convar_t r_vbo_detail;
extern convar_t r_vbo_overbrightmode; extern convar_t r_vbo_overbrightmode;
extern convar_t r_studio_sort_textures; extern convar_t r_studio_sort_textures;
extern convar_t r_studio_drawelements; extern convar_t r_studio_drawelements;
extern convar_t r_studio_builtin_renderer;
extern convar_t r_shadows; extern convar_t r_shadows;
extern convar_t r_ripple; extern convar_t r_ripple;
extern convar_t r_ripple_updatetime; extern convar_t r_ripple_updatetime;

View File

@@ -1149,6 +1149,7 @@ static void GL_InitCommands( void )
gEngfuncs.Cvar_RegisterVariable( &r_traceglow ); gEngfuncs.Cvar_RegisterVariable( &r_traceglow );
gEngfuncs.Cvar_RegisterVariable( &r_studio_sort_textures ); gEngfuncs.Cvar_RegisterVariable( &r_studio_sort_textures );
gEngfuncs.Cvar_RegisterVariable( &r_studio_drawelements ); gEngfuncs.Cvar_RegisterVariable( &r_studio_drawelements );
gEngfuncs.Cvar_RegisterVariable( &r_studio_builtin_renderer );
gEngfuncs.Cvar_RegisterVariable( &r_ripple ); gEngfuncs.Cvar_RegisterVariable( &r_ripple );
gEngfuncs.Cvar_RegisterVariable( &r_ripple_updatetime ); gEngfuncs.Cvar_RegisterVariable( &r_ripple_updatetime );
gEngfuncs.Cvar_RegisterVariable( &r_ripple_spawntime ); gEngfuncs.Cvar_RegisterVariable( &r_ripple_spawntime );

View File

@@ -566,7 +566,7 @@ static texture_t *R_TextureAnimation( msurface_t *s )
R_AddDynamicLights R_AddDynamicLights
=============== ===============
*/ */
static void R_AddDynamicLights( const msurface_t *surf ) static void R_AddDynamicLights( const msurface_t *surf, float sample_size, int smax, int tmax )
{ {
const mextrasurf_t *info = surf->info; const mextrasurf_t *info = surf->info;
int sample_frac = 1.0; int sample_frac = 1.0;
@@ -575,9 +575,6 @@ static void R_AddDynamicLights( const msurface_t *surf )
if( !surf->dlightbits ) if( !surf->dlightbits )
return; return;
float sample_size = gEngfuncs.Mod_SampleSizeForFace( surf );
int smax = (info->lightextents[0] / sample_size) + 1;
int tmax = (info->lightextents[1] / sample_size) + 1;
mtexinfo_t *tex = surf->texinfo; mtexinfo_t *tex = surf->texinfo;
if( FBitSet( tex->flags, TEX_WORLD_LUXELS )) if( FBitSet( tex->flags, TEX_WORLD_LUXELS ))
@@ -624,14 +621,22 @@ static void R_AddDynamicLights( const msurface_t *surf )
float sl = DotProduct( impact, info->lmvecs[0] ) + info->lmvecs[0][3] - info->lightmapmins[0]; float sl = DotProduct( impact, info->lmvecs[0] ) + info->lmvecs[0][3] - info->lightmapmins[0];
float tl = DotProduct( impact, info->lmvecs[1] ) + info->lmvecs[1][3] - info->lightmapmins[1]; float tl = DotProduct( impact, info->lmvecs[1] ) + info->lmvecs[1][3] - info->lightmapmins[1];
for( int t = 0; t < tmax; t++ ) // dist >= max( sd, td ), so a luxel can only pass the test below when both
// sd and td are under minlight
float half = ( minlight + 1.0f ) / ( sample_size * sample_frac );
int s0 = Q_max( 0, (int)( sl / sample_size - half ));
int s1 = Q_min( smax - 1, (int)( sl / sample_size + half ));
int t0 = Q_max( 0, (int)( tl / sample_size - half ));
int t1 = Q_min( tmax - 1, (int)( tl / sample_size + half ));
for( int t = t0; t <= t1; t++ )
{ {
int td = (tl - sample_size * t) * sample_frac; int td = (tl - sample_size * t) * sample_frac;
if( td < 0 ) if( td < 0 )
td = -td; td = -td;
for( int s = 0; s < smax; s++ ) for( int s = s0; s <= s1; s++ )
{ {
int sd = (sl - sample_size * s) * sample_frac; int sd = (sl - sample_size * s) * sample_frac;
float dist; float dist;
@@ -755,7 +760,7 @@ static void R_BuildLightMap( const msurface_t *surf, byte *dest, int stride, qbo
// add all the dynamic lights // add all the dynamic lights
if( surf->dlightframe == tr.framecount && dynamic ) if( surf->dlightframe == tr.framecount && dynamic )
R_AddDynamicLights( surf ); R_AddDynamicLights( surf, sample_size, smax, tmax );
for( int t = 0; t < tmax; t++ ) for( int t = 0; t < tmax; t++ )
{ {

View File

@@ -111,7 +111,7 @@ static float R_GetSpriteFrameInterpolant( cl_entity_t *ent, mspriteframe_t **old
// are positive, so we don't have to worry about division by zero // are positive, so we don't have to worry about division by zero
float targettime = time - ((int)(time / fullinterval)) * fullinterval; float targettime = time - ((int)(time / fullinterval)) * fullinterval;
// LordHavoc: since I can't measure the time properly when it loops from numframes - 1 to 0, // LadyHavoc: since I can't measure the time properly when it loops from numframes - 1 to 0,
// i instead measure the time of the first frame, hoping it is consistent // i instead measure the time of the first frame, hoping it is consistent
int i, j; int i, j;
for( i = 0, j = numframes - 1; i < (numframes - 1); i++ ) for( i = 0, j = numframes - 1; i < (numframes - 1); i++ )

View File

@@ -122,6 +122,7 @@ typedef struct
// studio-related cvars // studio-related cvars
CVAR_DEFINE_AUTO( r_studio_sort_textures, "0", FCVAR_GLCONFIG, "change draw order for additive meshes" ); CVAR_DEFINE_AUTO( r_studio_sort_textures, "0", FCVAR_GLCONFIG, "change draw order for additive meshes" );
CVAR_DEFINE_AUTO( r_studio_drawelements, "1", FCVAR_GLCONFIG, "use glDrawElements for studiomodels" ); CVAR_DEFINE_AUTO( r_studio_drawelements, "1", FCVAR_GLCONFIG, "use glDrawElements for studiomodels" );
CVAR_DEFINE_AUTO( r_studio_builtin_renderer, "0", 0, "use built-in studio model renderer instead of the one provided by client library (debugging)" );
static cvar_t *cl_righthand = NULL; static cvar_t *cl_righthand = NULL;
static r_studio_interface_t *pStudioDraw; static r_studio_interface_t *pStudioDraw;
@@ -1904,6 +1905,43 @@ static void R_StudioDrawArrays( uint startverts, uint startelems )
pglDisableClientState( GL_COLOR_ARRAY ); pglDisableClientState( GL_COLOR_ARRAY );
} }
/*
===============
R_StudioSubmitMesh
===============
*/
static void R_StudioSubmitMesh( short *ptricmds, vec3_t *pstudionorms, float s, float t, float shellscale, int tesslevel )
{
if( tesslevel > 0 )
{
// R_StudioDrawTruformMesh( ptricmds, pstudionorms, s, t, shellscale, tesslevel );
}
else if( r_studio_drawelements.value )
{
uint startArrayVerts = g_studio.numverts;
uint startArrayElems = g_studio.numelems;
if( FBitSet( g_nFaceFlags, STUDIO_NF_CHROME ))
R_StudioBuildArrayChromeMesh( ptricmds, pstudionorms, s, t, shellscale );
else if( FBitSet( g_nFaceFlags, STUDIO_NF_UV_COORDS ))
R_StudioBuildArrayFloatMesh( ptricmds, pstudionorms );
else
R_StudioBuildArrayNormalMesh( ptricmds, pstudionorms, s, t );
R_StudioDrawArrays( startArrayVerts, startArrayElems );
}
else
{
if( FBitSet( g_nFaceFlags, STUDIO_NF_CHROME ))
R_StudioDrawChromeMesh( ptricmds, pstudionorms, s, t, shellscale );
else if( FBitSet( g_nFaceFlags, STUDIO_NF_UV_COORDS ))
R_StudioDrawFloatMesh( ptricmds, pstudionorms );
else
R_StudioDrawNormalMesh( ptricmds, pstudionorms, s, t );
}
}
/* /*
=============== ===============
R_StudioDrawPoints R_StudioDrawPoints
@@ -2040,8 +2078,6 @@ static void R_StudioDrawPoints( void )
for( int j = 0; j < m_pSubModel->nummesh; j++ ) for( int j = 0; j < m_pSubModel->nummesh; j++ )
{ {
float oldblend = tr.blend; float oldblend = tr.blend;
uint startArrayVerts = g_studio.numverts;
uint startArrayElems = g_studio.numelems;
pmesh = g_studio.meshes[j].mesh; pmesh = g_studio.meshes[j].mesh;
short *ptricmds = (short *)((byte *)m_pStudioHeader + pmesh->triindex); short *ptricmds = (short *)((byte *)m_pStudioHeader + pmesh->triindex);
@@ -2073,24 +2109,7 @@ static void R_StudioDrawPoints( void )
R_StudioSetupSkin( m_pStudioHeader, pskinref[pmesh->skinref] ); R_StudioSetupSkin( m_pStudioHeader, pskinref[pmesh->skinref] );
if( r_studio_drawelements.value ) R_StudioSubmitMesh( ptricmds, pstudionorms, s, t, shellscale, 0 );
{
if( FBitSet( g_nFaceFlags, STUDIO_NF_CHROME ))
R_StudioBuildArrayChromeMesh( ptricmds, pstudionorms, s, t, shellscale );
else if( FBitSet( g_nFaceFlags, STUDIO_NF_UV_COORDS ))
R_StudioBuildArrayFloatMesh( ptricmds, pstudionorms );
else R_StudioBuildArrayNormalMesh( ptricmds, pstudionorms, s, t );
R_StudioDrawArrays( startArrayVerts, startArrayElems );
}
else
{
if( FBitSet( g_nFaceFlags, STUDIO_NF_CHROME ))
R_StudioDrawChromeMesh( ptricmds, pstudionorms, s, t, shellscale );
else if( FBitSet( g_nFaceFlags, STUDIO_NF_UV_COORDS ))
R_StudioDrawFloatMesh( ptricmds, pstudionorms );
else R_StudioDrawNormalMesh( ptricmds, pstudionorms, s, t );
}
if( FBitSet( g_nFaceFlags, STUDIO_NF_MASKED )) if( FBitSet( g_nFaceFlags, STUDIO_NF_MASKED ))
{ {
@@ -3192,6 +3211,12 @@ static void R_StudioDrawModelInternal( cl_entity_t *e, int flags )
R_StudioDrawPlayer( flags, &e->curstate ); R_StudioDrawPlayer( flags, &e->curstate );
else R_StudioDrawModel( flags ); else R_StudioDrawModel( flags );
} }
else if( unlikely( r_studio_builtin_renderer.value ))
{
if( e->player )
R_StudioDrawPlayer( flags, R_StudioGetPlayerState( e->index - 1 ));
else R_StudioDrawModel( flags );
}
else else
{ {
// select the properly method // select the properly method

View File

@@ -25,7 +25,7 @@ def configure(conf):
conf.check_cc(lib='log') conf.check_cc(lib='log')
def build(bld): def build(bld):
libs = [ 'ref_common', 'engine_includes', 'werror' ] libs = [ 'ref_common', 'engine_includes', 'werror', 'yy_thunks' ]
# on PSVita do not link any libraries that are already in the main executable, but add the includes target # 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': if bld.env.DEST_OS == 'psvita':
libs += [ 'sdk_includes', 'vgl_shim' ] libs += [ 'sdk_includes', 'vgl_shim' ]

View File

@@ -13,6 +13,6 @@ def build(bld):
source = 'r_context.c', source = 'r_context.c',
target = 'ref_null', target = 'ref_null',
defines = 'REF_DLL', defines = 'REF_DLL',
use = 'engine_includes sdk_includes werror', use = 'engine_includes sdk_includes werror yy_thunks',
install_path = bld.env.LIBDIR install_path = bld.env.LIBDIR
) )

View File

@@ -470,6 +470,8 @@ static decal_t *R_DecalIntersect( decalinfo_t *decalinfo, msurface_t *surf, int
} }
} }
} }
if( pDecal == pDecal->pnext ) break;
pDecal = pDecal->pnext; pDecal = pDecal->pnext;
} }
return plast; return plast;
@@ -523,7 +525,10 @@ static void R_AddDecalToSurface( decal_t *pdecal, msurface_t *surf, decalinfo_t
if( pold ) if( pold )
{ {
while( pold->pnext ) while( pold->pnext )
{
if( pold == pold->pnext ) break;
pold = pold->pnext; pold = pold->pnext;
}
pold->pnext = pdecal; pold->pnext = pdecal;
} }
else else
@@ -599,8 +604,10 @@ static void R_DecalSurface( msurface_t *surf, decalinfo_t *decalinfo )
{ {
// NOTE: we may have the decal on this surface that come from another level. // NOTE: we may have the decal on this surface that come from another level.
// check duplicate with same position and texture // check duplicate with same position and texture
while( decal != NULL && decal != decal->pnext ) while( decal != NULL )
{ {
if( decal == decal->pnext ) break;
if( VectorCompare( decal->position, decalinfo->m_Position ) && decal->texture == decalinfo->m_iTexture ) if( VectorCompare( decal->position, decalinfo->m_Position ) && decal->texture == decalinfo->m_iTexture )
return; // decal already exists, don't place it again return; // decal already exists, don't place it again
decal = decal->pnext; decal = decal->pnext;
@@ -743,7 +750,7 @@ void GAME_EXPORT R_DecalShoot( int textureIndex, int entityIndex, int modelIndex
if( model->type != mod_brush ) if( model->type != mod_brush )
{ {
gEngfuncs.Con_Printf( S_ERROR "Decals must hit mod_brush!\n" ); gEngfuncs.Con_Reportf( S_ERROR "Decals must hit mod_brush!\n" );
return; return;
} }

View File

@@ -955,6 +955,7 @@ extern convar_t sw_texfilt;
extern convar_t r_traceglow; extern convar_t r_traceglow;
extern convar_t sw_noalphabrushes; extern convar_t sw_noalphabrushes;
extern convar_t r_studio_sort_textures; extern convar_t r_studio_sort_textures;
extern convar_t r_studio_builtin_renderer;
extern struct qfrustum_s extern struct qfrustum_s
{ {

View File

@@ -1341,6 +1341,7 @@ qboolean GAME_EXPORT R_Init( void )
#endif #endif
gEngfuncs.Cvar_RegisterVariable( &r_novis ); gEngfuncs.Cvar_RegisterVariable( &r_novis );
gEngfuncs.Cvar_RegisterVariable( &r_studio_sort_textures ); gEngfuncs.Cvar_RegisterVariable( &r_studio_sort_textures );
gEngfuncs.Cvar_RegisterVariable( &r_studio_builtin_renderer );
r_temppool = Mem_AllocPool( "ref_soft zone" ); r_temppool = Mem_AllocPool( "ref_soft zone" );

View File

@@ -105,6 +105,7 @@ typedef struct
// studio-related cvars // studio-related cvars
CVAR_DEFINE_AUTO( r_studio_sort_textures, "0", FCVAR_GLCONFIG, "change draw order for additive meshes" ); CVAR_DEFINE_AUTO( r_studio_sort_textures, "0", FCVAR_GLCONFIG, "change draw order for additive meshes" );
CVAR_DEFINE_AUTO( r_studio_builtin_renderer, "0", 0, "use built-in studio model renderer instead of the one provided by client library (debugging)" );
static cvar_t *cl_righthand = NULL; static cvar_t *cl_righthand = NULL;
static r_studio_interface_t *pStudioDraw; static r_studio_interface_t *pStudioDraw;
@@ -2822,6 +2823,13 @@ static void R_StudioDrawModelInternal( cl_entity_t *e, int flags )
else else
R_StudioDrawModel( flags ); R_StudioDrawModel( flags );
} }
else if( unlikely( r_studio_builtin_renderer.value ))
{
if( e->player )
R_StudioDrawPlayer( flags, R_StudioGetPlayerState( e->index - 1 ));
else
R_StudioDrawModel( flags );
}
else else
{ {
// select the properly method // select the properly method

View File

@@ -14,7 +14,7 @@ def configure(conf):
return return
def build(bld): def build(bld):
libs = [ 'ref_common', 'engine_includes', 'werror' ] libs = [ 'ref_common', 'engine_includes', 'werror', 'yy_thunks' ]
# on PSVita do not link any libraries that are already in the main executable, but add the includes target # 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': if bld.env.DEST_OS == 'psvita':
libs += [ 'sdk_includes' ] libs += [ 'sdk_includes' ]

View File

@@ -8,6 +8,11 @@ mv "SDL2-$SDL_VERSION" SDL2_VC
if [ "$GH_CPU_ARCH" = "i386" ]; then if [ "$GH_CPU_ARCH" = "i386" ]; then
rustup target add i686-pc-windows-msvc rustup target add i686-pc-windows-msvc
# YY-Thunks obj for Windows XP support
curl -L "https://github.com/Chuyu-Team/YY-Thunks/releases/download/$YY_THUNKS_VERSION/YY-Thunks-Objs.zip" -o yy-thunks.zip
unzip -q -j -o yy-thunks.zip 'objs/x86/YY_Thunks_for_WinXP.obj' -d 3rdparty/yy-thunks
rm yy-thunks.zip
fi fi
curl -L https://github.com/FWGS/potential-meme/releases/download/prebuilts/mingw-w64-x86_64-pkgconf-1.2.3.0-1-any.pkg.tar.zst -o pkgconf.tar.zst curl -L https://github.com/FWGS/potential-meme/releases/download/prebuilts/mingw-w64-x86_64-pkgconf-1.2.3.0-1-any.pkg.tar.zst -o pkgconf.tar.zst

View File

@@ -0,0 +1,121 @@
#!/usr/bin/env python
# encoding: utf-8
# clang_format - `./waf format` runs clang-format -i in parallel on opted-in source files.
# Copyright (C) 2026 Xash3D FWGS contributors
#
# To override clang-format binary:
# CLANG_FORMAT=xxx ./waf configure
# CLANG_FORMAT=xxx ./waf format # for post-configure override, won't check for minimum version
#
# Opt task gens in:
# bld.stlib(features='format', source='...', target='...', ...)
from waflib import Build, Logs, Task, TaskGen, Utils
import os
import re
import subprocess
# Enforce minimum major version
CLANG_FORMAT_MIN_MAJOR = None
# File extensions to format
CLANG_FORMAT_EXTS = ['.c', '.cpp', '.h']
def probe_major(binary):
try:
out = subprocess.check_output([binary, '--version'], stderr=subprocess.DEVNULL).decode('utf-8', 'replace')
except (OSError, subprocess.CalledProcessError):
return None
m = re.search(r'version\s+(\d+)', out)
return int(m.group(1)) if m else None
def configure(conf):
conf.find_program('clang-format', var='CLANG_FORMAT', mandatory=False)
if conf.env.CLANG_FORMAT and CLANG_FORMAT_MIN_MAJOR is not None:
major = probe_major(conf.env.CLANG_FORMAT[0])
if major is None:
Logs.warn('clang-format is not an executable or invalid')
return
if major < CLANG_FORMAT_MIN_MAJOR:
Logs.warn('clang-format %d found, need >= %d, format command won\'t be available' % (major, CLANG_FORMAT_MIN_MAJOR))
conf.env.CLANG_FORMAT = []
return
conf.msg('Checking clang-format version', major)
class ClangFormatTask(Task.Task):
color = 'PINK'
always_run = True
binary = 'clang-format' # just a default, will be overriden
def keyword(self):
return 'Formatting'
def run(self):
return self.exec_command([self.binary, '-i', self.inputs[0].abspath()], env=self.env.env or None)
# need this because if we add headers to to `source`
# we can catch it in `format` feature, as nothing else
# catches .h
@TaskGen.extension('.h')
def format_stub(self, node):
pass
@TaskGen.feature('format')
def make_format_tasks(self):
if self.bld.cmd != 'format':
return
for n in self.to_nodes(Utils.to_list(getattr(self, 'source', []))):
if n.suffix() in CLANG_FORMAT_EXTS:
self.create_task('ClangFormatTask', n)
self.source = [] # remove anything build related
class FormatContext(Build.BuildContext):
cmd = 'format'
fun = 'build'
def execute(self):
self.restore()
if not self.all_envs:
self.load_envs()
self.recurse([self.run_dir])
binary = os.environ.get('CLANG_FORMAT') or (self.env.CLANG_FORMAT and self.env.CLANG_FORMAT[0])
if not binary:
self.fatal('format: clang-format not configured')
ClangFormatTask.binary = binary
# loops below filter taskgens by target name, format feature and by c-f task class
wanted = set(self.targets.split(',')) if self.targets else None
tasks = []
for group in self.groups:
for tg in group:
if not isinstance(tg, TaskGen.task_gen):
continue
if 'format' not in Utils.to_list(tg.features):
continue
if wanted and getattr(tg, 'name', None) not in wanted:
continue
tg.post()
for t in tg.tasks:
if isinstance(t, ClangFormatTask):
tasks.append(t)
if not tasks:
self.fatal("format: no sources matched (need features='format' on a task gen)")
Logs.info('format: %s on %d file(s)' % (binary, len(tasks)))
self.progress_bar = 0 # FIXME: doesn't work with progress bar for some reason
self.groups = [tasks]
self.cur_group = 0
self.targets = ''
self.compile()

View File

@@ -19,7 +19,7 @@ def build(bld):
bld.program(source = bld.path.ant_glob('*.c'), bld.program(source = bld.path.ant_glob('*.c'),
target = 'mdldec', target = 'mdldec',
includes = '.', includes = '.',
use = 'engine_includes public M werror', use = 'engine_includes public M werror yy_thunks',
install_path = bld.env.BINDIR, install_path = bld.env.BINDIR,
subsystem = bld.env.CONSOLE_SUBSYSTEM subsystem = bld.env.CONSOLE_SUBSYSTEM
) )

View File

@@ -1,4 +1,5 @@
#include <dlfcn.h> #include <dlfcn.h>
#include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@@ -6,12 +7,13 @@
#error #error
#endif #endif
typedef int (*FuzzFunc)(const char *Data, size_t Size); typedef int (*FuzzFunc)(const uint8_t *Data, size_t Size);
void *handle = NULL; static void *handle = NULL;
FuzzFunc f = NULL; static FuzzFunc f = NULL;
int LLVMFuzzerTestOneInput( const char *Data, size_t Size ) int LLVMFuzzerTestOneInput( const uint8_t *Data, size_t Size );
int LLVMFuzzerTestOneInput( const uint8_t *Data, size_t Size )
{ {
if( !handle ) if( !handle )
handle = dlopen( LIB, RTLD_NOW ); handle = dlopen( LIB, RTLD_NOW );

View File

@@ -37,4 +37,6 @@ def build(bld):
add_runner_target(bld, 'libxash.so', 'Image_LoadPNG') add_runner_target(bld, 'libxash.so', 'Image_LoadPNG')
add_runner_target(bld, 'libxash.so', 'Image_LoadDDS') add_runner_target(bld, 'libxash.so', 'Image_LoadDDS')
add_runner_target(bld, 'libxash.so', 'Image_LoadTGA') add_runner_target(bld, 'libxash.so', 'Image_LoadTGA')
add_runner_target(bld, 'libxash.so', 'Image_LoadSPR')
add_runner_target(bld, 'libxash.so', 'Mod_LoadModel') add_runner_target(bld, 'libxash.so', 'Mod_LoadModel')
add_runner_target(bld, 'libxash.so', 'Mod_LoadSpriteModel')

View File

@@ -12,7 +12,7 @@ def build(bld):
bld.program(source = bld.path.ant_glob('*.c'), bld.program(source = bld.path.ant_glob('*.c'),
target = 'xar', target = 'xar',
includes = '.', includes = '.',
use = 'public filesystem_includes werror', use = 'public filesystem_includes werror yy_thunks',
rpath = bld.env.DEFAULT_RPATH, rpath = bld.env.DEFAULT_RPATH,
install_path = bld.env.BINDIR, install_path = bld.env.BINDIR,
subsystem = bld.env.CONSOLE_SUBSYSTEM subsystem = bld.env.CONSOLE_SUBSYSTEM

View File

@@ -0,0 +1,91 @@
/*
config.c - .clang-format extension parser
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 <string.h>
#include "crtlib.h"
#include "xcf.h"
#define XCF_KEY "XashCollapseParens"
/*
============
ConfigDefault
============
*/
static void ConfigDefault( xcf_config_t *c )
{
c->collapse_parens = false;
}
/*
============
SkipWhitespace
============
*/
static const char *SkipWhitespace( const char *p, const char *end )
{
for( ; p < end && ( *p == ' ' || *p == '\t' ); p++ )
;
return p;
}
/*
============
ParseConfigText
Parse extension keys out of a YAML text body.
We only look at lines that begin (after optional whitespace) with `#`.
============
*/
qboolean ParseConfigText( const char *text, xcf_config_t *cfg )
{
ConfigDefault( cfg );
if( !text )
return true;
for( const char *p = text; *p; )
{
const char *end = Q_strchrnul( p, '\n' );
const char *q;
const size_t klen = sizeof( XCF_KEY ) - 1;
q = SkipWhitespace( p, end );
if( q < end && *q == '#' )
{
q = SkipWhitespace( q + 1, end );
if( end - q >= klen && memcmp( q, XCF_KEY, klen ) == 0 )
{
q = SkipWhitespace( q + klen, end );
if( q < end && *q == ':' )
{
q = SkipWhitespace( q + 1, end );
if( end - q >= 4 && memcmp( q, "true", 4 ) == 0 )
cfg->collapse_parens = true;
else if( end - q >= 5 && memcmp( q, "false", 5 ) == 0 )
cfg->collapse_parens = false;
}
}
}
if( !*end )
break;
p = end + 1;
}
return true;
}

View File

@@ -0,0 +1,402 @@
/*
main.c - xash-clang-format wrapper entry point
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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/stat.h>
#include "crtlib.h"
#include "xcf.h"
#ifndef XCF_CLANG_FORMAT_DEFAULT
#define XCF_CLANG_FORMAT_DEFAULT "clang-format"
#endif
// TODO: move to custom config keys
#define XCF_MIN_CLANG_FORMAT_MAJOR 17
static const char *g_clang_format = NULL;
/*
============
ResolveClangFormat
============
*/
static const char *ResolveClangFormat( void )
{
const char *e = getenv( "XASH_CLANG_FORMAT_BIN" );
if( !COM_StringEmptyOrNULL( e ))
return e;
return XCF_CLANG_FORMAT_DEFAULT;
}
/*
============
IsFileArg
Attempt to distinguish files from other args.
But to keep parsing args simple, it will parse value as --key value as
filename. :(
============
*/
static qboolean IsFileArg( const char *a )
{
if( COM_StringEmptyOrNULL( a ))
return false;
if( a[0] == '-' )
return false;
return true;
}
/*
============
ReadTheFile
============
*/
static qboolean ReadTheFile( const char *path, xcf_buf_t *out )
{
FILE *f = fopen( path, "rb" );
if( !f )
return false;
char buf[8192];
size_t n;
while(( n = fread( buf, 1, sizeof( buf ), f )) > 0 )
{
if( !BufPutMem( out, buf, n ))
{
fclose( f );
return false;
}
}
fclose( f );
return true;
}
/*
============
WriteTheFile
============
*/
static qboolean WriteTheFile( const char *path, const char *data, size_t len )
{
FILE *f = fopen( path, "wb" );
if( !f )
return false;
if( fwrite( data, 1, len, f ) != len )
{
fclose( f );
return false;
}
if( fclose( f ) != 0 )
return false;
return true;
}
/*
============
FindConfig
============
*/
static char *FindConfig( const char *startdir )
{
char dir[MAX_SYSPATH];
if( startdir )
{
Q_strncpy( dir, startdir, sizeof( dir ));
}
else if( !getcwd( dir, sizeof( dir )))
{
return NULL;
}
for( ;; )
{
char path[MAX_SYSPATH];
struct stat st;
char *slash;
Q_snprintf( path, sizeof( path ), "%s/.clang-format", dir );
if( stat( path, &st ) == 0 && S_ISREG( st.st_mode ))
{
size_t len = Q_strlen( path ) + 1;
char *r = malloc( len );
if( r )
Q_strncpy( r, path, len );
return r;
}
slash = Q_strrchr( dir, '/' );
if( !slash || slash == dir )
break;
*slash = 0;
}
return NULL;
}
/*
============
LoadConfig
============
*/
static qboolean LoadConfig( const char *path, xcf_config_t *cfg )
{
xcf_buf_t buf = { 0 };
qboolean ok = false;
if( path && ReadTheFile( path, &buf ) && BufPutChar( &buf, 0 ))
ok = ParseConfigText( buf.data, cfg );
else
ParseConfigText( "", cfg );
BufFree( &buf );
return ok;
}
/*
============
Emit
Transform pass on buffer and output to file
============
*/
static qboolean Emit( const xcf_config_t *cfg, const xcf_buf_t *raw, FILE *dst, const char *write_path )
{
xcf_buf_t out = { 0 };
const char *data;
size_t len;
if( cfg->collapse_parens && Transform( raw->data, raw->len, &out ))
{
data = out.data;
len = out.len;
}
else
{
data = raw->data;
len = raw->len;
}
qboolean ok = true;
if( write_path )
{
if( !WriteTheFile( write_path, data, len ))
{
fprintf( stderr, "xash-clang-format: cannot write %s: %s\n", write_path, strerror( errno ));
ok = false;
}
}
else if( len )
{
if( fwrite( data, 1, len, dst ) != len )
ok = false;
}
BufFree( &out );
return ok;
}
/*
============
PipeMode
Process stdout (when clang-format called without -i)
============
*/
static int PipeMode( int argc, char **argv, const xcf_config_t *cfg )
{
char **fwd = malloc( sizeof( char * ) * ( argc + 1 ));
if( !fwd )
return 1;
fwd[0] = (char *)g_clang_format;
for( int i = 1; i < argc; i++ )
fwd[i] = argv[i];
fwd[argc] = NULL;
xcf_buf_t raw = { 0 };
int rc = RunCapture( fwd, &raw, false );
free( fwd );
if( rc != 0 )
{
// pass through whatever clang-format produced
if( raw.len )
fwrite( raw.data, 1, raw.len, stdout );
BufFree( &raw );
return rc < 0 ? 1 : rc;
}
rc = Emit( cfg, &raw, stdout, NULL ) ? 0 : 1;
BufFree( &raw );
return rc;
}
/*
============
PostProcessFile
Read `path`, run the paren-collapse pass, write result back.
============
*/
static qboolean PostProcessFile( const char *path )
{
xcf_buf_t in = { 0 };
xcf_buf_t out = { 0 };
qboolean ok = false;
if( ReadTheFile( path, &in ) && Transform( in.data, in.len, &out ))
ok = WriteTheFile( path, out.data, out.len );
BufFree( &in );
BufFree( &out );
return ok;
}
/*
============
InPlaceMode
Post-process mode, when clang-format is called with -i
============
*/
static int InPlaceMode( int argc, char **argv, const xcf_config_t *cfg )
{
char **fwd = malloc( sizeof( char * ) * ( argc + 1 ));
if( !fwd )
return 1;
fwd[0] = (char *)g_clang_format;
for( int i = 1; i < argc; i++ )
fwd[i] = argv[i];
fwd[argc] = NULL;
xcf_buf_t sink = { 0 };
int rc = RunCapture( fwd, &sink, false );
BufFree( &sink );
free( fwd );
if( rc != 0 )
return rc < 0 ? 1 : rc;
if( !cfg->collapse_parens )
return 0;
for( int i = 1; i < argc; i++ )
{
if( !IsFileArg( argv[i] ))
continue;
if( !PostProcessFile( argv[i] ))
{
fprintf( stderr, "xash-clang-format: post-process failed for %s\n", argv[i] );
rc = 1;
}
}
return rc;
}
static void Usage( FILE *dst )
{
fprintf( dst,
"xash-clang-format: clang-format wrapper with xash3d-fwgs extensions\n"
"\n"
"Usage: xash-clang-format [clang-format options] [<file> ...]\n"
" xash-clang-format -i <file> ...\n"
"\n"
"Most clang-format flags are forwarded as-is but there are limitations:\n"
"* --key value must be passed as --key=value\n"
"\n"
"Use XASH_CLANG_FORMAT_BIN envvar to override the clang-format binary;\n"
" setting it also skips the version probe.\n"
"\n"
"Wrapper's config is shared with clang-format config, but extended keys\n"
" start with comment hash.\n"
"New keys:\n"
" XashCollapseParens, accepts 'true' or 'false'\n" );
}
int main( int argc, char **argv )
{
g_clang_format = ResolveClangFormat();
qboolean has_inplace = false;
qboolean has_version = false;
qboolean has_help = false;
for( int i = 1; i < argc; i++ )
{
if( Q_strcmp( argv[i], "-i" ) == 0 )
has_inplace = true;
else if( Q_strcmp( argv[i], "--version" ) == 0 || Q_strcmp( argv[i], "-version" ) == 0 )
has_version = true;
else if( Q_strcmp( argv[i], "--help" ) == 0 || Q_strcmp( argv[i], "-h" ) == 0 || Q_strcmp( argv[i], "-help" ) == 0 )
has_help = true;
}
if( has_help )
{
Usage( stdout );
return 0;
}
if( has_version )
{
// transparent passthrough so tools that scrape `clang-format --version`
// (e.g. git clang-format auto-detection) keep working
execlp( g_clang_format, g_clang_format, "--version", (char *)NULL );
fprintf( stderr, "xash-clang-format: cannot exec %s: %s\n", g_clang_format, strerror( errno ));
return 127;
}
// if caller sets XASH_CLANG_FORMAT_BIN — they own version compatibility
// so skip the probe (lets ./waf format do the check once for parallel invocation)
if( COM_StringEmptyOrNULL( getenv( "XASH_CLANG_FORMAT_BIN" )))
{
int major = 0;
if( !ClangFormatVersion( g_clang_format, &major ))
{
fprintf( stderr, "xash-clang-format: cannot probe clang-format version (binary: %s)\n", g_clang_format );
return 2;
}
if( major < XCF_MIN_CLANG_FORMAT_MAJOR )
{
fprintf( stderr, "xash-clang-format: clang-format %d found, need >= %d\n",
major, XCF_MIN_CLANG_FORMAT_MAJOR );
return 2;
}
}
xcf_config_t cfg;
char *cfgpath = FindConfig( NULL );
LoadConfig( cfgpath, &cfg );
free( cfgpath );
return has_inplace ? InPlaceMode( argc, argv, &cfg ) : PipeMode( argc, argv, &cfg );
}

View File

@@ -0,0 +1,99 @@
/*
proc.c - subprocess utilities
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 <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include "xcf.h"
/*
============
RunCapture
Spawn argv as a child, capture stdout into `out`.
============
*/
int RunCapture( char *const argv[], xcf_buf_t *out, qboolean silent_stderr )
{
int p[2];
pid_t pid;
char buf[8192];
int status;
if( pipe( p ) < 0 )
return -1;
pid = fork();
if( pid < 0 )
{
close( p[0] );
close( p[1] );
return -1;
}
if( pid == 0 )
{
close( p[0] );
dup2( p[1], STDOUT_FILENO );
close( p[1] );
if( silent_stderr )
{
int devnull = open( "/dev/null", O_WRONLY );
if( devnull >= 0 )
{
dup2( devnull, STDERR_FILENO );
close( devnull );
}
}
execvp( argv[0], argv );
_exit( 127 );
}
close( p[1] );
for( ;; )
{
ssize_t r = read( p[0], buf, sizeof( buf ));
if( r < 0 )
{
if( errno == EINTR )
continue;
break;
}
if( r == 0 )
break;
if( !BufPutMem( out, buf, r ))
{
close( p[0] );
kill( pid, SIGKILL );
waitpid( pid, NULL, 0 );
return -1;
}
}
close( p[0] );
if( waitpid( pid, &status, 0 ) < 0 )
return -1;
// heh wife excited
if( WIFEXITED( status ))
return WEXITSTATUS( status );
return -1;
}

View File

@@ -0,0 +1,77 @@
#include <stdio.h>
#include "xcf.h"
static int run_case( const char *label, const char *yaml, qboolean expect )
{
xcf_config_t cfg;
ParseConfigText( yaml, &cfg );
if( cfg.collapse_parens != expect )
{
fprintf( stderr, "[%s] collapse_parens=%d, expected %d\n yaml:\n%s\n",
label, cfg.collapse_parens, expect, yaml );
return 0;
}
return 1;
}
int main( void )
{
int n = 0;
if( !run_case( "empty", "", false ))
return ++n;
xcf_config_t cfg2;
ParseConfigText( NULL, &cfg2 );
if( cfg2.collapse_parens )
return ++n;
if( !run_case( "explicit true",
"# XashCollapseParens: true\n", true ))
return ++n;
if( !run_case( "explicit false",
"# XashCollapseParens: false\n", false ))
return ++n;
if( !run_case( "leading ws",
" # XashCollapseParens: true\n", true ))
return ++n;
if( !run_case( "tabs",
"#\tXashCollapseParens\t:\ttrue\n", true ))
return ++n;
if( !run_case( "unrelated key",
"# SomeOtherKey: true\nIndentWidth: 4\n", false ))
return ++n;
if( !run_case( "in larger config",
"---\n"
"Language: Cpp\n"
"# XashCollapseParens: true\n"
"IndentWidth: 4\n",
true ))
return ++n;
if( !run_case( "junk value",
"# XashCollapseParens: maybe\n", false ))
return ++n;
if( !run_case( "no colon",
"# XashCollapseParens true\n", false ))
return ++n;
if( !run_case( "last wins",
"# XashCollapseParens: false\n# XashCollapseParens: true\n", true ))
return ++n;
if( !run_case( "no trailing nl",
"# XashCollapseParens: true", true ))
return ++n;
return 0;
}

View File

@@ -0,0 +1,171 @@
#include <stdio.h>
#include <string.h>
#include "xcf.h"
static int run_case( const char *label, const char *in, const char *expect )
{
xcf_buf_t out = { 0 };
int ok;
if( !Transform( in, strlen( in ), &out ))
{
fprintf( stderr, "[%s] Transform returned error\n", label );
BufFree( &out );
return 0;
}
ok = ( out.len == strlen( expect ) && memcmp( out.data, expect, out.len ) == 0 );
if( !ok )
{
fprintf( stderr, "[%s] mismatch\n in: %s\n out: %.*s\n expect: %s\n",
label, in, (int)out.len, out.data, expect );
}
BufFree( &out );
return ok;
}
int main( void )
{
int n = 0;
if( !run_case( "outer parens",
"if( ( a + b ) )\n",
"if(( a + b ))\n" ))
return ++n;
if( !run_case( "triple nesting",
"x = ( ( ( a ) ) );\n",
"x = ((( a )));\n" ))
return ++n;
if( !run_case( "mixed [ )",
"arr[i] )\n",
"arr[i] )\n" ))
return ++n;
if( !run_case( "mixed ) ]",
"f( a ) ];\n",
"f( a ) ];\n" ))
return ++n;
if( !run_case( "newline boundary",
"(\n(\n",
"(\n(\n" ))
return ++n;
if( !run_case( "string literal",
"x = \"( ( hello ) )\";\n",
"x = \"( ( hello ) )\";\n" ))
return ++n;
if( !run_case( "escaped quote in string",
"x = \"a \\\" ( ( b ) ) \";\n",
"x = \"a \\\" ( ( b ) ) \";\n" ))
return ++n;
if( !run_case( "line comment",
"a; // ( ( comment ) )\nb;\n",
"a; // ( ( comment ) )\nb;\n" ))
return ++n;
if( !run_case( "block comment",
"/* ( ( leave\nthis ) ) alone */ x;\n",
"/* ( ( leave\nthis ) ) alone */ x;\n" ))
return ++n;
if( !run_case( "char literal '('",
"if( c == '(' )\n",
"if( c == '(' )\n" ))
return ++n;
if( !run_case( "raw string",
"const char *s = R\"x(( (\nstays )x\";\n",
"const char *s = R\"x(( (\nstays )x\";\n" ))
return ++n;
if( !run_case( "raw string empty delim",
"R\"(( (\n))\"",
"R\"(( (\n))\"" ))
return ++n;
if( !run_case( "ident Rabbit not raw",
"int Rabbit = ( ( 1 ) );\n",
"int Rabbit = (( 1 ));\n" ))
return ++n;
if( !run_case( "u8R raw",
"const auto s = u8R\"(( ( )\";\n",
"const auto s = u8R\"(( ( )\";\n" ))
return ++n;
if( !run_case( "include angle",
"#include <foo.h>\nf( ( x ) );\n",
"#include <foo.h>\nf(( x ));\n" ))
return ++n;
if( !run_case( "tabs between parens",
"f(\t(\tx\t)\t);\n",
"f((\tx\t));\n" ))
return ++n;
if( !run_case( "empty", "", "" ))
return ++n;
if( !run_case( "idempotent",
"f((( x )));\n",
"f((( x )));\n" ))
return ++n;
if( !run_case( "already touching",
"((a))",
"((a))" ))
return ++n;
if( !run_case( "single paren",
"f( a, b )",
"f( a, b )" ))
return ++n;
if( !run_case( "multiple pairs",
"f( ( a ) ) + g( ( b ) );\n",
"f(( a )) + g(( b ));\n" ))
return ++n;
if( !run_case( "L wide string",
"wchar_t *s = L\"( ( x ) )\";\n",
"wchar_t *s = L\"( ( x ) )\";\n" ))
return ++n;
if( !run_case( "u utf16 string",
"auto s = u\"( ( x ) )\";\n",
"auto s = u\"( ( x ) )\";\n" ))
return ++n;
if( !run_case( "U utf32 string",
"auto s = U\"( ( x ) )\";\n",
"auto s = U\"( ( x ) )\";\n" ))
return ++n;
if( !run_case( "u8 utf8 string",
"auto s = u8\"( ( x ) )\";\n",
"auto s = u8\"( ( x ) )\";\n" ))
return ++n;
if( !run_case( "L wide char",
"if( c == L'(' )\n",
"if( c == L'(' )\n" ))
return ++n;
if( !run_case( "u8 char",
"if( c == u8'(' )\n",
"if( c == u8'(' )\n" ))
return ++n;
if( !run_case( "ident L not prefix",
"int callL = ( ( 1 ) );\n",
"int callL = (( 1 ));\n" ))
return ++n;
return 0;
}

View File

@@ -0,0 +1,58 @@
#include <stdio.h>
#include "xcf.h"
static int run_case( const char *label, const char *s, qboolean expect_ok, int expect_major )
{
int major = -1;
qboolean ok = ParseVersion( s, &major );
if( ok != expect_ok )
{
fprintf( stderr, "[%s] ok=%d expected %d (input: %s)\n", label, ok, expect_ok, s ? s : "(null)" );
return 0;
}
if( ok && major != expect_major )
{
fprintf( stderr, "[%s] major=%d expected %d (input: %s)\n", label, major, expect_major, s );
return 0;
}
return 1;
}
int main( void )
{
int n = 0;
if( !run_case( "vanilla 17",
"clang-format version 17.0.6 (Fedora 17.0.6-1.fc40)\n", true, 17 ))
return ++n;
if( !run_case( "ubuntu 18",
"Ubuntu clang-format version 18.1.3 (1ubuntu1)\n", true, 18 ))
return ++n;
if( !run_case( "large major",
"clang-format version 100.0.0\n", true, 100 ))
return ++n;
if( !run_case( "garbage",
"this has no version here\n", false, 0 ))
return ++n;
if( !run_case( "null", NULL, false, 0 ))
return ++n;
if( !run_case( "distro tag with digits",
"Ubuntu99 clang-format version 17.0.6\n", true, 17 ))
return ++n;
if( !run_case( "bare digits",
"foo 42 bar\n", false, 0 ))
return ++n;
if( !run_case( "no version word",
"fake clang-format 19.1.0\n", false, 0 ))
return ++n;
return 0;
}

View File

@@ -0,0 +1,325 @@
/*
transform.c - source post-processing pass
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 <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "xcf.h"
#define XCF_RAW_DELIM_MAX 16 // C++11: 16 chars max in raw-string delimiter
/*
============
BufFree
============
*/
void BufFree( xcf_buf_t *b )
{
free( b->data );
b->data = NULL;
b->len = b->cap = 0;
}
/*
============
BufGrow
============
*/
static qboolean BufGrow( xcf_buf_t *b, size_t need )
{
size_t cap;
char *p;
if( b->len + need <= b->cap )
return true;
cap = b->cap ? b->cap : 256;
while( cap < b->len + need )
cap *= 2;
p = realloc( b->data, cap );
if( !p )
return false;
b->data = p;
b->cap = cap;
return true;
}
/*
============
BufPutChar
============
*/
qboolean BufPutChar( xcf_buf_t *b, char c )
{
if( !BufGrow( b, 1 ))
return false;
b->data[b->len++] = c;
return true;
}
/*
============
BufPutMem
============
*/
qboolean BufPutMem( xcf_buf_t *b, const char *src, size_t n )
{
if( !n )
return true;
if( !BufGrow( b, n ))
return false;
memcpy( b->data + b->len, src, n );
b->len += n;
return true;
}
/*
============
TryCollapse
Collapse spaces between the same type parentheses
============
*/
static void TryCollapse( xcf_buf_t *out, char paren )
{
size_t i = out->len;
while( i > 0 && ( out->data[i - 1] == ' ' || out->data[i - 1] == '\t' ))
i--;
if( i == 0 || i == out->len )
return; // nothing to collapse, or no whitespace between
if( out->data[i - 1] != paren )
return; // different bracket types or different char entirely
out->len = i;
}
/*
============
DetectRaw
Detect C++11 raw string because why not
============
*/
static qboolean DetectRaw( const char *s, size_t len, size_t i, size_t *prefix_end, size_t *paren )
{
size_t j = i, k;
// must not be in the middle of an identifier
if( i > 0 && ( isalnum( s[i - 1] ) || s[i - 1] == '_' ))
return false;
if( j + 2 < len && s[j] == 'u' && s[j + 1] == '8' && s[j + 2] == 'R' )
j += 3;
else if( j + 1 < len && ( s[j] == 'u' || s[j] == 'U' || s[j] == 'L' ) && s[j + 1] == 'R' )
j += 2;
else if( j < len && s[j] == 'R' )
j += 1;
else
return false;
if( j >= len || s[j] != '"' )
return false;
j++; // past the opening quote
*prefix_end = j;
k = j;
while( k < len && s[k] != '(' && k - j < XCF_RAW_DELIM_MAX )
k++;
if( k >= len || s[k] != '(' )
return false;
*paren = k;
return true;
}
enum
{
S_NORMAL,
S_LINE_CMT,
S_BLOCK_CMT,
S_STRING,
S_CHAR,
S_RAW,
};
/*
============
Transform
Parse source, find parens and try collapse them
============
*/
qboolean Transform( const char *in, size_t in_len, xcf_buf_t *out )
{
size_t raw_dlen = 0;
char raw_delim[XCF_RAW_DELIM_MAX + 1];
int state = S_NORMAL;
for( size_t i = 0; i < in_len; i++ )
{
char c1 = in[i];
char c2 = ( i + 1 < in_len ) ? in[i + 1] : 0;
switch( state )
{
case S_NORMAL:
{
size_t pe, pp;
if( c1 == '/' && c2 == '/' )
{
if( !BufPutMem( out, in + i, 2 ))
return false;
i++;
state = S_LINE_CMT;
break;
}
if( c1 == '/' && c2 == '*' )
{
if( !BufPutMem( out, in + i, 2 ))
return false;
i++;
state = S_BLOCK_CMT;
break;
}
if( DetectRaw( in, in_len, i, &pe, &pp ))
{
size_t dl = pp - pe;
if( dl > XCF_RAW_DELIM_MAX )
dl = XCF_RAW_DELIM_MAX;
if( !BufPutMem( out, in + i, pp - i + 1 ))
return false;
memcpy( raw_delim, in + pe, dl );
raw_delim[dl] = 0;
raw_dlen = dl;
i = pp; // sit on the '('
state = S_RAW;
break;
}
if( c1 == '"' )
{
if( !BufPutChar( out, c1 ))
return false;
state = S_STRING;
break;
}
if( c1 == '\'' )
{
if( !BufPutChar( out, c1 ))
return false;
state = S_CHAR;
break;
}
if( c1 == '(' || c1 == ')' )
TryCollapse( out, c1 );
if( !BufPutChar( out, c1 ))
return false;
break;
}
case S_LINE_CMT:
if( !BufPutChar( out, c1 ))
return false;
if( c1 == '\n' )
state = S_NORMAL;
break;
case S_BLOCK_CMT:
if( !BufPutChar( out, c1 ))
return false;
if( c1 == '*' && c2 == '/' )
{
if( !BufPutChar( out, c2 ))
return false;
i++;
state = S_NORMAL;
}
break;
case S_STRING:
if( !BufPutChar( out, c1 ))
return false;
if( c1 == '\\' && c2 )
{
if( !BufPutChar( out, c2 ))
return false;
i++;
}
else if( c1 == '"' )
{
state = S_NORMAL;
}
else if( c1 == '\n' )
{
// unterminated; recover at next line
state = S_NORMAL;
}
break;
case S_CHAR:
if( !BufPutChar( out, c1 ))
return false;
if( c1 == '\\' && c2 )
{
if( !BufPutChar( out, c2 ))
return false;
i++;
}
else if( c1 == '\'' )
{
state = S_NORMAL;
}
else if( c1 == '\n' )
{
state = S_NORMAL;
}
break;
case S_RAW:
if( !BufPutChar( out, c1 ))
return false;
if( c1 == ')'
&& i + 1 + raw_dlen + 1 <= in_len
&& memcmp( in + i + 1, raw_delim, raw_dlen ) == 0
&& in[i + 1 + raw_dlen] == '"' )
{
if( !BufPutMem( out, in + i + 1, raw_dlen + 1 ))
return false;
i += raw_dlen + 1;
state = S_NORMAL;
}
break;
}
}
return true;
}

View File

@@ -0,0 +1,67 @@
/*
version.c - clang-format version probe
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 "crtlib.h"
#include "xcf.h"
/*
============
ParseVersion
Parse the major version number out of a `clang-format --version`
============
*/
qboolean ParseVersion( const char *s, int *major )
{
const char *p;
if( COM_StringEmptyOrNULL( s ) || !major )
return false;
// anchor on "version "
p = Q_strstr( s, "version " );
if( !p )
return false;
p += sizeof( "version " ) - 1;
while( *p == ' ' || *p == '\t' )
p++;
if( *p < '0' || *p > '9' )
return false;
*major = Q_atoi( p );
return true;
}
/*
============
ClangFormatVersion
============
*/
qboolean ClangFormatVersion( const char *exe, int *major )
{
char *argv[] = { (char *)exe, (char *)"--version", NULL };
xcf_buf_t out = { 0 };
qboolean ok = false;
if( !exe || !major )
return false;
if( RunCapture( argv, &out, true ) == 0 && BufPutChar( &out, 0 ))
ok = ParseVersion( out.data, major );
BufFree( &out );
return ok;
}

View File

@@ -0,0 +1,42 @@
#! /usr/bin/env python
# encoding: utf-8
def options(opt):
grp = opt.get_option_group('Utilities options')
grp.add_option('--disable-utils-xcf', action='store_true',
dest='DISABLE_UTILS_XCF', default = False,
help='disable xash-clang-format wrapper utility [default: %(default)s]')
def configure(conf):
# wrapper relies on POSIX, disable on Windows for now
conf.env.DISABLE_UTILS_XCF = conf.options.DISABLE_UTILS_XCF or conf.env.DEST_OS == 'win32'
def build(bld):
if bld.env.DISABLE_UTILS_XCF:
return
bld.stlib(source = bld.path.ant_glob(['*.c', '*.h'], excl='main.c'),
target = 'xcflib',
features = 'format',
includes = '.',
use = 'public werror',
install_path = None)
bld.program(source = 'main.c',
target = 'xash-clang-format',
features = 'format',
use = 'xcflib werror',
install_path = bld.env.BINDIR,
subsystem = bld.env.CONSOLE_SUBSYSTEM)
if bld.env.TESTS:
tests = ['transform', 'config', 'version']
for name in tests:
bld.program(features = 'test format',
source = 'tests/test_%s.c' % name,
target = 'test_xcflib_%s' % name,
includes = '.',
use = 'xcflib werror',
install_path = None)

View File

@@ -0,0 +1,57 @@
/*
xcf.h - clang-format wrapper internals
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 XCF_H
#define XCF_H
#include <stddef.h>
#include "xash3d_types.h"
//
// transform.c
//
typedef struct
{
char *data;
size_t len;
size_t cap;
} xcf_buf_t;
void BufFree( xcf_buf_t *b );
qboolean BufPutChar( xcf_buf_t *b, char c );
qboolean BufPutMem( xcf_buf_t *b, const char *src, size_t n );
qboolean Transform( const char *in, size_t in_len, xcf_buf_t *out );
//
// config.c
//
typedef struct
{
qboolean collapse_parens; // default: false
} xcf_config_t;
qboolean ParseConfigText( const char *text, xcf_config_t *cfg );
//
// version.c
//
qboolean ParseVersion( const char *s, int *major );
qboolean ClangFormatVersion( const char *exe, int *major );
//
// proc.c
//
int RunCapture( char *const argv[], xcf_buf_t *out, qboolean silent_stderr );
#endif // XCF_H

34
wscript
View File

@@ -4,9 +4,13 @@
from waflib import Build, Context, Logs, TaskGen from waflib import Build, Context, Logs, TaskGen
from waflib.Tools import waf_unit_test, c_tests from waflib.Tools import waf_unit_test, c_tests
import clang_format
import sys import sys
import os import os
# we need version 17 minimum
clang_format.CLANG_FORMAT_MIN_MAJOR = 17
VERSION = '0.99' VERSION = '0.99'
APPNAME = 'xash3d-fwgs' APPNAME = 'xash3d-fwgs'
top = '.' top = '.'
@@ -32,6 +36,24 @@ def apply_subsystem_msvc(self):
if 'test' in self.features: if 'test' in self.features:
self.subsystem = self.env.CONSOLE_SUBSYSTEM self.subsystem = self.env.CONSOLE_SUBSYSTEM
@TaskGen.feature('format')
@TaskGen.after_method('make_format_tasks')
def use_xash_clang_format(self):
if self.bld.cmd != 'format':
return
wrapper = self.bld.bldnode.find_node('utils/xash-clang-format/xash-clang-format')
if not wrapper:
self.bld.fatal('no xash-clang-format found in build directory, reconfigure and rebuild with --enable-utils')
return
real_cf = clang_format.ClangFormatTask.binary
for t in self.tasks:
if isinstance(t, clang_format.ClangFormatTask):
t.binary = wrapper.abspath()
t.env.env = dict(os.environ, XASH_CLANG_FORMAT_BIN=real_cf)
class Subproject: class Subproject:
def __init__(self, name, fnFilter = None): def __init__(self, name, fnFilter = None):
self.name = name self.name = name
@@ -85,6 +107,8 @@ SUBDIRS = [
Subproject('stub/server'), Subproject('stub/server'),
Subproject('3rdparty/libbacktrace'), Subproject('3rdparty/libbacktrace'),
Subproject('3rdparty/library_suffix'), Subproject('3rdparty/library_suffix'),
Subproject('3rdparty/yy-thunks'),
Subproject('utils/run-fuzzer', lambda x: x.env.ENABLE_FUZZER),
# disable only by engine feature, makes no sense to even parse subprojects in dedicated mode # disable only by engine feature, makes no sense to even parse subprojects in dedicated mode
Subproject('3rdparty/extras', lambda x: x.env.CLIENT and x.env.DEST_OS != 'android'), Subproject('3rdparty/extras', lambda x: x.env.CLIENT and x.env.DEST_OS != 'android'),
@@ -111,9 +135,9 @@ SUBDIRS = [
Subproject('engine'), # keep latest for static linking Subproject('engine'), # keep latest for static linking
# enabled optionally # enabled optionally
Subproject('utils/mdldec', lambda x: x.env.ENABLE_UTILS), Subproject('utils/mdldec', lambda x: x.env.ENABLE_UTILS),
Subproject('utils/xar', lambda x: x.env.ENABLE_UTILS and x.env.ENABLE_XAR), Subproject('utils/xar', lambda x: x.env.ENABLE_UTILS and x.env.ENABLE_XAR),
Subproject('utils/run-fuzzer', lambda x: x.env.ENABLE_FUZZER), Subproject('utils/xash-clang-format', lambda x: x.env.ENABLE_UTILS),
# enabled on PSVita only # enabled on PSVita only
Subproject('ref/gl/vgl_shim', lambda x: x.env.DEST_OS == 'psvita'), Subproject('ref/gl/vgl_shim', lambda x: x.env.DEST_OS == 'psvita'),
@@ -235,7 +259,7 @@ def configure(conf):
if conf.env.COMPILER_CC == 'msvc': if conf.env.COMPILER_CC == 'msvc':
conf.load('msvc_pdb') conf.load('msvc_pdb')
conf.load('msvs subproject clang_compilation_database strip_on_install waf_unit_test enforce_pic force_32bit ninja') conf.load('msvs subproject clang_compilation_database strip_on_install waf_unit_test enforce_pic force_32bit ninja clang_format')
conf.env.MSVC_SUBSYSTEM = 'WINDOWS' conf.env.MSVC_SUBSYSTEM = 'WINDOWS'
conf.env.CONSOLE_SUBSYSTEM = 'CONSOLE' conf.env.CONSOLE_SUBSYSTEM = 'CONSOLE'
@@ -341,6 +365,7 @@ def configure(conf):
'-Werror=duplicated-cond', '-Werror=duplicated-cond',
'-Werror=format=2', '-Werror=format=2',
'-Werror=free-nonheap-object', '-Werror=free-nonheap-object',
'-Werror=init-self',
'-Werror=implicit-fallthrough=2', '-Werror=implicit-fallthrough=2',
'-Werror=logical-op', '-Werror=logical-op',
'-Werror=nonnull', '-Werror=nonnull',
@@ -361,7 +386,6 @@ def configure(conf):
# unstable diagnostics, may cause false positives # unstable diagnostics, may cause false positives
'-Walloc-zero', '-Walloc-zero',
'-Winit-self',
'-Wmisleading-indentation', '-Wmisleading-indentation',
'-Wmismatched-dealloc', '-Wmismatched-dealloc',
'-Wstringop-overflow', '-Wstringop-overflow',