diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml index e53a8b16..37ff641f 100644 --- a/.github/workflows/c-cpp.yml +++ b/.github/workflows/c-cpp.yml @@ -48,11 +48,11 @@ jobs: UPLOADTOOL_ISPRERELEASE: true steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: submodules: recursive - name: Checkout xash-extras - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: repository: FWGS/xash-extras path: xash-extras @@ -66,7 +66,7 @@ jobs: - name: Upload engine (prereleases) run: bash scripts/continious_upload.sh artifacts/* - name: Upload engine (artifacts) - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: artifact-${{ matrix.targetos }}-${{ matrix.targetarch }} path: artifacts/* diff --git a/.gitignore b/.gitignore index fe2a728f..58eb4a76 100644 --- a/.gitignore +++ b/.gitignore @@ -331,4 +331,5 @@ __pycache__ .vscode/* *.code-workspace .history/* -.cache/* \ No newline at end of file +.cache/* +enc_temp_folder/ diff --git a/.gitmodules b/.gitmodules index f281fbd6..e2ac7c73 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,15 +1,21 @@ [submodule "mainui"] - path = mainui + path = 3rdparty/mainui url = https://github.com/FWGS/mainui_cpp [submodule "ref_gl/nanogl"] - path = ref_gl/nanogl + path = 3rdparty/nanogl url = https://github.com/FWGS/nanogl [submodule "ref_gl/gl-wes-v2"] - path = ref_gl/gl-wes-v2 + path = 3rdparty/gl-wes-v2 url = https://github.com/FWGS/gl-wes-v2 -[submodule "vgui-dev"] - path = vgui-dev - url = https://github.com/FWGS/vgui-dev [submodule "ref_gl/gl4es"] - path = ref_gl/gl4es + path = 3rdparty/gl4es/gl4es url = https://github.com/ptitSeb/gl4es +[submodule "vgui_support"] + path = 3rdparty/vgui_support + url = https://github.com/FWGS/vgui_support +[submodule "opus"] + path = 3rdparty/opus/opus + url = https://github.com/xiph/opus +[submodule "3rdparty/xash-extras"] + path = 3rdparty/extras/xash-extras + url = https://github.com/FWGS/xash-extras diff --git a/3rdparty/extras/wscript b/3rdparty/extras/wscript new file mode 100644 index 00000000..9d249bac --- /dev/null +++ b/3rdparty/extras/wscript @@ -0,0 +1,29 @@ +#! /usr/bin/env python +# encoding: utf-8 + +import os + +def options(opt): + pass + +def configure(conf): + if not conf.path.find_dir('xash-extras'): + conf.fatal('Can\'t find xash-extras submodule.') + return + + conf.load('zip') + +def build(bld): + srcdir = bld.path.find_dir('xash-extras') + + if bld.env.DEST_OS in ['android']: + install_path = bld.env.PREFIX + else: + install_path = os.path.join(bld.env.SHAREDIR, bld.env.GAMEDIR) + + bld(features='zip', + name = 'extras.pk3', + files = srcdir.ant_glob('**/*'), + relative_to = srcdir, + compresslevel = 0, + install_path = install_path) diff --git a/3rdparty/extras/xash-extras b/3rdparty/extras/xash-extras new file mode 160000 index 00000000..e16b9826 --- /dev/null +++ b/3rdparty/extras/xash-extras @@ -0,0 +1 @@ +Subproject commit e16b9826c2a0960a537139737241f498f9c0e938 diff --git a/ref_gl/gl-wes-v2 b/3rdparty/gl-wes-v2 similarity index 100% rename from ref_gl/gl-wes-v2 rename to 3rdparty/gl-wes-v2 diff --git a/3rdparty/gl4es/gl4es b/3rdparty/gl4es/gl4es new file mode 160000 index 00000000..5c28420a --- /dev/null +++ b/3rdparty/gl4es/gl4es @@ -0,0 +1 @@ +Subproject commit 5c28420a384c93345a7a5d060a56a0de5f2ac871 diff --git a/3rdparty/gl4es/wscript b/3rdparty/gl4es/wscript new file mode 100644 index 00000000..cad76650 --- /dev/null +++ b/3rdparty/gl4es/wscript @@ -0,0 +1,24 @@ +#! /usr/bin/env python +# encoding: utf-8 + +import os + +def options(opt): + pass + +def configure(conf): + if not conf.path.find_dir('gl4es') or not conf.path.find_dir('gl4es/src'): + conf.fatal('Can\'t find gl4es submodule. Run `git submodule update --init --recursive`.') + return + +def build(bld): + gl4es_srcdir = bld.path.find_node('gl4es/src') + + bld.stlib(source = gl4es_srcdir.ant_glob(['gl/*.c', 'gl/*/*.c', 'glx/hardext.c']), + target = 'gl4es', + features = 'c', + includes = ['gl4es/src', 'gl4es/src/gl', 'gl4es/src/glx', 'gl4es/include'], + defines = ['NOX11', 'NO_GBM', 'NO_INIT_CONSTRUCTOR', 'DEFAULT_ES=2', 'NOEGL', 'EXTERNAL_GETPROCADDRESS=GL4ES_GetProcAddress', 'NO_LOADER', 'STATICLIB'], + cflags = ['-w', '-fvisibility=hidden', '-std=gnu99'], + subsystem = bld.env.MSVC_SUBSYSTEM, + export_includes = '.') diff --git a/3rdparty/mainui b/3rdparty/mainui new file mode 160000 index 00000000..0f31c646 --- /dev/null +++ b/3rdparty/mainui @@ -0,0 +1 @@ +Subproject commit 0f31c646d623d75b46a9b16f887ac29a167319a3 diff --git a/ref_gl/nanogl b/3rdparty/nanogl similarity index 100% rename from ref_gl/nanogl rename to 3rdparty/nanogl diff --git a/3rdparty/opus/opus b/3rdparty/opus/opus new file mode 160000 index 00000000..997fdf54 --- /dev/null +++ b/3rdparty/opus/opus @@ -0,0 +1 @@ +Subproject commit 997fdf54e781ae1c04dee42018f35388a04fe483 diff --git a/3rdparty/opus/wscript b/3rdparty/opus/wscript new file mode 100644 index 00000000..1f6c9af4 --- /dev/null +++ b/3rdparty/opus/wscript @@ -0,0 +1,40 @@ +#! /usr/bin/env python +# encoding: utf-8 + +import os + +def options(opt): + pass + +def configure(conf): + if not conf.path.find_dir('opus') or not conf.path.find_dir('opus/src'): + conf.fatal('Can\'t find opus submodule. Run `git submodule update --init --recursive`.') + return + + # TODO: ARM/x86 intrinsics detection + # TODO: maybe call autotools/cmake/meson instead? + +def build(bld): + sources = bld.path.ant_glob([ + 'opus/src/*.c', + 'opus/celt/*.c', + 'opus/silk/*.c', + 'opus/silk/float/*.c' + ], excl = [ + 'opus/src/repacketizer_demo.c', + 'opus/src/opus_demo.c', + 'opus/src/opus_compare.c', + 'opus/celt/opus_custom_demo.c' + ]) + includes = ['opus/include/', 'opus/celt/', 'opus/silk/', 'opus/silk/float/'] + defines = ['USE_ALLOCA', 'OPUS_BUILD', 'FLOAT_APPROX', 'PACKAGE_VERSION="1.3.1"', 'CUSTOM_MODES'] + + bld.stlib( + source = sources, + target = 'opus', + features = 'c', + includes = includes, + defines = defines, + subsystem = bld.env.MSVC_SUBSYSTEM, + export_includes = ['opus/include/'] + ) diff --git a/3rdparty/vgui_support b/3rdparty/vgui_support new file mode 160000 index 00000000..63c134f1 --- /dev/null +++ b/3rdparty/vgui_support @@ -0,0 +1 @@ +Subproject commit 63c134f188e7c0891927f5a4149f4444b43b0be8 diff --git a/Documentation/bug-compatibility.md b/Documentation/bug-compatibility.md new file mode 100644 index 00000000..27ac9849 --- /dev/null +++ b/Documentation/bug-compatibility.md @@ -0,0 +1,17 @@ +# Bug-compatibility in Xash3D FWGS + +Xash3D FWGS has special mode for games that rely on original engine bugs. + +In this mode, we emulate the behaviour of selected functions that may help running mods relying on engine bugs, but enabling them by default may break majority of other games. + +At this time, we only have implemented GoldSrc bug-compatibility. It can be enabled with `-bugcomp` command line switch. + +## GoldSrc bug-compatibility + +### Emulated bugs + +* `pfnPEntityOfEntIndex` in GoldSrc returns NULL for last player due to incorrect player index comparison + +### Games and mods that require this + +* Counter-Strike: Condition Zero - Deleted Scenes diff --git a/Documentation/cross-compiling-for-windows-with-wine.md b/Documentation/cross-compiling-for-windows-with-wine.md new file mode 100644 index 00000000..5993e301 --- /dev/null +++ b/Documentation/cross-compiling-for-windows-with-wine.md @@ -0,0 +1,9 @@ +# Cross-compiling for Windows with Wine + +This can be useful to test engine in Wine without using virtual machines or dual-booting to Windows. + +0. Clone and install https://github.com/mstorsjo/msvc-wine (you can skip CMake part) +1. Set environment variable MSVC_WINE_PATH to the path to installed MSVC toolchain +2. Pre-load wine: `wineserver -k; wineserver -p; wine64 wineboot` +3. Run `./waf configure -T --enable-wine-msvc --sdl2=../SDL2_VC`. Configuration step will take more time than usual. +4. .. other typical steps to build from console ... diff --git a/Documentation/entity-tools.md b/Documentation/entity-tools.md new file mode 100644 index 00000000..8a8a16cf --- /dev/null +++ b/Documentation/entity-tools.md @@ -0,0 +1,150 @@ +# There are few new commands availiable in xash3d fork: + +## Commands: +### ent_create +Create entity with specified classname and key/values + +`ent_create ...` + +for example: + +`ent_create monster_zombie targetname zomb1` + +after creating entity, ent_last_xxx cvars are set to new entity and ent_last_cb called, look at ent_getvars description + +### ent_fire + +Make some actions on entity + +`ent_fire ` +Availiavle commands: +* Set fields (Only set entity field, does not call any functions): + * health + * gravity + * movetype + * solid + * rendermode + * rendercolor (vector) + * renderfx + * renderamt + * hullmin (vector) + * hullmax (vector) +* Actions + * rename: set entity targetname + * settarget: set entity target (only targetnames) + * setmodel: set entity model (does not update) + * set: set key/value by server library + * See game FGD to get list. + * command takes two arguments + * touch: touch entity by current player. + * use: use entity by current player. + * movehere: place entity in player fov. + * drop2floor: place entity to nearest floor surface + * moveup: move entity to 25 units up + * moveup (value): move by y axis relatively to specified value +* Flags (Set/clear specified flag bit, arg is bit number): + * setflag + * clearflag + * setspawnflag + * clearspawnflag + +### ent_info +Print information about entity by identificator + +`ent_info ` + +### ent_getvars +Set client cvars containing entity information (useful for [[Scripting]]) and call ent_last_cb + +`ent_getvars ` + +These cvars are set: +``` +ent_last_name +ent_last_num +ent_last_inst +ent_last_origin +ent_last_class +``` + +### ent_list +Print short information about antities, filtered by pattern + +`ent_list ` + +## Syntax description + +### \ + +* !cross: entity under aim +* Instance code: !\_\ + * set by ent_getvars command +* Entity index +* targetname pattern + +### \ + +Pattern is like identificator, but may filter many entities by classname + +### (vector) + +used by ent_fire command. vector means three float values, entered without quotes + +### key/value + +All entities parameters may be set by specifiing key and value strings. + +Originally, this mechanizm is used in map/bsp format, but it can be used in enttools too. + +Keys and values are passed to server library and processed by entity keyvalue function, setting edict and entity owns parameters. + +If value contains spaces, it must be put in quotes: + +`ent_fire !cross set origin "0 0 0"` + +## Using with scripting + +ent_create and ent_getvars commands are setting cvars on client + +It can be used with ent_last_cb alias that is executed after setting cvars. + +Simple example: + +``` +ent_create weapon_c4 +alias ent_last_cb "ent_fire \$ent_last_inst use" +``` + +Use weapon_c4 after creating it. + +Note that you cannot use many dfferent callbacks at the same time. + +You can set entity name by by pattern and create special script, contatning all callbacks. + +Example: + +example.cfg +``` +alias ent_last_cb exec entity_cb.cfg +ent create \ targetname my_ent1_$name +ent_create \ targetname my_ent2_$name +``` +entity_cb.cfg +``` +if $ent_last_name == my_ent1_$name +:(ent1 actions) +if $ent_last_name == my_ent2_$name +:(ent2 actions) +``` +Note that scripting cannot be blocking. You cannot wait for server answer and continue. But you can use small scripts, connected with ent_last_cb command. The best usage is user interaction. You can add touch buttons to screen or call user command menu actions by callbacks. +## Server side + +To enable entity tools on server, set sv_enttools_enable to 1 + +To change maximum number of entities, touched by ent_fire, change sv_enttools_maxfire to required number. + +To enable actions on players, set sv_enttools_players to 1. + +To enable entity tools for player by nickname, set sv_enttools_godplayer to nickname. Useful to temporary enable from rcon. + +To prevent crash on some actions, set host_mapdesign_fatal to 0 \ No newline at end of file diff --git a/Documentation/not-supported-mod-list-and-reasons-why.md b/Documentation/not-supported-mod-list-and-reasons-why.md index 89f834cc..f2ec68a2 100644 --- a/Documentation/not-supported-mod-list-and-reasons-why.md +++ b/Documentation/not-supported-mod-list-and-reasons-why.md @@ -7,6 +7,5 @@ |Counter Strike: Condition Zero |The latest steam release |Uses vgui2 library which xash3d does not support |Some work on vgui2 support was made here: https://github.com/FWGS/xash3d/tree/vinterface |Counter Strike: Condition Zero - Deleted scenes |The latest steam release |Uses vgui2 library which xash3d does not support |Some work on vgui2 support was made here: https://github.com/FWGS/xash3d/tree/vinterface |Day of Defeat |The latest steam release |Uses vgui2 library which xash3d does not support |Some work on vgui2 support was made here: https://github.com/FWGS/xash3d/tree/vinterface -|Sven-Coop |5.0+ |Uses filesystem_stdio library which xash3d does not support |filesystem_stdio replacement already was made: https://github.com/FWGS/filesystem_stdio_xash -|XDM |3.0.4.0 | | +|Sven-Coop |5.0+ |Uses custom GoldSrc engine | diff --git a/Documentation/opensource-mods.md b/Documentation/opensource-mods.md index 70e666e8..43c36958 100644 --- a/Documentation/opensource-mods.md +++ b/Documentation/opensource-mods.md @@ -17,13 +17,16 @@ Mirrored on github - https://github.com/JoelTroch/am_src_rebirth Mirrored on github - https://github.com/nekonomicon/BattleGrounds ## Bubblemod -Download page on official site - http://www.bubblemod.org/dl_default.php +Download page on official site - http://www.bubblemod.org/dl_default.php (dead link!) + +Mirrored on github - https://github.com/HLSources/BubbleMod ## Chicken Fortress Official github repository - https://github.com/CKFDevPowered/CKF3Alpha ## Cold Ice Version 1.9 is available on ModDB - https://www.moddb.com/mods/cold-ice/downloads/cold-ice-sdk + Version 1.9 mirrored on github - https://github.com/solidi/hl-mods/tree/master/ci ## Cold Ice Ressurection @@ -42,11 +45,13 @@ Official github repository - https://github.com/adslbarxatov/xash3d-for-ESHQ Available on GamerLab - http://gamer-lab.com/eng/code_mods_goldsrc/Half-Life_2D_(Flat-Life) ## Gang Wars -Mirrored on github - https://github.com/hammermaps/gw1.45src +Mirrored on github - https://github.com/nekonomicon/gw1.45src ## Go-mod 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 + ## GT mod Available on GamerLab - http://gamer-lab.com/eng/code_mods_goldsrc/GT_mod_(Polnie_ishodniki) @@ -63,10 +68,10 @@ Available on ModDB - https://www.moddb.com/mods/half-life-echoes Available on ModDB - https://www.moddb.com/mods/half-life-expanded-arsenal ## Half-Life: Gravgun mod -Branch **gravgun** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/gravgun +Branch **gravgun** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/gravgun ## Half-Life: Invasion -official github repository - https://github.com/jlecorre/hlinvasion +Official github repository - https://github.com/jlecorre/hlinvasion ## Half-Life: Pong Mirrored on github - https://github.com/solidi/hl-mods/tree/master/pong @@ -105,7 +110,7 @@ Official github repository - https://github.com/desukuran/half-screwed Version 1.3 on ModDB - https://www.moddb.com/mods/headcrab-frenzy/downloads/headcrab-frenzy-13-beta-source-code ## Heart of Evil -Available on ModDB - https://www.moddb.com/mods/heart-of-evil/downloads/heart-of-evil-dlls-source-code +Mirrored on github - https://github.com/nekonomicon/HeartOfEvil ## Ingram Chillin' Mod Official SourceForge repository - https://sourceforge.net/projects/icm-hl/ @@ -122,6 +127,9 @@ Official github repository - https://github.com/unknownworlds/NS ## Overturn Available in mod archive on ModDB - https://www.moddb.com/mods/overturn +## Oz Deathmatch +Mirrored on github - https://github.com/nekonomicon/OZDM + ## 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 @@ -168,7 +176,7 @@ Mirrored on github - https://github.com/ZXCmod/ZXCmod # Reimplementation ## Absolute Redemption -Branch **redempt** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/redempt +Branch **redempt** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/redempt ## Adrenaline Gamer OpenAG by YaLTeR - https://github.com/YaLTeR/OpenAG @@ -176,34 +184,34 @@ OpenAG by YaLTeR - https://github.com/YaLTeR/OpenAG ## Afraid of Monsters malortie's recreation - https://github.com/malortie/hl-aom -Branch **aom** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/aom +Branch **aom** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/aom ## Afraid of Monsters: Director's cut -Reverse-engineered code, branch **aomdc** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/aomdc +Reverse-engineered code, branch **aomdc** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/aomdc ## Azure Sheep malortie's recreation - https://github.com/malortie/hl-asheep -Reverse-engineered code, branch **asheep** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/asheep +Reverse-engineered code, branch **asheep** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/asheep ## Big Lolly malortie's recreation - https://github.com/malortie/hl-biglolly -Branch **biglolly** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/biglolly +Branch **biglolly** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/biglolly ## Black Ops malortie's recreation - https://github.com/malortie/hl-blackops -Branch **blackops** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/blackops +Branch **blackops** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/blackops ## Bloody Pizza: Vendetta -Branch **caseclosed** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/caseclosed +Branch **caseclosed** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/caseclosed ## Case Closed -Branch **caseclosed** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/caseclosed +Branch **caseclosed** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/caseclosed ## Cleaner's Adventures -Branch **CAd** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/CAd +Branch **CAd** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/CAd ## Counter Strike Reverse-engineered code of client part by a1batross - https://github.com/FWGS/cs16-client/tree/v1.32 @@ -221,54 +229,60 @@ Recreation by lostgamer aka nillerusr - https://github.com/LostGamerHL/crack_lif ## Escape from the Darkness malortie's recreation - https://github.com/malortie/hl-eftd -Reverse-engineered code, branch **eftd** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/eftd +Reverse-engineered code, branch **eftd** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/eftd ## Half-Life: Blue Shift Unkle Mike's recreation - https://hlfx.ru/forum/showthread.php?s=&threadid=5253 -Reverse-engineered code, branch **bshift** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/bshift +Reverse-engineered code, branch **bshift** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/bshift ## Half-Life: Induction -Branch **induction** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/induction +Branch **induction** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/induction ## Half-Life: Opposing Force Recreation by lostgamer aka nillerusr - https://github.com/LostGamerHL/hlsdk-xash3d -Reverse-engineered code, clean branch **opfor** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/opfor +Reverse-engineered code, clean branch **opfor** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/opfor Spirit of Half Life: Opposing-Force Edition - https://github.com/Hammermaps-DEV/SOHL-V1.9-Opposing-Force-Edition ## Half-Life: Rebellion -Reverse-engineered code, branch **rebellion** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/rebellion +Reverse-engineered code, branch **rebellion** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/rebellion + +## Half-Life: Urbicide +Branch **hl_urbicide** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/hl_urbicide ## Half-Life: Visitors malortie's recreation - https://github.com/malortie/hl-visitors -Reverse-engineered code, branch **visitors** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/visitors +Reverse-engineered code, branch **visitors** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/visitors ## Half-Secret -Branch **half-secret** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/half-secret +Branch **half-secret** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/half-secret ## Night at the Office malortie's recreation - https://github.com/malortie/hl-nato -Branch **noffice** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/noffice +Branch **noffice** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/noffice ## Poke 646 malortie's recreation - https://github.com/malortie/hl-poke646 -Reverse-engineered code, branch **poke646** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/poke646 +Reverse-engineered code, branch **poke646** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/poke646 ## Poke 646: Vendetta malortie's recreation - https://github.com/malortie/hl-poke646-vendetta -Reverse-engineered code, branch **poke646_vendetta** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/poke646_vendetta +Reverse-engineered code, branch **poke646_vendetta** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/poke646_vendetta ## Residual Life -Reverse-engineered code, branch **residual_point** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/residual_point +Reverse-engineered code, branch **residual_point** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/residual_point ## Residual Point -Reverse-engineered code, branch **residual_point** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/residual_point +Reverse-engineered code, branch **residual_point** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/residual_point + +## Sewer Beta +Branch **sewer_beta** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/sewer_beta ## Team Fortress Classic Reverse-engineered code by Velaron - https://github.com/Velaron/tf15-client @@ -276,59 +290,64 @@ Reverse-engineered code by Velaron - https://github.com/Velaron/tf15-client ## The Gate malortie's recreation - https://github.com/malortie/hl-thegate -Reverse-engineered code, branch **thegate** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/thegate +Reverse-engineered code, branch **thegate** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/thegate ## They Hunger malortie's recreation - https://github.com/malortie/hl-theyhunger -Reverse-engineered code, branch **theyhunger** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/theyhunger +Reverse-engineered code, branch **theyhunger** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/theyhunger They Hunger: Tactical - https://www.moddb.com/mods/they-hunger-tactical/downloads/tht-source-code-documentation ## Times of Troubles malortie's recreation - https://github.com/malortie/hl-tot -Branch **tot** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/tot +Branch **tot** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/tot # Derived work ## Adrenaline Gamer -Branch **aghl** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/aghl +Branch **aghl** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/aghl ## Bubblemod -Branch **bubblemod** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/bubblemod +Branch **bubblemod** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/bubblemod ## Deathmatch Classic Deathmatch Classic: Adrenaline Gamer Edition - https://github.com/martinwebrant/agmod/tree/master/src/dmc Deathmatch Quaked - https://www.moddb.com/games/deathmatch-classic/downloads/dmq2 -Branch **dmc** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/dmc +Branch **dmc** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/dmc ## Half-Life: Echoes -Branch **echoes** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/echoes +Branch **echoes** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/echoes ## Half-Life: Invasion Port to HLSDK 2.4 by malortie - https://github.com/malortie/hl-invasion Port to Linux by fmoraw - https://github.com/fmoraw/hlinvasion +Branch **invasion** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/invasion + ## Half-Life: Top-Down -Branch **hltopdown** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/hltopdown +Branch **hltopdown** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/hltopdown ## Half-Screwed -Branch **half-screwed** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/half-screwed +Branch **half-screwed** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/half-screwed ## Natural Selection Port to Linux - https://github.com/fmoraw/NS +## Spirit of Half-Life +Version 1.2, branch **sohl1.2** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/sohl1.2 + ## Swiss Cheese Halloween 2002 Just more playable version by malortie - https://github.com/malortie/hl-shall -Branch **halloween** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/halloween +Branch **halloween** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/halloween ## Threewave CTF -Branch **dmc** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/dmc +Branch **dmc** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/dmc ## Zombie-X -Branch **zombie-x** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/zombie-x +Branch **zombie-x** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/zombie-x diff --git a/Documentation/supported-mod-list.md b/Documentation/supported-mod-list.md index a155f7ba..54f35a3f 100644 --- a/Documentation/supported-mod-list.md +++ b/Documentation/supported-mod-list.md @@ -46,7 +46,7 @@ For mappacks - place *.bsp files to **valve/maps** folder, *.wad files to **valv 16. [Betrayal](http://www.moddb.com/mods/betrayal) (this mod has inner bugs; set **gl_allow_mirrors** to **0** to prevent drops of game's perfomance in some areas) 17. [Between Two Worlds](https://www.runthinkshootlive.com/posts/between-two-worlds/) 18. [Black Mesa Energy Testing Chamber](http://twhl.info/competitions.php?results=17) -19. [Black Mesa Sideline](http://www.isolated-design.de/half-life-mods/black-mesa-sideline/) (set **gl_allow_mirrors** to **0** to prevent drops of game's perfomance in some areas) +19. [Black Mesa Sideline](https://www.moddb.com/mods/black-mesa-sideline) (set **gl_allow_mirrors** to **0** to prevent drops of game's perfomance in some areas) 20. [BlackMesa 2007( Black Mesa Missions 2007 )](http://www.moddb.com/addons/blackmesa-2007) 21. [Blood and Bones](https://www.runthinkshootlive.com/posts/blood-and-bones/) 22. Boom (Huknenn) v1.0 & [HL Boom: Gold Edition v1.1](http://www.moddb.com/mods/boom) @@ -103,8 +103,8 @@ For mappacks - place *.bsp files to **valve/maps** folder, *.wad files to **valv 72. [Freeman](https://www.runthinkshootlive.com/posts/freeman/) 73. [Freeman's Escape](http://www.moddb.com/mods/freeman-escape-french) (2 maps by *JiggZ*) 74. [Freeman's Fight](https://www.runthinkshootlive.com/posts/freemans-fight/) -75. [Freeman's Return](http://jpmaps.wz.cz/epizody.htm) -76. [Freeman's Return 2](http://jpmaps.wz.cz/epizody.htm) +75. [Freeman's Return](https://www.moddb.com/games/half-life/addons/freemans-return-v12) +76. [Freeman's Return 2](https://www.moddb.com/games/half-life/addons/freemans-return-2-v11) 77. [Freeman's Revenge](https://www.runthinkshootlive.com/posts/freemans-revenge/) 78. [Freeman's Tomb( The Crypt )](http://twhl.info/vault.php?map=3787) 79. [G-Invasion v2.5](http://www.moddb.com/mods/g-man-invasion) aka G-Man Invasion (there is an inner crash bug on final titles: some text lines are too long and cannot be properly displayed) @@ -129,7 +129,7 @@ For mappacks - place *.bsp files to **valve/maps** folder, *.wad files to **valv 96. [Haunted](https://www.runthinkshootlive.com/posts/haunted/) (set **sv_validate_changelevel** to **0** to avoid a problem with level change between maps **pagan7** and **pagan8**) 97. [Hazardous Materials: Episode 1 & 2](http://www.moddb.com/mods/half-life-hazardous-materials) 98. [Hazardous-Course 2](http://www.moddb.com/mods/hazardous-course-2) -99. [Help Wanted](http://maps.mrsucko.org/work/) +99. [Help Wanted](https://www.moddb.com/games/half-life/addons/help-wanted) 100. [Hidden Evil v1.01](https://www.runthinkshootlive.com/posts/hidden-evil/) 101. [High Speed](http://www.moddb.com/games/half-life/addons/high-speed-english-version) 102. [hlife_hotdog_compo26](http://twhl.info/vault.php?map=5181) @@ -155,7 +155,7 @@ For mappacks - place *.bsp files to **valve/maps** folder, *.wad files to **valv 122. [Mario Keys](http://www.moddb.com/mods/mario-keys) 123. [McBeth](https://www.runthinkshootlive.com/posts/mcbeth/) 124. [Medieval World](http://www.moddb.com/mods/medieval-world) -125. [Mel Soaring 2: Star Rancor](http://www.etherealhell.com/etherealhell/index.php/my-levels/half-life) +125. [Mel Soaring 2: Star Rancor](https://www.moddb.com/addons/mel-soaring-2-star-rancor) 126. [MINIMICUS](http://twhl.info/vault.php?map=163) 127. [Mission Failed](http://www.moddb.com/mods/mission-failed) 128. [Mission MC Poker](http://www.moddb.com/mods/half-life-mission-mc-poker) @@ -176,7 +176,7 @@ For mappacks - place *.bsp files to **valve/maps** folder, *.wad files to **valv 143. [Operation: Nova](http://www.moddb.com/mods/half-life-operation-nova) (there is an inner bug with an M60 machinegun on a map with Osprey: it can be activated and used if you are staying in front of it, not behind it) 144. [Optimum Fear](https://www.fileplanet.com/7472/0/fileinfo/Optimum-Fear) 145. [Outrun](http://www.moddb.com/mods/outrun) -146. [Outwards (Day One)](http://www.visgi.info/maps/) +146. [Outwards (Day One)](https://drive.google.com/file/d/1mjQ8wUazYbwTq9Ocg0Vs0raq4avbTRne/view?usp=sharing) 147. [Overhaul Pack](http://www.moddb.com/mods/half-life-overhaul-pack) (Just HD pack for Half-Life) 148. [P.I.Z.D.E.C.](https://www.runthinkshootlive.com/posts/pizdec/) 149. [pagoda](http://www.moddb.com/mods/pagoda1) @@ -184,7 +184,7 @@ For mappacks - place *.bsp files to **valve/maps** folder, *.wad files to **valv 151. [Phobos IV](http://www.moddb.com/mods/phobos-iv) 152. [Pimp My Car](https://www.runthinkshootlive.com/posts/pimp-my-car/) (there is an inner issue with incorrect responses of buttons on combination locks) 153. [Point Blank – Stackdeath Complex](https://www.runthinkshootlive.com/posts/half-life-point-blank-stackdeath-complex/) -154. [Prisoner Escaped](http://four0four.org/downloads/maps/) +154. [Prisoner Escaped](https://www.moddb.com/mods/prisoner-escaped-1-2) 155. [Prisoner of Event](http://wp.vondur.net/?page_id=40) 156. [Prisoner of War](https://www.runthinkshootlive.com/posts/prisoner-of-war/) 157. [Project Quantum Leap](http://www.moddb.com/mods/project-quantum-leap) @@ -282,7 +282,7 @@ For mappacks - place *.bsp files to **valve/maps** folder, *.wad files to **valv 6. [Catacombs: Part 1](https://www.runthinkshootlive.com/posts/catacombs/) 7. [Cro-man's Office Mappack](http://twhl.info/vault.php?map=5547) 8. [Half-Life Episode Two Demo Alpha v1.0](http://hl.loess.ru/?mod=451) -9. [Hazardous-Course (first version of Hazardous-Course 2)](http://www.richmans-maps.ch.vu/) (link dead!) +9. [Hazardous-Course (first version of Hazardous-Course 2)](https://drive.google.com/file/d/16djJZSpNWKyScSxSyMqX-u_S-2i400rl/view?usp=sharing) 10. [High Tech v0.2](http://hl.loess.ru/?mod=499) 11. [HL Shadows, Part 1](https://www.fileplanet.com/7466/0/fileinfo/'HL-Shadows',-Part-1) 12. [HLNewEnd](http://twhl.info/vault.php?map=2275) @@ -305,11 +305,11 @@ For mappacks - place *.bsp files to **valve/maps** folder, *.wad files to **valv 29. [Shortcut v1.0 Beta](https://www.runthinkshootlive.com/posts/shortcut/) 30. [Stoka](https://www.ceskemody.cz/mapy.php?lng=1&clanek=222&razeni_hra=1&pn=stoka) 31. [Striker's Compo 26](http://twhl.info/vault.php?map=5190) (buggy) -32. [Technology Test 2](http://www.richmans-maps.ch.vu/) (link dead!) +32. [Technology Test 2](https://drive.google.com/file/d/1LfldzsMIN5j07bgtNkfY5v0Xl0S9de_G/view?usp=sharing) 33. [The Gate Playable Demo](https://www.fileplanet.com/116347/110000/fileinfo/The-Gate-Playable-Demo) 34. [They are Back](https://www.runthinkshootlive.com/posts/they-are-back/) 35. [Tiefseelabor( Deep Sea Laboratory )](https://www.runthinkshootlive.com/posts/deep-sea-laboratory/) -36. [Time-Shift](http://www.richmans-maps.ch.vu/) (link dead!) +36. [Time-Shift](https://drive.google.com/file/d/1pK1s-2SIlSMQHVRPMLdC_94wTa__8_DX/view?usp=sharing) 37. [Train Single Beta](https://cs-mapping.com.ua/forum/showthread.php?t=36394) (Remove **gfx.wad** from **TrainSingle** folder) 38. [WAR: The Killer Beta 0.1](http://www.moddb.com/mods/war) 39. [White Force Beta( Residual Point prototype )](http://www.moddb.com/mods/hl-residual-point/downloads/white-force-beta-2002) @@ -379,7 +379,7 @@ Mods: 5. [Half-Life Baby v1.4](http://www.moddb.com/games/half-life/addons/half-life-baby) (it's an unfinished but playable mod; after installing of the mod open **liblist.gam** or **gameinfo.txt** file in the mod's folder and correct the line **gamedll "..\hlbaby\dlls\hl.dll"** for **gamedll "dlls\hl.dll"**, otherwise you'll not be able to start a game) 6. [Half-Secret](http://www.moddb.com/mods/half-secret) (this mod has custom **weapon_snark** code) 7. [Induction](http://www.moddb.com/mods/half-life-induction) (this mod has new item - **item_flashlight**) -8. [Lost in Black Mesa(first version without HLFX)](http://half-life.ru/forum/showthread.php?threadid=13959) +8. [Lost in Black Mesa(first version without HLFX)](https://drive.google.com/file/d/1bEnm_AxJs-ly8hTEZIQBw_v2BsXdSiGa/view?usp=sharing) 9. [Soldier](http://www.moddb.com/mods/half-life-soldier) 10. [Solo Operations](http://www.moddb.com/mods/solo-operations) 11. [The Blood v1.1](http://hl.loess.ru/?mods=&search=The+Blood) (there are some inner bugs in the mod, but they don't interfere with a game progress) @@ -440,7 +440,7 @@ Mods: 51. [Data-base](https://www.runthinkshootlive.com/posts/database/) 52. [de_dust2_azabetfeN](https://cs-mapping.com.ua/forum/showthread.php?t=36394) (remove **cl_dlls** & **dlls** folders from inside of mod's directory before you start the game) 53. [De-railed](http://twhl.info/competitions.php?results=7) -54. [Dead Shift Beta](http://www.moddb.com/mods/dead-shift) - [Demo 1](https://www.gamewatcher.com/mods/half-life-mod/dead-shift-1-0-beta) & [Demo 2](https://www.gamefront.com/files/13532512) (3rd link dead!) +54. [Dead Shift Beta](http://www.moddb.com/mods/dead-shift) - [Demo 1](https://www.gamewatcher.com/mods/half-life-mod/dead-shift-1-0-beta) & [Demo 2](https://drive.google.com/file/d/1uHvr8xONogTTNOy-8X6G4ehFV6Eawvbq/view?usp=sharing) 55. [Deep](http://twhl.info/vault.php?map=1669) 56. [Desert Attack](http://fyzzer.narod.ru/index.html) 57. [Desert Combat Demo](https://www.fileplanet.com/190487/190000/fileinfo/Half-Life---Desert-Combat-Mod) (despite a big file size there's only one small unfinished map) @@ -469,7 +469,7 @@ Mods: 80. [Extinct Lifeform Hunt](https://www.fileplanet.com/13501/10000/fileinfo/Extinct-Lifeform-Hunt) 81. [Facility](http://twhl.info/vault.php?map=5482) 82. [Facility Escape](http://twhl.info/vault.php?map=3673) -83. [Fallout](http://www.thewall.de/forum/thread/hl1-sp-48h-mapping-contest/64975.4.html) (map by *simb*) +83. [Fallout](http://www.thewall.de/forum/thread/hl1-sp-48h-mapping-contest/64975.4.html) (map by *simb*) (link dead!) 84. [Final Assault](http://twhl.info/vault.php?map=4500) 85. [Flat](http://hl.loess.ru/?mods=&search=Flat) 86. [Freeman's Allegiance](https://www.runthinkshootlive.com/posts/freemans-allegiance/) @@ -498,8 +498,8 @@ Mods: 109. [Hospital](https://gamebanana.com/maps/167446) (you need to edit **liblist.gam** file in the mod's folder - delete *gamedll & type* strings from it before you start to play) 110. [Hostage](https://www.runthinkshootlive.com/posts/hostage-2/) 111. [House](http://www.artpeter.net/Data/HalfLife/Hl.php) -112. [Impulse 101 Fun - The Train](http://web.archive.org/web/20070305075816/http://www.twhl.co.za/mapvault/2817.zip) (map from *TWHL* by *Archie* aka *The Hunter*) -113. [In America](http://www.lambda-force.org/load/half_life/karty/half_life_in_america/18-1-0-476) +112. [Impulse 101 Fun - The Train](https://drive.google.com/file/d/1jAuMCCmREH0mfAEAscqaG8FQGa2uNknb/view?usp=sharing) (map from *TWHL* by *Archie* aka *The Hunter*) +113. [In America](https://drive.google.com/file/d/1gOC8zfnUvBxRy8Rr8juNiUNbLYjoZpnn/view?usp=sharing) 114. [In the Kitchen](http://twhl.info/vault.php?map=4314) 115. [Infiltration](https://www.fileplanet.com/7467/0/fileinfo/Infiltration) 116. [Interactivity & Lots of Entities](http://twhl.info/vault.php?map=5744) (link dead!) @@ -668,7 +668,7 @@ Mods: 279. [X-treme Violence](http://www.moddb.com/mods/x-treme-violence) 280. [XargoL's Entry for Vassy's Compo](http://twhl.info/vault.php?map=769) 281. [Xen Again](https://www.fileplanet.com/9132/0/fileinfo/XEN-AGAIN) -282. [Xen World](http://www.lambda-force.org/load/half_life/karty/half_life_xen_world/18-1-0-481) +282. [Xen World](http://www.lambda-force.org/load/half_life/karty/half_life_xen_world/18-1-0-481) (link dead!) 283. [XUnil](https://www.fileplanet.com/7883/0/fileinfo/XUNIL) 284. [Zeeba-G's TWHL Compo 26 Entry](http://twhl.info/competitions.php?results=26) 285. [Zombies!](https://gamebanana.com/maps/160812) @@ -694,7 +694,7 @@ For Linux and OS X you must download crossbuild from [here](http://www.moddb.com 2. [Big Scientists](http://www.moddb.com/mods/big-scientists) 3. [Black Silla Assault DEMO v1.2](http://www.moddb.com/mods/black-silla-assault) 4. [Blbej Den](http://www.moddb.com/mods/blbej-den) -5. [Cold Experiment v1](http://jpmaps.wz.cz/epizody.htm) (you will need some files from SoHL to play it properly; download SoHL 1.2, extract files and copy following folders into **coldexv1** folder: cl_dlls, dlls, models, sprites) +5. [Cold Experiment v1](https://www.moddb.com/games/half-life/addons/cold-experiment) (you will need some files from SoHL to play it properly; download SoHL 1.2, extract files and copy following folders into **coldexv1** folder: cl_dlls, dlls, models, sprites) 6. [Dead Sector v1.0a](http://www.moddb.com/mods/dead-sector) 7. [Death Is Dead](http://www.moddb.com/games/half-life/addons/death-is-dead) (there is a fog-related inner bug at the first map) 8. [Escape from Black Mesa Alpha](http://www.moddb.com/mods/escape-from-black-mesa) @@ -710,9 +710,9 @@ For Linux and OS X you must download crossbuild from [here](http://www.moddb.com 18. [Santa's Revenge](http://twhl.info/vault.php?map=4332) (set **fps_max** to **60** to avoid an inner problem of the last map with final scripted sequence, otherwise the mod can not be finished properly) 19. [Sector 6](https://www.moddb.com/mods/sector-6/) 20. [Space Prisoner v1.1](http://www.moddb.com/games/half-life/addons/space-prisoner-v11) (after installing of the mod open **liblist.gam** or **gameinfo.txt** file in the mod's folder and correct the line **gamedll "..\prison\dlls\spirit.dll"** for **gamedll "dlls\spirit.dll"**, otherwise you'll not be able to start a game; there is also a scripting error on a third map of the mod, so you'll be forced to use **noclip** to pass around bugged place) -21. [Terrorist Attack 2](http://terroristattack.wz.cz/mody.html) (link dead!) +21. [Terrorist Attack 2](https://www.moddb.com/games/half-life/addons/terrorist-attack-2) 22. [Timeline III: The Heart of Darkness](http://www.moddb.com/mods/timeline-series) -23. [Underground 2 Demo](http://www.isolated-design.de/half-life-mods/underground-2/) +23. [Underground 2 Demo](https://www.moddb.com/games/half-life/addons/underground-2-demo) ## Mods which based on modified Spirit of Half-Life 1.2 or older(Partially playable with SoHL 1.2 libraries or not playable at all) 1. [Black Death](http://www.moddb.com/mods/half-life-black-death) @@ -825,10 +825,10 @@ Mods: 3. [Bomb Squad](http://www.snarkpit.net/index.php?s=maps&map=3471) 4. [Corruption](http://www.snarkpit.net/index.php?s=maps&map=3154) 5. [Critical Mass](http://www.moddb.com/mods/half-life-critical-mass) [C3M1 Build 1 Demo](https://www.fileplanet.com/125746/120000/fileinfo/C3M1-Build-1) -6. [EPRST!](http://www.planetphillip.com/posts/ep-rst-opposing-force/) (link dead!) +6. [EPRST!](https://www.runthinkshootlive.com/posts/ep-rst/) 7. [Firing Range](https://www.runthinkshootlive.com/posts/firing-range/) 8. [Friendly Fire](https://www.runthinkshootlive.com/posts/friendly-fire/) -9. [Guitar Star Pre-Alpha](http://hl-lab.ru/eng/mods_goldsrc/Guitar_Star_(Prealpha)) (link dead!) +9. [Guitar Star Pre-Alpha](http://gamer-lab.com/rus/mods_goldsrc/Guitar_Star_(Prealpha)) 10. [J2000](https://www.runthinkshootlive.com/posts/j2000/) 11. [Killing House for OF](https://www.runthinkshootlive.com/posts/scientist-rescue-aka-cqb/) 12. [Klabautermann](https://www.runthinkshootlive.com/posts/klabautermann/) (though the map has some inner issues, it can be properly finished, just don't let the welder soldier die and don't press the fifth button until you mount a special gun in its' place) diff --git a/README.md b/README.md index 119cf2b0..fe9464d2 100644 --- a/README.md +++ b/README.md @@ -18,19 +18,22 @@ Read more about Xash3D on ModDB: https://www.moddb.com/engines/xash3d-engine * Mobility API: allows better game integration on mobile devices(vibration, touch controls) * Different input methods: touch, gamepad and classic mouse & keyboard. * TrueType font rendering, as a part of mainui_cpp. -* Multiple renderers support: OpenGL, GLESv1, GLESv2, Software +* Multiple renderers support: OpenGL, GLESv1, GLESv2, Software. +* Voice support. +* External filesystem module like in GoldSrc engine. +* External vgui support module. +* PNG image format support. * A set of small improvements, without broken compatibility. ## Planned fork features -* Virtual Reality support and game API -* Voice support -* Vulkan renderer +* Virtual Reality support and game API. +* Vulkan renderer. ## Installation & Running 0) Get Xash3D FWGS binaries: you can use [testing](https://github.com/FWGS/xash3d-fwgs/releases/tag/continuous) build or you can compile engine from source code. 1) Copy engine binaries to some directory. 2) Copy `valve` directory from [Half-Life](https://store.steampowered.com/app/70/HalfLife/) to directory with engine binaries. -If your CPU is NOT x86 compatible or you're running 64-bit version of the engine, you may want to compile [Half-Life SDK](https://github.com/FWGS/hlsdk-xash3d). +If your CPU is NOT x86 compatible or you're running 64-bit version of the engine, you may want to compile [Half-Life SDK](https://github.com/FWGS/hlsdk-portable). This repository contains our fork of HLSDK and restored source code for some of the mods. Not all of them, of course. You still needed to copy `valve` directory as all game resources located there. 3) Run the main executable (`xash3d.exe` or AppImage). @@ -49,10 +52,10 @@ NOTE: NEVER USE GitHub's ZIP ARCHIVES. GitHub doesn't include external dependenc ### Prerequisites -If your CPU is x86 compatible, we are building 32-bit code by default. This was dont for keeping compatibility with Steam releases of Half-Life and based on it's engine games. +If your CPU is x86 compatible, we are building 32-bit code by default. This was done to maintain compatibility with Steam releases of Half-Life and based on it's engine games. Even if Xash3D FWGS does support targetting 64-bit, you can't load games without recompiling them from source code! -If your CPU is NOT x86 compatible or you decided build 64-bit version of engine, you may want to compile [Half-Life SDK](https://github.com/FWGS/hlsdk-xash3d). +If your CPU is NOT x86 compatible or you decided build 64-bit version of engine, you may want to compile [Half-Life SDK](https://github.com/FWGS/hlsdk-portable). This repository contains our fork of HLSDK and restored source code for some of the mods. Not all of them, of course. #### Windows (Visual Studio) diff --git a/common/bspfile.h b/common/bspfile.h index 8087f2dc..1fa5721a 100644 --- a/common/bspfile.h +++ b/common/bspfile.h @@ -73,7 +73,8 @@ BRUSH MODELS #define MAX_MAP_FACES 262144 // can be increased without problems #define MAX_MAP_MARKSURFACES 524288 // can be increased without problems #else -#define MAX_MAP_MODELS 768 // embedded models +// increased to match PrimeXT compilers +#define MAX_MAP_MODELS 1024 // embedded models #define MAX_MAP_ENTSTRING 0x100000 // 1 Mb should be enough #define MAX_MAP_PLANES 65536 // can be increased without problems #define MAX_MAP_NODES 32767 // because negative shorts are leafs diff --git a/common/com_model.h b/common/com_model.h index 9f8ceda8..36f2e936 100644 --- a/common/com_model.h +++ b/common/com_model.h @@ -530,7 +530,7 @@ typedef struct #define MAX_DEMOS 32 #define MAX_MOVIES 8 #define MAX_CDTRACKS 32 -#define MAX_CLIENT_SPRITES 256 // SpriteTextures +#define MAX_CLIENT_SPRITES 512 // SpriteTextures (0-256 hud, 256-512 client) #define MAX_EFRAGS 8192 // Arcane Dimensions required #define MAX_REQUESTS 64 diff --git a/common/port.h b/common/port.h index 435de115..ae60122f 100644 --- a/common/port.h +++ b/common/port.h @@ -47,14 +47,8 @@ GNU General Public License for more details. #define O_BINARY 0 #define O_TEXT 0 #define _mkdir( x ) mkdir( x, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH ) - #define LoadLibrary( x ) dlopen( x, RTLD_NOW ) - #define GetProcAddress( x, y ) dlsym( x, y ) - #define FreeLibrary( x ) dlclose( x ) #elif XASH_DOS4GW #define PATH_SPLITTER "\\" - #define LoadLibrary( x ) (0) - #define GetProcAddress( x, y ) (0) - #define FreeLibrary( x ) (0) #endif typedef void* HANDLE; diff --git a/common/render_api.h b/common/render_api.h index f41d297f..25897a85 100644 --- a/common/render_api.h +++ b/common/render_api.h @@ -161,7 +161,7 @@ struct ref_viewpass_s; typedef struct render_api_s { // Get renderer info (doesn't changes engine state at all) - int (*RenderGetParm)( int parm, int arg ); // generic + intptr_t (*RenderGetParm)( int parm, int arg ); // generic void (*GetDetailScaleForTexture)( int texture, float *xScale, float *yScale ); void (*GetExtraParmsForTexture)( int texture, byte *red, byte *green, byte *blue, byte *alpha ); lightstyle_t* (*GetLightStyle)( int number ); diff --git a/common/xash3d_types.h b/common/xash3d_types.h index 71ceca77..9b343e8c 100644 --- a/common/xash3d_types.h +++ b/common/xash3d_types.h @@ -10,6 +10,7 @@ #include // off_t #include STDINT_H +#include typedef unsigned char byte; typedef int sound_t; @@ -87,6 +88,27 @@ typedef uint64_t longtime_t; #define NORETURN #endif +#if ( __GNUC__ >= 3 ) + #define unlikely(x) __builtin_expect(x, 0) + #define likely(x) __builtin_expect(x, 1) +#elif defined( __has_builtin ) + #if __has_builtin( __builtin_expect ) + #define unlikely(x) __builtin_expect(x, 0) + #define likely(x) __builtin_expect(x, 1) + #else + #define unlikely(x) (x) + #define likely(x) (x) + #endif +#else + #define unlikely(x) (x) + #define likely(x) (x) +#endif + +#if defined( static_assert ) // C11 static_assert +#define STATIC_ASSERT static_assert +#else +#define STATIC_ASSERT( x, y ) extern int _static_assert_##__LINE__[( x ) ? 1 : -1] +#endif #ifdef XASH_BIG_ENDIAN #define LittleLong(x) (((int)(((x)&255)<<24)) + ((int)((((x)>>8)&255)<<16)) + ((int)(((x)>>16)&255)<<8) + (((x) >> 24)&255)) @@ -122,7 +144,6 @@ typedef unsigned int dword; typedef unsigned int uint; typedef char string[MAX_STRING]; typedef struct file_s file_t; // normal file -typedef struct wfile_s wfile_t; // wad file typedef struct stream_s stream_t; // sound stream for background music playing typedef off_t fs_offset_t; #if XASH_WIN32 diff --git a/engine/client/cl_demo.c b/engine/client/cl_demo.c index 7443d51b..089898ed 100644 --- a/engine/client/cl_demo.c +++ b/engine/client/cl_demo.c @@ -888,7 +888,7 @@ qboolean CL_DemoReadMessage( byte *buffer, size_t *length ) qboolean swallowmessages = true; static int tdlastdemoframe = 0; byte *userbuf = NULL; - size_t size; + size_t size = 0; byte cmd; if( !cls.demofile ) @@ -1408,7 +1408,7 @@ void CL_PlayDemo_f( void ) if( Cmd_Argc() < 2 ) { - Con_Printf( S_USAGE "playdemo \n" ); + Con_Printf( S_USAGE "%s \n", Cmd_Argv( 0 )); return; } @@ -1535,12 +1535,6 @@ timedemo */ void CL_TimeDemo_f( void ) { - if( Cmd_Argc() != 2 ) - { - Con_Printf( S_USAGE "timedemo \n" ); - return; - } - CL_PlayDemo_f (); // cls.td_starttime will be grabbed at the second frame of the demo, so diff --git a/engine/client/cl_efx.c b/engine/client/cl_efx.c index d7ad1196..6a07dd49 100644 --- a/engine/client/cl_efx.c +++ b/engine/client/cl_efx.c @@ -1111,9 +1111,13 @@ R_ParticleExplosion2 void GAME_EXPORT R_ParticleExplosion2( const vec3_t org, int colorStart, int colorLength ) { int i, j; - int colorMod = 0; + int colorMod = 0, packedColor; particle_t *p; + if( FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) + packedColor = 255; // use old code for blob particles + else packedColor = 0; + for( i = 0; i < 512; i++ ) { p = R_AllocParticle( NULL ); @@ -1121,7 +1125,7 @@ void GAME_EXPORT R_ParticleExplosion2( const vec3_t org, int colorStart, int col p->die = cl.time + 0.3f; p->color = colorStart + ( colorMod % colorLength ); - p->packedColor = 255; // use old code for blob particles + p->packedColor = packedColor; colorMod++; p->type = pt_blob; @@ -1143,15 +1147,19 @@ R_BlobExplosion void GAME_EXPORT R_BlobExplosion( const vec3_t org ) { particle_t *p; - int i, j; + int i, j, packedColor; + + if( FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) + packedColor = 255; // use old code for blob particles + else packedColor = 0; for( i = 0; i < 1024; i++ ) { p = R_AllocParticle( NULL ); if( !p ) return; - p->die = cl.time + COM_RandomFloat( 2.0f, 2.4f ); - p->packedColor = 255; // use old code for blob particles + p->die = cl.time + COM_RandomFloat( 1.0f, 1.4f ); + p->packedColor = packedColor; if( i & 1 ) { diff --git a/engine/client/cl_frame.c b/engine/client/cl_frame.c index dd1777e6..81976384 100644 --- a/engine/client/cl_frame.c +++ b/engine/client/cl_frame.c @@ -534,7 +534,7 @@ void CL_ComputePlayerOrigin( cl_entity_t *ent ) vec3_t origin; vec3_t angles; - if( !ent->player || ent->index == ( cl.playernum + 1 )) + if( !ent->player ) return; if( cl_nointerp->value > 0.f ) @@ -1094,6 +1094,9 @@ void CL_LinkPlayers( frame_t *frame ) if ( i == cl.playernum ) { + // using interpolation only for local player angles + CL_ComputePlayerOrigin( ent ); + if( cls.demoplayback == DEMO_QUAKE1 ) VectorLerp( ent->prevstate.origin, cl.lerpFrac, ent->curstate.origin, cl.simorg ); VectorCopy( cl.simorg, ent->origin ); diff --git a/engine/client/cl_game.c b/engine/client/cl_game.c index 2b64bec0..7a054b18 100644 --- a/engine/client/cl_game.c +++ b/engine/client/cl_game.c @@ -147,21 +147,6 @@ qboolean CL_IsThirdPerson( void ) return false; } -/* -==================== -CL_GetPlayerInfo - -get player info by render request -==================== -*/ -player_info_t *CL_GetPlayerInfo( int playerIndex ) -{ - if( playerIndex < 0 || playerIndex >= cl.maxclients ) - return NULL; - - return &cl.players[playerIndex]; -} - /* ==================== CL_CreatePlaylist @@ -1235,6 +1220,10 @@ static model_t *CL_LoadSpriteModel( const char *filename, uint type, uint texFla model_t *mod; int i; + // use high indices for client sprites + // for GoldSrc bug-compatibility + const int start = type != SPR_HUDSPRITE ? MAX_CLIENT_SPRITES / 2 : 0; + if( !COM_CheckString( filename )) { Con_Reportf( S_ERROR "CL_LoadSpriteModel: bad name!\n" ); @@ -1244,8 +1233,7 @@ static model_t *CL_LoadSpriteModel( const char *filename, uint type, uint texFla Q_strncpy( name, filename, sizeof( name )); COM_FixSlashes( name ); - // slot 0 isn't used - for( i = 1, mod = clgame.sprites; i < MAX_CLIENT_SPRITES; i++, mod++ ) + for( i = 0, mod = clgame.sprites + start; i < MAX_CLIENT_SPRITES / 2; i++, mod++ ) { if( !Q_stricmp( mod->name, name )) { @@ -1262,12 +1250,12 @@ static model_t *CL_LoadSpriteModel( const char *filename, uint type, uint texFla } // find a free model slot spot - for( i = 1, mod = clgame.sprites; i < MAX_CLIENT_SPRITES; i++, mod++ ) + for( i = 0, mod = clgame.sprites + start; i < MAX_CLIENT_SPRITES / 2; i++, mod++ ) if( !mod->name[0] ) break; // this is a valid spot - if( i == MAX_CLIENT_SPRITES ) + if( i == MAX_CLIENT_SPRITES / 2 ) { - Con_Printf( S_ERROR "MAX_CLIENT_SPRITES limit exceeded (%d)\n", MAX_CLIENT_SPRITES ); + Con_Printf( S_ERROR "MAX_CLIENT_SPRITES limit exceeded (%d)\n", MAX_CLIENT_SPRITES / 2 ); return NULL; } @@ -1308,7 +1296,7 @@ HSPRITE pfnSPR_LoadExt( const char *szPicName, uint texFlags ) if(( spr = CL_LoadSpriteModel( szPicName, SPR_CLIENT, texFlags )) == NULL ) return 0; - return (spr - clgame.sprites); // return index + return (spr - clgame.sprites) + 1; // return index } /* @@ -1324,7 +1312,7 @@ HSPRITE EXPORT pfnSPR_Load( const char *szPicName ) if(( spr = CL_LoadSpriteModel( szPicName, SPR_HUDSPRITE, 0 )) == NULL ) return 0; - return (spr - clgame.sprites); // return index + return (spr - clgame.sprites) + 1; // return index } /* @@ -1336,10 +1324,11 @@ CL_GetSpritePointer const model_t *CL_GetSpritePointer( HSPRITE hSprite ) { model_t *mod; + int index = hSprite - 1; - if( hSprite <= 0 || hSprite >= MAX_CLIENT_SPRITES ) + if( index < 0 || index >= MAX_CLIENT_SPRITES ) return NULL; // bad image - mod = &clgame.sprites[hSprite]; + mod = &clgame.sprites[index]; if( mod->needload == NL_NEEDS_LOADED ) { @@ -1947,7 +1936,14 @@ prints directly into console (can skip notify) */ static void GAME_EXPORT pfnConsolePrint( const char *string ) { - Con_Printf( "%s", string ); + if( !COM_CheckString( string )) + return; + + // WON GoldSrc behavior + if( string[0] != 1 ) + Con_Printf( "%s", string ); + else + Con_NPrintf( 0, "%s", string + 1 ); } /* @@ -2666,7 +2662,14 @@ pfnLoadMapSprite */ model_t *pfnLoadMapSprite( const char *filename ) { - return CL_LoadSpriteModel( filename, SPR_MAPSPRITE, 0 ); + model_t *mod; + + mod = Mod_FindName( filename, false ); + + if( CL_LoadHudSprite( filename, mod, SPR_MAPSPRITE, 0 )) + return mod; + + return NULL; } /* @@ -3087,11 +3090,7 @@ handle colon separately */ char *pfnParseFile( char *data, char *token ) { - char *out; - - out = _COM_ParseFileSafe( data, token, PFILE_TOKEN_MAX_LENGTH, PFILE_HANDLECOLON, NULL ); - - return out; + return COM_ParseFileSafe( data, token, PFILE_TOKEN_MAX_LENGTH, PFILE_HANDLECOLON, NULL, NULL ); } /* @@ -3312,7 +3311,7 @@ NetAPI_InitNetworking */ void GAME_EXPORT NetAPI_InitNetworking( void ) { - NET_Config( true ); // allow remote + NET_Config( true, false ); // allow remote } /* @@ -3865,7 +3864,7 @@ static cl_enginefunc_t gEngfuncs = (void*)Cmd_GetName, pfnGetClientOldTime, pfnGetGravity, - Mod_Handle, + CL_ModelHandle, pfnEnableTexSort, pfnSetLightmapColor, pfnSetLightmapScale, @@ -3910,11 +3909,13 @@ void CL_UnloadProgs( void ) if( Q_stricmp( GI->gamefolder, "hlfx" ) || GI->version != 0.5f ) clgame.dllFuncs.pfnShutdown(); + if( GI->internal_vgui_support ) + VGui_Shutdown(); + Cvar_FullSet( "cl_background", "0", FCVAR_READ_ONLY ); Cvar_FullSet( "host_clientloaded", "0", FCVAR_READ_ONLY ); COM_FreeLibrary( clgame.hInstance ); - VGui_Shutdown(); Mem_FreePool( &cls.mempool ); Mem_FreePool( &clgame.mempool ); memset( &clgame, 0, sizeof( clgame )); @@ -3939,10 +3940,6 @@ qboolean CL_LoadProgs( const char *name ) clgame.mempool = Mem_AllocPool( "Client Edicts Zone" ); clgame.entities = NULL; - // NOTE: important stuff! - // vgui must startup BEFORE loading client.dll to avoid get error ERROR_NOACESS - // during LoadLibrary - VGui_Startup( name, gameui.globals->scrWidth, gameui.globals->scrHeight ); // a1ba: we need to check if client.dll has direct dependency on SDL2 // and if so, disable relative mouse mode @@ -3961,8 +3958,29 @@ qboolean CL_LoadProgs( const char *name ) clgame.client_dll_uses_sdl = true; #endif + // NOTE: important stuff! + // vgui must startup BEFORE loading client.dll to avoid get error ERROR_NOACESS + // during LoadLibrary + if( !GI->internal_vgui_support && VGui_LoadProgs( NULL )) + { + VGui_Startup( refState.width, refState.height ); + } + else + { + // we failed to load vgui_support, but let's probe client.dll for support anyway + GI->internal_vgui_support = true; + } + clgame.hInstance = COM_LoadLibrary( name, false, false ); - if( !clgame.hInstance ) return false; + + if( !clgame.hInstance ) + return false; + + // delayed vgui initialization for internal support + if( GI->internal_vgui_support && VGui_LoadProgs( clgame.hInstance )) + { + VGui_Startup( refState.width, refState.height ); + } // clear exports for( func = cdll_exports; func && func->name; func++ ) diff --git a/engine/client/cl_gameui.c b/engine/client/cl_gameui.c index 6308e2e6..e5bf2683 100644 --- a/engine/client/cl_gameui.c +++ b/engine/client/cl_gameui.c @@ -980,7 +980,7 @@ pfnGetGamesList */ static GAMEINFO ** GAME_EXPORT pfnGetGamesList( int *numGames ) { - if( numGames ) *numGames = SI.numgames; + if( numGames ) *numGames = FI->numgames; return gameui.modsInfo; } @@ -1060,10 +1060,7 @@ pfnChangeInstance */ static void GAME_EXPORT pfnChangeInstance( const char *newInstance, const char *szFinalMessage ) { - if( !szFinalMessage ) szFinalMessage = ""; - if( !newInstance || !*newInstance ) return; - - Host_NewInstance( newInstance, szFinalMessage ); + Con_Reportf( S_ERROR "ChangeInstance menu call is deprecated!\n" ); } /* @@ -1120,6 +1117,30 @@ static char *pfnParseFile( char *buf, char *token ) return COM_ParseFile( buf, token, INT_MAX ); } +/* +============= +pfnFileExists + +legacy wrapper +============= +*/ +static int pfnFileExists( const char *path, int gamedironly ) +{ + return FS_FileExists( path, gamedironly ); +} + +/* +============= +pfnDelete + +legacy wrapper +============= +*/ +static int pfnDelete( const char *path ) +{ + return FS_Delete( path ); +} + // engine callbacks static ui_enginefuncs_t gEngfuncs = { @@ -1166,7 +1187,7 @@ static ui_enginefuncs_t gEngfuncs = pfnRenderScene, pfnAddEntity, Host_Error, - FS_FileExists, + pfnFileExists, pfnGetGameDir, Cmd_CheckMapsList, CL_Active, @@ -1205,7 +1226,7 @@ static ui_enginefuncs_t gEngfuncs = COM_CompareFileTime, VID_GetModeString, (void*)COM_SaveFile, - (void*)FS_Delete + pfnDelete }; static void pfnEnableTextInput( int enable ) @@ -1227,6 +1248,11 @@ static int pfnGetRenderers( unsigned int num, char *shortName, size_t size1, cha return 1; } +static char *pfnParseFileSafe( char *data, char *buf, const int size, unsigned int flags, int *len ) +{ + return COM_ParseFileSafe( data, buf, size, flags, len, NULL ); +} + static ui_extendedfuncs_t gExtendedfuncs = { pfnEnableTextInput, @@ -1235,7 +1261,7 @@ static ui_extendedfuncs_t gExtendedfuncs = Con_UtfMoveRight, pfnGetRenderers, Sys_DoubleTime, - _COM_ParseFileSafe, + pfnParseFileSafe, NET_AdrToString }; @@ -1356,13 +1382,13 @@ qboolean UI_LoadProgs( void ) Cvar_FullSet( "host_gameuiloaded", "1", FCVAR_READ_ONLY ); // setup gameinfo - for( i = 0; i < SI.numgames; i++ ) + for( i = 0; i < FI->numgames; i++ ) { gameui.modsInfo[i] = Mem_Calloc( gameui.mempool, sizeof( GAMEINFO )); - UI_ConvertGameInfo( gameui.modsInfo[i], SI.games[i] ); + UI_ConvertGameInfo( gameui.modsInfo[i], FI->games[i] ); } - UI_ConvertGameInfo( &gameui.gameInfo, SI.GameInfo ); // current gameinfo + UI_ConvertGameInfo( &gameui.gameInfo, FI->GameInfo ); // current gameinfo // setup globals gameui.globals->developer = host.allow_console; diff --git a/engine/client/cl_main.c b/engine/client/cl_main.c index 0aba692e..6886ab00 100644 --- a/engine/client/cl_main.c +++ b/engine/client/cl_main.c @@ -70,6 +70,7 @@ convar_t *cl_upmax; convar_t *cl_lw; convar_t *cl_charset; convar_t *cl_trace_messages; +convar_t *cl_nat; convar_t *hud_utf8; convar_t *ui_renderworld; @@ -195,8 +196,6 @@ void CL_CheckClientState( void ) Netchan_ReportFlow( &cls.netchan ); Con_DPrintf( "client connected at %.2f sec\n", Sys_DoubleTime() - cls.timestart ); - if(( cls.demoplayback || cls.disable_servercount != cl.servercount ) && cl.video_prepped ) - SCR_EndLoadingPlaque(); // get rid of loading plaque } } @@ -234,6 +233,7 @@ void CL_SignonReply( void ) Mem_PrintStats(); break; case 2: + SCR_EndLoadingPlaque(); if( cl.proxy_redirect && !cls.spectator ) CL_Disconnect(); cl.proxy_redirect = false; @@ -302,7 +302,7 @@ static float CL_LerpPoint( void ) else if( server_frametime > 0.001f ) { // automatic lerp (classic mode) - frac = ( cl.time - cl.mtime[1] ) / server_frametime; + frac = ( cl.time - cl.mtime[1] ) / server_frametime; } #endif return frac; @@ -367,7 +367,7 @@ void CL_ComputeClientInterpolationAmount( usercmd_t *cmd ) min_interp = 1.0f / cl_updaterate->value; interpolation_time = CL_LerpInterval( ); - + if( (cl_interp->value + epsilon) < min_interp ) { Con_Printf( "ex_interp forced up to %.1f msec\n", min_interp * 1000.f ); @@ -743,8 +743,14 @@ void CL_WritePacket( void ) if( cls.state == ca_connected ) numbackup = 0; // clamp cmdrate - if( cl_cmdrate->value < 0.0f ) Cvar_SetValue( "cl_cmdrate", 0.0f ); - else if( cl_cmdrate->value > 100.0f ) Cvar_SetValue( "cl_cmdrate", 100.0f ); + if( cl_cmdrate->value < 10.0f ) + { + Cvar_SetValue( "cl_cmdrate", 10.0f ); + } + else if( cl_cmdrate->value > 100.0f ) + { + Cvar_SetValue( "cl_cmdrate", 100.0f ); + } // Check to see if we can actually send this command @@ -864,6 +870,9 @@ void CL_WritePacket( void ) cl.commands[cls.netchan.outgoing_sequence & CL_UPDATE_MASK].sendsize = MSG_GetNumBytesWritten( &buf ); cl.commands[cls.netchan.outgoing_sequence & CL_UPDATE_MASK].heldback = false; + // send voice data to the server + CL_AddVoiceToDatagram(); + // composite the rest of the datagram.. if( MSG_GetNumBitsWritten( &cls.datagram ) <= MSG_GetNumBitsLeft( &buf )) MSG_WriteBits( &buf, MSG_GetData( &cls.datagram ), MSG_GetNumBitsWritten( &cls.datagram )); @@ -1253,7 +1262,7 @@ void CL_Connect_f( void ) // if running a local server, kill it and reissue if( SV_Active( )) Host_ShutdownServer(); - NET_Config( true ); // allow remote + NET_Config( true, !CVAR_TO_BOOL( cl_nat )); // allow remote Con_Printf( "server %s\n", server ); CL_Disconnect(); @@ -1299,7 +1308,7 @@ void CL_Rcon_f( void ) message[3] = (char)255; message[4] = 0; - NET_Config( true ); // allow remote + NET_Config( true, false ); // allow remote Q_strcat( message, "rcon " ); Q_strcat( message, rcon_client_password->string ); @@ -1500,6 +1509,7 @@ void CL_Disconnect( void ) cls.connect_time = 0; cls.changedemo = false; cls.max_fragment_size = FRAGMENT_MAX_SIZE; // reset fragment size + Voice_Disconnect(); CL_Stop_f(); // send a disconnect message to the server @@ -1562,7 +1572,7 @@ void CL_LocalServers_f( void ) netadr_t adr; Con_Printf( "Scanning for servers on the local network area...\n" ); - NET_Config( true ); // allow remote + NET_Config( true, true ); // allow remote // send a broadcast packet adr.type = NA_BROADCAST; @@ -1586,12 +1596,12 @@ void CL_InternetServers_f( void ) char *info = fullquery + sizeof( MS_SCAN_REQUEST ) - 1; const size_t remaining = sizeof( fullquery ) - sizeof( MS_SCAN_REQUEST ); - NET_Config( true ); // allow remote + NET_Config( true, true ); // allow remote Con_Printf( "Scanning for servers on the internet area...\n" ); Info_SetValueForKey( info, "gamedir", GI->gamefolder, remaining ); Info_SetValueForKey( info, "clver", XASH_VERSION, remaining ); // let master know about client version - // Info_SetValueForKey( info, "nat", cl_nat->string, remaining ); + Info_SetValueForKey( info, "nat", cl_nat->string, remaining ); cls.internetservers_wait = NET_SendToMasters( NS_CLIENT, sizeof( MS_SCAN_REQUEST ) + Q_strlen( info ), fullquery ); cls.internetservers_pending = true; @@ -1715,16 +1725,23 @@ void CL_ParseStatusMessage( netadr_t from, sizebuf_t *msg ) static char infostring[MAX_INFO_STRING+8]; char *s = MSG_ReadString( msg ); int i; + const char *magic = ": wrong version\n"; + size_t len = Q_strlen( s ), magiclen = Q_strlen( magic ); - CL_FixupColorStringsForInfoString( s, infostring ); - - if( Q_strstr( infostring, "wrong version" ) ) + if( len >= magiclen && !Q_strcmp( s + len - magiclen, magic )) { Netchan_OutOfBandPrint( NS_CLIENT, from, "info %i", PROTOCOL_LEGACY_VERSION ); - Con_Printf( "^1Server^7: %s, Info: %s\n", NET_AdrToString( from ), infostring ); return; } + if( !Info_IsValid( s )) + { + Con_Printf( "^1Server^7: %s, invalid infostring\n", NET_AdrToString( from )); + return; + } + + CL_FixupColorStringsForInfoString( s, infostring ); + if( !COM_CheckString( Info_ValueForKey( infostring, "gamedir" ))) { Con_Printf( "^1Server^7: %s, Info: %s\n", NET_AdrToString( from ), infostring ); @@ -1734,11 +1751,13 @@ void CL_ParseStatusMessage( netadr_t from, sizebuf_t *msg ) if( !COM_CheckString( Info_ValueForKey( infostring, "p" ))) { Info_SetValueForKey( infostring, "legacy", "1", sizeof( infostring ) ); - Con_Print("Legacy: "); + Con_Printf( "^3Server^7: %s, Game: %s\n", NET_AdrToString( from ), Info_ValueForKey( infostring, "gamedir" )); + } + else + { + // more info about servers + Con_Printf( "^2Server^7: %s, Game: %s\n", NET_AdrToString( from ), Info_ValueForKey( infostring, "gamedir" )); } - - // more info about servers - Con_Printf( "^2Server^7: %s, Game: %s\n", NET_AdrToString( from ), Info_ValueForKey( infostring, "gamedir" )); UI_AddServerToList( from, infostring ); } @@ -2097,6 +2116,12 @@ void CL_ConnectionlessPacket( netadr_t from, sizebuf_t *msg ) } else if( !Q_strcmp( c, "f" )) { + if( !NET_IsMasterAdr( from )) + { + Con_Printf( S_WARN "unexpected server list packet from %s\n", NET_AdrToString( from )); + return; + } + // serverlist got from masterserver while( MSG_GetNumBitsLeft( msg ) > 8 ) { @@ -2155,7 +2180,7 @@ void CL_ConnectionlessPacket( netadr_t from, sizebuf_t *msg ) } else if( clgame.request_type == NET_REQUEST_GAMEUI ) { - NET_Config( true ); // allow remote + NET_Config( true, false ); // allow remote Netchan_OutOfBandPrint( NS_CLIENT, servadr, "info %i", PROTOCOL_VERSION ); } } @@ -2810,6 +2835,8 @@ void CL_InitLocal( void ) Cvar_RegisterVariable( &cl_logocolor ); Cvar_RegisterVariable( &cl_test_bandwidth ); + Voice_RegisterCvars(); + // register our variables cl_crosshair = Cvar_Get( "crosshair", "1", FCVAR_ARCHIVE, "show weapon chrosshair" ); cl_nodelta = Cvar_Get ("cl_nodelta", "0", 0, "disable delta-compression for server messages" ); @@ -2832,6 +2859,7 @@ void CL_InitLocal( void ) cl_updaterate = Cvar_Get( "cl_updaterate", "20", FCVAR_USERINFO|FCVAR_ARCHIVE, "refresh rate of server messages" ); cl_dlmax = Cvar_Get( "cl_dlmax", "0", FCVAR_USERINFO|FCVAR_ARCHIVE, "max allowed outcoming fragment size" ); cl_upmax = Cvar_Get( "cl_upmax", "1200", FCVAR_ARCHIVE, "max allowed incoming fragment size" ); + cl_nat = Cvar_Get( "cl_nat", "0", 0, "show servers running under NAT" ); rate = Cvar_Get( "rate", "3500", FCVAR_USERINFO|FCVAR_ARCHIVE|FCVAR_FILTERABLE, "player network rate" ); topcolor = Cvar_Get( "topcolor", "0", FCVAR_USERINFO|FCVAR_ARCHIVE, "player top color" ); bottomcolor = Cvar_Get( "bottomcolor", "0", FCVAR_USERINFO|FCVAR_ARCHIVE, "player bottom color" ); @@ -2843,9 +2871,9 @@ void CL_InitLocal( void ) cl_nosmooth = Cvar_Get( "cl_nosmooth", "0", FCVAR_ARCHIVE, "disable smooth up stair climbing" ); cl_nointerp = Cvar_Get( "cl_nointerp", "0", FCVAR_CLIENTDLL, "disable interpolation of entities and players" ); - cl_smoothtime = Cvar_Get( "cl_smoothtime", "0", FCVAR_ARCHIVE, "time to smooth up" ); + cl_smoothtime = Cvar_Get( "cl_smoothtime", "0.1", FCVAR_ARCHIVE, "time to smooth up" ); cl_cmdbackup = Cvar_Get( "cl_cmdbackup", "10", FCVAR_ARCHIVE, "how many additional history commands are sent" ); - cl_cmdrate = Cvar_Get( "cl_cmdrate", "30", FCVAR_ARCHIVE, "Max number of command packets sent to server per second" ); + cl_cmdrate = Cvar_Get( "cl_cmdrate", "60", FCVAR_ARCHIVE, "Max number of command packets sent to server per second" ); cl_draw_particles = Cvar_Get( "r_drawparticles", "1", FCVAR_CHEAT, "render particles" ); cl_draw_tracers = Cvar_Get( "r_drawtracers", "1", FCVAR_CHEAT, "render tracers" ); cl_draw_beams = Cvar_Get( "r_drawbeams", "1", FCVAR_CHEAT, "render beams" ); @@ -2874,7 +2902,12 @@ void CL_InitLocal( void ) Cmd_AddRestrictedCommand ("kill", NULL, "die instantly" ); Cmd_AddCommand ("god", NULL, "enable godmode" ); Cmd_AddCommand ("fov", NULL, "set client field of view" ); - Cmd_AddCommand ("log", NULL, "logging server events" ); + + Cmd_AddRestrictedCommand ("ent_list", NULL, "list entities on server" ); + Cmd_AddRestrictedCommand ("ent_fire", NULL, "fire entity command (be careful)" ); + Cmd_AddRestrictedCommand ("ent_info", NULL, "dump entity information" ); + Cmd_AddRestrictedCommand ("ent_create", NULL, "create entity with specified values (be careful)" ); + Cmd_AddRestrictedCommand ("ent_getvars", NULL, "put parameters of specified entities to client's' ent_last_* cvars" ); // register our commands Cmd_AddCommand ("pause", NULL, "pause the game (if the server allows pausing)" ); @@ -3011,8 +3044,8 @@ void Host_ClientFrame( void ) // a new portion updates from server CL_RedoPrediction (); - // TODO: implement -// Voice_Idle( host.frametime ); + // update voice + Voice_Idle( host.frametime ); // emit visible entities CL_EmitEntities (); @@ -3032,9 +3065,6 @@ void Host_ClientFrame( void ) // catch changes video settings VID_CheckChanges(); - // process VGUI - VGui_RunFrame (); - // update the screen SCR_UpdateScreen (); @@ -3066,6 +3096,7 @@ void CL_Init( void ) VID_Init(); // init video S_Init(); // init sound + Voice_Init( VOICE_DEFAULT_CODEC, 3 ); // init voice // unreliable buffer. unsed for unreliable commands and voice stream MSG_Init( &cls.datagram, "cls.datagram", cls.datagram_buf, sizeof( cls.datagram_buf )); @@ -3091,13 +3122,9 @@ CL_Shutdown */ void CL_Shutdown( void ) { - // already freed - if( !cls.initialized ) return; - cls.initialized = false; - Con_Printf( "CL_Shutdown()\n" ); - if( !host.crashed ) + if( !host.crashed && cls.initialized ) { Host_WriteOpenGLConfig (); Host_WriteVideoConfig (); @@ -3111,6 +3138,11 @@ void CL_Shutdown( void ) Mobile_Shutdown (); SCR_Shutdown (); CL_UnloadProgs (); + cls.initialized = false; + + // for client-side VGUI support we use other order + if( !GI->internal_vgui_support ) + VGui_Shutdown(); FS_Delete( "demoheader.tmp" ); // remove tmp file SCR_FreeCinematic (); // release AVI's *after* client.dll because custom renderer may use them @@ -3118,4 +3150,5 @@ void CL_Shutdown( void ) R_Shutdown (); Con_Shutdown (); + } diff --git a/engine/client/cl_mobile.c b/engine/client/cl_mobile.c index 2dc84d34..3e59b85a 100644 --- a/engine/client/cl_mobile.c +++ b/engine/client/cl_mobile.c @@ -103,6 +103,11 @@ static void pfnTouch_RemoveButton( const char *name ) Touch_RemoveButton( name, true ); } +static char *pfnParseFileSafe( char *data, char *buf, const int size, unsigned int flags, int *len ) +{ + return COM_ParseFileSafe( data, buf, size, flags, len, NULL ); +} + static mobile_engfuncs_t gpMobileEngfuncs = { MOBILITY_API_VERSION, @@ -118,7 +123,7 @@ static mobile_engfuncs_t gpMobileEngfuncs = Sys_Warn, pfnGetNativeObject, ID_SetCustomClientID, - _COM_ParseFileSafe + pfnParseFileSafe }; qboolean Mobile_Init( void ) diff --git a/engine/client/cl_netgraph.c b/engine/client/cl_netgraph.c index a361c9bd..7c9ee99a 100644 --- a/engine/client/cl_netgraph.c +++ b/engine/client/cl_netgraph.c @@ -158,6 +158,7 @@ static void NetGraph_InitColors( void ) f = (float)(i - hfrac) / (float)(NETGRAPH_LERP_HEIGHT - hfrac ); VectorMA( mincolor[1], f, dc[1], netcolors[NETGRAPH_NET_COLORS + i] ); } + netcolors[NETGRAPH_NET_COLORS + i][3] = 255; } } @@ -259,7 +260,7 @@ static void NetGraph_DrawTimes( wrect_t rect, int x, int w ) for( a = 0; a < w; a++ ) { i = ( cls.netchan.outgoing_sequence - a ) & NET_TIMINGS_MASK; - h = ( netstat_cmdinfo[i].cmd_lerp / 3.0f ) * NETGRAPH_LERP_HEIGHT; + h = Q_min(( netstat_cmdinfo[i].cmd_lerp / 3.0f ) * NETGRAPH_LERP_HEIGHT, net_graphheight->value * 0.7f); fill.left = x + w - a - 1; fill.right = fill.bottom = 1; @@ -395,10 +396,10 @@ static void NetGraph_DrawTextFields( int x, int y, int w, wrect_t rect, int coun if( !out ) out = lastout; else lastout = out; - Con_DrawString( x, y, va( "in : %i %.2f k/s", netstat_graph[j].msgbytes, cls.netchan.flow[FLOW_INCOMING].avgkbytespersec ), colors ); + Con_DrawString( x, y, va( "in : %i %.2f kb/s", netstat_graph[j].msgbytes, cls.netchan.flow[FLOW_INCOMING].avgkbytespersec ), colors ); y += 15; - Con_DrawString( x, y, va( "out: %i %.2f k/s", out, cls.netchan.flow[FLOW_OUTGOING].avgkbytespersec ), colors ); + Con_DrawString( x, y, va( "out: %i %.2f kb/s", out, cls.netchan.flow[FLOW_OUTGOING].avgkbytespersec ), colors ); y += 15; if( graphtype > 2 ) @@ -615,7 +616,7 @@ static void NetGraph_GetScreenPos( wrect_t *rect, int *w, int *x, int *y ) *x = rect->left + rect->right - 5 - *w; break; case 2: // center - *x = rect->left + ( rect->right - 10 - *w ) / 2; + *x = ( rect->left + ( rect->right - 10 - *w )) / 2; break; default: // left sided *x = rect->left + 5; @@ -672,7 +673,7 @@ void SCR_DrawNetGraph( void ) if( graphtype < 3 ) { - ref.dllFuncs.GL_SetRenderMode( kRenderTransAdd ); + ref.dllFuncs.GL_SetRenderMode( kRenderTransColor ); ref.dllFuncs.GL_Bind( XASH_TEXTURE0, R_GetBuiltinTexture( REF_WHITE_TEXTURE ) ); ref.dllFuncs.Begin( TRI_QUADS ); // draw all the fills as a long solid sequence of quads for speedup reasons diff --git a/engine/client/cl_parse.c b/engine/client/cl_parse.c index 27c1a3d4..b45f575a 100644 --- a/engine/client/cl_parse.c +++ b/engine/client/cl_parse.c @@ -914,11 +914,7 @@ void CL_ParseServerData( sizebuf_t *msg ) // set the background state if( cls.demoplayback && ( cls.demonum != -1 )) - { - // re-init mouse - host.mouse_visible = false; cl.background = true; - } else cl.background = background; if( cl.background ) // tell the game parts about background state @@ -1348,7 +1344,12 @@ void CL_UpdateUserinfo( sizebuf_t *msg ) if( slot == cl.playernum ) memcpy( &gameui.playerinfo, player, sizeof( player_info_t )); } - else memset( player, 0, sizeof( *player )); + else + { + COM_ClearCustomizationList( &player->customdata, true ); + + memset( player, 0, sizeof( *player )); + } } /* @@ -1680,7 +1681,10 @@ CL_ParseVoiceInit */ void CL_ParseVoiceInit( sizebuf_t *msg ) { - // TODO: ??? + char *pszCodec = MSG_ReadString( msg ); + int quality = MSG_ReadByte( msg ); + + Voice_Init( pszCodec, quality ); } /* @@ -1691,7 +1695,31 @@ CL_ParseVoiceData */ void CL_ParseVoiceData( sizebuf_t *msg ) { - // TODO: ??? + int size, idx, frames; + byte received[8192]; + + idx = MSG_ReadByte( msg ) + 1; + + frames = MSG_ReadByte( msg ); + + size = MSG_ReadShort( msg ); + size = Q_min( size, sizeof( received )); + + MSG_ReadBytes( msg, received, size ); + + if ( idx <= 0 || idx > cl.maxclients ) + return; + + // must notify through as both local player and normal client + if( idx == cl.playernum + 1 ) + Voice_StatusAck( &voice.local, VOICE_LOOPBACK_INDEX ); + + Voice_StatusAck( &voice.players_status[idx], idx ); + + if ( !size ) + return; + + Voice_AddIncomingData( idx, received, size, frames ); } /* @@ -2335,6 +2363,7 @@ void CL_ParseServerMessage( sizebuf_t *msg, qboolean normal_message ) break; case svc_voicedata: CL_ParseVoiceData( msg ); + cl.frames[cl.parsecountmod].graphdata.voicebytes += MSG_GetNumBytesRead( msg ) - bufStart; break; case svc_resourcelocation: CL_ParseResLocation( msg ); @@ -2427,8 +2456,8 @@ void CL_ParseLegacyServerData( sizebuf_t *msg ) i = MSG_ReadLong( msg ); //cls.serverProtocol = i; - if( i != 48 ) - Host_Error( "Server uses invalid protocol (%i should be %i)\n", i, PROTOCOL_VERSION ); + if( i != PROTOCOL_LEGACY_VERSION ) + Host_Error( "Server uses invalid protocol (%i should be %i)\n", i, PROTOCOL_LEGACY_VERSION ); cl.servercount = MSG_ReadLong( msg ); cl.checksum = MSG_ReadLong( msg ); @@ -2469,11 +2498,7 @@ void CL_ParseLegacyServerData( sizebuf_t *msg ) // set the background state if( cls.demoplayback && ( cls.demonum != -1 )) - { - // re-init mouse - host.mouse_visible = false; cl.background = true; - } else cl.background = background; if( cl.background ) // tell the game parts about background state @@ -3117,12 +3142,6 @@ void CL_ParseLegacyServerMessage( sizebuf_t *msg, qboolean normal_message ) case svc_director: CL_ParseDirector( msg ); break; - case svc_voiceinit: - CL_ParseVoiceInit( msg ); - break; - case svc_voicedata: - CL_ParseVoiceData( msg ); - break; case svc_resourcelocation: CL_ParseResLocation( msg ); break; diff --git a/engine/client/cl_pmove.c b/engine/client/cl_pmove.c index 3d3a9a98..04dedf1f 100644 --- a/engine/client/cl_pmove.c +++ b/engine/client/cl_pmove.c @@ -794,6 +794,8 @@ static float GAME_EXPORT pfnTraceModel( physent_t *pe, float *start, float *end, matrix4x4 matrix; hull_t *hull; + PM_InitTrace( trace, end ); + old_usehull = clgame.pmove->usehull; clgame.pmove->usehull = 2; diff --git a/engine/client/cl_qparse.c b/engine/client/cl_qparse.c index bfdf753e..be6752a0 100644 --- a/engine/client/cl_qparse.c +++ b/engine/client/cl_qparse.c @@ -237,10 +237,6 @@ static void CL_ParseQuakeServerInfo( sizebuf_t *msg ) } else Cvar_Reset( "r_decals" ); - // re-init mouse - if( cl.background ) - host.mouse_visible = false; - if( cl.background ) // tell the game parts about background state Cvar_FullSet( "cl_background", "1", FCVAR_READ_ONLY ); else Cvar_FullSet( "cl_background", "0", FCVAR_READ_ONLY ); @@ -864,7 +860,7 @@ void CL_QuakeExecStuff( void ) if( !*text ) break; - text = _COM_ParseFileSafe( text, token, sizeof( token ), PFILE_IGNOREBRACKET, NULL ); + text = COM_ParseFileSafe( text, token, sizeof( token ), PFILE_IGNOREBRACKET, NULL, NULL ); if( !text ) break; diff --git a/engine/client/cl_render.c b/engine/client/cl_render.c index 9af5aec7..4351cde1 100644 --- a/engine/client/cl_render.c +++ b/engine/client/cl_render.c @@ -133,7 +133,7 @@ const char *CL_GenericHandle( int fileindex ) return cl.files_precache[fileindex]; } -int CL_RenderGetParm( const int parm, const int arg, const qboolean checkRef ) +intptr_t CL_RenderGetParm( const int parm, const int arg, const qboolean checkRef ) { switch( parm ) { @@ -161,9 +161,9 @@ int CL_RenderGetParm( const int parm, const int arg, const qboolean checkRef ) case PARM_WATER_ALPHA: return FBitSet( world.flags, FWORLD_WATERALPHA ); case PARM_DELUXEDATA: - return *(int *)&world.deluxedata; + return (intptr_t)world.deluxedata; case PARM_SHADOWDATA: - return *(int *)&world.shadowdata; + return (intptr_t)world.shadowdata; default: // indicates call from client.dll if( checkRef ) @@ -204,7 +204,7 @@ int CL_RenderGetParm( const int parm, const int arg, const qboolean checkRef ) return 0; } -static int pfnRenderGetParm( int parm, int arg ) +static intptr_t pfnRenderGetParm( int parm, int arg ) { return CL_RenderGetParm( parm, arg, true ); } diff --git a/engine/client/cl_scrn.c b/engine/client/cl_scrn.c index 62a42047..b65c9e59 100644 --- a/engine/client/cl_scrn.c +++ b/engine/client/cl_scrn.c @@ -418,7 +418,6 @@ void SCR_BeginLoadingPlaque( qboolean is_background ) cls.draw_changelevel = !is_background; SCR_UpdateScreen(); cls.disable_screen = host.realtime; - cls.disable_servercount = cl.servercount; cl.background = is_background; // set right state before svc_serverdata is came if( !Host_IsDedicated() ) @@ -781,7 +780,6 @@ SCR_VidInit */ void SCR_VidInit( void ) { - string libpath; if( !ref.initialized ) // don't call VidInit too soon return; @@ -796,8 +794,11 @@ void SCR_VidInit( void ) gameui.globals->scrHeight = refState.height; } - COM_GetCommonLibraryPath( LIBRARY_CLIENT, libpath, sizeof( libpath )); - VGui_Startup( libpath, refState.width, refState.height ); + // notify vgui about screen size change + if( clgame.hInstance ) + { + VGui_Startup( refState.width, refState.height ); + } CL_ClearSpriteTextures(); // now all hud sprites are invalid diff --git a/engine/client/cl_tent.c b/engine/client/cl_tent.c index 1e9c19e9..8158ce2c 100644 --- a/engine/client/cl_tent.c +++ b/engine/client/cl_tent.c @@ -1904,7 +1904,7 @@ handle temp-entity messages void CL_ParseTempEntity( sizebuf_t *msg ) { sizebuf_t buf; - byte pbuf[256]; + byte pbuf[2048]; int iSize; int type, color, count, flags; int decalIndex, modelIndex, entityIndex; @@ -1923,6 +1923,10 @@ void CL_ParseTempEntity( sizebuf_t *msg ) decalIndex = modelIndex = entityIndex = 0; + // this will probably be fatal anyway + if( iSize > sizeof( pbuf )) + Con_Printf( S_ERROR "%s: Temp buffer overflow!\n", __FUNCTION__ ); + // parse user message into buffer MSG_ReadBytes( msg, pbuf, iSize ); @@ -1970,6 +1974,9 @@ void CL_ParseTempEntity( sizebuf_t *msg ) pos[1] = MSG_ReadCoord( &buf ); pos[2] = MSG_ReadCoord( &buf ); R_BlobExplosion( pos ); + + hSound = S_RegisterSound( cl_explode_sounds[0] ); + S_StartSound( pos, -1, CHAN_AUTO, hSound, VOL_NORM, 1.0f, PITCH_NORM, 0 ); break; case TE_SMOKE: pos[0] = MSG_ReadCoord( &buf ); @@ -2023,7 +2030,7 @@ void CL_ParseTempEntity( sizebuf_t *msg ) dl->decay = 300; hSound = S_RegisterSound( cl_explode_sounds[0] ); - S_StartSound( pos, 0, CHAN_STATIC, hSound, VOL_NORM, 0.6f, PITCH_NORM, 0 ); + S_StartSound( pos, -1, CHAN_AUTO, hSound, VOL_NORM, 0.6f, PITCH_NORM, 0 ); break; case TE_BSPDECAL: case TE_DECAL: diff --git a/engine/client/cl_view.c b/engine/client/cl_view.c index 310d3aac..cac27075 100644 --- a/engine/client/cl_view.c +++ b/engine/client/cl_view.c @@ -286,9 +286,6 @@ qboolean V_PreRender( void ) if( !ref.initialized ) return false; - if( host.status == HOST_NOFOCUS ) - return false; - if( host.status == HOST_SLEEP ) return false; diff --git a/engine/client/client.h b/engine/client/client.h index 129caca6..684150d1 100644 --- a/engine/client/client.h +++ b/engine/client/client.h @@ -33,6 +33,7 @@ GNU General Public License for more details. #include "net_api.h" #include "world.h" #include "ref_common.h" +#include "voice.h" // client sprite types #define SPR_CLIENT 0 // client sprite for temp-entities or user-textures @@ -465,7 +466,7 @@ typedef struct string cdtracks[MAX_CDTRACKS]; // 32 cd-tracks read from cdaudio.txt - model_t sprites[MAX_CLIENT_SPRITES]; // client spritetextures + model_t sprites[MAX_CLIENT_SPRITES]; // hud&client spritetexturesz int viewport[4]; // viewport sizes client_draw_t ds; // draw2d stuff (hud, weaponmenu etc) @@ -527,9 +528,6 @@ typedef struct float disable_screen; // showing loading plaque between levels // or changing rendering dlls // if time gets > 30 seconds ahead, break it - int disable_servercount; // when we receive a frame and cl.servercount - // > cls.disable_servercount, clear disable_screen - qboolean draw_changelevel; // draw changelevel image 'Loading...' keydest_t key_dest; @@ -705,7 +703,7 @@ dlight_t *CL_GetEntityLight( int number ); // // cl_cmds.c // -void CL_Quit_f( void ) NORETURN; +void CL_Quit_f( void ); void CL_ScreenShot_f( void ); void CL_SnapShot_f( void ); void CL_PlayCDTrack_f( void ); @@ -966,7 +964,7 @@ void CL_ClearAllRemaps( void ); // cl_render.c // qboolean R_InitRenderAPI( void ); -int CL_RenderGetParm( const int parm, const int arg, const qboolean checkRef ); +intptr_t CL_RenderGetParm( const int parm, const int arg, const qboolean checkRef ); lightstyle_t *CL_GetLightStyle( int number ); int R_FatPVS( const vec3_t org, float radius, byte *visbuffer, qboolean merge, qboolean fullvis ); const ref_overview_t *GL_GetOverviewParms( void ); diff --git a/engine/client/console.c b/engine/client/console.c index e53aafce..21f76b67 100644 --- a/engine/client/console.c +++ b/engine/client/console.c @@ -283,6 +283,8 @@ void Con_ToggleConsole_f( void ) if( !host.allow_console || UI_CreditsActive( )) return; // disabled + SCR_EndLoadingPlaque(); + // show console only in game or by special call from menu if( cls.state != ca_active || cls.key_dest == key_menu ) return; @@ -912,7 +914,7 @@ static int Con_DrawGenericChar( int x, int y, int number, rgba_t color ) if( !con.curFont || !con.curFont->valid ) return 0; - number = Con_UtfProcessChar(number); + number = Con_UtfProcessChar( number ); if( !number ) return 0; @@ -2039,7 +2041,7 @@ void Con_DrawDebug( void ) if( scr_download->value != -1.0f ) { Q_snprintf( dlstring, sizeof( dlstring ), "Downloading [%d remaining]: ^2%s^7 %5.1f%% time %.f secs", - host.downloadcount, host.downloadfile, scr_download->value, Sys_DoubleTime() - timeStart ); + host.downloadcount, host.downloadfile, scr_download->value, Sys_DoubleTime() - timeStart ); x = refState.width - 500; y = con.curFont->charHeight * 1.05f; Con_DrawString( x, y, dlstring, g_color_table[7] ); @@ -2049,7 +2051,7 @@ void Con_DrawDebug( void ) timeStart = Sys_DoubleTime(); } - if( !host_developer.value || Cvar_VariableInteger( "cl_background" ) || Cvar_VariableInteger( "sv_background" )) + if( !host.allow_console || Cvar_VariableInteger( "cl_background" ) || Cvar_VariableInteger( "sv_background" )) return; if( con.draw_notify && !Con_Visible( )) @@ -2075,7 +2077,7 @@ void Con_DrawNotify( void ) x = con.curFont->charWidths[' ']; // offset one space at left screen side - if( host_developer.value && ( !Cvar_VariableInteger( "cl_background" ) && !Cvar_VariableInteger( "sv_background" ))) + if( host.allow_console && ( !Cvar_VariableInteger( "cl_background" ) && !Cvar_VariableInteger( "sv_background" ))) { for( i = CON_LINES_COUNT - con.num_times; i < CON_LINES_COUNT; i++ ) { @@ -2401,11 +2403,21 @@ void Con_RunConsole( void ) FBitSet( cl_charset->flags, FCVAR_CHANGED ) ) { // update codepage parameters - g_codepage = 0; - if( !Q_stricmp( con_charset->string, "cp1251" ) ) + if( !Q_stricmp( con_charset->string, "cp1251" )) + { g_codepage = 1251; - else if( !Q_stricmp( con_charset->string, "cp1252" ) ) + } + else if( !Q_stricmp( con_charset->string, "cp1252" )) + { g_codepage = 1252; + } + else + { + Con_Printf( S_WARN "Unknown charset %s, defaulting to cp1252", con_charset->string ); + + Cvar_DirectSet( con_charset, "cp1252" ); + g_codepage = 1252; + } g_utf8 = !Q_stricmp( cl_charset->string, "utf-8" ); Con_InvalidateFonts(); diff --git a/engine/client/in_touch.c b/engine/client/in_touch.c index 5bd454d0..f8136e6d 100644 --- a/engine/client/in_touch.c +++ b/engine/client/in_touch.c @@ -151,7 +151,6 @@ convar_t *touch_exp_mult; convar_t *touch_grid_enable; convar_t *touch_grid_count; convar_t *touch_config_file; -convar_t *touch_enable; convar_t *touch_in_menu; convar_t *touch_joy_radius; convar_t *touch_dpad_radius; @@ -162,7 +161,9 @@ convar_t *touch_highlight_b; convar_t *touch_highlight_a; convar_t *touch_precise_amount; convar_t *touch_joy_texture; -convar_t *touch_emulate; + +CVAR_DEFINE_AUTO( touch_enable, DEFAULT_TOUCH_ENABLE, FCVAR_ARCHIVE | FCVAR_FILTERABLE, "enable touch controls" ); +CVAR_DEFINE_AUTO( touch_emulate, "0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "emulate touch with mouse" ); // code looks smaller with it #define B(x) (button->x) @@ -214,6 +215,74 @@ static inline int Touch_ExportButtonToConfig( file_t *f, touch_button_t *button, return 0; } +/* +================= +Touch_DumpConfig + +Dump config to file +================= +*/ +qboolean Touch_DumpConfig( const char *name, const char *profilename ) +{ + file_t *f; + touch_button_t *button; + + f = FS_Open( name, "w", true ); + + if( !f ) + { + Con_Printf( S_ERROR "Couldn't write %s.\n", name ); + return false; + } + + FS_Printf( f, "//=======================================================================\n"); + FS_Printf( f, "//\tCopyright FWGS & XashXT group %s (c)\n", Q_timestamp( TIME_YEAR_ONLY )); + FS_Printf( f, "//\t\t\ttouchscreen config\n" ); + FS_Printf( f, "//=======================================================================\n" ); + FS_Printf( f, "\ntouch_config_file \"%s\"\n", profilename ); + FS_Printf( f, "\n// touch cvars\n" ); + FS_Printf( f, "\n// sensitivity settings\n" ); + FS_Printf( f, "touch_pitch \"%f\"\n", touch_pitch->value ); + FS_Printf( f, "touch_yaw \"%f\"\n", touch_yaw->value ); + FS_Printf( f, "touch_forwardzone \"%f\"\n", touch_forwardzone->value ); + FS_Printf( f, "touch_sidezone \"%f\"\n", touch_sidezone->value ); + FS_Printf( f, "touch_nonlinear_look \"%d\"\n", CVAR_TO_BOOL(touch_nonlinear_look)); + FS_Printf( f, "touch_pow_factor \"%f\"\n", touch_pow_factor->value ); + FS_Printf( f, "touch_pow_mult \"%f\"\n", touch_pow_mult->value ); + FS_Printf( f, "touch_exp_mult \"%f\"\n", touch_exp_mult->value ); + FS_Printf( f, "\n// grid settings\n" ); + FS_Printf( f, "touch_grid_count \"%d\"\n", (int)touch_grid_count->value ); + FS_Printf( f, "touch_grid_enable \"%d\"\n", CVAR_TO_BOOL(touch_grid_enable)); + FS_Printf( f, "\n// global overstroke (width, r, g, b, a)\n" ); + FS_Printf( f, "touch_set_stroke %d %d %d %d %d\n", touch.swidth, touch.scolor[0], touch.scolor[1], touch.scolor[2], touch.scolor[3] ); + FS_Printf( f, "\n// highlight when pressed\n" ); + FS_Printf( f, "touch_highlight_r \"%f\"\n", touch_highlight_r->value ); + FS_Printf( f, "touch_highlight_g \"%f\"\n", touch_highlight_g->value ); + FS_Printf( f, "touch_highlight_b \"%f\"\n", touch_highlight_b->value ); + FS_Printf( f, "touch_highlight_a \"%f\"\n", touch_highlight_a->value ); + FS_Printf( f, "\n// _joy and _dpad options\n" ); + FS_Printf( f, "touch_dpad_radius \"%f\"\n", touch_dpad_radius->value ); + FS_Printf( f, "touch_joy_radius \"%f\"\n", touch_joy_radius->value ); + FS_Printf( f, "\n// how much slowdown when Precise Look button pressed\n" ); + FS_Printf( f, "touch_precise_amount \"%f\"\n", touch_precise_amount->value ); + FS_Printf( f, "\n// enable/disable move indicator\n" ); + FS_Printf( f, "touch_move_indicator \"%f\"\n", touch_move_indicator->value ); + + FS_Printf( f, "\n// reset menu state when execing config\n" ); + FS_Printf( f, "touch_setclientonly 0\n" ); + FS_Printf( f, "\n// touch buttons\n" ); + FS_Printf( f, "touch_removeall\n" ); + + for( button = touch.list_user.first; button; button = button->next ) + { + Touch_ExportButtonToConfig( f, button, false ); + } + + FS_Close( f ); + + return true; +} + /* ================= Touch_WriteConfig @@ -237,61 +306,14 @@ void Touch_WriteConfig( void ) Q_snprintf( newconfigfile, sizeof( newconfigfile ), "%s.new", touch_config_file->string ); Q_snprintf( oldconfigfile, sizeof( oldconfigfile ), "%s.bak", touch_config_file->string ); - f = FS_Open( newconfigfile, "w", true ); - if( f ) + if( Touch_DumpConfig( newconfigfile, touch_config_file->string )) { - touch_button_t *button; - FS_Printf( f, "//=======================================================================\n"); - FS_Printf( f, "//\tCopyright FWGS & XashXT group %s (c)\n", Q_timestamp( TIME_YEAR_ONLY )); - FS_Printf( f, "//\t\t\ttouchscreen config\n" ); - FS_Printf( f, "//=======================================================================\n" ); - FS_Printf( f, "\ntouch_config_file \"%s\"\n", touch_config_file->string ); - FS_Printf( f, "\n// touch cvars\n" ); - FS_Printf( f, "\n// sensitivity settings\n" ); - FS_Printf( f, "touch_pitch \"%f\"\n", touch_pitch->value ); - FS_Printf( f, "touch_yaw \"%f\"\n", touch_yaw->value ); - FS_Printf( f, "touch_forwardzone \"%f\"\n", touch_forwardzone->value ); - FS_Printf( f, "touch_sidezone \"%f\"\n", touch_sidezone->value ); - FS_Printf( f, "touch_nonlinear_look \"%d\"\n", CVAR_TO_BOOL(touch_nonlinear_look)); - FS_Printf( f, "touch_pow_factor \"%f\"\n", touch_pow_factor->value ); - FS_Printf( f, "touch_pow_mult \"%f\"\n", touch_pow_mult->value ); - FS_Printf( f, "touch_exp_mult \"%f\"\n", touch_exp_mult->value ); - FS_Printf( f, "\n// grid settings\n" ); - FS_Printf( f, "touch_grid_count \"%d\"\n", (int)touch_grid_count->value ); - FS_Printf( f, "touch_grid_enable \"%d\"\n", CVAR_TO_BOOL(touch_grid_enable)); - FS_Printf( f, "\n// global overstroke (width, r, g, b, a)\n" ); - FS_Printf( f, "touch_set_stroke %d %d %d %d %d\n", touch.swidth, touch.scolor[0], touch.scolor[1], touch.scolor[2], touch.scolor[3] ); - FS_Printf( f, "\n// highlight when pressed\n" ); - FS_Printf( f, "touch_highlight_r \"%f\"\n", touch_highlight_r->value ); - FS_Printf( f, "touch_highlight_g \"%f\"\n", touch_highlight_g->value ); - FS_Printf( f, "touch_highlight_b \"%f\"\n", touch_highlight_b->value ); - FS_Printf( f, "touch_highlight_a \"%f\"\n", touch_highlight_a->value ); - FS_Printf( f, "\n// _joy and _dpad options\n" ); - FS_Printf( f, "touch_dpad_radius \"%f\"\n", touch_dpad_radius->value ); - FS_Printf( f, "touch_joy_radius \"%f\"\n", touch_joy_radius->value ); - FS_Printf( f, "\n// how much slowdown when Precise Look button pressed\n" ); - FS_Printf( f, "touch_precise_amount \"%f\"\n", touch_precise_amount->value ); - FS_Printf( f, "\n// enable/disable move indicator\n" ); - FS_Printf( f, "touch_move_indicator \"%f\"\n", touch_move_indicator->value ); - - FS_Printf( f, "\n// reset menu state when execing config\n" ); - FS_Printf( f, "touch_setclientonly 0\n" ); - FS_Printf( f, "\n// touch buttons\n" ); - FS_Printf( f, "touch_removeall\n" ); - - for( button = touch.list_user.first; button; button = button->next ) - { - Touch_ExportButtonToConfig( f, button, false ); - } - - FS_Close( f ); - FS_Delete( oldconfigfile ); FS_Rename( touch_config_file->string, oldconfigfile ); + FS_Delete( touch_config_file->string ); FS_Rename( newconfigfile, touch_config_file->string ); } - else Con_Printf( S_ERROR "Couldn't write %s.\n", touch_config_file->string ); } /* @@ -303,8 +325,8 @@ export current touch configuration into profile */ static void Touch_ExportConfig_f( void ) { - file_t *f; const char *name; + string profilename, profilebase; if( Cmd_Argc() != 2 ) { @@ -316,65 +338,15 @@ static void Touch_ExportConfig_f( void ) name = Cmd_Argv( 1 ); - Con_Reportf( "Exporting config to %s\n", name ); - f = FS_Open( name, "w", true ); - if( f ) + if( Q_strstr( name, "touch_presets/" )) { - string profilename, profilebase; - touch_button_t *button; - - if( Q_strstr( name, "touch_presets/" ) ) - { - COM_FileBase( name, profilebase ); - Q_snprintf( profilename, sizeof( profilebase ), "touch_profiles/%s (copy).cfg", profilebase ); - } - else Q_strncpy( profilename, name, sizeof( profilename )); - FS_Printf( f, "//=======================================================================\n"); - FS_Printf( f, "//\tCopyright FWGS & XashXT group %s (c)\n", Q_timestamp( TIME_YEAR_ONLY )); - FS_Printf( f, "//\t\t\ttouchscreen preset\n" ); - FS_Printf( f, "//=======================================================================\n" ); - FS_Printf( f, "\ntouch_config_file \"%s\"\n", profilename ); - FS_Printf( f, "\n// touch cvars\n" ); - FS_Printf( f, "\n// sensitivity settings\n" ); - FS_Printf( f, "touch_pitch \"%f\"\n", touch_pitch->value ); - FS_Printf( f, "touch_yaw \"%f\"\n", touch_yaw->value ); - FS_Printf( f, "touch_forwardzone \"%f\"\n", touch_forwardzone->value ); - FS_Printf( f, "touch_sidezone \"%f\"\n", touch_sidezone->value ); - FS_Printf( f, "touch_nonlinear_look \"%d\"\n", CVAR_TO_BOOL(touch_nonlinear_look) ); - FS_Printf( f, "touch_pow_factor \"%f\"\n", touch_pow_factor->value ); - FS_Printf( f, "touch_pow_mult \"%f\"\n", touch_pow_mult->value ); - FS_Printf( f, "touch_exp_mult \"%f\"\n", touch_exp_mult->value ); - FS_Printf( f, "\n// grid settings\n" ); - FS_Printf( f, "touch_grid_count \"%d\"\n", (int)touch_grid_count->value ); - FS_Printf( f, "touch_grid_enable \"%d\"\n", CVAR_TO_BOOL(touch_grid_enable) ); - FS_Printf( f, "\n// global overstroke (width, r, g, b, a)\n" ); - FS_Printf( f, "touch_set_stroke %d %d %d %d %d\n", touch.swidth, touch.scolor[0], touch.scolor[1], touch.scolor[2], touch.scolor[3] ); - FS_Printf( f, "\n// highlight when pressed\n" ); - FS_Printf( f, "touch_highlight_r \"%f\"\n", touch_highlight_r->value ); - FS_Printf( f, "touch_highlight_g \"%f\"\n", touch_highlight_g->value ); - FS_Printf( f, "touch_highlight_b \"%f\"\n", touch_highlight_b->value ); - FS_Printf( f, "touch_highlight_a \"%f\"\n", touch_highlight_a->value ); - FS_Printf( f, "\n// _joy and _dpad options\n" ); - FS_Printf( f, "touch_dpad_radius \"%f\"\n", touch_dpad_radius->value ); - FS_Printf( f, "touch_joy_radius \"%f\"\n", touch_joy_radius->value ); - FS_Printf( f, "\n// how much slowdown when Precise Look button pressed\n" ); - FS_Printf( f, "touch_precise_amount \"%f\"\n", touch_precise_amount->value ); - FS_Printf( f, "\n// enable/disable move indicator\n" ); - FS_Printf( f, "touch_move_indicator \"%f\"\n", touch_move_indicator->value ); - - FS_Printf( f, "\n// reset menu state when execing config\n" ); - FS_Printf( f, "touch_setclientonly 0\n" ); - FS_Printf( f, "\n// touch buttons\n" ); - FS_Printf( f, "touch_removeall\n" ); - for( button = touch.list_user.first; button; button = button->next ) - { - Touch_ExportButtonToConfig( f, button, true ); - } - FS_Printf( f, "\n// round button coordinates to grid\n" ); - FS_Printf( f, "touch_roundall\n" ); - FS_Close( f ); + COM_FileBase( name, profilebase ); + Q_snprintf( profilename, sizeof( profilebase ), "touch_profiles/%s (copy).cfg", profilebase ); } - else Con_Printf( S_ERROR "Couldn't write %s.\n", name ); + else Q_strncpy( profilename, name, sizeof( profilename )); + + Con_Reportf( "Exporting config to \"%s\", profile name \"%s\"\n", name, profilename ); + Touch_DumpConfig( name, profilename ); } /* @@ -497,8 +469,8 @@ static touch_button_t *Touch_FindFirst( touchbuttonlist_t *list, const char *nam void Touch_SetClientOnly( byte state ) { + // TODO: fix clash with vgui cursors touch.clientonly = state; - host.mouse_visible = state; touch.move_finger = touch.look_finger = -1; touch.forward = touch.side = 0; @@ -994,7 +966,7 @@ static void Touch_InitEditor( void ) Touch_ClearList( &touch.list_edit ); - temp = Touch_AddButton( &touch.list_edit, "close", "touch_default/edit_close.tga", "touch_disableedit", 0, y, x, y + 0.1f, color, true ); + temp = Touch_AddButton( &touch.list_edit, "close", "touch_default/edit_close", "touch_disableedit", 0, y, x, y + 0.1f, color, true ); SetBits( temp->flags, TOUCH_FL_NOEDIT ); temp = Touch_AddButton( &touch.list_edit, "close", "#Close and save", "", x, y, x + 0.2f, y + 0.1f, color, true ); @@ -1002,7 +974,7 @@ static void Touch_InitEditor( void ) y += 0.2f; - temp = Touch_AddButton( &touch.list_edit, "cancel", "touch_default/edit_reset.tga", "touch_reloadconfig", 0, y, x, y + 0.1f, color, true ); + temp = Touch_AddButton( &touch.list_edit, "cancel", "touch_default/edit_reset", "touch_reloadconfig", 0, y, x, y + 0.1f, color, true ); SetBits( temp->flags, TOUCH_FL_NOEDIT ); temp = Touch_AddButton( &touch.list_edit, "close", "#Cancel and reset", "", x, y, x + 0.2f, y + 0.1f, color, true ); @@ -1010,7 +982,7 @@ static void Touch_InitEditor( void ) y += 0.2f; - touch.hidebutton = Touch_AddButton( &touch.list_edit, "showhide", "touch_default/edit_hide.tga", "touch_toggleselection", 0, y, x, y + 0.1f, color, true ); + touch.hidebutton = Touch_AddButton( &touch.list_edit, "showhide", "touch_default/edit_hide", "touch_toggleselection", 0, y, x, y + 0.1f, color, true ); SetBits( touch.hidebutton->flags, TOUCH_FL_HIDE | TOUCH_FL_NOEDIT ); } @@ -1038,23 +1010,23 @@ void Touch_Init( void ) MakeRGBA( color, 255, 255, 255, 255 ); Touch_AddDefaultButton( "look", "", "_look", 0.500000, 0.000000, 1.000000, 1, color, 0, 0, 0 ); Touch_AddDefaultButton( "move", "", "_move", 0.000000, 0.000000, 0.500000, 1, color, 0, 0, 0 ); - Touch_AddDefaultButton( "invnext", "touch_default/next_weap.tga", "invnext", 0.000000, 0.530200, 0.120000, 0.757428, color, 2, 1, 0 ); - Touch_AddDefaultButton( "invprev", "touch_default/prev_weap.tga", "invprev", 0.000000, 0.075743, 0.120000, 0.302971, color, 2, 1, 0 ); - Touch_AddDefaultButton( "use", "touch_default/use.tga", "+use", 0.880000, 0.454457, 1.000000, 0.681685, color, 2, 1, 0 ); - Touch_AddDefaultButton( "jump", "touch_default/jump.tga", "+jump", 0.880000, 0.227228, 1.000000, 0.454457, color, 2, 1, 0 ); - Touch_AddDefaultButton( "attack", "touch_default/shoot.tga", "+attack", 0.760000, 0.530200, 0.880000, 0.757428, color, 2, 1, 0 ); - Touch_AddDefaultButton( "attack2", "touch_default/shoot_alt.tga", "+attack2", 0.760000, 0.302971, 0.880000, 0.530200, color, 2, 1, 0 ); - Touch_AddDefaultButton( "loadquick", "touch_default/load.tga", "loadquick", 0.760000, 0.000000, 0.840000, 0.151486, color, 2, 1, 16 ); - Touch_AddDefaultButton( "savequick", "touch_default/save.tga", "savequick", 0.840000, 0.000000, 0.920000, 0.151486, color, 2, 1, 16 ); - Touch_AddDefaultButton( "messagemode", "touch_default/keyboard.tga", "messagemode", 0.840000, 0.000000, 0.920000, 0.151486, color, 2, 1, 8 ); - Touch_AddDefaultButton( "reload", "touch_default/reload.tga", "+reload", 0.000000, 0.302971, 0.120000, 0.530200, color, 2, 1, 0 ); - Touch_AddDefaultButton( "flashlight", "touch_default/flash_light_filled.tga", "impulse 100", 0.920000, 0.000000, 1.000000, 0.151486, color, 2, 1, 0 ); - Touch_AddDefaultButton( "scores", "touch_default/map.tga", "+showscores", 0.760000, 0.000000, 0.840000, 0.151486, color, 2, 1, 8 ); - Touch_AddDefaultButton( "show_numbers", "touch_default/show_weapons.tga", "exec touch_default/numbers.cfg", 0.440000, 0.833171, 0.520000, 0.984656, color, 2, 1, 0 ); - Touch_AddDefaultButton( "duck", "touch_default/crouch.tga", "+duck", 0.880000, 0.757428, 1.000000, 0.984656, color, 2, 1, 0 ); - Touch_AddDefaultButton( "tduck", "touch_default/tduck.tga", ";+duck", 0.560000, 0.833171, 0.620000, 0.946785, color, 2, 1, 0 ); - Touch_AddDefaultButton( "edit", "touch_default/settings.tga", "touch_enableedit", 0.420000, 0.000000, 0.500000, 0.151486, color, 2, 1, 32 ); - Touch_AddDefaultButton( "menu", "touch_default/menu.tga", "escape", 0.000000, 0.833171, 0.080000, 0.984656, color, 2, 1, 0 ); + Touch_AddDefaultButton( "invnext", "touch_default/next_weap", "invnext", 0.000000, 0.530200, 0.120000, 0.757428, color, 2, 1, 0 ); + Touch_AddDefaultButton( "invprev", "touch_default/prev_weap", "invprev", 0.000000, 0.075743, 0.120000, 0.302971, color, 2, 1, 0 ); + Touch_AddDefaultButton( "use", "touch_default/use", "+use", 0.880000, 0.454457, 1.000000, 0.681685, color, 2, 1, 0 ); + Touch_AddDefaultButton( "jump", "touch_default/jump", "+jump", 0.880000, 0.227228, 1.000000, 0.454457, color, 2, 1, 0 ); + Touch_AddDefaultButton( "attack", "touch_default/shoot", "+attack", 0.760000, 0.530200, 0.880000, 0.757428, color, 2, 1, 0 ); + Touch_AddDefaultButton( "attack2", "touch_default/shoot_alt", "+attack2", 0.760000, 0.302971, 0.880000, 0.530200, color, 2, 1, 0 ); + Touch_AddDefaultButton( "loadquick", "touch_default/load", "loadquick", 0.760000, 0.000000, 0.840000, 0.151486, color, 2, 1, 16 ); + Touch_AddDefaultButton( "savequick", "touch_default/save", "savequick", 0.840000, 0.000000, 0.920000, 0.151486, color, 2, 1, 16 ); + Touch_AddDefaultButton( "messagemode", "touch_default/keyboard", "messagemode", 0.840000, 0.000000, 0.920000, 0.151486, color, 2, 1, 8 ); + Touch_AddDefaultButton( "reload", "touch_default/reload", "+reload", 0.000000, 0.302971, 0.120000, 0.530200, color, 2, 1, 0 ); + Touch_AddDefaultButton( "flashlight", "touch_default/flash_light_filled", "impulse 100", 0.920000, 0.000000, 1.000000, 0.151486, color, 2, 1, 0 ); + Touch_AddDefaultButton( "scores", "touch_default/map", "+showscores", 0.760000, 0.000000, 0.840000, 0.151486, color, 2, 1, 8 ); + Touch_AddDefaultButton( "show_numbers", "touch_default/show_weapons", "exec touch_default/numbers.cfg", 0.440000, 0.833171, 0.520000, 0.984656, color, 2, 1, 0 ); + Touch_AddDefaultButton( "duck", "touch_default/crouch", "+duck", 0.880000, 0.757428, 1.000000, 0.984656, color, 2, 1, 0 ); + Touch_AddDefaultButton( "tduck", "touch_default/tduck", ";+duck", 0.560000, 0.833171, 0.620000, 0.946785, color, 2, 1, 0 ); + Touch_AddDefaultButton( "edit", "touch_default/settings", "touch_enableedit", 0.420000, 0.000000, 0.500000, 0.151486, color, 2, 1, 32 ); + Touch_AddDefaultButton( "menu", "touch_default/menu", "escape", 0.000000, 0.833171, 0.080000, 0.984656, color, 2, 1, 0 ); Cmd_AddCommand( "touch_addbutton", Touch_AddButton_f, "add native touch button" ); @@ -1106,11 +1078,11 @@ void Touch_Init( void ) touch_dpad_radius = Cvar_Get( "touch_dpad_radius", "1.0", FCVAR_FILTERABLE, "dpad radius multiplier" ); touch_joy_radius = Cvar_Get( "touch_joy_radius", "1.0", FCVAR_FILTERABLE, "joy radius multiplier" ); touch_move_indicator = Cvar_Get( "touch_move_indicator", "0.0", FCVAR_FILTERABLE, "indicate move events (0 to disable)" ); - touch_joy_texture = Cvar_Get( "touch_joy_texture", "touch_default/joy.tga", FCVAR_FILTERABLE, "texture for move indicator"); + touch_joy_texture = Cvar_Get( "touch_joy_texture", "touch_default/joy", FCVAR_FILTERABLE, "texture for move indicator"); // input devices cvar - touch_enable = Cvar_Get( "touch_enable", DEFAULT_TOUCH_ENABLE, FCVAR_ARCHIVE | FCVAR_FILTERABLE, "enable touch controls" ); - touch_emulate = Cvar_Get( "touch_emulate", "0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "emulate touch with mouse" ); + Cvar_RegisterVariable( &touch_enable ); + Cvar_RegisterVariable( &touch_emulate ); /// TODO: touch sdl platform // SDL_SetHint( SDL_HINT_ANDROID_SEPARATE_MOUSE_AND_TOUCH, "1" ); @@ -1372,7 +1344,7 @@ void Touch_Draw( void ) { touch_button_t *button; - if( !touch.initialized || (!CVAR_TO_BOOL(touch_enable) && !touch.clientonly) ) + if( !touch.initialized || ( !touch_enable.value && !touch.clientonly )) return; Touch_InitConfig(); @@ -1515,9 +1487,9 @@ static void Touch_EditMove( touchEventType type, int fingerID, float x, float y, touch.hidebutton->flags &= ~TOUCH_FL_HIDE; if( FBitSet( button->flags, TOUCH_FL_HIDE )) - Q_strcpy( touch.hidebutton->texturefile, "touch_default/edit_show.tga" ); + Q_strcpy( touch.hidebutton->texturefile, "touch_default/edit_show" ); else - Q_strcpy( touch.hidebutton->texturefile, "touch_default/edit_hide.tga" ); + Q_strcpy( touch.hidebutton->texturefile, "touch_default/edit_hide" ); } } if( type == event_motion ) // shutdown button move @@ -1966,9 +1938,8 @@ static int Touch_ControlsEvent( touchEventType type, int fingerID, float x, floa int IN_TouchEvent( touchEventType type, int fingerID, float x, float y, float dx, float dy ) { - // simulate menu mouse click - if( cls.key_dest != key_game && !CVAR_TO_BOOL(touch_in_menu) ) + if( cls.key_dest != key_game && !CVAR_TO_BOOL( touch_in_menu )) { touch.move_finger = touch.resize_finger = touch.look_finger = -1; // Hack for keyboard, hope it help @@ -2011,25 +1982,20 @@ int IN_TouchEvent( touchEventType type, int fingerID, float x, float y, float dx { VGui_MouseMove( TO_SCRN_X(x), TO_SCRN_Y(y) ); - if( type != event_motion ) - VGui_KeyEvent( K_MOUSE1, type == event_down ? 1 : 0 ); - - // allow scoreboard scroll - if( host.mouse_visible && type == event_motion ) - return 0; + switch( type ) + { + case event_down: + VGui_MouseEvent( K_MOUSE1, 1 ); + break; + case event_up: + VGui_MouseEvent( K_MOUSE1, 0 ); + break; + default: break; + } } - if( !touch.initialized || (!CVAR_TO_BOOL(touch_enable) && !touch.clientonly) ) - { -#if 0 - if( type == event_down ) - Key_Event( K_MOUSE1, true ); - if( type == event_up ) - Key_Event( K_MOUSE1, false ); - Android_AddMove( dx * (float)refState.width, dy * (float)refState.height ); -#endif + if( !touch.initialized || ( !touch_enable.value && !touch.clientonly )) return 0; - } if( clgame.dllFuncs.pfnTouchEvent && clgame.dllFuncs.pfnTouchEvent( type, fingerID, x, y, dx, dy ) ) return true; @@ -2048,35 +2014,65 @@ void Touch_GetMove( float *forward, float *side, float *yaw, float *pitch ) void Touch_KeyEvent( int key, int down ) { - int xi, yi; - float x, y; static float lx, ly; + static int kidNamedFinger = -1; + touchEventType event; + float x, y; + int finger, xi, yi; - if( !CVAR_TO_BOOL(touch_emulate) ) + if( !touch_emulate.value ) { - if( CVAR_TO_BOOL(touch_enable) ) + if( touch_enable.value ) return; if( !touch.clientonly ) return; } - Platform_GetMousePos( &xi, &yi ); - - x = xi/SCR_W; - y = yi/SCR_H; - - if( cls.key_dest == key_menu && down < 2 && key == K_MOUSE1 ) + if( !key ) { - UI_MouseMove( xi, yi ); - UI_KeyEvent( key, down ); + if( kidNamedFinger < 0 ) + return; + + finger = kidNamedFinger; + event = event_motion; + } + else + { + finger = key == K_MOUSE1 ? 0 : 1; + if( down ) + { + event = event_down; + kidNamedFinger = finger; + } + else + { + event = event_up; + kidNamedFinger = -1; + } } - if( down == 1 ) - Touch_ControlsEvent( event_down, key == K_MOUSE1?0:1, x, y, 0, 0 ); - else - Touch_ControlsEvent( down? event_motion: event_up, key == K_MOUSE1?0:1, x, y, x-lx, y-ly ); - lx = x, ly = y; + // don't deactivate mouse in game + // checking a case when mouse and touchscreen + // can be used simultaneously + Platform_SetCursorType( dc_arrow ); + Platform_GetMousePos( &xi, &yi ); + + x = xi / SCR_W; + y = yi / SCR_H; + + Con_DPrintf( "event %d %.2f %.2f %.2f %.2f\n", + event, x, y, x - lx, y - ly ); + + IN_TouchEvent( event, finger, x, y, x - lx, y - ly ); + + lx = x; + ly = y; +} + +qboolean Touch_WantVisibleCursor( void ) +{ + return ( touch_enable.value && touch_emulate.value ) || touch.clientonly; } void Touch_Shutdown( void ) diff --git a/engine/client/input.c b/engine/client/input.c index 83c89a06..24e71349 100644 --- a/engine/client/input.c +++ b/engine/client/input.c @@ -47,8 +47,6 @@ convar_t *cl_backspeed; convar_t *look_filter; convar_t *m_rawinput; -static qboolean s_bRawInput, s_bMouseGrab; - /* ================ IN_CollectInputDevices @@ -63,7 +61,7 @@ uint IN_CollectInputDevices( void ) if( !m_ignore->value ) // no way to check is mouse connected, so use cvar only ret |= INPUT_DEVICE_MOUSE; - if( CVAR_TO_BOOL(touch_enable) ) + if( touch_enable.value ) ret |= INPUT_DEVICE_TOUCH; if( Joy_IsActive() ) // connected or enabled @@ -94,13 +92,13 @@ void IN_LockInputDevices( qboolean lock ) { SetBits( m_ignore->flags, FCVAR_READ_ONLY ); SetBits( joy_enable->flags, FCVAR_READ_ONLY ); - SetBits( touch_enable->flags, FCVAR_READ_ONLY ); + SetBits( touch_enable.flags, FCVAR_READ_ONLY ); } else { ClearBits( m_ignore->flags, FCVAR_READ_ONLY ); ClearBits( joy_enable->flags, FCVAR_READ_ONLY ); - ClearBits( touch_enable->flags, FCVAR_READ_ONLY ); + ClearBits( touch_enable.flags, FCVAR_READ_ONLY ); } } @@ -173,19 +171,15 @@ Called when key_dest is changed */ void IN_ToggleClientMouse( int newstate, int oldstate ) { - if( newstate == oldstate ) return; + if( newstate == oldstate ) + return; - if( oldstate == key_game ) + // since SetCursorType controls cursor visibility + // execute it first, and then check mouse grab state + if( newstate == key_menu || newstate == key_console || newstate == key_message ) { - IN_DeactivateMouse(); - } - else if( newstate == key_game ) - { - IN_ActivateMouse(); - } + Platform_SetCursorType( dc_arrow ); - if( ( newstate == key_menu || newstate == key_console || newstate == key_message ) && ( !CL_IsBackgroundMap() || CL_IsBackgroundDemo( ))) - { #if XASH_ANDROID Android_ShowMouse( true ); #endif @@ -195,6 +189,8 @@ void IN_ToggleClientMouse( int newstate, int oldstate ) } else { + Platform_SetCursorType( dc_none ); + #if XASH_ANDROID Android_ShowMouse( false ); #endif @@ -202,10 +198,21 @@ void IN_ToggleClientMouse( int newstate, int oldstate ) Evdev_SetGrab( true ); #endif } + + if( oldstate == key_game ) + { + IN_DeactivateMouse(); + } + else if( newstate == key_game ) + { + IN_ActivateMouse(); + } } void IN_CheckMouseState( qboolean active ) { + static qboolean s_bRawInput, s_bMouseGrab; + #if XASH_WIN32 qboolean useRawInput = CVAR_TO_BOOL( m_rawinput ) && clgame.client_dll_uses_sdl || clgame.dllFuncs.pfnLookEvent; #else @@ -276,7 +283,8 @@ void IN_ActivateMouse( void ) return; IN_CheckMouseState( true ); - clgame.dllFuncs.IN_ActivateMouse(); + if( clgame.dllFuncs.IN_ActivateMouse ) + clgame.dllFuncs.IN_ActivateMouse(); in_mouseactive = true; } @@ -293,7 +301,8 @@ void IN_DeactivateMouse( void ) return; IN_CheckMouseState( false ); - clgame.dllFuncs.IN_DeactivateMouse(); + if( clgame.dllFuncs.IN_DeactivateMouse ) + clgame.dllFuncs.IN_DeactivateMouse(); in_mouseactive = false; } @@ -306,25 +315,25 @@ IN_MouseMove */ void IN_MouseMove( void ) { - POINT current_pos; + int x, y; if( !in_mouseinitialized ) return; + if( touch_emulate.value ) + { + // touch emulation overrides all input + Touch_KeyEvent( 0, 0 ); + return; + } + // find mouse movement - Platform_GetMousePos( ¤t_pos.x, ¤t_pos.y ); + Platform_GetMousePos( &x, &y ); - VGui_MouseMove( current_pos.x, current_pos.y ); - - // HACKHACK: show cursor in UI, as mainui doesn't call - // platform-dependent SetCursor anymore -#if XASH_SDL - if( UI_IsVisible() ) - SDL_ShowCursor( SDL_TRUE ); -#endif + VGui_MouseMove( x, y ); // if the menu is visible, move the menu cursor - UI_MouseMove( current_pos.x, current_pos.y ); + UI_MouseMove( x, y ); } /* @@ -334,8 +343,6 @@ IN_MouseEvent */ void IN_MouseEvent( int key, int down ) { - int i; - if( !in_mouseinitialized ) return; @@ -343,10 +350,15 @@ void IN_MouseEvent( int key, int down ) SetBits( in_mstate, BIT( key )); else ClearBits( in_mstate, BIT( key )); - if( cls.key_dest == key_game ) + // touch emulation overrides all input + if( touch_emulate.value ) + { + Touch_KeyEvent( K_MOUSE1 + key, down ); + } + else if( cls.key_dest == key_game ) { // perform button actions - VGui_KeyEvent( K_MOUSE1 + key, down ); + VGui_MouseEvent( K_MOUSE1 + key, down ); // don't do Key_Event here // client may override IN_MouseEvent @@ -361,6 +373,23 @@ void IN_MouseEvent( int key, int down ) } } +/* +============== +IN_MWheelEvent + +direction is negative for wheel down, otherwise wheel up +============== +*/ +void IN_MWheelEvent( int y ) +{ + int b = y > 0 ? K_MWHEELUP : K_MWHEELDOWN; + + VGui_MWheelEvent( y ); + + Key_Event( b, true ); + Key_Event( b, false ); +} + /* =========== IN_Shutdown diff --git a/engine/client/input.h b/engine/client/input.h index 32ad2b6b..6b992490 100644 --- a/engine/client/input.h +++ b/engine/client/input.h @@ -34,6 +34,7 @@ void IN_Init( void ); void Host_InputFrame( void ); void IN_Shutdown( void ); void IN_MouseEvent( int key, int down ); +void IN_MWheelEvent( int direction ); void IN_ActivateMouse( void ); void IN_DeactivateMouse( void ); void IN_MouseSavePos( void ); @@ -57,8 +58,8 @@ typedef enum event_motion } touchEventType; -extern convar_t *touch_enable; -extern convar_t *touch_emulate; +extern convar_t touch_enable; +extern convar_t touch_emulate; void Touch_Draw( void ); void Touch_SetClientOnly( byte state ); @@ -73,6 +74,7 @@ void Touch_GetMove( float * forward, float *side, float *yaw, float *pitch ); void Touch_ResetDefaultButtons( void ); int IN_TouchEvent( touchEventType type, int fingerID, float x, float y, float dx, float dy ); void Touch_KeyEvent( int key, int down ); +qboolean Touch_WantVisibleCursor( void ); // // in_joy.c diff --git a/engine/client/keys.c b/engine/client/keys.c index 682f44ad..a1e696c8 100644 --- a/engine/client/keys.c +++ b/engine/client/keys.c @@ -712,7 +712,6 @@ void GAME_EXPORT Key_Event( int key, int down ) } VGui_KeyEvent( key, down ); - Touch_KeyEvent( key, down ); // console key is hardcoded, so the user can never unbind it if( key == '`' || key == '~' ) diff --git a/engine/client/ref_common.c b/engine/client/ref_common.c index 0760d904..2687a509 100644 --- a/engine/client/ref_common.c +++ b/engine/client/ref_common.c @@ -60,7 +60,7 @@ void GL_RenderFrame( const ref_viewpass_t *rvp ) ref.dllFuncs.GL_RenderFrame( rvp ); } -static int pfnEngineGetParm( int parm, int arg ) +static intptr_t pfnEngineGetParm( int parm, int arg ) { return CL_RenderGetParm( parm, arg, false ); // prevent recursion } @@ -151,7 +151,7 @@ static player_info_t *pfnPlayerInfo( int index ) if( index == -1 ) // special index for menu return &gameui.playerinfo; - if( index < 0 || index > cl.maxclients ) + if( index < 0 || index >= cl.maxclients ) return NULL; return &cl.players[index]; @@ -334,10 +334,6 @@ static ref_api_t gEngfuncs = COM_FreeLibrary, COM_GetProcAddress, - FS_LoadFile, - FS_FileExists, - FS_AllowDirectPaths, - R_Init_Video_, R_Free_Video, @@ -383,7 +379,9 @@ static ref_api_t gEngfuncs = pfnDrawNormalTriangles, pfnDrawTransparentTriangles, - &clgame.drawFuncs + &clgame.drawFuncs, + + &g_fsapi, }; static void R_UnloadProgs( void ) diff --git a/engine/client/s_main.c b/engine/client/s_main.c index 6c926552..5a77e24f 100644 --- a/engine/client/s_main.c +++ b/engine/client/s_main.c @@ -533,8 +533,6 @@ void S_StartSound( const vec3_t pos, int ent, int chan, sound_t handle, float fv // spatialize memset( target_chan, 0, sizeof( *target_chan )); - pitch *= (sys_timescale.value + 1) / 2; - VectorCopy( pos, target_chan->origin ); target_chan->staticsound = ( ent == 0 ) ? true : false; target_chan->use_loop = (flags & SND_STOP_LOOPING) ? false : true; @@ -1129,7 +1127,7 @@ static uint S_RawSamplesStereo( portable_samplepair_t *rawsamples, uint rawend, S_RawEntSamples =================== */ -static void S_RawEntSamples( int entnum, uint samples, uint rate, word width, word channels, const byte *data, int snd_vol ) +void S_RawEntSamples( int entnum, uint samples, uint rate, word width, word channels, const byte *data, int snd_vol ) { rawchan_t *ch; @@ -1288,6 +1286,9 @@ static void S_FreeIdleRawChannels( void ) if( ch->s_rawend >= paintedtime ) continue; + + if ( ch->entnum > 0 ) + SND_ForceCloseMouth( ch->entnum ); if(( paintedtime - ch->s_rawend ) / SOUND_DMA_SPEED >= S_RAW_SOUND_IDLE_SEC ) { @@ -1850,7 +1851,7 @@ S_SoundInfo_f */ void S_SoundInfo_f( void ) { - Con_Printf( "Audio: DirectSound\n" ); + Con_Printf( "Audio backend: %s\n", dma.backendName ); Con_Printf( "%5d channel(s)\n", 2 ); Con_Printf( "%5d samples\n", dma.samples ); Con_Printf( "%5d bits/sample\n", 16 ); @@ -1860,6 +1861,33 @@ void S_SoundInfo_f( void ) S_PrintBackgroundTrackState (); } +/* +================= +S_VoiceRecordStart_f +================= +*/ +void S_VoiceRecordStart_f( void ) +{ + if( cls.state != ca_active || cls.legacymode ) + return; + + Voice_RecordStart(); +} + +/* +================= +S_VoiceRecordStop_f +================= +*/ +void S_VoiceRecordStop_f( void ) +{ + if( cls.state != ca_active || !Voice_IsRecording() ) + return; + + CL_AddVoiceToDatagram(); + Voice_RecordStop(); +} + /* ================ S_Init @@ -1894,11 +1922,12 @@ qboolean S_Init( void ) Cmd_AddCommand( "soundlist", S_SoundList_f, "display loaded sounds" ); Cmd_AddCommand( "s_info", S_SoundInfo_f, "print sound system information" ); Cmd_AddCommand( "s_fade", S_SoundFade_f, "fade all sounds then stop all" ); - Cmd_AddCommand( "+voicerecord", Cmd_Null_f, "start voice recording (non-implemented)" ); - Cmd_AddCommand( "-voicerecord", Cmd_Null_f, "stop voice recording (non-implemented)" ); + Cmd_AddCommand( "+voicerecord", S_VoiceRecordStart_f, "start voice recording" ); + Cmd_AddCommand( "-voicerecord", S_VoiceRecordStop_f, "stop voice recording" ); Cmd_AddCommand( "spk", S_SayReliable_f, "reliable play a specified sententce" ); Cmd_AddCommand( "speak", S_Say_f, "playing a specified sententce" ); + dma.backendName = "None"; if( !SNDDMA_Init( ) ) { Con_Printf( "Audio: sound system can't be initialized\n" ); diff --git a/engine/client/s_mix.c b/engine/client/s_mix.c index 8cceac98..721f05fa 100644 --- a/engine/client/s_mix.c +++ b/engine/client/s_mix.c @@ -619,6 +619,8 @@ void MIX_MixChannelsToPaintbuffer( int endtime, int rate, int outputRate ) ch->pitch = VOX_ModifyPitch( ch, ch->basePitch * 0.01f ); else ch->pitch = ch->basePitch * 0.01f; + ch->pitch *= ( sys_timescale.value + 1 ) / 2; + if( CL_GetEntityByIndex( ch->entnum ) && ( ch->entchannel == CHAN_VOICE )) { if( pSource->width == 1 ) @@ -956,6 +958,9 @@ void MIX_MixRawSamplesBuffer( int end ) pbuf[j-paintedtime].left += ( ch->rawsamples[j & ( ch->max_samples - 1 )].left * ch->leftvol ) >> 8; pbuf[j-paintedtime].right += ( ch->rawsamples[j & ( ch->max_samples - 1 )].right * ch->rightvol ) >> 8; } + + if ( ch->entnum > 0 ) + SND_MoveMouthRaw( ch, &ch->rawsamples[paintedtime & ( ch->max_samples - 1 )], stop - paintedtime ); } } diff --git a/engine/client/s_mouth.c b/engine/client/s_mouth.c index 42acd578..3b93c8c8 100644 --- a/engine/client/s_mouth.c +++ b/engine/client/s_mouth.c @@ -150,3 +150,68 @@ void SND_MoveMouth16( channel_t *ch, wavdata_t *pSource, int count ) pMouth->sndcount = 0; } } + +void SND_ForceInitMouth( int entnum ) +{ + cl_entity_t *clientEntity; + + clientEntity = CL_GetEntityByIndex( entnum ); + + if ( clientEntity ) + { + clientEntity->mouth.mouthopen = 0; + clientEntity->mouth.sndavg = 0; + clientEntity->mouth.sndcount = 0; + } +} + +void SND_ForceCloseMouth( int entnum ) +{ + cl_entity_t *clientEntity; + + clientEntity = CL_GetEntityByIndex( entnum ); + + if ( clientEntity ) + clientEntity->mouth.mouthopen = 0; +} + +void SND_MoveMouthRaw( rawchan_t *ch, portable_samplepair_t *pData, int count ) +{ + cl_entity_t *clientEntity; + mouth_t *pMouth = NULL; + int savg, data; + int scount = 0; + uint i; + + clientEntity = CL_GetEntityByIndex( ch->entnum ); + if( !clientEntity ) return; + + pMouth = &clientEntity->mouth; + + if( pData == NULL ) + return; + + i = 0; + scount = pMouth->sndcount; + savg = 0; + + while ( i < count && scount < CAVGSAMPLES ) + { + data = pData[i].left; // mono sound anyway + data = ( bound( -32767, data, 0x7ffe ) >> 8 ); + savg += abs( data ); + + i += 80 + ( (byte)data & 0x1F ); + scount++; + } + + pMouth->sndavg += savg; + pMouth->sndcount = (byte)scount; + + if ( pMouth->sndcount >= CAVGSAMPLES ) + { + pMouth->mouthopen = pMouth->sndavg / CAVGSAMPLES; + pMouth->sndavg = 0; + pMouth->sndcount = 0; + } +} \ No newline at end of file diff --git a/engine/client/sound.h b/engine/client/sound.h index 930d114d..19f9eead 100644 --- a/engine/client/sound.h +++ b/engine/client/sound.h @@ -113,10 +113,11 @@ typedef struct snd_format_s typedef struct { snd_format_t format; - int samples; // mono samples in buffer - int samplepos; // in mono samples - byte *buffer; + int samples; // mono samples in buffer + int samplepos; // in mono samples + byte *buffer; qboolean initialized; // sound engine is active + const char *backendName; } dma_t; #include "vox.h" @@ -202,7 +203,7 @@ typedef struct #define MAX_DYNAMIC_CHANNELS (60 + NUM_AMBIENTS) #define MAX_CHANNELS (256 + MAX_DYNAMIC_CHANNELS) // Scourge Of Armagon has too many static sounds on hip2m4.bsp -#define MAX_RAW_CHANNELS 16 +#define MAX_RAW_CHANNELS 48 #define MAX_RAW_SAMPLES 8192 extern sound_t ambient_sfx[NUM_AMBIENTS]; @@ -271,6 +272,7 @@ int S_GetCurrentStaticSounds( soundlist_t *pout, int size ); int S_GetCurrentDynamicSounds( soundlist_t *pout, int size ); sfx_t *S_GetSfxByHandle( sound_t handle ); rawchan_t *S_FindRawChannel( int entnum, qboolean create ); +void S_RawEntSamples( int entnum, uint samples, uint rate, word width, word channels, const byte *data, int snd_vol ); void S_RawSamples( uint samples, uint rate, word width, word channels, const byte *data, int entnum ); void S_StopSound( int entnum, int channel, const char *soundname ); void S_UpdateFrame( struct ref_viewpass_s *rvp ); @@ -283,9 +285,12 @@ void S_FreeSounds( void ); // s_mouth.c // void SND_InitMouth( int entnum, int entchannel ); +void SND_ForceInitMouth( int entnum ); void SND_MoveMouth8( channel_t *ch, wavdata_t *pSource, int count ); void SND_MoveMouth16( channel_t *ch, wavdata_t *pSource, int count ); +void SND_MoveMouthRaw( rawchan_t *ch, portable_samplepair_t *pData, int count ); void SND_CloseMouth( channel_t *ch ); +void SND_ForceCloseMouth( int entnum ); // // s_stream.c diff --git a/engine/client/vgui/vgui_draw.c b/engine/client/vgui/vgui_draw.c index 94ff455b..8ae08584 100644 --- a/engine/client/vgui/vgui_draw.c +++ b/engine/client/vgui/vgui_draw.c @@ -23,93 +23,101 @@ GNU General Public License for more details. #include "input.h" #include "platform/platform.h" -static enum VGUI_KeyCode s_pVirtualKeyTrans[256]; -static VGUI_DefaultCursor s_currentCursor; -static HINSTANCE s_pVGuiSupport; // vgui_support library -static convar_t *vgui_utf8 = NULL; +CVAR_DEFINE_AUTO( vgui_utf8, "0", FCVAR_ARCHIVE, "enable utf-8 support for vgui text" ); -void GAME_EXPORT *VGUI_EngineMalloc(size_t size) +static void GAME_EXPORT *VGUI_EngineMalloc( size_t size ); +static void GAME_EXPORT VGUI_GetMousePos( int *, int * ); +static void GAME_EXPORT VGUI_CursorSelect( VGUI_DefaultCursor ); +static byte GAME_EXPORT VGUI_GetColor( int, int ); +static int GAME_EXPORT VGUI_UtfProcessChar( int in ); +static qboolean GAME_EXPORT VGUI_IsInGame( void ); + +static struct +{ + qboolean initialized; + vguiapi_t dllFuncs; + VGUI_DefaultCursor cursor; + + HINSTANCE hInstance; + + enum VGUI_KeyCode virtualKeyTrans[256]; +} vgui = +{ + false, + { + false, // Not initialized yet + NULL, // VGUI_DrawInit, + NULL, // VGUI_DrawShutdown, + NULL, // VGUI_SetupDrawingText, + NULL, // VGUI_SetupDrawingRect, + NULL, // VGUI_SetupDrawingImage, + NULL, // VGUI_BindTexture, + NULL, // VGUI_EnableTexture, + NULL, // VGUI_CreateTexture, + NULL, // VGUI_UploadTexture, + NULL, // VGUI_UploadTextureBlock, + NULL, // VGUI_DrawQuad, + NULL, // VGUI_GetTextureSizes, + NULL, // VGUI_GenerateTexture, + VGUI_EngineMalloc, + VGUI_CursorSelect, + VGUI_GetColor, + VGUI_IsInGame, + NULL, + VGUI_GetMousePos, + VGUI_UtfProcessChar, + Platform_GetClipboardText, + Platform_SetClipboardText, + Platform_GetKeyModifiers, + }, + -1 +}; + +static void GAME_EXPORT *VGUI_EngineMalloc( size_t size ) { return Z_Malloc( size ); } -qboolean GAME_EXPORT VGUI_IsInGame( void ) +static qboolean GAME_EXPORT VGUI_IsInGame( void ) { return cls.state == ca_active && cls.key_dest == key_game; } -void GAME_EXPORT VGUI_GetMousePos( int *_x, int *_y ) +static void GAME_EXPORT VGUI_GetMousePos( int *_x, int *_y ) { float xscale = (float)refState.width / (float)clgame.scrInfo.iWidth; float yscale = (float)refState.height / (float)clgame.scrInfo.iHeight; int x, y; Platform_GetMousePos( &x, &y ); - *_x = x / xscale, *_y = y / yscale; + *_x = x / xscale; + *_y = y / yscale; } -void GAME_EXPORT VGUI_CursorSelect( VGUI_DefaultCursor cursor ) +static void GAME_EXPORT VGUI_CursorSelect( VGUI_DefaultCursor cursor ) { - Platform_SetCursorType( cursor ); - s_currentCursor = cursor; + if( vgui.cursor != cursor ) + Platform_SetCursorType( cursor ); } -byte GAME_EXPORT VGUI_GetColor( int i, int j) +static byte GAME_EXPORT VGUI_GetColor( int i, int j ) { return g_color_table[i][j]; } -// Define and initialize vgui API -int GAME_EXPORT VGUI_UtfProcessChar( int in ) +static int GAME_EXPORT VGUI_UtfProcessChar( int in ) { - if( CVAR_TO_BOOL( vgui_utf8 )) + if( vgui_utf8.value ) return Con_UtfProcessCharForce( in ); - else - return in; + return in; } -vguiapi_t vgui = -{ - false, // Not initialized yet - NULL, // VGUI_DrawInit, - NULL, // VGUI_DrawShutdown, - NULL, // VGUI_SetupDrawingText, - NULL, // VGUI_SetupDrawingRect, - NULL, // VGUI_SetupDrawingImage, - NULL, // VGUI_BindTexture, - NULL, // VGUI_EnableTexture, - NULL, // VGUI_CreateTexture, - NULL, // VGUI_UploadTexture, - NULL, // VGUI_UploadTextureBlock, - NULL, // VGUI_DrawQuad, - NULL, // VGUI_GetTextureSizes, - NULL, // VGUI_GenerateTexture, - VGUI_EngineMalloc, - VGUI_CursorSelect, - VGUI_GetColor, - VGUI_IsInGame, - NULL, - VGUI_GetMousePos, - VGUI_UtfProcessChar, - Platform_GetClipboardText, - Platform_SetClipboardText, - Platform_GetKeyModifiers, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL -}; - qboolean VGui_IsActive( void ) { return vgui.initialized; } -void VGui_FillAPIFromRef( vguiapi_t *to, const ref_interface_t *from ) +static void VGui_FillAPIFromRef( vguiapi_t *to, const ref_interface_t *from ) { to->DrawInit = from->VGUI_DrawInit; to->DrawShutdown = from->VGUI_DrawShutdown; @@ -126,128 +134,86 @@ void VGui_FillAPIFromRef( vguiapi_t *to, const ref_interface_t *from ) to->GenerateTexture = from->VGUI_GenerateTexture; } +void VGui_RegisterCvars( void ) +{ + Cvar_RegisterVariable( &vgui_utf8 ); +} + +qboolean VGui_LoadProgs( HINSTANCE hInstance ) +{ + void (*F)( vguiapi_t* ); + qboolean client = hInstance != NULL; + + // not loading interface from client.dll, load vgui_support.dll instead + if( !client ) + { + string vguiloader, vguilib; + + // HACKHACK: try to load path from custom path + // to support having different versions of VGUI + if( Sys_GetParmFromCmdLine( "-vguilib", vguilib ) && !COM_LoadLibrary( vguilib, false, false )) + { + Con_Reportf( S_WARN "VGUI preloading failed. Default library will be used! Reason: %s", COM_GetLibraryError()); + } + + if( !Sys_GetParmFromCmdLine( "-vguiloader", vguiloader )) + { + Q_strncpy( vguiloader, VGUI_SUPPORT_DLL, sizeof( vguiloader )); + } + + hInstance = vgui.hInstance = COM_LoadLibrary( vguiloader, false, false ); + + if( !vgui.hInstance ) + { + if( FS_FileExists( vguiloader, false )) + Con_Reportf( S_ERROR "Failed to load vgui_support library: %s\n", COM_GetLibraryError() ); + else Con_Reportf( "vgui_support: not found\n" ); + + return false; + } + } + + // try legacy API first + F = COM_GetProcAddress( hInstance, client ? "InitVGUISupportAPI" : "InitAPI" ); + + if( F ) + { + VGui_FillAPIFromRef( &vgui.dllFuncs, &ref.dllFuncs ); + F( &vgui.dllFuncs ); + + vgui.initialized = vgui.dllFuncs.initialized = true; + Con_Reportf( "vgui_support: initialized legacy API in %s module\n", client ? "client" : "support" ); + + return true; + } + + Con_Reportf( S_ERROR "Failed to find VGUI support API entry point in %s module\n", client ? "client" : "support" ); + return false; +} + /* ================ VGui_Startup -Load vgui_support library and call VGui_Startup ================ */ -void VGui_Startup( const char *clientlib, int width, int height ) +void VGui_Startup( int width, int height ) { - static qboolean failed = false; - - void (*F) ( vguiapi_t * ); - char vguiloader[256]; - char vguilib[256]; - - vguiloader[0] = vguilib[0] = '\0'; - - if( failed ) + // vgui not initialized from both support and client modules, skip + if( !vgui.initialized ) return; - if( !vgui.initialized ) - { - vgui_utf8 = Cvar_Get( "vgui_utf8", "0", FCVAR_ARCHIVE, "enable utf-8 support for vgui text" ); + height = Q_max( 480, height ); - VGui_FillAPIFromRef( &vgui, &ref.dllFuncs ); + if( width <= 640 ) width = 640; + else if( width <= 800 ) width = 800; + else if( width <= 1024 ) width = 1024; + else if( width <= 1152 ) width = 1152; + else if( width <= 1280 ) width = 1280; + else if( width <= 1600 ) width = 1600; - s_pVGuiSupport = COM_LoadLibrary( clientlib, false, false ); - - if( s_pVGuiSupport ) - { - F = COM_GetProcAddress( s_pVGuiSupport, "InitVGUISupportAPI" ); - if( F ) - { - F( &vgui ); - vgui.initialized = true; - Con_Reportf( "vgui_support: found internal client support\n" ); - } - else - { - COM_FreeLibrary( s_pVGuiSupport ); - } - } - - if( !vgui.initialized ) - { - // HACKHACK: load vgui with correct path first if specified. - // it will be reused while resolving vgui support and client deps - if( Sys_GetParmFromCmdLine( "-vguilib", vguilib )) - { - if( Q_strstr( vguilib, ".dll" )) - Q_strncpy( vguiloader, "vgui_support.dll", 256 ); - else - Q_strncpy( vguiloader, VGUI_SUPPORT_DLL, 256 ); - - if( !COM_LoadLibrary( vguilib, false, false )) - Con_Reportf( S_WARN "VGUI preloading failed. Default library will be used! Reason: %s\n", COM_GetLibraryError() ); - } - - if( Q_strstr( clientlib, ".dll" )) - Q_strncpy( vguiloader, "vgui_support.dll", 256 ); - - if( !vguiloader[0] && !Sys_GetParmFromCmdLine( "-vguiloader", vguiloader )) - Q_strncpy( vguiloader, VGUI_SUPPORT_DLL, 256 ); - - s_pVGuiSupport = COM_LoadLibrary( vguiloader, false, false ); - - if( !s_pVGuiSupport ) - { - s_pVGuiSupport = COM_LoadLibrary( va( "../%s", vguiloader ), false, false ); - } - - if( !s_pVGuiSupport ) - { - if( FS_FileExists( vguiloader, false )) - Con_Reportf( S_ERROR "Failed to load vgui_support library: %s\n", COM_GetLibraryError() ); - else - Con_Reportf( "vgui_support: not found\n" ); - } - else - { - F = COM_GetProcAddress( s_pVGuiSupport, "InitAPI" ); - if( F ) - { - F( &vgui ); - vgui.initialized = true; - } - else - Con_Reportf( S_ERROR "Failed to find vgui_support library entry point!\n" ); - } - } - } - - if( height < 480 ) - height = 480; - - if( width <= 640 ) - width = 640; - else if( width <= 800 ) - width = 800; - else if( width <= 1024 ) - width = 1024; - else if( width <= 1152 ) - width = 1152; - else if( width <= 1280 ) - width = 1280; - else if( width <= 1600 ) - width = 1600; -#ifdef XASH_DLL_LOADER - else if ( Q_strstr( vguiloader, ".dll" ) ) - width = 1600; -#endif - - - if( vgui.initialized ) - { - //host.mouse_visible = true; - vgui.Startup( width, height ); - } - else if ( COM_CheckString( clientlib ) ) - { - failed = true; - } + if( vgui.dllFuncs.Startup ) + vgui.dllFuncs.Startup( width, height ); } @@ -261,214 +227,223 @@ Unload vgui_support library and call VGui_Shutdown */ void VGui_Shutdown( void ) { - if( vgui.Shutdown ) - vgui.Shutdown(); + if( vgui.dllFuncs.Shutdown ) + vgui.dllFuncs.Shutdown(); - if( s_pVGuiSupport ) - COM_FreeLibrary( s_pVGuiSupport ); - s_pVGuiSupport = NULL; + if( vgui.hInstance ) + COM_FreeLibrary( vgui.hInstance ); + vgui.hInstance = NULL; vgui.initialized = false; } -void VGUI_InitKeyTranslationTable( void ) +static void VGUI_InitKeyTranslationTable( void ) { - static qboolean bInitted = false; + static qboolean initialized = false; - if( bInitted ) - return; - bInitted = true; + if( initialized ) return; + + initialized = true; // set virtual key translation table - memset( s_pVirtualKeyTrans, -1, sizeof( s_pVirtualKeyTrans ) ); + memset( vgui.virtualKeyTrans, -1, sizeof( vgui.virtualKeyTrans ) ); - s_pVirtualKeyTrans['0'] = KEY_0; - s_pVirtualKeyTrans['1'] = KEY_1; - s_pVirtualKeyTrans['2'] = KEY_2; - s_pVirtualKeyTrans['3'] = KEY_3; - s_pVirtualKeyTrans['4'] = KEY_4; - s_pVirtualKeyTrans['5'] = KEY_5; - s_pVirtualKeyTrans['6'] = KEY_6; - s_pVirtualKeyTrans['7'] = KEY_7; - s_pVirtualKeyTrans['8'] = KEY_8; - s_pVirtualKeyTrans['9'] = KEY_9; - s_pVirtualKeyTrans['A'] = s_pVirtualKeyTrans['a'] = KEY_A; - s_pVirtualKeyTrans['B'] = s_pVirtualKeyTrans['b'] = KEY_B; - s_pVirtualKeyTrans['C'] = s_pVirtualKeyTrans['c'] = KEY_C; - s_pVirtualKeyTrans['D'] = s_pVirtualKeyTrans['d'] = KEY_D; - s_pVirtualKeyTrans['E'] = s_pVirtualKeyTrans['e'] = KEY_E; - s_pVirtualKeyTrans['F'] = s_pVirtualKeyTrans['f'] = KEY_F; - s_pVirtualKeyTrans['G'] = s_pVirtualKeyTrans['g'] = KEY_G; - s_pVirtualKeyTrans['H'] = s_pVirtualKeyTrans['h'] = KEY_H; - s_pVirtualKeyTrans['I'] = s_pVirtualKeyTrans['i'] = KEY_I; - s_pVirtualKeyTrans['J'] = s_pVirtualKeyTrans['j'] = KEY_J; - s_pVirtualKeyTrans['K'] = s_pVirtualKeyTrans['k'] = KEY_K; - s_pVirtualKeyTrans['L'] = s_pVirtualKeyTrans['l'] = KEY_L; - s_pVirtualKeyTrans['M'] = s_pVirtualKeyTrans['m'] = KEY_M; - s_pVirtualKeyTrans['N'] = s_pVirtualKeyTrans['n'] = KEY_N; - s_pVirtualKeyTrans['O'] = s_pVirtualKeyTrans['o'] = KEY_O; - s_pVirtualKeyTrans['P'] = s_pVirtualKeyTrans['p'] = KEY_P; - s_pVirtualKeyTrans['Q'] = s_pVirtualKeyTrans['q'] = KEY_Q; - s_pVirtualKeyTrans['R'] = s_pVirtualKeyTrans['r'] = KEY_R; - s_pVirtualKeyTrans['S'] = s_pVirtualKeyTrans['s'] = KEY_S; - s_pVirtualKeyTrans['T'] = s_pVirtualKeyTrans['t'] = KEY_T; - s_pVirtualKeyTrans['U'] = s_pVirtualKeyTrans['u'] = KEY_U; - s_pVirtualKeyTrans['V'] = s_pVirtualKeyTrans['v'] = KEY_V; - s_pVirtualKeyTrans['W'] = s_pVirtualKeyTrans['w'] = KEY_W; - s_pVirtualKeyTrans['X'] = s_pVirtualKeyTrans['x'] = KEY_X; - s_pVirtualKeyTrans['Y'] = s_pVirtualKeyTrans['y'] = KEY_Y; - s_pVirtualKeyTrans['Z'] = s_pVirtualKeyTrans['z'] = KEY_Z; + // TODO: engine keys are not enough here! + // make crossplatform way to pass SDL keys here - s_pVirtualKeyTrans[K_KP_5 - 5] = KEY_PAD_0; - s_pVirtualKeyTrans[K_KP_5 - 4] = KEY_PAD_1; - s_pVirtualKeyTrans[K_KP_5 - 3] = KEY_PAD_2; - s_pVirtualKeyTrans[K_KP_5 - 2] = KEY_PAD_3; - s_pVirtualKeyTrans[K_KP_5 - 1] = KEY_PAD_4; - s_pVirtualKeyTrans[K_KP_5 - 0] = KEY_PAD_5; - s_pVirtualKeyTrans[K_KP_5 + 1] = KEY_PAD_6; - s_pVirtualKeyTrans[K_KP_5 + 2] = KEY_PAD_7; - s_pVirtualKeyTrans[K_KP_5 + 3] = KEY_PAD_8; - s_pVirtualKeyTrans[K_KP_5 + 4] = KEY_PAD_9; - s_pVirtualKeyTrans[K_KP_SLASH] = KEY_PAD_DIVIDE; - s_pVirtualKeyTrans['*'] = KEY_PAD_MULTIPLY; - s_pVirtualKeyTrans[K_KP_MINUS] = KEY_PAD_MINUS; - s_pVirtualKeyTrans[K_KP_PLUS] = KEY_PAD_PLUS; - s_pVirtualKeyTrans[K_KP_ENTER] = KEY_PAD_ENTER; - //s_pVirtualKeyTrans[K_KP_DECIMAL] = KEY_PAD_DECIMAL; - s_pVirtualKeyTrans['['] = KEY_LBRACKET; - s_pVirtualKeyTrans[']'] = KEY_RBRACKET; - s_pVirtualKeyTrans[';'] = KEY_SEMICOLON; - s_pVirtualKeyTrans['\''] = KEY_APOSTROPHE; - s_pVirtualKeyTrans['`'] = KEY_BACKQUOTE; - s_pVirtualKeyTrans[','] = KEY_COMMA; - s_pVirtualKeyTrans['.'] = KEY_PERIOD; - s_pVirtualKeyTrans[K_KP_SLASH] = KEY_SLASH; - s_pVirtualKeyTrans['\\'] = KEY_BACKSLASH; - s_pVirtualKeyTrans['-'] = KEY_MINUS; - s_pVirtualKeyTrans['='] = KEY_EQUAL; - s_pVirtualKeyTrans[K_ENTER] = KEY_ENTER; - s_pVirtualKeyTrans[K_SPACE] = KEY_SPACE; - s_pVirtualKeyTrans[K_BACKSPACE] = KEY_BACKSPACE; - s_pVirtualKeyTrans[K_TAB] = KEY_TAB; - s_pVirtualKeyTrans[K_CAPSLOCK] = KEY_CAPSLOCK; - s_pVirtualKeyTrans[K_KP_NUMLOCK] = KEY_NUMLOCK; - s_pVirtualKeyTrans[K_ESCAPE] = KEY_ESCAPE; - //s_pVirtualKeyTrans[K_KP_SCROLLLOCK] = KEY_SCROLLLOCK; - s_pVirtualKeyTrans[K_INS] = KEY_INSERT; - s_pVirtualKeyTrans[K_DEL] = KEY_DELETE; - s_pVirtualKeyTrans[K_HOME] = KEY_HOME; - s_pVirtualKeyTrans[K_END] = KEY_END; - s_pVirtualKeyTrans[K_PGUP] = KEY_PAGEUP; - s_pVirtualKeyTrans[K_PGDN] = KEY_PAGEDOWN; - s_pVirtualKeyTrans[K_PAUSE] = KEY_BREAK; - //s_pVirtualKeyTrans[K_SHIFT] = KEY_RSHIFT; - s_pVirtualKeyTrans[K_SHIFT] = KEY_LSHIFT; // SHIFT -> left SHIFT - //s_pVirtualKeyTrans[SDLK_RALT] = KEY_RALT; - s_pVirtualKeyTrans[K_ALT] = KEY_LALT; // ALT -> left ALT - //s_pVirtualKeyTrans[SDLK_RCTRL] = KEY_RCONTROL; - s_pVirtualKeyTrans[K_CTRL] = KEY_LCONTROL; // CTRL -> left CTRL - s_pVirtualKeyTrans[K_WIN] = KEY_LWIN; - //s_pVirtualKeyTrans[SDLK_APPLICATION] = KEY_RWIN; - //s_pVirtualKeyTrans[K_WIN] = KEY_APP; - s_pVirtualKeyTrans[K_UPARROW] = KEY_UP; - s_pVirtualKeyTrans[K_LEFTARROW] = KEY_LEFT; - s_pVirtualKeyTrans[K_DOWNARROW] = KEY_DOWN; - s_pVirtualKeyTrans[K_RIGHTARROW] = KEY_RIGHT; - s_pVirtualKeyTrans[K_F1] = KEY_F1; - s_pVirtualKeyTrans[K_F2] = KEY_F2; - s_pVirtualKeyTrans[K_F3] = KEY_F3; - s_pVirtualKeyTrans[K_F4] = KEY_F4; - s_pVirtualKeyTrans[K_F5] = KEY_F5; - s_pVirtualKeyTrans[K_F6] = KEY_F6; - s_pVirtualKeyTrans[K_F7] = KEY_F7; - s_pVirtualKeyTrans[K_F8] = KEY_F8; - s_pVirtualKeyTrans[K_F9] = KEY_F9; - s_pVirtualKeyTrans[K_F10] = KEY_F10; - s_pVirtualKeyTrans[K_F11] = KEY_F11; - s_pVirtualKeyTrans[K_F12] = KEY_F12; + vgui.virtualKeyTrans['0'] = KEY_0; + vgui.virtualKeyTrans['1'] = KEY_1; + vgui.virtualKeyTrans['2'] = KEY_2; + vgui.virtualKeyTrans['3'] = KEY_3; + vgui.virtualKeyTrans['4'] = KEY_4; + vgui.virtualKeyTrans['5'] = KEY_5; + vgui.virtualKeyTrans['6'] = KEY_6; + vgui.virtualKeyTrans['7'] = KEY_7; + vgui.virtualKeyTrans['8'] = KEY_8; + vgui.virtualKeyTrans['9'] = KEY_9; + vgui.virtualKeyTrans['A'] = vgui.virtualKeyTrans['a'] = KEY_A; + vgui.virtualKeyTrans['B'] = vgui.virtualKeyTrans['b'] = KEY_B; + vgui.virtualKeyTrans['C'] = vgui.virtualKeyTrans['c'] = KEY_C; + vgui.virtualKeyTrans['D'] = vgui.virtualKeyTrans['d'] = KEY_D; + vgui.virtualKeyTrans['E'] = vgui.virtualKeyTrans['e'] = KEY_E; + vgui.virtualKeyTrans['F'] = vgui.virtualKeyTrans['f'] = KEY_F; + vgui.virtualKeyTrans['G'] = vgui.virtualKeyTrans['g'] = KEY_G; + vgui.virtualKeyTrans['H'] = vgui.virtualKeyTrans['h'] = KEY_H; + vgui.virtualKeyTrans['I'] = vgui.virtualKeyTrans['i'] = KEY_I; + vgui.virtualKeyTrans['J'] = vgui.virtualKeyTrans['j'] = KEY_J; + vgui.virtualKeyTrans['K'] = vgui.virtualKeyTrans['k'] = KEY_K; + vgui.virtualKeyTrans['L'] = vgui.virtualKeyTrans['l'] = KEY_L; + vgui.virtualKeyTrans['M'] = vgui.virtualKeyTrans['m'] = KEY_M; + vgui.virtualKeyTrans['N'] = vgui.virtualKeyTrans['n'] = KEY_N; + vgui.virtualKeyTrans['O'] = vgui.virtualKeyTrans['o'] = KEY_O; + vgui.virtualKeyTrans['P'] = vgui.virtualKeyTrans['p'] = KEY_P; + vgui.virtualKeyTrans['Q'] = vgui.virtualKeyTrans['q'] = KEY_Q; + vgui.virtualKeyTrans['R'] = vgui.virtualKeyTrans['r'] = KEY_R; + vgui.virtualKeyTrans['S'] = vgui.virtualKeyTrans['s'] = KEY_S; + vgui.virtualKeyTrans['T'] = vgui.virtualKeyTrans['t'] = KEY_T; + vgui.virtualKeyTrans['U'] = vgui.virtualKeyTrans['u'] = KEY_U; + vgui.virtualKeyTrans['V'] = vgui.virtualKeyTrans['v'] = KEY_V; + vgui.virtualKeyTrans['W'] = vgui.virtualKeyTrans['w'] = KEY_W; + vgui.virtualKeyTrans['X'] = vgui.virtualKeyTrans['x'] = KEY_X; + vgui.virtualKeyTrans['Y'] = vgui.virtualKeyTrans['y'] = KEY_Y; + vgui.virtualKeyTrans['Z'] = vgui.virtualKeyTrans['z'] = KEY_Z; + + vgui.virtualKeyTrans[K_KP_5 - 5] = KEY_PAD_0; + vgui.virtualKeyTrans[K_KP_5 - 4] = KEY_PAD_1; + vgui.virtualKeyTrans[K_KP_5 - 3] = KEY_PAD_2; + vgui.virtualKeyTrans[K_KP_5 - 2] = KEY_PAD_3; + vgui.virtualKeyTrans[K_KP_5 - 1] = KEY_PAD_4; + vgui.virtualKeyTrans[K_KP_5 - 0] = KEY_PAD_5; + vgui.virtualKeyTrans[K_KP_5 + 1] = KEY_PAD_6; + vgui.virtualKeyTrans[K_KP_5 + 2] = KEY_PAD_7; + vgui.virtualKeyTrans[K_KP_5 + 3] = KEY_PAD_8; + vgui.virtualKeyTrans[K_KP_5 + 4] = KEY_PAD_9; + vgui.virtualKeyTrans[K_KP_SLASH] = KEY_PAD_DIVIDE; + vgui.virtualKeyTrans['*'] = KEY_PAD_MULTIPLY; + vgui.virtualKeyTrans[K_KP_MINUS] = KEY_PAD_MINUS; + vgui.virtualKeyTrans[K_KP_PLUS] = KEY_PAD_PLUS; + vgui.virtualKeyTrans[K_KP_ENTER] = KEY_PAD_ENTER; + vgui.virtualKeyTrans[K_KP_NUMLOCK] = KEY_NUMLOCK; + vgui.virtualKeyTrans['['] = KEY_LBRACKET; + vgui.virtualKeyTrans[']'] = KEY_RBRACKET; + vgui.virtualKeyTrans[';'] = KEY_SEMICOLON; + vgui.virtualKeyTrans['`'] = KEY_BACKQUOTE; + vgui.virtualKeyTrans[','] = KEY_COMMA; + vgui.virtualKeyTrans['.'] = KEY_PERIOD; + vgui.virtualKeyTrans['-'] = KEY_MINUS; + vgui.virtualKeyTrans['='] = KEY_EQUAL; + vgui.virtualKeyTrans['/'] = KEY_SLASH; + vgui.virtualKeyTrans['\\'] = KEY_BACKSLASH; + vgui.virtualKeyTrans['\''] = KEY_APOSTROPHE; + vgui.virtualKeyTrans[K_TAB] = KEY_TAB; + vgui.virtualKeyTrans[K_ENTER] = KEY_ENTER; + vgui.virtualKeyTrans[K_SPACE] = KEY_SPACE; + vgui.virtualKeyTrans[K_CAPSLOCK] = KEY_CAPSLOCK; + vgui.virtualKeyTrans[K_BACKSPACE] = KEY_BACKSPACE; + vgui.virtualKeyTrans[K_ESCAPE] = KEY_ESCAPE; + vgui.virtualKeyTrans[K_INS] = KEY_INSERT; + vgui.virtualKeyTrans[K_DEL] = KEY_DELETE; + vgui.virtualKeyTrans[K_HOME] = KEY_HOME; + vgui.virtualKeyTrans[K_END] = KEY_END; + vgui.virtualKeyTrans[K_PGUP] = KEY_PAGEUP; + vgui.virtualKeyTrans[K_PGDN] = KEY_PAGEDOWN; + vgui.virtualKeyTrans[K_PAUSE] = KEY_BREAK; + vgui.virtualKeyTrans[K_SHIFT] = KEY_LSHIFT; // SHIFT -> left SHIFT + vgui.virtualKeyTrans[K_ALT] = KEY_LALT; // ALT -> left ALT + vgui.virtualKeyTrans[K_CTRL] = KEY_LCONTROL; // CTRL -> left CTRL + vgui.virtualKeyTrans[K_WIN] = KEY_LWIN; + vgui.virtualKeyTrans[K_UPARROW] = KEY_UP; + vgui.virtualKeyTrans[K_LEFTARROW] = KEY_LEFT; + vgui.virtualKeyTrans[K_DOWNARROW] = KEY_DOWN; + vgui.virtualKeyTrans[K_RIGHTARROW] = KEY_RIGHT; + vgui.virtualKeyTrans[K_F1] = KEY_F1; + vgui.virtualKeyTrans[K_F2] = KEY_F2; + vgui.virtualKeyTrans[K_F3] = KEY_F3; + vgui.virtualKeyTrans[K_F4] = KEY_F4; + vgui.virtualKeyTrans[K_F5] = KEY_F5; + vgui.virtualKeyTrans[K_F6] = KEY_F6; + vgui.virtualKeyTrans[K_F7] = KEY_F7; + vgui.virtualKeyTrans[K_F8] = KEY_F8; + vgui.virtualKeyTrans[K_F9] = KEY_F9; + vgui.virtualKeyTrans[K_F10] = KEY_F10; + vgui.virtualKeyTrans[K_F11] = KEY_F11; + vgui.virtualKeyTrans[K_F12] = KEY_F12; } -enum VGUI_KeyCode VGUI_MapKey( int keyCode ) +static enum VGUI_KeyCode VGUI_MapKey( int keyCode ) { VGUI_InitKeyTranslationTable(); - if( keyCode < 0 || keyCode >= (int)sizeof( s_pVirtualKeyTrans ) / (int)sizeof( s_pVirtualKeyTrans[0] )) - { - //Assert( false ); - return (enum VGUI_KeyCode)-1; - } - else - { - return s_pVirtualKeyTrans[keyCode]; - } + if( keyCode >= 0 && keyCode < ARRAYSIZE( vgui.virtualKeyTrans )) + return vgui.virtualKeyTrans[keyCode]; + + return (enum VGUI_KeyCode)-1; } -void VGui_KeyEvent( int key, int down ) +void VGui_MouseEvent( int key, int clicks ) { - if( !vgui.initialized ) + enum VGUI_MouseAction mact; + enum VGUI_MouseCode code; + + if( !vgui.dllFuncs.Mouse ) return; switch( key ) { - case K_MOUSE1: - if( down && host.mouse_visible ) { - Key_EnableTextInput(true, false); - } - vgui.Mouse( down ? MA_PRESSED : MA_RELEASED, MOUSE_LEFT ); - return; - case K_MOUSE2: - vgui.Mouse( down ? MA_PRESSED : MA_RELEASED, MOUSE_RIGHT ); - return; - case K_MOUSE3: - vgui.Mouse( down ? MA_PRESSED : MA_RELEASED, MOUSE_MIDDLE ); - return; - case K_MWHEELDOWN: - vgui.Mouse( MA_WHEEL, 1 ); - return; - case K_MWHEELUP: - vgui.Mouse( MA_WHEEL, -1 ); - return; - default: - break; + case K_MOUSE1: code = MOUSE_LEFT; break; + case K_MOUSE2: code = MOUSE_RIGHT; break; + case K_MOUSE3: code = MOUSE_MIDDLE; break; + default: return; } - if( down == 2 ) - vgui.Key( KA_TYPED, VGUI_MapKey( key ) ); + if( clicks >= 2 ) + mact = MA_DOUBLE; + else if( clicks == 1 ) + mact = MA_PRESSED; else - vgui.Key( down?KA_PRESSED:KA_RELEASED, VGUI_MapKey( key ) ); - //Msg("VGui_KeyEvent %d %d %d\n", key, VGUI_MapKey( key ), down ); + mact = MA_RELEASED; + + vgui.dllFuncs.Mouse( mact, code ); +} + +void VGui_MWheelEvent( int y ) +{ + if( !vgui.dllFuncs.Mouse ) + return; + + vgui.dllFuncs.Mouse( MA_WHEEL, y ); +} + +void VGui_KeyEvent( int key, int down ) +{ + enum VGUI_KeyCode code; + + if( !vgui.dllFuncs.Key ) + return; + + if(( code = VGUI_MapKey( key )) < 0 ) + return; + + if( down ) + { + vgui.dllFuncs.Key( KA_PRESSED, code ); + vgui.dllFuncs.Key( KA_TYPED, code ); + } + else vgui.dllFuncs.Key( KA_RELEASED, code ); } void VGui_MouseMove( int x, int y ) { - float xscale = (float)refState.width / (float)clgame.scrInfo.iWidth; - float yscale = (float)refState.height / (float)clgame.scrInfo.iHeight; - if( vgui.initialized ) - vgui.MouseMove( x / xscale, y / yscale ); + if( vgui.dllFuncs.MouseMove ) + { + float xscale = (float)refState.width / (float)clgame.scrInfo.iWidth; + float yscale = (float)refState.height / (float)clgame.scrInfo.iHeight; + vgui.dllFuncs.MouseMove( x / xscale, y / yscale ); + } } void VGui_Paint( void ) { - if( vgui.initialized ) - vgui.Paint(); + if( vgui.dllFuncs.Paint ) + vgui.dllFuncs.Paint(); } -void VGui_RunFrame( void ) +void VGui_UpdateInternalCursorState( VGUI_DefaultCursor cursorType ) { - //stub + vgui.cursor = cursorType; } - void *GAME_EXPORT VGui_GetPanel( void ) { - if( vgui.initialized ) - return vgui.GetPanel(); + if( vgui.dllFuncs.GetPanel ) + return vgui.dllFuncs.GetPanel(); return NULL; } void VGui_ReportTextInput( const char *text ) { - if ( vgui.initialized ) - vgui.TextInput( text ); + if( vgui.dllFuncs.TextInput ) + vgui.dllFuncs.TextInput( text ); } + diff --git a/engine/client/vgui/vgui_draw.h b/engine/client/vgui/vgui_draw.h index dd5a4ad2..583c0fdc 100644 --- a/engine/client/vgui/vgui_draw.h +++ b/engine/client/vgui/vgui_draw.h @@ -16,25 +16,22 @@ GNU General Public License for more details. #ifndef VGUI_DRAW_H #define VGUI_DRAW_H -#ifdef __cplusplus -extern "C" { -#endif - -#include "port.h" - // // vgui_draw.c // -void VGui_Startup( const char *clientlib, int width, int height ); +void VGui_RegisterCvars( void ); +qboolean VGui_LoadProgs( HINSTANCE hInstance ); +void VGui_Startup( int width, int height ); void VGui_Shutdown( void ); void VGui_Paint( void ); void VGui_RunFrame( void ); +void VGui_MouseEvent( int key, int clicks ); +void VGui_MWheelEvent( int y ); void VGui_KeyEvent( int key, int down ); void VGui_MouseMove( int x, int y ); qboolean VGui_IsActive( void ); void *VGui_GetPanel( void ); void VGui_ReportTextInput( const char *text ); -#ifdef __cplusplus -} -#endif -#endif//VGUI_DRAW_H +void VGui_UpdateInternalCursorState( VGUI_DefaultCursor cursorType ); + +#endif // VGUI_DRAW_H diff --git a/engine/client/vid_common.c b/engine/client/vid_common.c index 1f1d1a74..ac80c22f 100644 --- a/engine/client/vid_common.c +++ b/engine/client/vid_common.c @@ -21,7 +21,6 @@ GNU General Public License for more details. #include "platform/platform.h" #define WINDOW_NAME XASH_ENGINE_NAME " Window" // Half-Life -convar_t *vid_displayfrequency; convar_t *vid_fullscreen; convar_t *vid_mode; convar_t *vid_brightness; @@ -181,7 +180,6 @@ void VID_Init( void ) vid_gamma = Cvar_Get( "gamma", "2.5", FCVAR_ARCHIVE, "gamma amount" ); vid_brightness = Cvar_Get( "brightness", "0.0", FCVAR_ARCHIVE, "brightness factor" ); - vid_displayfrequency = Cvar_Get ( "vid_displayfrequency", "0", FCVAR_RENDERINFO|FCVAR_VIDRESTART, "fullscreen refresh rate" ); vid_fullscreen = Cvar_Get( "fullscreen", "0", FCVAR_RENDERINFO|FCVAR_VIDRESTART, "enable fullscreen mode" ); vid_mode = Cvar_Get( "vid_mode", "0", FCVAR_RENDERINFO, "current video mode index (used just for storage)" ); vid_highdpi = Cvar_Get( "vid_highdpi", "1", FCVAR_RENDERINFO|FCVAR_VIDRESTART, "enable High-DPI mode" ); diff --git a/engine/client/vid_common.h b/engine/client/vid_common.h index 1014419e..57ef8b7c 100644 --- a/engine/client/vid_common.h +++ b/engine/client/vid_common.h @@ -31,7 +31,6 @@ extern glwstate_t glw_state; #define VID_MIN_WIDTH 320 extern convar_t *vid_fullscreen; -extern convar_t *vid_displayfrequency; extern convar_t *vid_highdpi; extern convar_t *vid_rotate; extern convar_t *vid_scale; diff --git a/engine/client/voice.c b/engine/client/voice.c new file mode 100644 index 00000000..98eb5266 --- /dev/null +++ b/engine/client/voice.c @@ -0,0 +1,633 @@ +/* +voice.c - voice chat implementation +Copyright (C) 2022 Velaron +Copyright (C) 2022 SNMetamorph + +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. +*/ + +#define CUSTOM_MODES 1 // required to correctly link with Opus Custom +#include +#include "common.h" +#include "client.h" +#include "voice.h" + +voice_state_t voice = { 0 }; + +CVAR_DEFINE_AUTO( voice_enable, "1", FCVAR_PRIVILEGED|FCVAR_ARCHIVE, "enable voice chat" ); +CVAR_DEFINE_AUTO( voice_loopback, "0", FCVAR_PRIVILEGED, "loopback voice back to the speaker" ); +CVAR_DEFINE_AUTO( voice_scale, "1.0", FCVAR_PRIVILEGED|FCVAR_ARCHIVE, "incoming voice volume scale" ); +CVAR_DEFINE_AUTO( voice_avggain, "0.5", FCVAR_PRIVILEGED|FCVAR_ARCHIVE, "automatic voice gain control (average)" ); +CVAR_DEFINE_AUTO( voice_maxgain, "5.0", FCVAR_PRIVILEGED|FCVAR_ARCHIVE, "automatic voice gain control (maximum)" ); +CVAR_DEFINE_AUTO( voice_inputfromfile, "0", FCVAR_PRIVILEGED, "input voice from voice_input.wav" ); + +static void Voice_ApplyGainAdjust( int16_t *samples, int count ); + +/* +=============================================================================== + + OPUS INTEGRATION + +=============================================================================== +*/ + +/* +========================= +Voice_InitOpusDecoder + +========================= +*/ +static qboolean Voice_InitOpusDecoder( void ) +{ + int err; + + voice.width = sizeof( opus_int16 ); + voice.samplerate = VOICE_OPUS_CUSTOM_SAMPLERATE; + voice.frame_size = VOICE_OPUS_CUSTOM_FRAME_SIZE; + + voice.custom_mode = opus_custom_mode_create( SOUND_44k, voice.frame_size, &err ); + if( !voice.custom_mode ) + { + Con_Printf( S_ERROR "Can't create Opus Custom mode: %s\n", opus_strerror( err )); + return false; + } + + voice.decoder = opus_custom_decoder_create( voice.custom_mode, VOICE_PCM_CHANNELS, &err ); + if( !voice.decoder ) + { + Con_Printf( S_ERROR "Can't create Opus encoder: %s\n", opus_strerror( err )); + return false; + } + + return true; +} + +/* +========================= +Voice_InitOpusEncoder + +========================= +*/ +static qboolean Voice_InitOpusEncoder( int quality ) +{ + int err; + + voice.encoder = opus_custom_encoder_create( voice.custom_mode, VOICE_PCM_CHANNELS, &err ); + if( !voice.encoder ) + { + Con_Printf( S_ERROR "Can't create Opus encoder: %s\n", opus_strerror( err )); + return false; + } + + switch( quality ) + { + case 1: // 6 kbps + opus_custom_encoder_ctl( voice.encoder, OPUS_SET_BITRATE( 6000 )); + break; + case 2: // 12 kbps + opus_custom_encoder_ctl( voice.encoder, OPUS_SET_BITRATE( 12000 )); + break; + case 4: // 64 kbps + opus_custom_encoder_ctl( voice.encoder, OPUS_SET_BITRATE( 64000 )); + break; + case 5: // 96 kbps + opus_custom_encoder_ctl( voice.encoder, OPUS_SET_BITRATE( 96000 )); + break; + default: // 36 kbps + opus_custom_encoder_ctl( voice.encoder, OPUS_SET_BITRATE( 36000 )); + break; + } + + return true; +} + +/* +========================= +Voice_ShutdownOpusDecoder + +========================= +*/ +static void Voice_ShutdownOpusDecoder( void ) +{ + if( voice.decoder ) + { + opus_custom_decoder_destroy( voice.decoder ); + voice.decoder = NULL; + } +} + +/* +========================= +Voice_ShutdownOpusEncoder + +========================= +*/ +static void Voice_ShutdownOpusEncoder( void ) +{ + if( voice.encoder ) + { + opus_custom_encoder_destroy( voice.encoder ); + voice.encoder = NULL; + } + + if( voice.custom_mode ) + { + opus_custom_mode_destroy( voice.custom_mode ); + voice.custom_mode = NULL; + } +} + +/* +========================= +Voice_GetOpusCompressedData + +========================= +*/ +static uint Voice_GetOpusCompressedData( byte *out, uint maxsize, uint *frames ) +{ + uint ofs = 0, size = 0; + uint frame_size_bytes = voice.frame_size * voice.width; + + if( voice.input_file ) + { + uint numbytes; + double updateInterval, curtime = Sys_DoubleTime(); + + updateInterval = curtime - voice.start_time; + voice.start_time = curtime; + + numbytes = updateInterval * voice.samplerate * voice.width * VOICE_PCM_CHANNELS; + numbytes = Q_min( numbytes, voice.input_file->size - voice.input_file_pos ); + numbytes = Q_min( numbytes, sizeof( voice.input_buffer ) - voice.input_buffer_pos ); + + memcpy( voice.input_buffer + voice.input_buffer_pos, voice.input_file->buffer + voice.input_file_pos, numbytes ); + voice.input_buffer_pos += numbytes; + voice.input_file_pos += numbytes; + } + + if( !voice.input_file ) + VoiceCapture_Lock( true ); + + for( ofs = 0; voice.input_buffer_pos - ofs >= frame_size_bytes && ofs <= voice.input_buffer_pos; ofs += frame_size_bytes ) + { + int bytes; + +#if 1 + if( !voice.input_file ) + { + // adjust gain before encoding, but only for input from voice + Voice_ApplyGainAdjust((opus_int16*)(voice.input_buffer + ofs), voice.frame_size); + } +#endif + + bytes = opus_custom_encode( voice.encoder, (const opus_int16 *)( voice.input_buffer + ofs ), + voice.frame_size, out + size + sizeof( uint16_t ), maxsize ); + + if( bytes > 0 ) + { + // write compressed frame size + *((uint16_t *)&out[size]) = bytes; + + size += bytes + sizeof( uint16_t ); + maxsize -= bytes + sizeof( uint16_t ); + + (*frames)++; + } + else + { + Con_Printf( S_ERROR "%s: failed to encode frame: %s\n", __func__, opus_strerror( bytes )); + } + } + + // did we compress anything? update counters + if( ofs ) + { + fs_offset_t remaining = voice.input_buffer_pos - ofs; + + // move remaining samples to the beginning of buffer + memmove( voice.input_buffer, voice.input_buffer + ofs, remaining ); + + voice.input_buffer_pos = remaining; + } + + if( !voice.input_file ) + VoiceCapture_Lock( false ); + + return size; +} + +/* +=============================================================================== + + VOICE CHAT INTEGRATION + +=============================================================================== +*/ + +/* +========================= +Voice_ApplyGainAdjust + +========================= +*/ +static void Voice_ApplyGainAdjust( int16_t *samples, int count ) +{ + float gain, modifiedMax; + int average, adjustedSample, blockOffset = 0; + + for( ;; ) + { + int i, localMax = 0, localSum = 0; + int blockSize = Q_min( count - ( blockOffset + voice.autogain.block_size ), voice.autogain.block_size ); + + if( blockSize < 1 ) + break; + + for( i = 0; i < blockSize; ++i ) + { + int sample = samples[blockOffset + i]; + int absSample = abs( sample ); + + if( absSample > localMax ) + localMax = absSample; + + localSum += absSample; + + gain = voice.autogain.current_gain + i * voice.autogain.gain_multiplier; + adjustedSample = Q_min( SHRT_MAX, Q_max(( int )( sample * gain ), SHRT_MIN )); + samples[blockOffset + i] = adjustedSample; + } + + if( blockOffset % voice.autogain.block_size == 0 ) + { + average = localSum / blockSize; + modifiedMax = average + ( localMax - average ) * voice_avggain.value; + + voice.autogain.current_gain = voice.autogain.next_gain * voice_scale.value; + voice.autogain.next_gain = Q_min( (float)SHRT_MAX / modifiedMax, voice_maxgain.value ) * voice_scale.value; + voice.autogain.gain_multiplier = ( voice.autogain.next_gain - voice.autogain.current_gain ) / ( voice.autogain.block_size - 1 ); + } + blockOffset += blockSize; + } +} + +/* +========================= +Voice_Status + +Notify user dll aboit voice transmission +========================= +*/ +static void Voice_Status( int entindex, qboolean bTalking ) +{ + if( cls.state == ca_active && clgame.dllFuncs.pfnVoiceStatus ) + clgame.dllFuncs.pfnVoiceStatus( entindex, bTalking ); +} + +/* +========================= +Voice_StatusTimeout + +Waits few milliseconds and if there was no +voice transmission, sends notification +========================= +*/ +static void Voice_StatusTimeout( voice_status_t *status, int entindex, double frametime ) +{ + if( status->talking_ack ) + { + status->talking_timeout += frametime; + if( status->talking_timeout > 0.2 ) + { + status->talking_ack = false; + Voice_Status( entindex, false ); + } + } +} + +/* +========================= +Voice_StatusAck + +Sends notification to user dll and +zeroes timeouts for this client +========================= +*/ +void Voice_StatusAck( voice_status_t *status, int playerIndex ) +{ + if( !status->talking_ack ) + Voice_Status( playerIndex, true ); + + status->talking_ack = true; + status->talking_timeout = 0.0; +} + +/* +========================= +Voice_IsRecording + +========================= +*/ +qboolean Voice_IsRecording( void ) +{ + return voice.is_recording; +} + +/* +========================= +Voice_RecordStop + +========================= +*/ +void Voice_RecordStop( void ) +{ + if( voice.input_file ) + { + FS_FreeSound( voice.input_file ); + voice.input_file = NULL; + } + + VoiceCapture_Activate( false ); + voice.is_recording = false; + + Voice_Status( VOICE_LOCALCLIENT_INDEX, false ); + + voice.input_buffer_pos = 0; + memset( voice.input_buffer, 0, sizeof( voice.input_buffer )); +} + +/* +========================= +Voice_RecordStart + +========================= +*/ +void Voice_RecordStart( void ) +{ + Voice_RecordStop(); + + if( voice_inputfromfile.value ) + { + voice.input_file = FS_LoadSound( "voice_input.wav", NULL, 0 ); + + if( voice.input_file ) + { + Sound_Process( &voice.input_file, voice.samplerate, voice.width, SOUND_RESAMPLE ); + voice.input_file_pos = 0; + + voice.start_time = Sys_DoubleTime(); + voice.is_recording = true; + } + else + { + FS_FreeSound( voice.input_file ); + voice.input_file = NULL; + } + } + + if( !Voice_IsRecording() ) + voice.is_recording = VoiceCapture_Activate( true ); + + if( Voice_IsRecording() ) + Voice_Status( VOICE_LOCALCLIENT_INDEX, true ); +} + +/* +========================= +Voice_Disconnect + +We're disconnected from server +stop recording and notify user dlls +========================= +*/ +void Voice_Disconnect( void ) +{ + int i; + + Voice_RecordStop(); + + if( voice.local.talking_ack ) + { + Voice_Status( VOICE_LOOPBACK_INDEX, false ); + voice.local.talking_ack = false; + } + + for( i = 0; i < MAX_CLIENTS; i++ ) + { + if( voice.players_status[i].talking_ack ) + { + Voice_Status( i, false ); + voice.players_status[i].talking_ack = false; + } + } +} + +/* +========================= +Voice_StartChannel + +Feed the decoded data to engine sound subsystem +========================= +*/ +static void Voice_StartChannel( uint samples, byte *data, int entnum ) +{ + SND_ForceInitMouth( entnum ); + S_RawEntSamples( entnum, samples, voice.samplerate, voice.width, VOICE_PCM_CHANNELS, data, 255 ); +} + +/* +========================= +Voice_AddIncomingData + +Received encoded voice data, decode it +========================= +*/ +void Voice_AddIncomingData( int ent, const byte *data, uint size, uint frames ) +{ + int samples = 0; + int ofs = 0; + + if( !voice.decoder ) + return; + + // decode frame by frame + for( ;; ) + { + int frame_samples; + uint16_t compressed_size; + + // no compressed size mark + if( ofs + sizeof( uint16_t ) > size ) + break; + + compressed_size = *(const uint16_t *)(data + ofs); + ofs += sizeof( uint16_t ); + + // no frame data + if( ofs + compressed_size > size ) + break; + + frame_samples = opus_custom_decode( voice.decoder, data + ofs, compressed_size, + (opus_int16*)voice.decompress_buffer + samples, voice.frame_size ); + + ofs += compressed_size; + samples += frame_samples; + } + + if( samples > 0 ) + Voice_StartChannel( samples, voice.decompress_buffer, ent ); +} + +/* +========================= +CL_AddVoiceToDatagram + +Encode our voice data and send it to server +========================= +*/ +void CL_AddVoiceToDatagram( void ) +{ + uint size, frames = 0; + + if( cls.state != ca_active || !Voice_IsRecording() || !voice.encoder ) + return; + + size = Voice_GetOpusCompressedData( voice.output_buffer, sizeof( voice.output_buffer ), &frames ); + + if( size > 0 && MSG_GetNumBytesLeft( &cls.datagram ) >= size + 32 ) + { + MSG_BeginClientCmd( &cls.datagram, clc_voicedata ); + MSG_WriteByte( &cls.datagram, voice_loopback.value != 0 ); + MSG_WriteByte( &cls.datagram, frames ); + MSG_WriteShort( &cls.datagram, size ); + MSG_WriteBytes( &cls.datagram, voice.output_buffer, size ); + } +} + +/* +========================= +Voice_RegisterCvars + +Register voice related cvars and commands +========================= +*/ +void Voice_RegisterCvars( void ) +{ + Cvar_RegisterVariable( &voice_enable ); + Cvar_RegisterVariable( &voice_loopback ); + Cvar_RegisterVariable( &voice_scale ); + Cvar_RegisterVariable( &voice_avggain ); + Cvar_RegisterVariable( &voice_maxgain ); + Cvar_RegisterVariable( &voice_inputfromfile ); +} + +/* +========================= +Voice_Shutdown + +Completely shutdown the voice subsystem +========================= +*/ +static void Voice_Shutdown( void ) +{ + int i; + + Voice_RecordStop(); + Voice_ShutdownOpusEncoder(); + Voice_ShutdownOpusDecoder(); + VoiceCapture_Shutdown(); + + if( voice.local.talking_ack ) + Voice_Status( VOICE_LOOPBACK_INDEX, false ); + + for( i = 0; i < MAX_CLIENTS; i++ ) + { + if( voice.players_status[i].talking_ack ) + Voice_Status( i, false ); + } + + memset( &voice, 0, sizeof( voice )); +} + +/* +========================= +Voice_Idle + +Run timeout for all clients +========================= +*/ +void Voice_Idle( double frametime ) +{ + int i; + + if( FBitSet( voice_enable.flags, FCVAR_CHANGED ) && !voice_enable.value ) + { + Voice_Shutdown(); + return; + } + + // update local player status first + Voice_StatusTimeout( &voice.local, VOICE_LOOPBACK_INDEX, frametime ); + + for( i = 0; i < MAX_CLIENTS; i++ ) + Voice_StatusTimeout( &voice.players_status[i], i, frametime ); +} + +/* +========================= +Voice_Init + +Initialize the voice subsystem +========================= +*/ +qboolean Voice_Init( const char *pszCodecName, int quality ) +{ + if( !voice_enable.value ) + return false; + + if( Q_strcmp( pszCodecName, VOICE_OPUS_CUSTOM_CODEC )) + { + Con_Printf( S_ERROR "Server requested unsupported codec: %s\n", pszCodecName ); + return false; + } + + // reinitialize only if codec parameters are different + if( Q_strcmp( voice.codec, pszCodecName ) && voice.quality != quality ) + Voice_Shutdown(); + + voice.autogain.block_size = 128; + + if( !Voice_InitOpusDecoder( )) + { + // no reason to init encoder and open audio device + // if we can't hear other players + Con_Printf( S_ERROR "Voice chat disabled.\n" ); + Voice_Shutdown(); + return false; + } + + // we can hear others players, so it's fine to fail now + voice.initialized = true; + Q_strncpy( voice.codec, pszCodecName, sizeof( voice.codec )); + + if( !Voice_InitOpusEncoder( quality )) + { + Con_Printf( S_WARN "Other players will not be able to hear you.\n" ); + return false; + } + + voice.quality = quality; + + if( !VoiceCapture_Init( )) + Con_Printf( S_WARN "No microphone is available.\n" ); + + return true; +} diff --git a/engine/client/voice.h b/engine/client/voice.h new file mode 100644 index 00000000..f1d878cb --- /dev/null +++ b/engine/client/voice.h @@ -0,0 +1,104 @@ +/* +voice.h - voice chat implementation +Copyright (C) 2022 Velaron +Copyright (C) 2022 SNMetamorph + +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 VOICE_H +#define VOICE_H + +#include "common.h" +#include "protocol.h" // MAX_CLIENTS +#include "sound.h" + +typedef struct OpusCustomEncoder OpusCustomEncoder; +typedef struct OpusCustomDecoder OpusCustomDecoder; +typedef struct OpusCustomMode OpusCustomMode; + +#define VOICE_LOOPBACK_INDEX (-2) +#define VOICE_LOCALCLIENT_INDEX (-1) + +#define VOICE_PCM_CHANNELS 1 // always mono + +// never change these parameters when using opuscustom +#define VOICE_OPUS_CUSTOM_SAMPLERATE SOUND_44k +// must follow opus custom requirements +// also be divisible with MAX_RAW_SAMPLES +#define VOICE_OPUS_CUSTOM_FRAME_SIZE 1024 +#define VOICE_OPUS_CUSTOM_CODEC "opus_custom_44k_512" + +// a1ba: do not change, we don't have any re-encoding support now +#define VOICE_DEFAULT_CODEC VOICE_OPUS_CUSTOM_CODEC + +typedef struct voice_status_s +{ + qboolean talking_ack; + double talking_timeout; +} voice_status_t; + +typedef struct voice_state_s +{ + string codec; + int quality; + + qboolean initialized; + qboolean is_recording; + double start_time; + + voice_status_t local; + voice_status_t players_status[MAX_CLIENTS]; + + // opus stuff + OpusCustomMode *custom_mode; + OpusCustomEncoder *encoder; + OpusCustomDecoder *decoder; + + // audio info + uint width; + uint samplerate; + uint frame_size; // in samples + + // buffers + byte input_buffer[MAX_RAW_SAMPLES]; + byte output_buffer[MAX_RAW_SAMPLES]; + byte decompress_buffer[MAX_RAW_SAMPLES]; + fs_offset_t input_buffer_pos; // in bytes + + // input from file + wavdata_t *input_file; + fs_offset_t input_file_pos; // in bytes + + // automatic gain control + struct { + int block_size; + float current_gain; + float next_gain; + float gain_multiplier; + } autogain; +} voice_state_t; + +extern voice_state_t voice; + +void CL_AddVoiceToDatagram( void ); + +void Voice_RegisterCvars( void ); +qboolean Voice_Init( const char *pszCodecName, int quality ); +void Voice_Idle( double frametime ); +qboolean Voice_IsRecording( void ); +void Voice_RecordStop( void ); +void Voice_RecordStart( void ); +void Voice_Disconnect( void ); +void Voice_AddIncomingData( int ent, const byte *data, uint size, uint frames ); +void Voice_StatusAck( voice_status_t *status, int playerIndex ); + +#endif // VOICE_H diff --git a/engine/common/cmd.c b/engine/common/cmd.c index 3a268e5a..9eeb1c68 100644 --- a/engine/common/cmd.c +++ b/engine/common/cmd.c @@ -647,7 +647,7 @@ void Cmd_TokenizeString( const char *text ) if( cmd_argc == 1 ) cmd_args = text; - text = _COM_ParseFileSafe( (char*)text, cmd_token, sizeof( cmd_token ), PFILE_IGNOREBRACKET, NULL ); + text = COM_ParseFileSafe( (char*)text, cmd_token, sizeof( cmd_token ), PFILE_IGNOREBRACKET, NULL, NULL ); if( !text ) return; @@ -1171,17 +1171,21 @@ void Cmd_List_f( void ) { cmd_t *cmd; int i = 0; - const char *match; + size_t matchlen = 0; + const char *match = NULL; - if( Cmd_Argc() > 1 ) match = Cmd_Argv( 1 ); - else match = NULL; + if( Cmd_Argc() > 1 ) + { + match = Cmd_Argv( 1 ); + matchlen = Q_strlen( match ); + } for( cmd = cmd_functions; cmd; cmd = cmd->next ) { if( cmd->name[0] == '@' ) continue; // never show system cmds - if( match && !Q_stricmpext( match, cmd->name )) + if( match && !Q_strnicmpext( match, cmd->name, matchlen )) continue; Con_Printf( " %-*s ^3%s^7\n", 32, cmd->name, cmd->desc ); diff --git a/engine/common/com_strings.h b/engine/common/com_strings.h index bc495b42..50bde373 100644 --- a/engine/common/com_strings.h +++ b/engine/common/com_strings.h @@ -65,6 +65,8 @@ GNU General Public License for more details. #define DEFAULT_UPDATE_PAGE "https://github.com/FWGS/xash3d-fwgs/releases/latest" #define XASH_ENGINE_NAME "Xash3D FWGS" +#define XASH_VERSION "0.20" // engine current version +#define XASH_COMPAT_VERSION "0.99" // version we are based on // renderers order is important, software is always a last chance fallback #define DEFAULT_RENDERERS { "gl", "gles1", "gles2", "gl4es", "soft" } diff --git a/engine/common/common.c b/engine/common/common.c index c8baf28b..684718dd 100644 --- a/engine/common/common.c +++ b/engine/common/common.c @@ -585,23 +585,6 @@ void COM_TrimSpace( const char *source, char *dest ) dest[length] = 0; } -/* -============ -COM_FixSlashes - -Changes all '/' characters into '\' characters, in place. -============ -*/ -void COM_FixSlashes( char *pname ) -{ - while( *pname ) - { - if( *pname == '\\' ) - *pname = '/'; - pname++; - } -} - /* ================== COM_Nibble @@ -1182,22 +1165,22 @@ void Test_RunCommon( void ) Msg( "Checking COM_ParseFile...\n" ); - file = _COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len ); + file = COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len, NULL ); TASSERT( !Q_strcmp( buf, "q" ) && len == 1); - file = _COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len ); + file = COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len, NULL ); TASSERT( !Q_strcmp( buf, "asdf" ) && len == 4); - file = _COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len ); + file = COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len, NULL ); TASSERT( !Q_strcmp( buf, "qwer" ) && len == -1); - file = _COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len ); + file = COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len, NULL ); TASSERT( !Q_strcmp( buf, "f \"f" ) && len == 4); - file = _COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len ); + file = COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len, NULL ); TASSERT( !Q_strcmp( buf, "meow" ) && len == -1); - file = _COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len ); + file = COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len, NULL ); TASSERT( !Q_strcmp( buf, "bark" ) && len == 4); } #endif diff --git a/engine/common/common.h b/engine/common/common.h index 333f31aa..36cdb234 100644 --- a/engine/common/common.h +++ b/engine/common/common.h @@ -76,13 +76,6 @@ XASH SPECIFIC - sort of hack that works only in Xash3D not in GoldSrc #define HACKS_RELATED_HLMODS // some HL-mods works differently under Xash and can't be fixed without some hacks at least at current time -typedef struct -{ - int numfilenames; - char **filenames; - char *filenamesbuffer; -} search_t; - enum { DEV_NONE = 0, @@ -118,9 +111,8 @@ typedef enum #include "cvar.h" #include "con_nprint.h" #include "crclib.h" - -#define XASH_VERSION "0.20" // engine current version -#define XASH_COMPAT_VERSION "0.99" // version we are based on +#include "ref_api.h" +#include "fscallback.h" // PERFORMANCE INFO #define MIN_FPS 20.0f // host minimum fps value for maxfps. @@ -150,18 +142,6 @@ typedef enum #define MAX_STATIC_ENTITIES 32 // static entities that moved on the client when level is spawn #endif -// filesystem flags -#define FS_STATIC_PATH ( 1U << 0 ) // FS_ClearSearchPath will be ignore this path -#define FS_NOWRITE_PATH ( 1U << 1 ) // default behavior - last added gamedir set as writedir. This flag disables it -#define FS_GAMEDIR_PATH ( 1U << 2 ) // just a marker for gamedir path -#define FS_CUSTOM_PATH ( 1U << 3 ) // custom directory -#define FS_GAMERODIR_PATH ( 1U << 4 ) // caseinsensitive - -#define FS_GAMEDIRONLY_SEARCH_FLAGS ( FS_GAMEDIR_PATH | FS_CUSTOM_PATH | FS_GAMERODIR_PATH ) - -#define GI SI.GameInfo -#define FS_Gamedir() SI.GameInfo->gamefolder -#define FS_Title() SI.GameInfo->title #define GameState (&host.game) #define FORCE_DRAW_VERSION_TIME 5.0f // draw version for 5 seconds @@ -179,7 +159,6 @@ extern convar_t *scr_download; extern convar_t *cmd_scripting; extern convar_t *sv_maxclients; extern convar_t *cl_allow_levelshots; -extern convar_t *vid_displayfrequency; extern convar_t host_developer; extern convar_t *host_limitlocal; extern convar_t *host_framerate; @@ -202,61 +181,6 @@ GAMEINFO stuff internal shared gameinfo structure (readonly for engine parts) ======================================================================== */ -typedef struct gameinfo_s -{ - // filesystem info - char gamefolder[MAX_QPATH]; // used for change game '-game x' - char basedir[MAX_QPATH]; // base game directory (like 'id1' for Quake or 'valve' for Half-Life) - char falldir[MAX_QPATH]; // used as second basedir - char startmap[MAX_QPATH];// map to start singleplayer game - char trainmap[MAX_QPATH];// map to start hazard course (if specified) - char title[64]; // Game Main Title - float version; // game version (optional) - - // .dll pathes - char dll_path[MAX_QPATH]; // e.g. "bin" or "cl_dlls" - char game_dll[MAX_QPATH]; // custom path for game.dll - - // .ico path - char iconpath[MAX_QPATH]; // "game.ico" by default - - // about mod info - string game_url; // link to a developer's site - string update_url; // link to updates page - char type[MAX_QPATH]; // single, toolkit, multiplayer etc - char date[MAX_QPATH]; - size_t size; - - int gamemode; - qboolean secure; // prevent to console acess - qboolean nomodels; // don't let player to choose model (use player.mdl always) - qboolean noskills; // disable skill menu selection - qboolean render_picbutton_text; // use font renderer to render WON buttons - - char sp_entity[32]; // e.g. info_player_start - char mp_entity[32]; // e.g. info_player_deathmatch - char mp_filter[32]; // filtering multiplayer-maps - - char ambientsound[NUM_AMBIENTS][MAX_QPATH]; // quake ambient sounds - - int max_edicts; // min edicts is 600, max edicts is 8196 - int max_tents; // min temp ents is 300, max is 2048 - int max_beams; // min beams is 64, max beams is 512 - int max_particles; // min particles is 4096, max particles is 32768 - - char game_dll_linux[64]; // custom path for game.dll - char game_dll_osx[64]; // custom path for game.dll - - qboolean added; -} gameinfo_t; - -typedef enum -{ - GAME_NORMAL, - GAME_SINGLEPLAYER_ONLY, - GAME_MULTIPLAYER_ONLY -} gametype_t; - typedef struct sysinfo_s { string exeName; // exe.filename @@ -264,9 +188,6 @@ typedef struct sysinfo_s string basedirName; // name of base directory string gamedll; string clientlib; - gameinfo_t *GameInfo; // current GameInfo - gameinfo_t *games[MAX_MODS]; // environment games (founded at each engine start) - int numgames; } sysinfo_t; typedef enum @@ -385,6 +306,16 @@ typedef struct float scale; // curstate.scale } tentlist_t; +typedef enum bugcomp_e +{ + // default mode, we assume that user dlls are not relying on engine bugs + BUGCOMP_OFF, + + // GoldSrc mode, user dlls are relying on GoldSrc specific bugs + // but fixing them may break regular Xash games + BUGCOMP_GOLDSRC, +} bugcomp_t; + typedef struct host_parm_s { HINSTANCE hInst; @@ -426,7 +357,7 @@ typedef struct host_parm_s qboolean allow_cheats; // this host will allow cheating qboolean con_showalways; // show console always (developer and dedicated) qboolean change_game; // initialize when game is changed - qboolean mouse_visible; // vgui override cursor control + qboolean mouse_visible; // vgui override cursor control (never change outside Platform_SetCursorType!) qboolean shutdown_issued; // engine is shutting down qboolean force_draw_version; // used when fraps is loaded float force_draw_version_time; @@ -459,6 +390,9 @@ typedef struct host_parm_s struct decallist_s *decalList; // used for keep decals, when renderer is restarted or changed int numdecals; + // bug compatibility level, for very "special" games + bugcomp_t bugcomp; + } host_parm_t; extern host_parm_t host; @@ -473,6 +407,13 @@ extern sysinfo_t SI; typedef void (*xcommand_t)( void ); +// +// filesystem_engine.c +// +qboolean FS_LoadProgs( void ); +void FS_Init( void ); +void FS_Shutdown( void ); + // // cmd.c // @@ -532,56 +473,6 @@ void Mem_PrintStats( void ); #define Mem_IsAllocated( mem ) Mem_IsAllocatedExt( NULL, mem ) #define Mem_Check() _Mem_Check( __FILE__, __LINE__ ) -// -// filesystem.c -// -void FS_Init( void ); -void FS_Path( void ); -void FS_Rescan( void ); -void FS_Shutdown( void ); -void FS_ClearSearchPath( void ); -void FS_AllowDirectPaths( qboolean enable ); -void FS_AddGameDirectory( const char *dir, uint flags ); -void FS_AddGameHierarchy( const char *dir, uint flags ); -void FS_LoadGameInfo( const char *rootfolder ); -const char *FS_GetDiskPath( const char *name, qboolean gamedironly ); -byte *W_LoadLump( wfile_t *wad, const char *lumpname, size_t *lumpsizeptr, const char type ); -void W_Close( wfile_t *wad ); -byte *FS_LoadFile( const char *path, fs_offset_t *filesizeptr, qboolean gamedironly ); -qboolean CRC32_File( dword *crcvalue, const char *filename ); -qboolean MD5_HashFile( byte digest[16], const char *pszFileName, uint seed[4] ); -byte *FS_LoadDirectFile( const char *path, fs_offset_t *filesizeptr ); -qboolean FS_WriteFile( const char *filename, const void *data, fs_offset_t len ); -qboolean COM_ParseVector( char **pfile, float *v, size_t size ); -void COM_NormalizeAngles( vec3_t angles ); -int COM_FileSize( const char *filename ); -void COM_FixSlashes( char *pname ); -void COM_FreeFile( void *buffer ); -int COM_CompareFileTime( const char *filename1, const char *filename2, int *iCompare ); -search_t *FS_Search( const char *pattern, int caseinsensitive, int gamedironly ); -file_t *FS_Open( const char *filepath, const char *mode, qboolean gamedironly ); -fs_offset_t FS_Write( file_t *file, const void *data, size_t datasize ); -fs_offset_t FS_Read( file_t *file, void *buffer, size_t buffersize ); -int FS_VPrintf( file_t *file, const char *format, va_list ap ); -int FS_Seek( file_t *file, fs_offset_t offset, int whence ); -int FS_Gets( file_t *file, byte *string, size_t bufsize ); -int FS_Printf( file_t *file, const char *format, ... ) _format( 2 ); -fs_offset_t FS_FileSize( const char *filename, qboolean gamedironly ); -int FS_FileTime( const char *filename, qboolean gamedironly ); -int FS_Print( file_t *file, const char *msg ); -qboolean FS_Rename( const char *oldname, const char *newname ); -int FS_FileExists( const char *filename, int gamedironly ); -int FS_SetCurrentDirectory( const char *path ); -qboolean FS_SysFileExists( const char *path, qboolean casesensitive ); -qboolean FS_FileCopy( file_t *pOutput, file_t *pInput, int fileSize ); -qboolean FS_Delete( const char *path ); -int FS_UnGetc( file_t *file, byte c ); -fs_offset_t FS_Tell( file_t *file ); -qboolean FS_Eof( file_t *file ); -int FS_Close( file_t *file ); -int FS_Getc( file_t *file ); -fs_offset_t FS_FileLength( file_t *f ); - // // imagelib // @@ -670,16 +561,6 @@ void FS_FreeStream( stream_t *stream ); qboolean Sound_Process( wavdata_t **wav, int rate, int width, uint flags ); uint Sound_GetApproxWavePlayLen( const char *filepath ); -// -// build.c -// -int Q_buildnum( void ); -int Q_buildnum_compat( void ); -const char *Q_buildos( void ); -const char *Q_buildarch( void ); -const char *Q_buildcommit( void ); - - // // host.c // @@ -699,7 +580,7 @@ void Host_WriteConfig( void ); qboolean Host_IsLocalGame( void ); qboolean Host_IsLocalClient( void ); void Host_ShutdownServer( void ); -void Host_Error( const char *error, ... ) _format( 1 ) NORETURN; +void Host_Error( const char *error, ... ) _format( 1 ); void Host_PrintEngineFeatures( void ); void Host_Frame( float time ); void Host_InitDecals( void ); @@ -879,7 +760,6 @@ void R_ClearAllDecals( void ); void CL_ClearStaticEntities( void ); qboolean S_StreamGetCurrentState( char *currentTrack, char *loopTrack, int *position ); struct cl_entity_s *CL_GetEntityByIndex( int index ); -struct player_info_s *CL_GetPlayerInfo( int playerIndex ); void CL_ServerCommand( qboolean reliable, const char *fmt, ... ) _format( 2 ); void CL_HudMessage( const char *pMessage ); const char *CL_MsgInfo( int cmd ); @@ -955,6 +835,11 @@ void UI_SetActiveMenu( qboolean fActive ); void UI_ShowConnectionWarning( void ); void Cmd_Null_f( void ); void Rcon_Print( const char *pMsg ); +qboolean COM_ParseVector( char **pfile, float *v, size_t size ); +void COM_NormalizeAngles( vec3_t angles ); +int COM_FileSize( const char *filename ); +void COM_FreeFile( void *buffer ); +int COM_CompareFileTime( const char *filename1, const char *filename2, int *iCompare ); // soundlib shared exports qboolean S_Init( void ); @@ -982,6 +867,7 @@ void GAME_EXPORT ID_SetCustomClientID( const char *id ); void NET_InitMasters( void ); void NET_SaveMasters( void ); qboolean NET_SendToMasters( netsrc_t sock, size_t len, const void *data ); +qboolean NET_IsMasterAdr( netadr_t adr ); #ifdef REF_DLL #error "common.h in ref_dll" diff --git a/engine/common/con_utils.c b/engine/common/con_utils.c index b07374e4..8bafcb31 100644 --- a/engine/common/con_utils.c +++ b/engine/common/con_utils.c @@ -761,10 +761,10 @@ qboolean Cmd_GetGamesList( const char *s, char *completedname, int length ) // compare gamelist with current keyword len = Q_strlen( s ); - for( i = 0, numgamedirs = 0; i < SI.numgames; i++ ) + for( i = 0, numgamedirs = 0; i < FI->numgames; i++ ) { - if(( *s == '*' ) || !Q_strnicmp( SI.games[i]->gamefolder, s, len)) - Q_strcpy( gamedirs[numgamedirs++], SI.games[i]->gamefolder ); + if(( *s == '*' ) || !Q_strnicmp( FI->games[i]->gamefolder, s, len)) + Q_strcpy( gamedirs[numgamedirs++], FI->games[i]->gamefolder ); } if( !numgamedirs ) return false; diff --git a/engine/common/cvar.c b/engine/common/cvar.c index 108b3956..6a8e5c0d 100644 --- a/engine/common/cvar.c +++ b/engine/common/cvar.c @@ -21,7 +21,7 @@ GNU General Public License for more details. convar_t *cvar_vars = NULL; // head of list convar_t *cmd_scripting; -CVAR_DEFINE_AUTO( cl_filterstuffcmd, "1", FCVAR_ARCHIVE | FCVAR_PRIVILEGED, "filter commands coming from server" ); +CVAR_DEFINE_AUTO( cl_filterstuffcmd, "0", FCVAR_ARCHIVE | FCVAR_PRIVILEGED, "filter commands coming from server" ); /* ============ @@ -220,6 +220,24 @@ const char *Cvar_ValidateString( convar_t *var, const char *value ) return pszValue; } +/* +============ +Cvar_ValidateVarName +============ +*/ +static qboolean Cvar_ValidateVarName( const char *s, qboolean isvalue ) +{ + if( !s ) + return false; + if( Q_strchr( s, '\\' ) && !isvalue ) + return false; + if( Q_strchr( s, '\"' )) + return false; + if( Q_strchr( s, ';' ) && !isvalue ) + return false; + return true; +} + /* ============ Cvar_UnlinkVar @@ -495,6 +513,113 @@ void Cvar_RegisterVariable( convar_t *var ) #endif } +/* +============ +Cvar_Set2 +============ +*/ +static convar_t *Cvar_Set2( const char *var_name, const char *value ) +{ + convar_t *var; + const char *pszValue; + qboolean dll_variable = false; + qboolean force = false; + + if( !Cvar_ValidateVarName( var_name, false )) + { + Con_DPrintf( S_ERROR "Invalid cvar name string: %s\n", var_name ); + return NULL; + } + + var = Cvar_FindVar( var_name ); + if( !var ) + { + // if cvar not found, create it + return Cvar_Get( var_name, value, FCVAR_USER_CREATED, NULL ); + } + else + { + if( !Cmd_CurrentCommandIsPrivileged( )) + { + if( FBitSet( var->flags, FCVAR_PRIVILEGED )) + { + Con_Printf( "%s is priveleged.\n", var->name ); + return var; + } + + if( cl_filterstuffcmd.value > 0.0f && FBitSet( var->flags, FCVAR_FILTERABLE )) + { + Con_Printf( "%s is filterable.\n", var->name ); + return var; + } + } + } + + // use this check to prevent acessing for unexisting fields + // for cvar_t: latched_string, description, etc + dll_variable = FBitSet( var->flags, FCVAR_EXTDLL ); + + // check value + if( !value ) + { + if( !FBitSet( var->flags, FCVAR_EXTENDED|FCVAR_ALLOCATED )) + { + Con_Printf( "%s has no default value and can't be reset.\n", var->name ); + return var; + } + + if( dll_variable ) + value = "0"; + else + value = var->def_string; // reset to default value + } + + if( !Q_strcmp( value, var->string )) + return var; + + // any latched values not allowed for game cvars + if( dll_variable ) + force = true; + + if( !force ) + { + if( FBitSet( var->flags, FCVAR_READ_ONLY )) + { + Con_Printf( "%s is read-only.\n", var->name ); + return var; + } + + if( FBitSet( var->flags, FCVAR_CHEAT ) && !host.allow_cheats ) + { + Con_Printf( "%s is cheat protected.\n", var->name ); + return var; + } + + // just tell user about deferred changes + if( FBitSet( var->flags, FCVAR_LATCH ) && ( SV_Active() || CL_Active( ))) + Con_Printf( "%s will be changed upon restarting.\n", var->name ); + } + + pszValue = Cvar_ValidateString( var, value ); + + // nothing to change + if( !Q_strcmp( pszValue, var->string )) + return var; + + // fill it cls.userinfo, svs.serverinfo + if( !Cvar_UpdateInfo( var, pszValue, true )) + return var; + + // and finally changed the cvar itself + freestring( var->string ); + var->string = copystring( pszValue ); + var->value = Q_atof( var->string ); + + // tell engine about changes + Cvar_Changed( var ); + return var; +} + /* ============ Cvar_DirectSet @@ -887,6 +1012,40 @@ void Cvar_Toggle_f( void ) Cvar_Set( Cmd_Argv( 1 ), va( "%i", v )); } +/* +============ +Cvar_Set_f + +Allows setting and defining of arbitrary cvars from console, even if they +weren't declared in C code. +============ +*/ +void Cvar_Set_f( void ) +{ + int i, c, l = 0, len; + char combined[MAX_CMD_TOKENS]; + + c = Cmd_Argc(); + if( c < 3 ) + { + Msg( S_USAGE "set \n" ); + return; + } + combined[0] = 0; + + for( i = 2; i < c; i++ ) + { + len = Q_strlen( Cmd_Argv(i) + 1 ); + if( l + len >= MAX_CMD_TOKENS - 2 ) + break; + Q_strcat( combined, Cmd_Argv( i )); + if( i != c-1 ) Q_strcat( combined, " " ); + l += len; + } + + Cvar_Set2( Cmd_Argv( 1 ), combined ); +} + /* ============ Cvar_SetGL_f @@ -934,16 +1093,20 @@ void Cvar_List_f( void ) const char *match = NULL; char *value; int count = 0; + size_t matchlen = 0; if( Cmd_Argc() > 1 ) + { match = Cmd_Argv( 1 ); + matchlen = Q_strlen( match ); + } for( var = cvar_vars; var; var = var->next ) { if( var->name[0] == '@' ) continue; // never shows system cvars - if( match && !Q_stricmpext( match, var->name )) + if( match && !Q_strnicmpext( match, var->name, matchlen )) continue; if( Q_colorstr( var->string )) @@ -995,12 +1158,12 @@ void Cvar_Init( void ) { cvar_vars = NULL; cmd_scripting = Cvar_Get( "cmd_scripting", "0", FCVAR_ARCHIVE|FCVAR_PRIVILEGED, "enable simple condition checking and variable operations" ); - Cvar_RegisterVariable (&host_developer); // early registering for dev + Cvar_RegisterVariable( &host_developer ); // early registering for dev Cvar_RegisterVariable( &cl_filterstuffcmd ); - Cmd_AddRestrictedCommand( "setgl", Cvar_SetGL_f, "change the value of a opengl variable" ); // OBSOLETE Cmd_AddRestrictedCommand( "toggle", Cvar_Toggle_f, "toggles a console variable's values (use for more info)" ); Cmd_AddRestrictedCommand( "reset", Cvar_Reset_f, "reset any type variable to initial value" ); + Cmd_AddCommand( "set", Cvar_Set_f, "create or change the value of a console variable" ); Cmd_AddCommand( "cvarlist", Cvar_List_f, "display all console variables beginning with the specified prefix" ); } diff --git a/engine/common/cvar.h b/engine/common/cvar.h index ad2dbb44..5d5477f7 100644 --- a/engine/common/cvar.h +++ b/engine/common/cvar.h @@ -48,6 +48,7 @@ typedef struct convar_s #define FCVAR_VIDRESTART (1<<20) // recreate the window is cvar with this flag was changed #define FCVAR_TEMPORARY (1<<21) // these cvars holds their values and can be unlink in any time #define FCVAR_MOVEVARS (1<<22) // this cvar is a part of movevars_t struct that shared between client and server +#define FCVAR_USER_CREATED (1<<23) // created by a set command (dll's used) #define CVAR_DEFINE( cv, cvname, cvstr, cvflags, cvdesc ) \ convar_t cv = { (char*)cvname, (char*)cvstr, cvflags, 0.0f, (void *)CVAR_SENTINEL, (char*)cvdesc, NULL } diff --git a/engine/common/filesystem.h b/engine/common/filesystem.h deleted file mode 100644 index 3abba403..00000000 --- a/engine/common/filesystem.h +++ /dev/null @@ -1,200 +0,0 @@ -/* -filesystem.h - engine FS -Copyright (C) 2007 Uncle Mike - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. -*/ - -#ifndef FILESYSTEM_H -#define FILESYSTEM_H - -/* -======================================================================== -PAK FILES - -The .pak files are just a linear collapse of a directory tree -======================================================================== -*/ -// header -#define IDPACKV1HEADER (('K'<<24)+('C'<<16)+('A'<<8)+'P') // little-endian "PACK" - -#define MAX_FILES_IN_PACK 65536 // pak - -typedef struct -{ - int ident; - int dirofs; - int dirlen; -} dpackheader_t; - -typedef struct -{ - char name[56]; // total 64 bytes - int filepos; - int filelen; -} dpackfile_t; - -/* -======================================================================== -.WAD archive format (WhereAllData - WAD) - -List of compressed files, that can be identify only by TYPE_* - - -header: dwadinfo_t[dwadinfo_t] -file_1: byte[dwadinfo_t[num]->disksize] -file_2: byte[dwadinfo_t[num]->disksize] -file_3: byte[dwadinfo_t[num]->disksize] -... -file_n: byte[dwadinfo_t[num]->disksize] -infotable dlumpinfo_t[dwadinfo_t->numlumps] -======================================================================== -*/ -#define WAD3_NAMELEN 16 -#define HINT_NAMELEN 5 // e.g. _mask, _norm -#define MAX_FILES_IN_WAD 65535 // real limit as above <2Gb size not a lumpcount - -#include "const.h" - -typedef struct -{ - int ident; // should be WAD3 - int numlumps; // num files - int infotableofs; // LUT offset -} dwadinfo_t; - -typedef struct -{ - int filepos; // file offset in WAD - int disksize; // compressed or uncompressed - int size; // uncompressed - signed char type; // TYP_* - signed char attribs; // file attribs - signed char pad0; - signed char pad1; - char name[WAD3_NAMELEN]; // must be null terminated -} dlumpinfo_t; - -#include "custom.h" - -/* -======================================================================== -.HPK archive format (Hash PAK - HPK) - -List of compressed files, that can be identify only by TYPE_* - - -header: dwadinfo_t[dwadinfo_t] -file_1: byte[dwadinfo_t[num]->disksize] -file_2: byte[dwadinfo_t[num]->disksize] -file_3: byte[dwadinfo_t[num]->disksize] -... -file_n: byte[dwadinfo_t[num]->disksize] -infotable dlumpinfo_t[dwadinfo_t->numlumps] -======================================================================== -*/ - -#define IDHPAKHEADER (('K'<<24)+('A'<<16)+('P'<<8)+'H') // little-endian "HPAK" -#define IDHPAK_VERSION 1 - -typedef struct -{ - int ident; // should be equal HPAK - int version; - int infotableofs; -} hpak_header_t; - -typedef struct -{ - resource_t resource; - int filepos; - int disksize; -} hpak_lump_t; - -typedef struct -{ - int count; - hpak_lump_t *entries; // variable sized. -} hpak_info_t; - -#define ZIP_HEADER_LF (('K'<<8)+('P')+(0x03<<16)+(0x04<<24)) -#define ZIP_HEADER_SPANNED ((0x08<<24)+(0x07<<16)+('K'<<8)+'P') - -#define ZIP_HEADER_CDF ((0x02<<24)+(0x01<<16)+('K'<<8)+'P') -#define ZIP_HEADER_EOCD ((0x06<<24)+(0x05<<16)+('K'<<8)+'P') - -#define ZIP_COMPRESSION_NO_COMPRESSION 0 -#define ZIP_COMPRESSION_DEFLATED 8 - -#define ZIP_ZIP64 0xffffffff - -#pragma pack( push, 1 ) -typedef struct zip_header_s -{ - unsigned int signature; // little endian ZIP_HEADER - unsigned short version; // version of pkzip need to unpack - unsigned short flags; // flags (16 bits == 16 flags) - unsigned short compression_flags; // compression flags (bits) - unsigned int dos_date; // file modification time and file modification date - unsigned int crc32; //crc32 - unsigned int compressed_size; - unsigned int uncompressed_size; - unsigned short filename_len; - unsigned short extrafield_len; -} zip_header_t; - -/* - in zip64 comp and uncompr size == 0xffffffff remeber this - compressed and uncompress filesize stored in extra field -*/ - -typedef struct zip_header_extra_s -{ - unsigned int signature; // ZIP_HEADER_SPANNED - unsigned int crc32; - unsigned int compressed_size; - unsigned int uncompressed_size; -} zip_header_extra_t; - -typedef struct zip_cdf_header_s -{ - unsigned int signature; - unsigned short version; - unsigned short version_need; - unsigned short generalPurposeBitFlag; - unsigned short flags; - unsigned short modification_time; - unsigned short modification_date; - unsigned int crc32; - unsigned int compressed_size; - unsigned int uncompressed_size; - unsigned short filename_len; - unsigned short extrafield_len; - unsigned short file_commentary_len; - unsigned short disk_start; - unsigned short internal_attr; - unsigned int external_attr; - unsigned int local_header_offset; -} zip_cdf_header_t; - -typedef struct zip_header_eocd_s -{ - unsigned short disk_number; - unsigned short start_disk_number; - unsigned short number_central_directory_record; - unsigned short total_central_directory_record; - unsigned int size_of_central_directory; - unsigned int central_directory_offset; - unsigned short commentary_len; -} zip_header_eocd_t; -#pragma pack( pop ) - -#endif//FILESYSTEM_H diff --git a/engine/common/filesystem_engine.c b/engine/common/filesystem_engine.c new file mode 100644 index 00000000..6d3ff03e --- /dev/null +++ b/engine/common/filesystem_engine.c @@ -0,0 +1,153 @@ + /* +filesystem.c - game filesystem based on DP fs +Copyright (C) 2007 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "common.h" +#include "library.h" + +fs_api_t g_fsapi; +fs_globals_t *FI; + +static HINSTANCE fs_hInstance; + +static void FS_Rescan_f( void ) +{ + FS_Rescan(); +} + +static void FS_ClearPaths_f( void ) +{ + FS_ClearSearchPath(); +} + +static void FS_Path_f_( void ) +{ + FS_Path_f(); +} + +static fs_interface_t fs_memfuncs = +{ + Con_Printf, + Con_DPrintf, + Con_Reportf, + Sys_Error, + + _Mem_AllocPool, + _Mem_FreePool, + _Mem_Alloc, + _Mem_Realloc, + _Mem_Free, +}; + +static void FS_UnloadProgs( void ) +{ + COM_FreeLibrary( fs_hInstance ); + fs_hInstance = 0; +} + +#ifdef XASH_INTERNAL_GAMELIBS +#define FILESYSTEM_STDIO_DLL "filesystem_stdio" +#else +#define FILESYSTEM_STDIO_DLL "filesystem_stdio." OS_LIB_EXT +#endif + +qboolean FS_LoadProgs( void ) +{ + const char *name = FILESYSTEM_STDIO_DLL; + FSAPI GetFSAPI; + + fs_hInstance = COM_LoadLibrary( name, false, true ); + + if( !fs_hInstance ) + { + Host_Error( "FS_LoadProgs: can't load filesystem library %s: %s\n", name, COM_GetLibraryError() ); + return false; + } + + if( !( GetFSAPI = (FSAPI)COM_GetProcAddress( fs_hInstance, GET_FS_API ))) + { + FS_UnloadProgs(); + Host_Error( "FS_LoadProgs: can't find GetFSAPI entry point in %s\n", name ); + return false; + } + + if( !GetFSAPI( FS_API_VERSION, &g_fsapi, &FI, &fs_memfuncs )) + { + FS_UnloadProgs(); + Host_Error( "FS_LoadProgs: can't initialize filesystem API: wrong version\n" ); + return false; + } + + Con_DPrintf( "FS_LoadProgs: filesystem_stdio successfully loaded\n" ); + + return true; +} + +/* +================ +FS_Init +================ +*/ +void FS_Init( void ) +{ + qboolean hasBaseDir = false; + qboolean hasGameDir = false; + qboolean caseinsensitive = true; + int i; + string gamedir; + + Cmd_AddRestrictedCommand( "fs_rescan", FS_Rescan_f, "rescan filesystem search pathes" ); + Cmd_AddRestrictedCommand( "fs_path", FS_Path_f_, "show filesystem search pathes" ); + Cmd_AddRestrictedCommand( "fs_clearpaths", FS_ClearPaths_f, "clear filesystem search pathes" ); + +#if !XASH_WIN32 + if( Sys_CheckParm( "-casesensitive" ) ) + caseinsensitive = false; +#endif + + if( !Sys_GetParmFromCmdLine( "-game", gamedir )) + Q_strncpy( gamedir, SI.basedirName, sizeof( gamedir )); // gamedir == basedir + + if( !FS_InitStdio( caseinsensitive, host.rootdir, SI.basedirName, gamedir, host.rodir )) + { + Host_Error( "Can't init filesystem_stdio!\n" ); + return; + } + + if( !Sys_GetParmFromCmdLine( "-dll", SI.gamedll )) + SI.gamedll[0] = 0; + + if( !Sys_GetParmFromCmdLine( "-clientlib", SI.clientlib )) + SI.clientlib[0] = 0; +} + +/* +================ +FS_Shutdown +================ +*/ +void FS_Shutdown( void ) +{ + int i; + + FS_ShutdownStdio(); + + memset( &SI, 0, sizeof( sysinfo_t )); + + FS_UnloadProgs(); +} + + + + diff --git a/engine/common/host.c b/engine/common/host.c index 7b55e802..702422f0 100644 --- a/engine/common/host.c +++ b/engine/common/host.c @@ -74,7 +74,7 @@ void Sys_PrintUsage( void ) #if XASH_MESSAGEBOX == MSGBOX_STDERR "\n" // dirty hack to not have Xash Error: Usage: on same line #endif // XASH_MESSAGEBOX == MSGBOX_STDERR - "Usage:\n" + S_USAGE "\n" #if !XASH_MOBILE_PLATFORM #if XASH_WIN32 O(".exe [options] [+command1] [+command2 arg]","") @@ -151,6 +151,8 @@ void Sys_PrintUsage( void ) O("-clientlib ","override client DLL path") #endif O("-rodir ","set read-only base directory, experimental") + O("-bugcomp ","enable precise bug compatibility. Will break games that don't require it") + O(" ","Refer to engine documentation for more info") O("-ip ","set custom ip") O("-port ","set custom host port") @@ -274,6 +276,13 @@ void Host_CheckSleep( void ) { int sleeptime = host_sleeptime->value; +#ifndef XASH_DEDICATED + // never sleep in timedemo for benchmarking purposes + // also don't sleep with vsync for less lag + if( CL_IsTimeDemo( ) || CVAR_TO_BOOL( gl_vsync )) + return; +#endif + if( Host_IsDedicated() ) { // let the dedicated server some sleep @@ -305,7 +314,9 @@ void Host_NewInstance( const char *name, const char *finalmsg ) host.change_game = true; Q_strncpy( host.finalmsg, finalmsg, sizeof( host.finalmsg )); - pChangeGame( name ); // call from hl.exe + + if( !Sys_NewInstance( name )) + pChangeGame( name ); // call from hl.exe } /* @@ -326,13 +337,13 @@ void Host_ChangeGame_f( void ) } // validate gamedir - for( i = 0; i < SI.numgames; i++ ) + for( i = 0; i < FI->numgames; i++ ) { - if( !Q_stricmp( SI.games[i]->gamefolder, Cmd_Argv( 1 ))) + if( !Q_stricmp( FI->games[i]->gamefolder, Cmd_Argv( 1 ))) break; } - if( i == SI.numgames ) + if( i == FI->numgames ) { Con_Printf( "%s not exist\n", Cmd_Argv( 1 )); } @@ -343,7 +354,7 @@ void Host_ChangeGame_f( void ) else { const char *arg1 = va( "%s%s", (host.type == HOST_NORMAL) ? "" : "#", Cmd_Argv( 1 )); - const char *arg2 = va( "change game to '%s'", SI.games[i]->title ); + const char *arg2 = va( "change game to '%s'", FI->games[i]->title ); Host_NewInstance( arg1, arg2 ); } @@ -593,24 +604,16 @@ double Host_CalcFPS( void ) } else if( Host_IsLocalGame( )) { - fps = host_maxfps->value; + if( !CVAR_TO_BOOL( gl_vsync )) + fps = host_maxfps->value; } else { - fps = host_maxfps->value; - if( fps == 0.0 ) fps = MAX_FPS; - fps = bound( MIN_FPS, fps, MAX_FPS ); - } - - // probably left part of this condition is redundant :-) - if( host.type != HOST_DEDICATED && Host_IsLocalGame( ) && !CL_IsTimeDemo( )) - { - // ajdust fps for vertical synchronization - if( CVAR_TO_BOOL( gl_vsync )) + if( !CVAR_TO_BOOL( gl_vsync )) { - if( vid_displayfrequency->value != 0.0f ) - fps = vid_displayfrequency->value; - else fps = 60.0; // default + fps = host_maxfps->value; + if( fps == 0.0 ) fps = MAX_FPS; + fps = bound( MIN_FPS, fps, MAX_FPS ); } } #endif @@ -671,11 +674,19 @@ Host_Frame */ void Host_Frame( float time ) { - Host_CheckSleep(); + static qboolean slept = false; // decide the simulation time if( !Host_FilterTime( time )) + { + if( !slept ) + { + Host_CheckSleep(); + slept = true; + } return; + } + slept = false; Host_InputFrame (); // input frame Host_ClientBegin (); // begin client @@ -699,15 +710,6 @@ void GAME_EXPORT Host_Error( const char *error, ... ) static qboolean recursive = false; va_list argptr; - if( host.mouse_visible && !CL_IsInMenu( )) - { - // hide VGUI mouse -#ifdef XASH_SDL - SDL_ShowCursor( 0 ); -#endif - host.mouse_visible = false; - } - va_start( argptr, error ); Q_vsprintf( hosterror1, error, argptr ); va_end( argptr ); @@ -854,10 +856,10 @@ void Host_InitCommon( int argc, char **argv, const char *progname, qboolean bCha if( !Sys_CheckParm( "-disablehelp" ) ) { - if( Sys_CheckParm( "-help" ) || Sys_CheckParm( "-h" ) || Sys_CheckParm( "--help" ) ) - { + if( Sys_CheckParm( "-help" ) || Sys_CheckParm( "-h" ) || Sys_CheckParm( "--help" ) ) + { Sys_PrintUsage(); - } + } } if( !Sys_CheckParm( "-noch" ) ) @@ -865,7 +867,7 @@ void Host_InitCommon( int argc, char **argv, const char *progname, qboolean bCha host.enabledll = !Sys_CheckParm( "-nodll" ); - host.change_game = bChangeGame; + host.change_game = bChangeGame || Sys_CheckParm( "-changegame" ); host.config_executed = false; host.status = HOST_INIT; // initialzation started @@ -936,6 +938,13 @@ void Host_InitCommon( int argc, char **argv, const char *progname, qboolean bCha // member console allowing host.allow_console_init = host.allow_console; + if( Sys_CheckParm( "-bugcomp" )) + { + // add argument check here when we add other levels + // of bugcompatibility + host.bugcomp = BUGCOMP_GOLDSRC; + } + // timeBeginPeriod( 1 ); // a1ba: Do we need this? // NOTE: this message couldn't be passed into game console but it doesn't matter @@ -1022,18 +1031,33 @@ void Host_InitCommon( int argc, char **argv, const char *progname, qboolean bCha if( len && host.rodir[len - 1] == '/' ) host.rodir[len - 1] = 0; - if( !COM_CheckStringEmpty( host.rootdir ) || FS_SetCurrentDirectory( host.rootdir ) != 0 ) + if( !COM_CheckStringEmpty( host.rootdir )) + { + Sys_Error( "Changing working directory failed (empty working directory)\n" ); + return; + } + + FS_LoadProgs(); + + if( FS_SetCurrentDirectory( host.rootdir ) != 0 ) Con_Reportf( "%s is working directory now\n", host.rootdir ); else Sys_Error( "Changing working directory to %s failed.\n", host.rootdir ); + FS_Init(); + Sys_InitLog(); + // print bugcompatibility level here, after log was initialized + if( host.bugcomp == BUGCOMP_GOLDSRC ) + { + Con_Printf( "^3BUGCOMP^7: GoldSrc bug-compatibility enabled\n" ); + } + Cmd_AddCommand( "exec", Host_Exec_f, "execute a script file" ); Cmd_AddCommand( "memlist", Host_MemStats_f, "prints memory pool information" ); Cmd_AddRestrictedCommand( "userconfigd", Host_Userconfigd_f, "execute all scripts from userconfig.d" ); - FS_Init(); Image_Init(); Sound_Init(); @@ -1043,8 +1067,16 @@ void Host_InitCommon( int argc, char **argv, const char *progname, qboolean bCha #endif FS_LoadGameInfo( NULL ); + + if( FS_FileExists( va( "%s.rc", SI.basedirName ), false )) + Q_strncpy( SI.rcName, SI.basedirName, sizeof( SI.rcName )); // e.g. valve.rc + else Q_strncpy( SI.rcName, SI.exeName, sizeof( SI.rcName )); // e.g. quake.rc + Q_strncpy( host.gamefolder, GI->gamefolder, sizeof( host.gamefolder )); + Image_CheckPaletteQ1 (); + Host_InitDecals (); // reload decals + // DEPRECATED: by FWGS fork #if 0 if( GI->secure ) diff --git a/engine/common/hpak.c b/engine/common/hpak.c index 0da8e8a7..9a179439 100644 --- a/engine/common/hpak.c +++ b/engine/common/hpak.c @@ -14,7 +14,7 @@ GNU General Public License for more details. */ #include "common.h" -#include "filesystem.h" +#include "hpak.h" #define HPAK_MAX_ENTRIES 0x8000 #define HPAK_MIN_SIZE (1 * 1024) @@ -49,6 +49,18 @@ const char *HPAK_TypeFromIndex( int type ) return "?"; } +static inline void HPAK_ResourceToCompat( dresource_t *dest, resource_t *src ) +{ + memcpy( dest, src, sizeof( *dest )); + dest->pNext = dest->pPrev = 0xDEADBEEF; +} + +static inline void HPAK_ResourceFromCompat( resource_t *dest, dresource_t *src ) +{ + memcpy( dest, src, sizeof( *src )); + dest->pNext = dest->pPrev = (void*)0xDEADBEEF; +} + static void HPAK_AddToQueue( const char *name, resource_t *pResource, void *data, file_t *f ) { hash_pack_queue_t *p; @@ -89,6 +101,7 @@ void HPAK_CreatePak( const char *filename, resource_t *pResource, byte *pData, f byte md5[16]; file_t *fout; MD5Context_t ctx; + dresource_t dresource; if( !COM_CheckString( filename )) return; @@ -145,7 +158,7 @@ void HPAK_CreatePak( const char *filename, resource_t *pResource, byte *pData, f hash_pack_info.count = 1; hash_pack_info.entries = Z_Malloc( sizeof( hpak_lump_t )); - hash_pack_info.entries[0].resource = *pResource; + HPAK_ResourceToCompat( &hash_pack_info.entries[0].resource, pResource ); hash_pack_info.entries[0].filepos = FS_Tell( fout ); hash_pack_info.entries[0].disksize = pResource->nDownloadSize; @@ -181,7 +194,7 @@ static qboolean HPAK_FindResource( hpak_info_t *hpk, byte *hash, resource_t *pRe if( !memcmp( hpk->entries[i].resource.rgucMD5_hash, hash, 16 )) { if( pResource ) - *pResource = hpk->entries[i].resource; + HPAK_ResourceFromCompat( pResource, &hpk->entries[i].resource ); return true; } } @@ -330,7 +343,7 @@ void HPAK_AddLump( qboolean bUseQueue, const char *name, resource_t *pResource, memset( pCurrentEntry, 0, sizeof( hpak_lump_t )); FS_Seek( file_dst, hash_pack_header.infotableofs, SEEK_SET ); - pCurrentEntry->resource = *pResource; + HPAK_ResourceToCompat( &pCurrentEntry->resource, pResource ); pCurrentEntry->filepos = FS_Tell( file_dst ); pCurrentEntry->disksize = pResource->nDownloadSize; @@ -370,7 +383,7 @@ static qboolean HPAK_Validate( const char *filename, qboolean quiet ) int i, num_lumps; MD5Context_t MD5_Hash; string pakname; - resource_t *pRes; + dresource_t *pRes; byte md5[16]; if( quiet ) HPAK_FlushHostQueue(); @@ -402,7 +415,7 @@ static qboolean HPAK_Validate( const char *filename, qboolean quiet ) FS_Seek( f, hdr.infotableofs, SEEK_SET ); FS_Read( f, &num_lumps, sizeof( num_lumps )); - if( num_lumps < 1 || num_lumps > MAX_FILES_IN_WAD ) + if( num_lumps < 1 || num_lumps > HPAK_MAX_ENTRIES ) { Con_DPrintf( S_ERROR "HPAK_ValidatePak: %s has too many lumps %u.\n", pakname, num_lumps ); FS_Close( f ); @@ -621,7 +634,7 @@ static qboolean HPAK_ResourceForIndex( const char *filename, int index, resource directory.entries = Z_Malloc( sizeof( hpak_lump_t ) * directory.count ); FS_Read( f, directory.entries, sizeof( hpak_lump_t ) * directory.count ); - *pResource = directory.entries[index-1].resource; + HPAK_ResourceFromCompat( pResource, &directory.entries[index-1].resource ); Z_Free( directory.entries ); FS_Close( f ); @@ -647,7 +660,7 @@ qboolean HPAK_GetDataPointer( const char *filename, resource_t *pResource, byte for( p = gp_hpak_queue; p != NULL; p = p->next ) { - if( !Q_stricmp(p->name, filename ) && !memcmp( p->resource.rgucMD5_hash, pResource->rgucMD5_hash, 16 )) + if( !Q_stricmp( p->name, filename ) && !memcmp( p->resource.rgucMD5_hash, pResource->rgucMD5_hash, 16 )) { if( buffer ) { @@ -702,11 +715,13 @@ qboolean HPAK_GetDataPointer( const char *filename, resource_t *pResource, byte { entry = &directory.entries[i]; - if( !memcmp( entry->resource.rgucMD5_hash, pResource->rgucMD5_hash, 16 )) + if( entry->filepos > 0 && + entry->disksize > 0 && + !memcmp( entry->resource.rgucMD5_hash, pResource->rgucMD5_hash, 16 )) { FS_Seek( f, entry->filepos, SEEK_SET ); - if( buffer && entry->disksize > 0 ) + if( buffer ) { tmpbuf = Z_Malloc( entry->disksize ); FS_Read( f, tmpbuf, entry->disksize ); diff --git a/engine/common/hpak.h b/engine/common/hpak.h new file mode 100644 index 00000000..5e71eec8 --- /dev/null +++ b/engine/common/hpak.h @@ -0,0 +1,89 @@ +/* +hpak.c - custom user package to send other clients +Copyright (C) 2010 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ +#ifndef HPAK_H +#define HPAK_H + +#include "custom.h" + +/* +======================================================================== +.HPK archive format (Hash PAK - HPK) + +List of compressed files, that can be identify only by TYPE_* + + +header: dwadinfo_t[dwadinfo_t] +file_1: byte[dwadinfo_t[num]->disksize] +file_2: byte[dwadinfo_t[num]->disksize] +file_3: byte[dwadinfo_t[num]->disksize] +... +file_n: byte[dwadinfo_t[num]->disksize] +infotable dlumpinfo_t[dwadinfo_t->numlumps] +======================================================================== +*/ + +#define IDHPAKHEADER (('K'<<24)+('A'<<16)+('P'<<8)+'H') // little-endian "HPAK" +#define IDHPAK_VERSION 1 + +// a1ba: because Valve for some reason writes resource_t to file +// I had to make it crossplatform version +#pragma pack( push, 8 ) +typedef struct dresource_s +{ + char szFileName[64]; /* 0 64 */ + /* --- cacheline 1 boundary (64 bytes) --- */ + resourcetype_t type; /* 64 4 */ + int nIndex; /* 68 4 */ + int nDownloadSize; /* 72 4 */ + unsigned char ucFlags; /* 76 1 */ + unsigned char rgucMD5_hash[16]; /* 77 16 */ + unsigned char playernum; /* 93 1 */ + unsigned char rguc_reserved[32]; /* 94 32 */ + + /* XXX 2 bytes hole, try to pack */ + + /* --- cacheline 2 boundary (128 bytes) --- */ + uint32_t pNext; /* 128 4 */ + uint32_t pPrev; /* 132 4 */ + + /* size: 136, cachelines: 3, members: 10 */ + /* sum members: 134, holes: 1, sum holes: 2 */ + /* last cacheline: 8 bytes */ +} dresource_t; +#pragma pack( pop ) + +STATIC_ASSERT( sizeof( dresource_t ) == 136, "invalid dresource_t size, HPAKs won't be compatible (no custom logo in multiplayer!)" ); + +typedef struct +{ + int ident; // should be equal HPAK + int version; + int infotableofs; +} hpak_header_t; + +typedef struct +{ + dresource_t resource; + int filepos; + int disksize; +} hpak_lump_t; + +typedef struct +{ + int count; + hpak_lump_t *entries; // variable sized. +} hpak_info_t; + +#endif // HPAK_H diff --git a/engine/common/imagelib/img_png.c b/engine/common/imagelib/img_png.c index 9569a241..de6ed399 100644 --- a/engine/common/imagelib/img_png.c +++ b/engine/common/imagelib/img_png.c @@ -13,7 +13,6 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ -#define MINIZ_HEADER_FILE_ONLY #include "miniz.h" #include "imagelib.h" #include "xash3d_mathlib.h" @@ -27,6 +26,8 @@ GNU General Public License for more details. static const char png_sign[] = {0x89, 'P', 'N', 'G', '\r', '\n', 0x1a, '\n'}; static const char ihdr_sign[] = {'I', 'H', 'D', 'R'}; +static const char trns_sign[] = {'t', 'R', 'N', 'S'}; +static const char plte_sign[] = {'P', 'L', 'T', 'E'}; static const char idat_sign[] = {'I', 'D', 'A', 'T'}; static const char iend_sign[] = {'I', 'E', 'N', 'D'}; static const int iend_crc32 = 0xAE426082; @@ -40,9 +41,10 @@ qboolean Image_LoadPNG( const char *name, const byte *buffer, fs_offset_t filesi { int ret; short p, a, b, c, pa, pb, pc; - byte *buf_p, *pixbuf, *raw, *prior, *idat_buf = NULL, *uncompressed_buffer = NULL, *rowend; - uint chunk_len, crc32, crc32_check, oldsize = 0, newsize = 0, rowsize; - uint uncompressed_size, pixel_size, i, y, filter_type, chunk_sign; + byte *buf_p, *pixbuf, *raw, *prior, *idat_buf = NULL, *uncompressed_buffer = NULL; + byte *pallete = NULL, *trns = NULL; + uint chunk_len, trns_len, crc32, crc32_check, oldsize = 0, newsize = 0, rowsize; + uint uncompressed_size, pixel_size, pixel_count, i, y, filter_type, chunk_sign, r_alpha, g_alpha, b_alpha; qboolean has_iend_chunk = false; z_stream stream = {0}; png_t png_hdr; @@ -68,7 +70,7 @@ qboolean Image_LoadPNG( const char *name, const byte *buffer, fs_offset_t filesi // check IHDR chunk length (valid value - 13) if( png_hdr.ihdr_len != sizeof( png_ihdr_t ) ) { - Con_DPrintf( S_ERROR "Image_LoadPNG: Invalid IHDR chunk size (%s)\n", name ); + Con_DPrintf( S_ERROR "Image_LoadPNG: Invalid IHDR chunk size %u (%s)\n", png_hdr.ihdr_len, name ); return false; } @@ -85,7 +87,7 @@ qboolean Image_LoadPNG( const char *name, const byte *buffer, fs_offset_t filesi if( png_hdr.ihdr_chunk.height == 0 || png_hdr.ihdr_chunk.width == 0 ) { - Con_DPrintf( S_ERROR "Image_LoadPNG: Invalid image size %dx%d (%s)\n", png_hdr.ihdr_chunk.width, png_hdr.ihdr_chunk.height, name ); + Con_DPrintf( S_ERROR "Image_LoadPNG: Invalid image size %ux%u (%s)\n", png_hdr.ihdr_chunk.width, png_hdr.ihdr_chunk.height, name ); return false; } @@ -95,21 +97,25 @@ qboolean Image_LoadPNG( const char *name, const byte *buffer, fs_offset_t filesi return false; } - if( png_hdr.ihdr_chunk.colortype != PNG_CT_RGB && png_hdr.ihdr_chunk.colortype != PNG_CT_RGBA ) + if( !( png_hdr.ihdr_chunk.colortype == PNG_CT_RGB + || png_hdr.ihdr_chunk.colortype == PNG_CT_RGBA + || png_hdr.ihdr_chunk.colortype == PNG_CT_GREY + || png_hdr.ihdr_chunk.colortype == PNG_CT_ALPHA + || png_hdr.ihdr_chunk.colortype == PNG_CT_PALLETE ) ) { - Con_DPrintf( S_WARN "Image_LoadPNG: Only 8-bit RGB and RGBA images is supported (%s)\n", name ); + Con_DPrintf( S_WARN "Image_LoadPNG: Unknown color type %u (%s)\n", png_hdr.ihdr_chunk.colortype, name ); return false; } if( png_hdr.ihdr_chunk.compression > 0 ) { - Con_DPrintf( S_ERROR "Image_LoadPNG: Unknown compression method (%s)\n", name ); + Con_DPrintf( S_ERROR "Image_LoadPNG: Unknown compression method %u (%s)\n", png_hdr.ihdr_chunk.compression, name ); return false; } if( png_hdr.ihdr_chunk.filter > 0 ) { - Con_DPrintf( S_ERROR "Image_LoadPNG: Unknown filter type (%s)\n", name ); + Con_DPrintf( S_ERROR "Image_LoadPNG: Unknown filter type %u (%s)\n", png_hdr.ihdr_chunk.filter, name ); return false; } @@ -121,7 +127,7 @@ qboolean Image_LoadPNG( const char *name, const byte *buffer, fs_offset_t filesi if( png_hdr.ihdr_chunk.interlace > 0 ) { - Con_DPrintf( S_ERROR "Image_LoadPNG: Unknown interlacing type (%s)\n", name ); + Con_DPrintf( S_ERROR "Image_LoadPNG: Unknown interlacing type %u (%s)\n", png_hdr.ihdr_chunk.interlace, name ); return false; } @@ -159,8 +165,19 @@ qboolean Image_LoadPNG( const char *name, const byte *buffer, fs_offset_t filesi // move pointer buf_p += sizeof( chunk_sign ); + // find transparency + if( !memcmp( buf_p, trns_sign, sizeof( trns_sign ) ) ) + { + trns = buf_p + sizeof( trns_sign ); + trns_len = chunk_len; + } + // find pallete for indexed image + else if( !memcmp( buf_p, plte_sign, sizeof( plte_sign ) ) ) + { + pallete = buf_p + sizeof( plte_sign ); + } // get all IDAT chunks data - if( !memcmp( buf_p, idat_sign, sizeof( idat_sign ) ) ) + else if( !memcmp( buf_p, idat_sign, sizeof( idat_sign ) ) ) { newsize = oldsize + chunk_len; idat_buf = (byte *)Mem_Realloc( host.imagepool, idat_buf, newsize ); @@ -194,6 +211,13 @@ qboolean Image_LoadPNG( const char *name, const byte *buffer, fs_offset_t filesi buf_p += sizeof( crc32 ); } + if( png_hdr.ihdr_chunk.colortype == PNG_CT_PALLETE && !pallete ) + { + Con_DPrintf( S_ERROR "Image_LoadPNG: PLTE chunk not found (%s)\n", name ); + Mem_Free( idat_buf ); + return false; + } + if( !has_iend_chunk ) { Con_DPrintf( S_ERROR "Image_LoadPNG: IEND chunk not found (%s)\n", name ); @@ -203,7 +227,7 @@ qboolean Image_LoadPNG( const char *name, const byte *buffer, fs_offset_t filesi if( chunk_len != 0 ) { - Con_DPrintf( S_ERROR "Image_LoadPNG: IEND chunk has wrong size (%s)\n", name ); + Con_DPrintf( S_ERROR "Image_LoadPNG: IEND chunk has wrong size %u (%s)\n", chunk_len, name ); Mem_Free( idat_buf ); return false; } @@ -216,6 +240,13 @@ qboolean Image_LoadPNG( const char *name, const byte *buffer, fs_offset_t filesi switch( png_hdr.ihdr_chunk.colortype ) { + case PNG_CT_GREY: + case PNG_CT_PALLETE: + pixel_size = 1; + break; + case PNG_CT_ALPHA: + pixel_size = 2; + break; case PNG_CT_RGB: pixel_size = 3; break; @@ -231,10 +262,13 @@ qboolean Image_LoadPNG( const char *name, const byte *buffer, fs_offset_t filesi image.type = PF_RGBA_32; // always exctracted to 32-bit buffer image.width = png_hdr.ihdr_chunk.width; image.height = png_hdr.ihdr_chunk.height; - image.size = image.height * image.width * 4; - image.flags |= IMAGE_HAS_COLOR; + pixel_count = image.height * image.width; + image.size = pixel_count * 4; - if( png_hdr.ihdr_chunk.colortype == PNG_CT_RGBA ) + if( png_hdr.ihdr_chunk.colortype & PNG_CT_RGB ) + image.flags |= IMAGE_HAS_COLOR; + + if( trns || ( png_hdr.ihdr_chunk.colortype & PNG_CT_ALPHA ) ) image.flags |= IMAGE_HAS_ALPHA; image.depth = 1; @@ -276,7 +310,7 @@ qboolean Image_LoadPNG( const char *name, const byte *buffer, fs_offset_t filesi raw = uncompressed_buffer; - if( png_hdr.ihdr_chunk.colortype == PNG_CT_RGB ) + if( png_hdr.ihdr_chunk.colortype != PNG_CT_RGBA ) prior = pixbuf = raw; filter_type = *raw++; @@ -378,23 +412,73 @@ qboolean Image_LoadPNG( const char *name, const byte *buffer, fs_offset_t filesi prior = pixbuf; } - // convert RGB-to-RGBA - if( png_hdr.ihdr_chunk.colortype == PNG_CT_RGB ) - { - pixbuf = image.rgba; - raw = uncompressed_buffer; + pixbuf = image.rgba; + raw = uncompressed_buffer; - for( y = 0; y < image.height; y++ ) + switch( png_hdr.ihdr_chunk.colortype ) + { + case PNG_CT_RGB: + if( trns ) { - rowend = raw + rowsize; - for( ; raw < rowend; raw += pixel_size ) - { - *pixbuf++ = raw[0]; - *pixbuf++ = raw[1]; - *pixbuf++ = raw[2]; - *pixbuf++ = 0xFF; - } + r_alpha = trns[0] << 8 | trns[1]; + g_alpha = trns[2] << 8 | trns[3]; + b_alpha = trns[4] << 8 | trns[5]; } + + for( y = 0; y < pixel_count; y++, raw += pixel_size ) + { + *pixbuf++ = raw[0]; + *pixbuf++ = raw[1]; + *pixbuf++ = raw[2]; + + if( trns && r_alpha == raw[0] + && g_alpha == raw[1] + && b_alpha == raw[2] ) + *pixbuf++ = 0; + else + *pixbuf++ = 0xFF; + } + break; + case PNG_CT_GREY: + if( trns ) + r_alpha = trns[0] << 8 | trns[1]; + + for( y = 0; y < pixel_count; y++, raw += pixel_size ) + { + *pixbuf++ = raw[0]; + *pixbuf++ = raw[0]; + *pixbuf++ = raw[0]; + + if( trns && r_alpha == raw[0] ) + *pixbuf++ = 0; + else + *pixbuf++ = 0xFF; + } + break; + case PNG_CT_ALPHA: + for( y = 0; y < pixel_count; y++, raw += pixel_size ) + { + *pixbuf++ = raw[0]; + *pixbuf++ = raw[0]; + *pixbuf++ = raw[0]; + *pixbuf++ = raw[1]; + } + break; + case PNG_CT_PALLETE: + for( y = 0; y < pixel_count; y++, raw += pixel_size ) + { + *pixbuf++ = pallete[raw[0] + 2]; + *pixbuf++ = pallete[raw[0] + 1]; + *pixbuf++ = pallete[raw[0] + 0]; + + if( trns && raw[0] < trns_len ) + *pixbuf++ = trns[raw[0]]; + else + *pixbuf++ = 0xFF; + } + break; + default: + break; } Mem_Free( uncompressed_buffer ); diff --git a/engine/common/imagelib/img_png.h b/engine/common/imagelib/img_png.h index 3477805e..ab1255df 100644 --- a/engine/common/imagelib/img_png.h +++ b/engine/common/imagelib/img_png.h @@ -25,8 +25,8 @@ GNU General Public License for more details. enum png_colortype { PNG_CT_GREY, - PNG_CT_PALLETE = BIT(0), PNG_CT_RGB = BIT(1), + PNG_CT_PALLETE = (PNG_CT_RGB|BIT(0)), PNG_CT_ALPHA = BIT(2), PNG_CT_RGBA = (PNG_CT_RGB|PNG_CT_ALPHA) }; diff --git a/engine/common/imagelib/img_utils.c b/engine/common/imagelib/img_utils.c index 5ff568cf..ce2e57bd 100644 --- a/engine/common/imagelib/img_utils.c +++ b/engine/common/imagelib/img_utils.c @@ -96,8 +96,8 @@ static const loadpixformat_t load_null[] = static const loadpixformat_t load_game[] = { { "%s%s.%s", "dds", Image_LoadDDS, IL_HINT_NO }, // dds for world and studio models -{ "%s%s.%s", "tga", Image_LoadTGA, IL_HINT_NO }, // hl vgui menus { "%s%s.%s", "bmp", Image_LoadBMP, IL_HINT_NO }, // WON menu images +{ "%s%s.%s", "tga", Image_LoadTGA, IL_HINT_NO }, // hl vgui menus { "%s%s.%s", "png", Image_LoadPNG, IL_HINT_NO }, // NightFire 007 menus { "%s%s.%s", "mip", Image_LoadMIP, IL_HINT_NO }, // hl textures from wad or buffer { "%s%s.%s", "mdl", Image_LoadMDL, IL_HINT_HL }, // hl studio model skins diff --git a/engine/common/launcher.c b/engine/common/launcher.c index 7f85ff06..598c1c17 100644 --- a/engine/common/launcher.c +++ b/engine/common/launcher.c @@ -23,66 +23,44 @@ GNU General Public License for more details. #if XASH_EMSCRIPTEN #include -#endif - -#if XASH_WIN32 -#define XASH_NOCONHOST 1 -#endif - -static char szGameDir[128]; // safe place to keep gamedir -static int g_iArgc; -static char **g_pszArgv; - -void Launcher_ChangeGame( const char *progname ) +#elif XASH_WIN32 +extern "C" { +// Enable NVIDIA High Performance Graphics while using Integrated Graphics. +__declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; + +// Enable AMD High Performance Graphics while using Integrated Graphics. +__declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; +} +#endif + +#define E_GAME "XASH3D_GAME" // default env dir to start from +#ifndef XASH_GAMEDIR +#define XASH_GAMEDIR "valve" +#endif + +static char szGameDir[128]; // safe place to keep gamedir +static int szArgc; +static char **szArgv; + +static void Sys_ChangeGame( const char *progname ) +{ + // a1ba: may never be called within engine + // if platform supports execv() function Q_strncpy( szGameDir, progname, sizeof( szGameDir ) - 1 ); Host_Shutdown( ); - exit( Host_Main( g_iArgc, g_pszArgv, szGameDir, 1, &Launcher_ChangeGame ) ); + exit( Host_Main( szArgc, szArgv, szGameDir, 1, &Sys_ChangeGame ) ); } -#if XASH_NOCONHOST -#include -#include // CommandLineToArgvW -int __stdcall WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR cmdLine, int nShow) +_inline int Sys_Start( void ) { - int szArgc; - char **szArgv; - LPWSTR* lpArgv = CommandLineToArgvW(GetCommandLineW(), &szArgc); - int i = 0; + int ret; + const char *game = getenv( E_GAME ); - szArgv = (char**)malloc(szArgc*sizeof(char*)); - for (; i < szArgc; ++i) - { - size_t size = wcslen(lpArgv[i]) + 1; - szArgv[i] = (char*)malloc(size); - wcstombs(szArgv[i], lpArgv[i], size); - } - szArgv[i] = NULL; - - LocalFree(lpArgv); - - main( szArgc, szArgv ); - - for( i = 0; i < szArgc; ++i ) - free( szArgv[i] ); - free( szArgv ); -} -#endif -int main( int argc, char** argv ) -{ - char gamedir_buf[32] = ""; - const char *gamedir = getenv( "XASH3D_GAMEDIR" ); - - if( !COM_CheckString( gamedir ) ) - { - gamedir = "valve"; - } - else - { - Q_strncpy( gamedir_buf, gamedir, 32 ); - gamedir = gamedir_buf; - } + if( !game ) + game = XASH_GAMEDIR; + Q_strncpy( szGameDir, game, sizeof( szGameDir )); #if XASH_EMSCRIPTEN #ifdef EMSCRIPTEN_LIB_FS // For some unknown reason emscripten refusing to load libraries later @@ -98,17 +76,56 @@ int main( int argc, char** argv ) FS.chdir('/xash'); }catch(e){};); #endif -#endif - - g_iArgc = argc; - g_pszArgv = argv; -#if XASH_IOS +#elif XASH_IOS { void IOS_LaunchDialog( void ); IOS_LaunchDialog(); } #endif - return Host_Main( g_iArgc, g_pszArgv, gamedir, 0, &Launcher_ChangeGame ); + + ret = Host_Main( szArgc, szArgv, game, 0, Sys_ChangeGame ); + + return ret; } +#if !XASH_WIN32 +int main( int argc, char **argv ) +{ + szArgc = argc; + szArgv = argv; + + return Sys_Start(); +} +#else +int __stdcall WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR cmdLine, int nShow) +{ + LPWSTR* lpArgv; + int ret, i; + + lpArgv = CommandLineToArgvW( GetCommandLineW(), &szArgc ); + szArgv = ( char** )malloc( (szArgc + 1) * sizeof( char* )); + + for( i = 0; i < szArgc; ++i ) + { + size_t size = wcslen(lpArgv[i]) + 1; + + // just in case, allocate some more memory + szArgv[i] = ( char * )malloc( size * sizeof( wchar_t )); + wcstombs( szArgv[i], lpArgv[i], size ); + } + szArgv[szArgc] = 0; + + LocalFree( lpArgv ); + + ret = Sys_Start(); + + for( ; i < szArgc; ++i ) + free( szArgv[i] ); + free( szArgv ); + + return ret; +} +#endif // XASH_WIN32 + + #endif diff --git a/engine/common/lib_common.c b/engine/common/lib_common.c index e18161db..cdd3f8b6 100644 --- a/engine/common/lib_common.c +++ b/engine/common/lib_common.c @@ -86,6 +86,38 @@ const char *COM_OffsetNameForFunction( void *function ) return sname; } +dll_user_t *FS_FindLibrary( const char *dllname, qboolean directpath ) +{ + dll_user_t *p; + fs_dllinfo_t dllInfo; + + // no fs loaded yet, but let engine find fs + if( !g_fsapi.FindLibrary ) + { + p = Mem_Calloc( host.mempool, sizeof( dll_user_t )); + Q_strncpy( p->shortPath, dllname, sizeof( p->shortPath )); + Q_strncpy( p->fullPath, dllname, sizeof( p->fullPath )); + Q_strncpy( p->dllName, dllname, sizeof( p->dllName )); + + return p; + } + + // fs can't find library + if( !g_fsapi.FindLibrary( dllname, directpath, &dllInfo )) + return NULL; + + // NOTE: for libraries we not fail even if search is NULL + // let the OS find library himself + p = Mem_Calloc( host.mempool, sizeof( dll_user_t )); + Q_strncpy( p->shortPath, dllInfo.shortPath, sizeof( p->shortPath )); + Q_strncpy( p->fullPath, dllInfo.fullPath, sizeof( p->fullPath )); + Q_strncpy( p->dllName, dllname, sizeof( p->dllName )); + p->custom_loader = dllInfo.custom_loader; + p->encrypted = dllInfo.encrypted; + + return p; +} + /* ============================================================================= diff --git a/engine/common/masterlist.c b/engine/common/masterlist.c index 192372e7..bb2edcc8 100644 --- a/engine/common/masterlist.c +++ b/engine/common/masterlist.c @@ -21,6 +21,7 @@ typedef struct master_s qboolean sent; qboolean save; string address; + netadr_t adr; // temporary, rewritten after each send } master_t; struct masterlist_s @@ -44,31 +45,32 @@ qboolean NET_SendToMasters( netsrc_t sock, size_t len, const void *data ) for( list = ml.list; list; list = list->next ) { - netadr_t adr; int res; if( list->sent ) continue; - res = NET_StringToAdrNB( list->address, &adr ); + res = NET_StringToAdrNB( list->address, &list->adr ); if( !res ) { Con_Reportf( "Can't resolve adr: %s\n", list->address ); list->sent = true; + list->adr.type = NA_UNUSED; continue; } if( res == 2 ) { list->sent = false; + list->adr.type = NA_UNUSED; wait = true; continue; } list->sent = true; - NET_SendPacket( sock, len, data, adr ); + NET_SendPacket( sock, len, data, list->adr ); } if( !wait ) @@ -85,6 +87,25 @@ qboolean NET_SendToMasters( netsrc_t sock, size_t len, const void *data ) return wait; } +/* +======================== +NET_IsMasterAdr + +======================== +*/ +qboolean NET_IsMasterAdr( netadr_t adr ) +{ + master_t *master; + + for( master = ml.list; master; master = master->next ) + { + if( NET_CompareAdr( adr, master->adr )) + return true; + } + + return false; +} + /* ======================== NET_AddMaster @@ -107,6 +128,7 @@ static void NET_AddMaster( const char *addr, qboolean save ) master->sent = false; master->save = save; master->next = NULL; + master->adr.type = NA_UNUSED; // link in if( last ) @@ -161,7 +183,10 @@ static void NET_ListMasters_f( void ) for( i = 1, list = ml.list; list; i++, list = list->next ) { - Msg( "%d\t%s\n", i, list->address ); + Msg( "%d\t%s", i, list->address ); + if( list->adr.type != NA_UNUSED ) + Msg( "\t%s\n", NET_AdrToString( list->adr )); + else Msg( "\n" ); } } diff --git a/engine/common/mod_local.h b/engine/common/mod_local.h index 0a794e5f..5c7e579d 100644 --- a/engine/common/mod_local.h +++ b/engine/common/mod_local.h @@ -143,7 +143,6 @@ model_t *Mod_ForName( const char *name, qboolean crash, qboolean trackCRC ); qboolean Mod_ValidateCRC( const char *name, CRC32_t crc ); void Mod_NeedCRC( const char *name, qboolean needCRC ); void Mod_FreeUnused( void ); -model_t *Mod_Handle( int handle ); // // mod_bmodel.c diff --git a/engine/common/model.c b/engine/common/model.c index 1b75a2df..ad8e80ca 100644 --- a/engine/common/model.c +++ b/engine/common/model.c @@ -600,20 +600,3 @@ void Mod_NeedCRC( const char *name, qboolean needCRC ) if( needCRC ) SetBits( p->flags, FCRC_SHOULD_CHECKSUM ); else ClearBits( p->flags, FCRC_SHOULD_CHECKSUM ); } - - -/* -================== -Mod_Handle - -================== -*/ -model_t *GAME_EXPORT Mod_Handle( int handle ) -{ - if( handle < 0 || handle >= MAX_MODELS ) - { - Con_Reportf( "Mod_Handle: bad handle #%i\n", handle ); - return NULL; - } - return &mod_known[handle]; -} diff --git a/engine/common/net_ws.c b/engine/common/net_ws.c index 79dce12e..ae6c152c 100644 --- a/engine/common/net_ws.c +++ b/engine/common/net_ws.c @@ -35,7 +35,7 @@ GNU General Public License for more details. #define MAX_ROUTEABLE_PACKET 1400 #define SPLITPACKET_MIN_SIZE 508 // RFC 791: 576(min ip packet) - 60 (ip header) - 8 (udp header) #define SPLITPACKET_MAX_SIZE 64000 -#define NET_MAX_FRAGMENTS ( NET_MAX_FRAGMENT / (SPLITPACKET_MIN_SIZE - sizeof( SPLITPACKET )) ) +#define NET_MAX_FRAGMENTS ( NET_MAX_FRAGMENT / (SPLITPACKET_MIN_SIZE - sizeof( SPLITPACKET ))) // ff02:1 static const uint8_t k_ipv6Bytes_LinkLocalAllNodes[16] = @@ -219,7 +219,7 @@ void NET_NetadrToIP6Bytes( uint8_t *ip6, const netadr_t *adr ) void NET_IP6BytesToNetadr( netadr_t *adr, const uint8_t *ip6 ) { #if XASH_LITTLE_ENDIAN - memcpy( adr->ip6, ip6, sizeof( adr->ip6 ) ); + memcpy( adr->ip6, ip6, sizeof( adr->ip6 )); #elif XASH_BIG_ENDIAN memcpy( adr->ip6_0, ip6, sizeof( adr->ip6_0 )); memcpy( adr->ip6_2, ip6 + sizeof( adr->ip6_0 ), sizeof( adr->ip6_2 )); @@ -356,7 +356,7 @@ qboolean NET_GetHostByName( const char *hostname, int family, struct sockaddr_st #endif } -#if !defined XASH_NO_ASYNC_NS_RESOLVE && ( XASH_WIN32 || !(XASH_EMSCRIPTEN || XASH_DOS4GW) ) +#if !defined XASH_NO_ASYNC_NS_RESOLVE && ( XASH_WIN32 || !( XASH_EMSCRIPTEN || XASH_DOS4GW )) #define CAN_ASYNC_NS_RESOLVE #endif @@ -516,7 +516,7 @@ static int NET_StringToSockaddr( const char *s, struct sockaddr_storage *sadr, q return 2; } - if( !Q_strcmp( copy, nsthread.hostname ) ) + if( !Q_strcmp( copy, nsthread.hostname )) { ret = nsthread.result; @@ -534,7 +534,7 @@ static int NET_StringToSockaddr( const char *s, struct sockaddr_storage *sadr, q nsthread.busy = true; mutex_unlock( &nsthread.mutexres ); - if( create_thread( NET_ThreadStart ) ) + if( create_thread( NET_ThreadStart )) { asyncfailed = false; return 2; @@ -874,17 +874,20 @@ qboolean NET_IsReservedAdr( netadr_t a ) if( a.type6 == NA_IP6 ) { + uint8_t ip6[16]; + + NET_NetadrToIP6Bytes( ip6, &a ); + // Private addresses, fc00::/7 // Range is fc00:: to fdff:ffff:etc - if ( a.ipx[0] >= 0xFC && a.ipx[1] <= 0xFD ) + if( ip6[0] >= 0xFC && ip6[1] <= 0xFD ) { return true; } // Link-local fe80::/10 // Range is fe80:: to febf:: - if ( a.ipx[0] == 0xFE - && ( a.ipx[1] >= 0x80 && a.ipx[1] <= 0xBF ) ) + if( ip6[0] == 0xFE && ( ip6[1] >= 0x80 && ip6[1] <= 0xBF )) { return true; } @@ -950,7 +953,7 @@ qboolean NET_StringToAdrEx( const char *string, netadr_t *adr, int family ) memset( adr, 0, sizeof( netadr_t )); - if( !Q_stricmp( string, "localhost" ) || !Q_stricmp( string, "loopback" ) ) + if( !Q_stricmp( string, "localhost" ) || !Q_stricmp( string, "loopback" )) { adr->type = NA_LOOPBACK; return true; @@ -975,7 +978,7 @@ int NET_StringToAdrNB( const char *string, netadr_t *adr ) int res; memset( adr, 0, sizeof( netadr_t )); - if( !Q_stricmp( string, "localhost" ) || !Q_stricmp( string, "loopback" ) ) + if( !Q_stricmp( string, "localhost" ) || !Q_stricmp( string, "loopback" )) { adr->type = NA_LOOPBACK; return true; @@ -1393,13 +1396,13 @@ static qboolean NET_QueuePacket( netsrc_t sock, netadr_t *from, byte *data, size memcpy( data, buf, ret ); *length = ret; #if !XASH_DEDICATED - if( CL_LegacyMode() ) + if( CL_LegacyMode( )) return NET_LagPacket( true, sock, from, length, data ); // check for split message if( sock == NS_CLIENT && *(int *)data == NET_HEADER_SPLITPACKET ) { - return NET_GetLong( data, ret, length, CL_GetSplitSize() ); + return NET_GetLong( data, ret, length, CL_GetSplitSize( )); } #endif // lag the packet, if needed @@ -1572,7 +1575,7 @@ void NET_SendPacketEx( netsrc_t sock, size_t length, const void *data, netadr_t if( err == WSAEADDRNOTAVAIL && ( to.type == NA_BROADCAST || to.type6 == NA_MULTICAST_IP6 )) return; - if( Host_IsDedicated() ) + if( Host_IsDedicated( )) { Con_DPrintf( S_ERROR "NET_SendPacket: %s to %s\n", NET_ErrorString(), NET_AdrToString( to )); } @@ -1678,7 +1681,7 @@ static int NET_IPSocket( const char *net_iface, int port, int family ) else if( family == AF_INET ) pfamily = PF_INET; - if( NET_IsSocketError(( net_socket = socket( pfamily, SOCK_DGRAM, IPPROTO_UDP )) ) ) + if( NET_IsSocketError(( net_socket = socket( pfamily, SOCK_DGRAM, IPPROTO_UDP )))) { err = WSAGetLastError(); if( err != WSAEAFNOSUPPORT ) @@ -1686,7 +1689,7 @@ static int NET_IPSocket( const char *net_iface, int port, int family ) return INVALID_SOCKET; } - if( NET_IsSocketError( ioctlsocket( net_socket, FIONBIO, (void*)&_true ) ) ) + if( NET_IsSocketError( ioctlsocket( net_socket, FIONBIO, (void*)&_true ))) { struct timeval timeout; @@ -1697,12 +1700,12 @@ static int NET_IPSocket( const char *net_iface, int port, int family ) } // make it broadcast capable - if( NET_IsSocketError( setsockopt( net_socket, SOL_SOCKET, SO_BROADCAST, (char *)&_true, sizeof( _true ) ) ) ) + if( NET_IsSocketError( setsockopt( net_socket, SOL_SOCKET, SO_BROADCAST, (char *)&_true, sizeof( _true )))) { Con_DPrintf( S_WARN "NET_UDPSocket: port: %d setsockopt SO_BROADCAST: %s\n", port, NET_ErrorString( )); } - if( NET_IsSocketError( setsockopt( net_socket, SOL_SOCKET, SO_REUSEADDR, (const char *)&optval, sizeof( optval )) ) ) + if( NET_IsSocketError( setsockopt( net_socket, SOL_SOCKET, SO_REUSEADDR, (const char *)&optval, sizeof( optval )))) { Con_DPrintf( S_WARN "NET_UDPSocket: port: %d setsockopt SO_REUSEADDR: %s\n", port, NET_ErrorString( )); closesocket( net_socket ); @@ -1786,29 +1789,67 @@ static int NET_IPSocket( const char *net_iface, int port, int family ) NET_OpenIP ==================== */ -static void NET_OpenIP( int *sockets, const char *net_iface, int hostport, int clientport, int family ) +static void NET_OpenIP( qboolean change_port, int *sockets, const char *net_iface, int hostport, int clientport, int family ) { int port; + qboolean sv_nat = Cvar_VariableInteger("sv_nat"); + qboolean cl_nat = Cvar_VariableInteger("cl_nat"); + + if( change_port && ( FBitSet( net_hostport->flags, FCVAR_CHANGED ) || sv_nat )) + { + // reopen socket to set random port + if( NET_IsSocketValid( sockets[NS_SERVER] )) + closesocket( sockets[NS_SERVER] ); + + sockets[NS_SERVER] = INVALID_SOCKET; + ClearBits( net_hostport->flags, FCVAR_CHANGED ); + } if( !NET_IsSocketValid( sockets[NS_SERVER] )) { - port = hostport; - if( !port ) port = net_hostport->value; - if( !port ) port = PORT_SERVER; // forcing to default + port = net_iphostport->value; + if( !port ) + { + if( sv_nat ) + port = PORT_ANY; + else + port = net_hostport->value; + + if( !port ) + port = PORT_SERVER; // forcing to default + } sockets[NS_SERVER] = NET_IPSocket( net_iface, port, family ); - if( !NET_IsSocketValid( sockets[NS_SERVER] ) && Host_IsDedicated() ) + if( !NET_IsSocketValid( sockets[NS_SERVER] ) && Host_IsDedicated( )) Host_Error( "Couldn't allocate dedicated server IP port %d.\n", port ); } // dedicated servers don't need client ports - if( Host_IsDedicated() ) return; + if( Host_IsDedicated( )) return; + + if( change_port && ( FBitSet( net_clientport->flags, FCVAR_CHANGED ) || cl_nat )) + { + // reopen socket to set random port + if( NET_IsSocketValid( sockets[NS_CLIENT] )) + closesocket( sockets[NS_CLIENT] ); + + sockets[NS_CLIENT] = INVALID_SOCKET; + ClearBits( net_clientport->flags, FCVAR_CHANGED ); + } if( !NET_IsSocketValid( sockets[NS_CLIENT] )) { - port = clientport; - if( !port ) port = net_clientport->value; - if( !port ) port = PORT_ANY; // forcing to default + port = net_ipclientport->value; + if( !port ) + { + if( cl_nat ) + port = PORT_ANY; + else + port = net_clientport->value; + + if( !port ) + port = PORT_ANY; // forcing to default + } sockets[NS_CLIENT] = NET_IPSocket( net_iface, port, family ); if( !NET_IsSocketValid( sockets[NS_CLIENT] )) @@ -1894,7 +1935,7 @@ NET_Config A single player game will only use the loopback code ==================== */ -void NET_Config( qboolean multiplayer ) +void NET_Config( qboolean multiplayer, qboolean changeport ) { static qboolean bFirst = true; static qboolean old_config; @@ -1911,10 +1952,10 @@ void NET_Config( qboolean multiplayer ) { // open sockets if( net.allow_ip ) - NET_OpenIP( net.ip_sockets, net_ipname->string, net_iphostport->value, net_ipclientport->value, AF_INET ); + NET_OpenIP( changeport, net.ip_sockets, net_ipname->string, net_iphostport->value, net_ipclientport->value, AF_INET ); if( net.allow_ip6 ) - NET_OpenIP( net.ip6_sockets, net_ip6name->string, net_ip6hostport->value, net_ip6clientport->value, AF_INET6 ); + NET_OpenIP( changeport, net.ip6_sockets, net_ip6name->string, net_ip6hostport->value, net_ip6clientport->value, AF_INET6 ); // get our local address, if possible if( bFirst ) @@ -2029,7 +2070,7 @@ void NET_Init( void ) net_clockwindow = Cvar_Get( "clockwindow", "0.5", FCVAR_PRIVILEGED, "timewindow to execute client moves" ); net_address = Cvar_Get( "net_address", "0", FCVAR_READ_ONLY, "contain local address of current client" ); - net_ipname = Cvar_Get( "ip", "localhost", FCVAR_READ_ONLY, "network ip address" ); + net_ipname = Cvar_Get( "ip", "localhost", 0, "network ip address" ); net_iphostport = Cvar_Get( "ip_hostport", "0", FCVAR_READ_ONLY, "network ip host port" ); net_hostport = Cvar_Get( "hostport", va( "%i", PORT_SERVER ), FCVAR_READ_ONLY, "network default host port" ); net_ipclientport = Cvar_Get( "ip_clientport", "0", FCVAR_READ_ONLY, "network ip client port" ); @@ -2053,7 +2094,7 @@ void NET_Init( void ) } #if XASH_WIN32 - if( WSAStartup( MAKEWORD( 1, 1 ), &net.winsockdata ) ) + if( WSAStartup( MAKEWORD( 1, 1 ), &net.winsockdata )) { Con_DPrintf( S_ERROR "network initialization failed.\n" ); return; @@ -2104,7 +2145,7 @@ void NET_Shutdown( void ) NET_ClearLagData( true, true ); - NET_Config( false ); + NET_Config( false, false ); #if XASH_WIN32 WSACleanup(); #endif @@ -2222,7 +2263,7 @@ static void HTTP_FreeFile( httpfile_t *file, qboolean error ) if( error ) { // Switch to next fastdl server if present - if( file->server && ( file->state > HTTP_QUEUE ) && (file->state != HTTP_FREE ) ) + if( file->server && ( file->state > HTTP_QUEUE ) && ( file->state != HTTP_FREE )) { file->server = file->server->next; file->state = HTTP_QUEUE; // Reset download state, HTTP_Run() will open file again @@ -2316,7 +2357,7 @@ static qboolean HTTP_ProcessStream( httpfile_t *curfile ) return false; } - while( ( res = recv( curfile->socket, buf, BUFSIZ - curfile->header_size, 0 ) ) > 0) // if we got there, we are receiving data + while( ( res = recv( curfile->socket, buf, BUFSIZ - curfile->header_size, 0 )) > 0) // if we got there, we are receiving data { curfile->blocktime = 0; @@ -2333,7 +2374,7 @@ static qboolean HTTP_ProcessStream( httpfile_t *curfile ) Con_Reportf( "HTTP: Got response!\n" ); - if( !Q_strstr( curfile->buf, "200 OK" ) ) + if( !Q_strstr( curfile->buf, "200 OK" )) { *begin = 0; // cut string to print out response begin = Q_strchr( curfile->buf, '\r' ); @@ -2355,7 +2396,7 @@ static qboolean HTTP_ProcessStream( httpfile_t *curfile ) Con_Reportf( "HTTP: File size is %d\n", size ); - if( ( curfile->size != -1 ) && ( curfile->size != size ) ) // check size if specified, not used + if( ( curfile->size != -1 ) && ( curfile->size != size )) // check size if specified, not used Con_Reportf( S_WARN "Server reports wrong file size!\n" ); curfile->size = size; @@ -2525,7 +2566,7 @@ void HTTP_Run( void ) if( curfile->state < HTTP_CONNECTED ) // Connection not enstabilished { - res = connect( curfile->socket, (struct sockaddr*)&addr, sizeof( addr ) ); + res = connect( curfile->socket, (struct sockaddr*)&addr, sizeof( addr )); if( res ) { @@ -2533,7 +2574,7 @@ void HTTP_Run( void ) curfile->state = HTTP_CONNECTED; else { - Con_Printf( S_ERROR "cannot connect to server: %s\n", NET_ErrorString( ) ); + Con_Printf( S_ERROR "cannot connect to server: %s\n", NET_ErrorString( )); HTTP_FreeFile( curfile, true ); // Cannot connect break; } @@ -2567,7 +2608,7 @@ void HTTP_Run( void ) { if( WSAGetLastError() != WSAEWOULDBLOCK && WSAGetLastError() != WSAENOTCONN ) { - Con_Printf( S_ERROR "failed to send request: %s\n", NET_ErrorString() ); + Con_Printf( S_ERROR "failed to send request: %s\n", NET_ErrorString( )); HTTP_FreeFile( curfile, true ); wait = true; break; @@ -2596,11 +2637,11 @@ void HTTP_Run( void ) continue; Con_Reportf( "HTTP: Request sent!\n"); - memset( curfile->buf, 0, sizeof( curfile->buf ) ); + memset( curfile->buf, 0, sizeof( curfile->buf )); curfile->state = HTTP_REQUEST_SENT; } - if( !HTTP_ProcessStream( curfile ) ) + if( !HTTP_ProcessStream( curfile )) break; if( curfile->size > 0 ) @@ -2614,8 +2655,8 @@ void HTTP_Run( void ) HTTP_FreeFile( curfile, false ); // success break; } - else if( (WSAGetLastError() != WSAEWOULDBLOCK) && (WSAGetLastError() != WSAEINPROGRESS) ) - Con_Reportf( "problem downloading %s:\n%s\n", curfile->path, NET_ErrorString() ); + else if(( WSAGetLastError( ) != WSAEWOULDBLOCK ) && ( WSAGetLastError( ) != WSAEINPROGRESS )) + Con_Reportf( "problem downloading %s:\n%s\n", curfile->path, NET_ErrorString( )); else curfile->blocktime += host.frametime; @@ -2628,7 +2669,7 @@ void HTTP_Run( void ) } // update progress - if( !Host_IsDedicated() ) + if( !Host_IsDedicated() && iProgressCount != 0 ) Cvar_SetValue( "scr_download", flProgress/iProgressCount * 100 ); HTTP_AutoClean(); @@ -2643,14 +2684,14 @@ Add new download to end of queue */ void HTTP_AddDownload( const char *path, int size, qboolean process ) { - httpfile_t *httpfile = Z_Calloc( sizeof( httpfile_t ) ); + httpfile_t *httpfile = Z_Calloc( sizeof( httpfile_t )); Con_Reportf( "File %s queued to download\n", path ); httpfile->size = size; httpfile->downloaded = 0; httpfile->socket = -1; - Q_strncpy ( httpfile->path, path, sizeof( httpfile->path ) ); + Q_strncpy ( httpfile->path, path, sizeof( httpfile->path )); if( http.last_file ) { @@ -2707,12 +2748,12 @@ static httpserver_t *HTTP_ParseURL( const char *url ) return NULL; url += 7; - server = Z_Calloc( sizeof( httpserver_t ) ); + server = Z_Calloc( sizeof( httpserver_t )); i = 0; - while( *url && ( *url != ':' ) && ( *url != '/' ) && ( *url != '\r' ) && ( *url != '\n' ) ) + while( *url && ( *url != ':' ) && ( *url != '/' ) && ( *url != '\r' ) && ( *url != '\n' )) { - if( i > sizeof( server->host ) ) + if( i > sizeof( server->host )) return NULL; server->host[i++] = *url++; @@ -2724,7 +2765,7 @@ static httpserver_t *HTTP_ParseURL( const char *url ) { server->port = Q_atoi( ++url ); - while( *url && ( *url != '/' ) && ( *url != '\r' ) && ( *url != '\n' ) ) + while( *url && ( *url != '/' ) && ( *url != '\r' ) && ( *url != '\n' )) url++; } else @@ -2732,9 +2773,9 @@ static httpserver_t *HTTP_ParseURL( const char *url ) i = 0; - while( *url && ( *url != '\r' ) && ( *url != '\n' ) ) + while( *url && ( *url != '\r' ) && ( *url != '\n' )) { - if( i > sizeof( server->path ) ) + if( i > sizeof( server->path )) return NULL; server->path[i++] = *url++; @@ -2776,7 +2817,7 @@ static void HTTP_AddCustomServer_f( void ) { if( Cmd_Argc() == 2 ) { - HTTP_AddCustomServer( Cmd_Argv( 1 ) ); + HTTP_AddCustomServer( Cmd_Argv( 1 )); } } @@ -2907,7 +2948,7 @@ void HTTP_Init( void ) if( serverfile ) { - while( ( line = COM_ParseFile( line, token, sizeof( token ) ) ) ) + while(( line = COM_ParseFile( line, token, sizeof( token )))) { httpserver_t *server = HTTP_ParseURL( token ); diff --git a/engine/common/net_ws.h b/engine/common/net_ws.h index f5591a6d..dc984347 100644 --- a/engine/common/net_ws.h +++ b/engine/common/net_ws.h @@ -31,9 +31,9 @@ typedef enum #if !XASH_LOW_MEMORY -#define MAX_INIT_MSG 0x20000 // max length of possible message +#define MAX_INIT_MSG 0x30000 // max length of possible message #else -#define MAX_INIT_MSG 0x8000 +#define MAX_INIT_MSG 0x8000 #endif // net packets type #define NET_HEADER_OUTOFBANDPACKET -1 @@ -51,7 +51,7 @@ void NET_Shutdown( void ); void NET_Sleep( int msec ); qboolean NET_IsActive( void ); qboolean NET_IsConfigured( void ); -void NET_Config( qboolean net_enable ); +void NET_Config( qboolean net_enable, qboolean changeport ); qboolean NET_IsLocalAddress( netadr_t adr ); const char *NET_AdrToString( const netadr_t a ); const char *NET_BaseAdrToString( const netadr_t a ); diff --git a/engine/common/pm_local.h b/engine/common/pm_local.h index d73dd375..6ad6d8e2 100644 --- a/engine/common/pm_local.h +++ b/engine/common/pm_local.h @@ -40,6 +40,22 @@ int PM_TruePointContents( playermove_t *pmove, const vec3_t p ); int PM_PointContents( playermove_t *pmove, const vec3_t p ); void PM_ConvertTrace( trace_t *out, pmtrace_t *in, edict_t *ent ); +static inline void PM_InitTrace( trace_t *trace, const vec3_t end ) +{ + memset( trace, 0, sizeof( *trace )); + VectorCopy( end, trace->endpos ); + trace->allsolid = true; + trace->fraction = 1.0f; +} + +static inline void PM_InitPMTrace( pmtrace_t *trace, const vec3_t end ) +{ + memset( trace, 0, sizeof( *trace )); + VectorCopy( end, trace->endpos ); + trace->allsolid = true; + trace->fraction = 1.0f; +} + // // pm_surface.c // diff --git a/engine/common/pm_trace.c b/engine/common/pm_trace.c index c60a55eb..968cb23e 100644 --- a/engine/common/pm_trace.c +++ b/engine/common/pm_trace.c @@ -111,9 +111,17 @@ hull_t *PM_HullForBox( const vec3_t mins, const vec3_t maxs ) void PM_ConvertTrace( trace_t *out, pmtrace_t *in, edict_t *ent ) { - memcpy( out, in, 48 ); // matched + out->allsolid = in->allsolid; + out->startsolid = in->startsolid; + out->inopen = in->inopen; + out->inwater = in->inwater; + out->fraction = in->fraction; + out->plane.dist = in->plane.dist; out->hitgroup = in->hitgroup; out->ent = ent; + + VectorCopy( in->endpos, out->endpos ); + VectorCopy( in->plane.normal, out->plane.normal ); } /* @@ -446,10 +454,7 @@ pmtrace_t PM_PlayerTraceExt( playermove_t *pmove, vec3_t start, vec3_t end, int VectorSubtract( end, offset, end_l ); } - memset( &trace_bbox, 0, sizeof( trace_bbox )); - VectorCopy( end, trace_bbox.endpos ); - trace_bbox.allsolid = true; - trace_bbox.fraction = 1.0f; + PM_InitPMTrace( &trace_bbox, end ); if( hullcount < 1 ) { @@ -475,10 +480,7 @@ pmtrace_t PM_PlayerTraceExt( playermove_t *pmove, vec3_t start, vec3_t end, int for( last_hitgroup = 0, j = 0; j < hullcount; j++ ) { - memset( &trace_hitbox, 0, sizeof( trace_hitbox )); - VectorCopy( end, trace_hitbox.endpos ); - trace_hitbox.allsolid = true; - trace_hitbox.fraction = 1.0f; + PM_InitPMTrace( &trace_hitbox, end ); PM_RecursiveHullCheck( &hull[j], hull[j].firstclipnode, 0, 1, start_l, end_l, &trace_hitbox ); @@ -622,10 +624,7 @@ int PM_TestPlayerPosition( playermove_t *pmove, vec3_t pos, pmtrace_t *ptrace, p { pmtrace_t trace; - memset( &trace, 0, sizeof( trace )); - VectorCopy( pos, trace.endpos ); - trace.allsolid = true; - trace.fraction = 1.0f; + PM_InitPMTrace( &trace, pos ); // run custom sweep callback if( pmove->server || Host_IsLocalClient( )) diff --git a/engine/common/soundlib/snd_utils.c b/engine/common/soundlib/snd_utils.c index e6ffd977..98626066 100644 --- a/engine/common/soundlib/snd_utils.c +++ b/engine/common/soundlib/snd_utils.c @@ -126,31 +126,7 @@ uint GAME_EXPORT Sound_GetApproxWavePlayLen( const char *filepath ) return msecs; } -/* -================ -Sound_ConvertToSigned - -Convert unsigned data to signed -================ -*/ -void Sound_ConvertToSigned( const byte *data, int channels, int samples ) -{ - int i; - - if( channels == 2 ) - { - for( i = 0; i < samples; i++ ) - { - ((signed char *)sound.tempbuffer)[i*2+0] = (int)((byte)(data[i*2+0]) - 128); - ((signed char *)sound.tempbuffer)[i*2+1] = (int)((byte)(data[i*2+1]) - 128); - } - } - else - { - for( i = 0; i < samples; i++ ) - ((signed char *)sound.tempbuffer)[i] = (int)((unsigned char)(data[i]) - 128); - } -} +#define drint( v ) (int)( v + 0.5 ) /* ================ @@ -161,13 +137,15 @@ We need convert sound to signed even if nothing to resample */ qboolean Sound_ResampleInternal( wavdata_t *sc, int inrate, int inwidth, int outrate, int outwidth ) { - float stepscale; - int outcount, srcsample; - int i, sample, sample2, samplefrac, fracstep; - byte *data; + double stepscale, j; + int outcount; + int i; + qboolean handled = false; - data = sc->buffer; - stepscale = (float)inrate / outrate; // this is usually 0.5, 1, or 2 + if( inrate == outrate && inwidth == outwidth ) + return false; + + stepscale = (double)inrate / outrate; // this is usually 0.5, 1, or 2 outcount = sc->samples / stepscale; sc->size = outcount * outwidth * sc->channels; @@ -177,69 +155,112 @@ qboolean Sound_ResampleInternal( wavdata_t *sc, int inrate, int inwidth, int out if( sc->loopStart != -1 ) sc->loopStart = sc->loopStart / stepscale; - // resample / decimate to the current source rate - if( stepscale == 1.0f && inwidth == 1 && outwidth == 1 ) + if( inrate == outrate ) { - Sound_ConvertToSigned( data, sc->channels, outcount ); + if( inwidth == 1 && outwidth == 2 ) // S8 to S16 + { + for( i = 0; i < outcount * sc->channels; i++ ) + ((int16_t*)sound.tempbuffer)[i] = ((int8_t *)sc->buffer)[i] * 256; + handled = true; + } + else if( inwidth == 2 && outwidth == 1 ) // S16 to S8 + { + for( i = 0; i < outcount * sc->channels; i++ ) + ((int8_t*)sound.tempbuffer)[i] = ((int16_t *)sc->buffer)[i] / 256; + handled = true; + } } + else // resample case + { + if( inwidth == 1 ) + { + int8_t *data = (int8_t *)sc->buffer; + + if( outwidth == 1 ) + { + if( sc->channels == 2 ) + { + for( i = 0, j = 0; i < outcount; i++, j += stepscale ) + { + ((int8_t*)sound.tempbuffer)[i*2+0] = data[((int)j)*2+0]; + ((int8_t*)sound.tempbuffer)[i*2+1] = data[((int)j)*2+1]; + } + } + else + { + for( i = 0, j = 0; i < outcount; i++, j += stepscale ) + ((int8_t*)sound.tempbuffer)[i] = data[(int)j]; + } + handled = true; + } + else if( outwidth == 2 ) + { + if( sc->channels == 2 ) + { + for( i = 0, j = 0; i < outcount; i++, j += stepscale ) + { + ((int16_t*)sound.tempbuffer)[i*2+0] = data[((int)j)*2+0] * 256; + ((int16_t*)sound.tempbuffer)[i*2+1] = data[((int)j)*2+1] * 256; + } + } + else + { + for( i = 0, j = 0; i < outcount; i++, j += stepscale ) + ((int16_t*)sound.tempbuffer)[i] = data[(int)j] * 256; + } + handled = true; + } + } + else if( inwidth == 2 ) + { + int16_t *data = (int16_t *)sc->buffer; + + if( outwidth == 1 ) + { + if( sc->channels == 2 ) + { + for( i = 0, j = 0; i < outcount; i++, j += stepscale ) + { + ((int8_t*)sound.tempbuffer)[i*2+0] = data[((int)j)*2+0] / 256; + ((int8_t*)sound.tempbuffer)[i*2+1] = data[((int)j)*2+1] / 256; + } + } + else + { + for( i = 0, j = 0; i < outcount; i++, j += stepscale ) + ((int8_t*)sound.tempbuffer)[i] = data[(int)j] / 256; + } + handled = true; + } + else if( outwidth == 2 ) + { + if( sc->channels == 2 ) + { + for( i = 0, j = 0; i < outcount; i++, j += stepscale ) + { + ((int16_t*)sound.tempbuffer)[i*2+0] = data[((int)j)*2+0]; + ((int16_t*)sound.tempbuffer)[i*2+1] = data[((int)j)*2+1]; + } + } + else + { + for( i = 0, j = 0; i < outcount; i++, j += stepscale ) + ((int16_t*)sound.tempbuffer)[i] = data[(int)j]; + } + handled = true; + } + } + } + + if( handled ) + Con_Reportf( "Sound_Resample: from [%d bit %d Hz] to [%d bit %d Hz]\n", inwidth * 8, inrate, outwidth * 8, outrate ); else - { - // general case - samplefrac = 0; - fracstep = stepscale * 256; - - if( sc->channels == 2 ) - { - for( i = 0; i < outcount; i++ ) - { - srcsample = samplefrac >> 8; - samplefrac += fracstep; - - if( inwidth == 2 ) - { - sample = ((short *)data)[srcsample*2+0]; - sample2 = ((short *)data)[srcsample*2+1]; - } - else - { - sample = (int)((char)(data[srcsample*2+0])) << 8; - sample2 = (int)((char)(data[srcsample*2+1])) << 8; - } - - if( outwidth == 2 ) - { - ((short *)sound.tempbuffer)[i*2+0] = sample; - ((short *)sound.tempbuffer)[i*2+1] = sample2; - } - else - { - ((signed char *)sound.tempbuffer)[i*2+0] = sample >> 8; - ((signed char *)sound.tempbuffer)[i*2+1] = sample2 >> 8; - } - } - } - else - { - for( i = 0; i < outcount; i++ ) - { - srcsample = samplefrac >> 8; - samplefrac += fracstep; - - if( inwidth == 2 ) sample = ((short *)data)[srcsample]; - else sample = (int)( (char)(data[srcsample])) << 8; - - if( outwidth == 2 ) ((short *)sound.tempbuffer)[i] = sample; - else ((signed char *)sound.tempbuffer)[i] = sample >> 8; - } - } - - Con_Reportf( "Sound_Resample: from[%d bit %d kHz] to [%d bit %d kHz]\n", inwidth * 8, inrate, outwidth * 8, outrate ); - } + Con_Reportf( S_ERROR "Sound_Resample: unsupported from [%d bit %d Hz] to [%d bit %d Hz]\n", inwidth * 8, inrate, outwidth * 8, outrate ); sc->rate = outrate; sc->width = outwidth; - return true; + return handled; } qboolean Sound_Process( wavdata_t **wav, int rate, int width, uint flags ) diff --git a/engine/common/sys_con.c b/engine/common/sys_con.c index 96f4bb98..67c5d3bb 100644 --- a/engine/common/sys_con.c +++ b/engine/common/sys_con.c @@ -14,17 +14,23 @@ GNU General Public License for more details. */ #include "common.h" -#if XASH_ANDROID +#if XASH_WIN32 +#define STDOUT_FILENO 1 +#include +#elif XASH_ANDROID #include #endif #include #include -#if !XASH_WIN32 && !XASH_MOBILE_PLATFORM -#define XASH_COLORIZE_CONSOLE +// do not waste precious CPU cycles on mobiles or low memory devices +#if !XASH_WIN32 && !XASH_MOBILE_PLATFORM && !XASH_LOW_MEMORY +#define XASH_COLORIZE_CONSOLE true // use with caution, running engine in Qt Creator may cause a freeze in read() call // I was never encountered this bug anywhere else, so still enable by default // #define XASH_USE_SELECT 1 +#else +#define XASH_COLORIZE_CONSOLE false #endif #if XASH_USE_SELECT @@ -129,16 +135,18 @@ void Sys_InitLog( void ) if( s_ld.log_active ) { s_ld.logfile = fopen( s_ld.log_path, mode ); - if( !s_ld.logfile ) + + if ( !s_ld.logfile ) { Con_Reportf( S_ERROR "Sys_InitLog: can't create log file %s: %s\n", s_ld.log_path, strerror( errno ) ); + return; } - else - { - fprintf( s_ld.logfile, "=================================================================================\n" ); - fprintf( s_ld.logfile, "\t%s (build %i) started at %s\n", s_ld.title, Q_buildnum(), Q_timestamp( TIME_FULL )); - fprintf( s_ld.logfile, "=================================================================================\n" ); - } + + s_ld.logfileno = fileno( s_ld.logfile ); + + fprintf( s_ld.logfile, "=================================================================================\n" ); + fprintf( s_ld.logfile, "\t%s (build %i) started at %s\n", s_ld.title, Q_buildnum(), Q_timestamp( TIME_FULL ) ); + fprintf( s_ld.logfile, "=================================================================================\n" ); } } @@ -176,53 +184,92 @@ void Sys_CloseLog( void ) } } -static void Sys_PrintColorized( const char *logtime, const char *msg ) +#if XASH_COLORIZE_CONSOLE == true +static void Sys_WriteEscapeSequenceForColorcode( int fd, int c ) { - char colored[4096]; - int len = 0; - - while( *msg && ( len < 4090 ) ) + static const char *q3ToAnsi[ 8 ] = { - static char q3ToAnsi[ 8 ] = - { - '0', // COLOR_BLACK - '1', // COLOR_RED - '2', // COLOR_GREEN - '3', // COLOR_YELLOW - '4', // COLOR_BLUE - '6', // COLOR_CYAN - '5', // COLOR_MAGENTA - 0 // COLOR_WHITE - }; + "\033[30m", // COLOR_BLACK + "\033[31m", // COLOR_RED + "\033[32m", // COLOR_GREEN + "\033[33m", // COLOR_YELLOW + "\033[34m", // COLOR_BLUE + "\033[36m", // COLOR_CYAN + "\033[35m", // COLOR_MAGENTA + "\033[0m", // COLOR_WHITE + }; + const char *esc = q3ToAnsi[c]; - if( IsColorString( msg ) ) - { - int color; + if( c == 7 ) + write( fd, esc, 4 ); + else write( fd, esc, 5 ); +} +#else +static void Sys_WriteEscapeSequenceForColorcode( int fd, int c ) {} +#endif - msg++; - color = q3ToAnsi[ *msg++ % 8 ]; - colored[len++] = '\033'; - colored[len++] = '['; - if( color ) - { - colored[len++] = '3'; - colored[len++] = color; - } - else - colored[len++] = '0'; - colored[len++] = 'm'; +static void Sys_PrintLogfile( const int fd, const char *logtime, const char *msg, const qboolean colorize ) +{ + const char *p = msg; + + write( fd, logtime, Q_strlen( logtime ) ); + + while( p && *p ) + { + p = Q_strchr( msg, '^' ); + + if( p == NULL ) + { + write( fd, msg, Q_strlen( msg )); + break; + } + else if( IsColorString( p )) + { + if( p != msg ) + write( fd, msg, p - msg ); + msg = p + 2; + + if( colorize ) + Sys_WriteEscapeSequenceForColorcode( fd, ColorIndex( p[1] )); } else - colored[len++] = *msg++; + { + write( fd, msg, p - msg + 1 ); + msg = p + 1; + } } - colored[len] = 0; - printf( "\033[34m%s\033[0m%s\033[0m", logtime, colored ); + // flush the color + if( colorize ) + Sys_WriteEscapeSequenceForColorcode( fd, 7 ); +} + +static void Sys_PrintStdout( const char *logtime, const char *msg ) +{ +#if XASH_MOBILE_PLATFORM + static char buf[MAX_PRINT_MSG]; + + // strip color codes + COM_StripColors( msg, buf ); + + // platform-specific output +#if XASH_ANDROID && !XASH_DEDICATED + __android_log_write( ANDROID_LOG_DEBUG, "Xash", buf ); +#endif // XASH_ANDROID && !XASH_DEDICATED + +#if TARGET_OS_IOS + void IOS_Log( const char * ); + IOS_Log( buf ); +#endif // TARGET_OS_IOS +#elif !XASH_WIN32 // Wcon does the job + Sys_PrintLogfile( STDOUT_FILENO, logtime, msg, XASH_COLORIZE_CONSOLE ); + Sys_FlushStdout(); +#endif } void Sys_PrintLog( const char *pMsg ) { - time_t crt_time; + time_t crt_time; const struct tm *crt_tm; char logtime[32] = ""; static char lastchar; @@ -230,39 +277,26 @@ void Sys_PrintLog( const char *pMsg ) time( &crt_time ); crt_tm = localtime( &crt_time ); - // platform-specific output -#if XASH_ANDROID && !XASH_DEDICATED - __android_log_print( ANDROID_LOG_DEBUG, "Xash", "%s", pMsg ); -#endif - -#if TARGET_OS_IOS - void IOS_Log(const char*); - IOS_Log(pMsg); -#endif - if( !lastchar || lastchar == '\n') strftime( logtime, sizeof( logtime ), "[%H:%M:%S] ", crt_tm ); //short time - // spew to stdout, except mobiles -#if !XASH_MOBILE_PLATFORM -#ifdef XASH_COLORIZE_CONSOLE - Sys_PrintColorized( logtime, pMsg ); -#else - printf( "%s %s", logtime, pMsg ); -#endif - Sys_FlushStdout(); -#endif - - // save last char to detect when line was not ended - lastchar = pMsg[strlen(pMsg)-1]; + // spew to stdout + Sys_PrintStdout( logtime, pMsg ); if( !s_ld.logfile ) + { + // save last char to detect when line was not ended + lastchar = pMsg[Q_strlen( pMsg ) - 1]; return; + } if( !lastchar || lastchar == '\n') - strftime( logtime, sizeof( logtime ), "[%Y:%m:%d|%H:%M:%S]", crt_tm ); //full time + strftime( logtime, sizeof( logtime ), "[%Y:%m:%d|%H:%M:%S] ", crt_tm ); //full time + + // save last char to detect when line was not ended + lastchar = pMsg[Q_strlen( pMsg ) - 1]; - fprintf( s_ld.logfile, "%s %s", logtime, pMsg ); + Sys_PrintLogfile( s_ld.logfileno, logtime, pMsg, false ); Sys_FlushLogfile(); } diff --git a/engine/common/system.c b/engine/common/system.c index 6916fdd1..72ae9197 100644 --- a/engine/common/system.c +++ b/engine/common/system.c @@ -17,6 +17,7 @@ GNU General Public License for more details. #include "xash3d_mathlib.h" #include "platform/platform.h" #include +#include #ifdef XASH_SDL #include @@ -32,10 +33,16 @@ GNU General Public License for more details. #endif #endif +#if XASH_WIN32 +#include +#endif + #include "menu_int.h" // _UPDATE_PAGE macro +#include "library.h" +#include "whereami.h" + qboolean error_on_exit = false; // arg for exit(); -#define DEBUG_BREAK /* ================ @@ -46,23 +53,28 @@ double GAME_EXPORT Sys_DoubleTime( void ) { return Platform_DoubleTime(); } + +/* +================ +Sys_DebugBreak +================ +*/ +void Sys_DebugBreak( void ) +{ #if XASH_LINUX || ( XASH_WIN32 && !XASH_64BIT ) - #undef DEBUG_BREAK - qboolean Sys_DebuggerPresent( void ); // see sys_linux.c - #if XASH_MSVC - #define DEBUG_BREAK \ - if( Sys_DebuggerPresent() ) \ - _asm{ int 3 } - #elif XASH_X86 - #define DEBUG_BREAK \ - if( Sys_DebuggerPresent() ) \ - asm volatile("int $3;") - #else - #define DEBUG_BREAK \ - if( Sys_DebuggerPresent() ) \ - raise( SIGINT ) - #endif +#if XASH_MSVC + if( Sys_DebuggerPresent() ) + _asm { int 3 } +#elif XASH_X86 + if( Sys_DebuggerPresent() ) + asm volatile( "int $3;" ); +#else + if( Sys_DebuggerPresent() ) + raise( SIGINT ); #endif +#endif +} + #if !XASH_DEDICATED /* ================ @@ -272,7 +284,8 @@ qboolean Sys_LoadLibrary( dll_info_t *dll ) *func->func = NULL; } - if( !dll->link ) dll->link = LoadLibrary ( dll->name ); // environment pathes + if( !dll->link ) + dll->link = COM_LoadLibrary( dll->name, false, true ); // environment pathes // no DLL found if( !dll->link ) @@ -307,7 +320,7 @@ void* Sys_GetProcAddress( dll_info_t *dll, const char* name ) if( !dll || !dll->link ) // invalid desc return NULL; - return (void *)GetProcAddress( dll->link, name ); + return (void *)COM_GetProcAddress( dll->link, name ); } qboolean Sys_FreeLibrary( dll_info_t *dll ) @@ -324,7 +337,7 @@ qboolean Sys_FreeLibrary( dll_info_t *dll ) } else Con_Reportf( "Sys_FreeLibrary: Unloading %s\n", dll->name ); - FreeLibrary( dll->link ); + COM_FreeLibrary( dll->link ); dll->link = NULL; return true; @@ -368,12 +381,14 @@ void Sys_Warn( const char *format, ... ) va_list argptr; char text[MAX_PRINT_MSG]; - DEBUG_BREAK; - va_start( argptr, format ); Q_vsnprintf( text, MAX_PRINT_MSG, format, argptr ); va_end( argptr ); + + Sys_DebugBreak(); + Msg( "Sys_Warn: %s\n", text ); + if( !Host_IsDedicated() ) // dedicated server should not hang on messagebox MSGBOX(text); } @@ -391,12 +406,14 @@ void Sys_Error( const char *error, ... ) va_list argptr; char text[MAX_PRINT_MSG]; - DEBUG_BREAK; + // enable cursor before debugger call + if( !Host_IsDedicated( )) + Platform_SetCursorType( dc_arrow ); if( host.status == HOST_ERR_FATAL ) return; // don't multiple executes - // make sure what console received last message + // make sure that console received last message if( host.change_game ) Sys_Sleep( 200 ); error_on_exit = true; @@ -405,6 +422,8 @@ void Sys_Error( const char *error, ... ) Q_vsnprintf( text, MAX_PRINT_MSG, error, argptr ); va_end( argptr ); + Sys_DebugBreak(); + SV_SysError( text ); if( !Host_IsDedicated() ) @@ -412,9 +431,12 @@ void Sys_Error( const char *error, ... ) #if XASH_SDL == 2 if( host.hWnd ) SDL_HideWindow( host.hWnd ); #endif +#if XASH_WIN32 + Wcon_ShowConsole( false ); +#endif + MSGBOX( text ); } - - if( host_developer.value ) + else { #if XASH_WIN32 Wcon_ShowConsole( true ); @@ -423,14 +445,7 @@ void Sys_Error( const char *error, ... ) Sys_Print( text ); // print error message Sys_WaitForQuit(); } - else - { -#if XASH_WIN32 - Wcon_ShowConsole( false ); -#endif - MSGBOX( text ); - } - + Sys_Quit(); } @@ -536,3 +551,67 @@ void Sys_Print( const char *pMsg ) Rcon_Print( pMsg ); } + +/* +================== +Sys_ChangeGame + +This is a special function + +Here we restart engine with new -game parameter +but since engine will be unloaded during this call +it explicitly doesn't use internal allocation or string copy utils +================== +*/ +qboolean Sys_NewInstance( const char *gamedir ) +{ + int i = 0; + qboolean replacedArg = false; + size_t exelen; + char *exe, **newargs; + + // don't use engine allocation utils here + // they will be freed after Host_Shutdown + newargs = calloc( host.argc + 4, sizeof( *newargs )); + while( i < host.argc ) + { + newargs[i] = strdup( host.argv[i] ); + + // replace existing -game argument + if( !Q_stricmp( newargs[i], "-game" )) + { + newargs[i + 1] = strdup( gamedir ); + replacedArg = true; + i += 2; + } + else i++; + } + + if( !replacedArg ) + { + newargs[i++] = strdup( "-game" ); + newargs[i++] = strdup( gamedir ); + } + + newargs[i++] = strdup( "-changegame" ); + newargs[i] = NULL; + + exelen = wai_getExecutablePath( NULL, 0, NULL ); + exe = malloc( exelen + 1 ); + wai_getExecutablePath( exe, exelen, NULL ); + exe[exelen] = 0; + + Host_Shutdown(); + + execv( exe, newargs ); + + // if execv returned, it's probably an error + printf( "execv failed: %s", strerror( errno )); + + for( ; i >= 0; i-- ) + free( newargs[i] ); + free( newargs ); + free( exe ); + + return false; +} diff --git a/engine/common/system.h b/engine/common/system.h index 48fb1807..adf34f3d 100644 --- a/engine/common/system.h +++ b/engine/common/system.h @@ -51,7 +51,7 @@ char *Sys_GetClipboardData( void ); const char *Sys_GetCurrentUser( void ); int Sys_CheckParm( const char *parm ); void Sys_Warn( const char *format, ... ) _format( 1 ); -void Sys_Error( const char *error, ... ) _format( 1 ) NORETURN; +void Sys_Error( const char *error, ... ) _format( 1 ); qboolean Sys_LoadLibrary( dll_info_t *dll ); void* Sys_GetProcAddress( dll_info_t *dll, const char* name ); qboolean Sys_FreeLibrary( dll_info_t *dll ); @@ -59,6 +59,7 @@ void Sys_ParseCommandLine( int argc, char **argv ); void Sys_MergeCommandLine( void ); void Sys_SetupCrashHandler( void ); void Sys_RestoreCrashHandler( void ); +void Sys_DebugBreak( void ); #define Sys_GetParmFromCmdLine( parm, out ) _Sys_GetParmFromCmdLine( parm, out, sizeof( out )) qboolean _Sys_GetParmFromCmdLine( const char *parm, char *out, size_t size ); qboolean Sys_GetIntFromCmdLine( const char *parm, int *out ); @@ -68,6 +69,7 @@ void Sys_PrintLog( const char *pMsg ); void Sys_InitLog( void ); void Sys_CloseLog( void ); void Sys_Quit( void ) NORETURN; +qboolean Sys_NewInstance( const char *gamedir ); // // sys_con.c diff --git a/engine/common/whereami.c b/engine/common/whereami.c new file mode 100644 index 00000000..4d3401a1 --- /dev/null +++ b/engine/common/whereami.c @@ -0,0 +1,806 @@ +// (‑●‑●)> dual licensed under the WTFPL v2 and MIT licenses +// without any warranty. +// by Gregory Pakosz (@gpakosz) +// https://github.com/gpakosz/whereami + +// in case you want to #include "whereami.c" in a larger compilation unit +#if !defined(WHEREAMI_H) +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(__linux__) || defined(__CYGWIN__) +#undef _DEFAULT_SOURCE +#define _DEFAULT_SOURCE +#elif defined(__APPLE__) +#undef _DARWIN_C_SOURCE +#define _DARWIN_C_SOURCE +#define _DARWIN_BETTER_REALPATH +#endif + +#if !defined(WAI_MALLOC) || !defined(WAI_FREE) || !defined(WAI_REALLOC) +#include +#endif + +#if !defined(WAI_MALLOC) +#define WAI_MALLOC(size) malloc(size) +#endif + +#if !defined(WAI_FREE) +#define WAI_FREE(p) free(p) +#endif + +#if !defined(WAI_REALLOC) +#define WAI_REALLOC(p, size) realloc(p, size) +#endif + +#ifndef WAI_NOINLINE +#if defined(_MSC_VER) +#define WAI_NOINLINE __declspec(noinline) +#elif defined(__GNUC__) +#define WAI_NOINLINE __attribute__((noinline)) +#else +#error unsupported compiler +#endif +#endif + +#if defined(_MSC_VER) +#define WAI_RETURN_ADDRESS() _ReturnAddress() +#elif defined(__GNUC__) +#define WAI_RETURN_ADDRESS() __builtin_extract_return_addr(__builtin_return_address(0)) +#else +#error unsupported compiler +#endif + +#if defined(_WIN32) + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#if defined(_MSC_VER) +#pragma warning(push, 3) +#endif +#include +#include +#if defined(_MSC_VER) +#pragma warning(pop) +#endif +#include + +static int WAI_PREFIX(getModulePath_)(HMODULE module, char* out, int capacity, int* dirname_length) +{ + wchar_t buffer1[MAX_PATH]; + wchar_t buffer2[MAX_PATH]; + wchar_t* path = NULL; + int length = -1; + bool ok; + + for (ok = false; !ok; ok = true) + { + DWORD size; + int length_, length__; + + size = GetModuleFileNameW(module, buffer1, sizeof(buffer1) / sizeof(buffer1[0])); + + if (size == 0) + break; + else if (size == (DWORD)(sizeof(buffer1) / sizeof(buffer1[0]))) + { + DWORD size_ = size; + do + { + wchar_t* path_; + + path_ = (wchar_t*)WAI_REALLOC(path, sizeof(wchar_t) * size_ * 2); + if (!path_) + break; + size_ *= 2; + path = path_; + size = GetModuleFileNameW(module, path, size_); + } + while (size == size_); + + if (size == size_) + break; + } + else + path = buffer1; + + if (!_wfullpath(buffer2, path, MAX_PATH)) + break; + length_ = (int)wcslen(buffer2); + length__ = WideCharToMultiByte(CP_UTF8, 0, buffer2, length_ , out, capacity, NULL, NULL); + + if (length__ == 0) + length__ = WideCharToMultiByte(CP_UTF8, 0, buffer2, length_, NULL, 0, NULL, NULL); + if (length__ == 0) + break; + + if (length__ <= capacity && dirname_length) + { + int i; + + for (i = length__ - 1; i >= 0; --i) + { + if (out[i] == '\\') + { + *dirname_length = i; + break; + } + } + } + + length = length__; + } + + if (path != buffer1) + WAI_FREE(path); + + return ok ? length : -1; +} + +WAI_NOINLINE WAI_FUNCSPEC +int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length) +{ + return WAI_PREFIX(getModulePath_)(NULL, out, capacity, dirname_length); +} + +WAI_NOINLINE WAI_FUNCSPEC +int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length) +{ + HMODULE module; + int length = -1; + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable: 4054) +#endif + if (GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (LPCTSTR)WAI_RETURN_ADDRESS(), &module)) +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + { + length = WAI_PREFIX(getModulePath_)(module, out, capacity, dirname_length); + } + + return length; +} + +#elif defined(__linux__) || defined(__CYGWIN__) || defined(__sun) || defined(WAI_USE_PROC_SELF_EXE) + +#include +#include +#include +#if defined(__linux__) +#include +#else +#include +#endif +#ifndef __STDC_FORMAT_MACROS +#define __STDC_FORMAT_MACROS +#endif +#include +#include + +#if !defined(WAI_PROC_SELF_EXE) +#if defined(__sun) +#define WAI_PROC_SELF_EXE "/proc/self/path/a.out" +#else +#define WAI_PROC_SELF_EXE "/proc/self/exe" +#endif +#endif + +WAI_FUNCSPEC +int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length) +{ + char buffer[PATH_MAX]; + char* resolved = NULL; + int length = -1; + bool ok; + + for (ok = false; !ok; ok = true) + { + resolved = realpath(WAI_PROC_SELF_EXE, buffer); + if (!resolved) + break; + + length = (int)strlen(resolved); + if (length <= capacity) + { + memcpy(out, resolved, length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + } + + return ok ? length : -1; +} + +#if !defined(WAI_PROC_SELF_MAPS_RETRY) +#define WAI_PROC_SELF_MAPS_RETRY 5 +#endif + +#if !defined(WAI_PROC_SELF_MAPS) +#if defined(__sun) +#define WAI_PROC_SELF_MAPS "/proc/self/map" +#else +#define WAI_PROC_SELF_MAPS "/proc/self/maps" +#endif +#endif + +#if defined(__ANDROID__) || defined(ANDROID) +#include +#include +#include +#endif +#include + +WAI_NOINLINE WAI_FUNCSPEC +int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length) +{ + int length = -1; + FILE* maps = NULL; + int r; + + for (r = 0; r < WAI_PROC_SELF_MAPS_RETRY; ++r) + { + maps = fopen(WAI_PROC_SELF_MAPS, "r"); + if (!maps) + break; + + for (;;) + { + char buffer[PATH_MAX < 1024 ? 1024 : PATH_MAX]; + uint64_t low, high; + char perms[5]; + uint64_t offset; + uint32_t major, minor; + char path[PATH_MAX]; + uint32_t inode; + + if (!fgets(buffer, sizeof(buffer), maps)) + break; + + if (sscanf(buffer, "%" PRIx64 "-%" PRIx64 " %s %" PRIx64 " %x:%x %u %s\n", &low, &high, perms, &offset, &major, &minor, &inode, path) == 8) + { + uint64_t addr = (uintptr_t)WAI_RETURN_ADDRESS(); + if (low <= addr && addr <= high) + { + char* resolved; + + resolved = realpath(path, buffer); + if (!resolved) + break; + + length = (int)strlen(resolved); +#if defined(__ANDROID__) || defined(ANDROID) + if (length > 4 + &&buffer[length - 1] == 'k' + &&buffer[length - 2] == 'p' + &&buffer[length - 3] == 'a' + &&buffer[length - 4] == '.') + { + char *begin, *p; + int fd = open(path, O_RDONLY); + if (fd == -1) + { + length = -1; // retry + break; + } + + begin = (char*)mmap(0, offset, PROT_READ, MAP_SHARED, fd, 0); + if (begin == MAP_FAILED) + { + close(fd); + length = -1; // retry + break; + } + + p = begin + offset - 30; // minimum size of local file header + while (p >= begin) // scan backwards + { + if (*((uint32_t*)p) == 0x04034b50UL) // local file header signature found + { + uint16_t length_ = *((uint16_t*)(p + 26)); + + if (length + 2 + length_ < (int)sizeof(buffer)) + { + memcpy(&buffer[length], "!/", 2); + memcpy(&buffer[length + 2], p + 30, length_); + length += 2 + length_; + } + + break; + } + + --p; + } + + munmap(begin, offset); + close(fd); + } +#endif + if (length <= capacity) + { + memcpy(out, resolved, length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + + break; + } + } + } + + fclose(maps); + maps = NULL; + + if (length != -1) + break; + } + + return length; +} + +#elif defined(__APPLE__) + +#include +#include +#include +#include +#include +#include + +WAI_FUNCSPEC +int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length) +{ + char buffer1[PATH_MAX]; + char buffer2[PATH_MAX]; + char* path = buffer1; + char* resolved = NULL; + int length = -1; + bool ok; + + for (ok = false; !ok; ok = true) + { + uint32_t size = (uint32_t)sizeof(buffer1); + if (_NSGetExecutablePath(path, &size) == -1) + { + path = (char*)WAI_MALLOC(size); + if (!_NSGetExecutablePath(path, &size)) + break; + } + + resolved = realpath(path, buffer2); + if (!resolved) + break; + + length = (int)strlen(resolved); + if (length <= capacity) + { + memcpy(out, resolved, length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + } + + if (path != buffer1) + WAI_FREE(path); + + return ok ? length : -1; +} + +WAI_NOINLINE WAI_FUNCSPEC +int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length) +{ + char buffer[PATH_MAX]; + char* resolved = NULL; + int length = -1; + + for(;;) + { + Dl_info info; + + if (dladdr(WAI_RETURN_ADDRESS(), &info)) + { + resolved = realpath(info.dli_fname, buffer); + if (!resolved) + break; + + length = (int)strlen(resolved); + if (length <= capacity) + { + memcpy(out, resolved, length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + } + + break; + } + + return length; +} + +#elif defined(__QNXNTO__) + +#include +#include +#include +#include +#include +#include + +#if !defined(WAI_PROC_SELF_EXE) +#define WAI_PROC_SELF_EXE "/proc/self/exefile" +#endif + +WAI_FUNCSPEC +int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length) +{ + char buffer1[PATH_MAX]; + char buffer2[PATH_MAX]; + char* resolved = NULL; + FILE* self_exe = NULL; + int length = -1; + bool ok; + + for (ok = false; !ok; ok = true) + { + self_exe = fopen(WAI_PROC_SELF_EXE, "r"); + if (!self_exe) + break; + + if (!fgets(buffer1, sizeof(buffer1), self_exe)) + break; + + resolved = realpath(buffer1, buffer2); + if (!resolved) + break; + + length = (int)strlen(resolved); + if (length <= capacity) + { + memcpy(out, resolved, length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + } + + fclose(self_exe); + + return ok ? length : -1; +} + +WAI_FUNCSPEC +int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length) +{ + char buffer[PATH_MAX]; + char* resolved = NULL; + int length = -1; + + for(;;) + { + Dl_info info; + + if (dladdr(WAI_RETURN_ADDRESS(), &info)) + { + resolved = realpath(info.dli_fname, buffer); + if (!resolved) + break; + + length = (int)strlen(resolved); + if (length <= capacity) + { + memcpy(out, resolved, length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + } + + break; + } + + return length; +} + +#elif defined(__DragonFly__) || defined(__FreeBSD__) || \ + defined(__FreeBSD_kernel__) || defined(__NetBSD__) || defined(__OpenBSD__) + +#include +#include +#include +#include +#include +#include +#include + +#if defined(__OpenBSD__) + +#include + +WAI_FUNCSPEC +int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length) +{ + char buffer1[4096]; + char buffer2[PATH_MAX]; + char buffer3[PATH_MAX]; + char** argv = (char**)buffer1; + char* resolved = NULL; + int length = -1; + bool ok; + + for (ok = false; !ok; ok = true) + { + int mib[4] = { CTL_KERN, KERN_PROC_ARGS, getpid(), KERN_PROC_ARGV }; + size_t size; + + if (sysctl(mib, 4, NULL, &size, NULL, 0) != 0) + break; + + if (size > sizeof(buffer1)) + { + argv = (char**)WAI_MALLOC(size); + if (!argv) + break; + } + + if (sysctl(mib, 4, argv, &size, NULL, 0) != 0) + break; + + if (strchr(argv[0], '/')) + { + resolved = realpath(argv[0], buffer2); + if (!resolved) + break; + } + else + { + const char* PATH = getenv("PATH"); + if (!PATH) + break; + + size_t argv0_length = strlen(argv[0]); + + const char* begin = PATH; + while (1) + { + const char* separator = strchr(begin, ':'); + const char* end = separator ? separator : begin + strlen(begin); + + if (end - begin > 0) + { + if (*(end -1) == '/') + --end; + + if (((end - begin) + 1 + argv0_length + 1) <= sizeof(buffer2)) + { + memcpy(buffer2, begin, end - begin); + buffer2[end - begin] = '/'; + memcpy(buffer2 + (end - begin) + 1, argv[0], argv0_length + 1); + + resolved = realpath(buffer2, buffer3); + if (resolved) + break; + } + } + + if (!separator) + break; + + begin = ++separator; + } + + if (!resolved) + break; + } + + length = (int)strlen(resolved); + if (length <= capacity) + { + memcpy(out, resolved, length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + } + + if (argv != (char**)buffer1) + WAI_FREE(argv); + + return ok ? length : -1; +} + +#else + +WAI_FUNCSPEC +int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length) +{ + char buffer1[PATH_MAX]; + char buffer2[PATH_MAX]; + char* path = buffer1; + char* resolved = NULL; + int length = -1; + bool ok; + + for (ok = false; !ok; ok = true) + { +#if defined(__NetBSD__) + int mib[4] = { CTL_KERN, KERN_PROC_ARGS, -1, KERN_PROC_PATHNAME }; +#else + int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 }; +#endif + size_t size = sizeof(buffer1); + + if (sysctl(mib, 4, path, &size, NULL, 0) != 0) + break; + + resolved = realpath(path, buffer2); + if (!resolved) + break; + + length = (int)strlen(resolved); + if (length <= capacity) + { + memcpy(out, resolved, length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + } + + return ok ? length : -1; +} + +#endif + +WAI_NOINLINE WAI_FUNCSPEC +int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length) +{ + char buffer[PATH_MAX]; + char* resolved = NULL; + int length = -1; + + for(;;) + { + Dl_info info; + + if (dladdr(WAI_RETURN_ADDRESS(), &info)) + { + resolved = realpath(info.dli_fname, buffer); + if (!resolved) + break; + + length = (int)strlen(resolved); + if (length <= capacity) + { + memcpy(out, resolved, length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + } + + break; + } + + return length; +} + +#else + +#error unsupported platform + +#endif + +#ifdef __cplusplus +} +#endif diff --git a/engine/common/whereami.h b/engine/common/whereami.h new file mode 100644 index 00000000..670db54c --- /dev/null +++ b/engine/common/whereami.h @@ -0,0 +1,67 @@ +// (‑●‑●)> dual licensed under the WTFPL v2 and MIT licenses +// without any warranty. +// by Gregory Pakosz (@gpakosz) +// https://github.com/gpakosz/whereami + +#ifndef WHEREAMI_H +#define WHEREAMI_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef WAI_FUNCSPEC + #define WAI_FUNCSPEC +#endif +#ifndef WAI_PREFIX +#define WAI_PREFIX(function) wai_##function +#endif + +/** + * Returns the path to the current executable. + * + * Usage: + * - first call `int length = wai_getExecutablePath(NULL, 0, NULL);` to + * retrieve the length of the path + * - allocate the destination buffer with `path = (char*)malloc(length + 1);` + * - call `wai_getExecutablePath(path, length, NULL)` again to retrieve the + * path + * - add a terminal NUL character with `path[length] = '\0';` + * + * @param out destination buffer, optional + * @param capacity destination buffer capacity + * @param dirname_length optional recipient for the length of the dirname part + * of the path. + * + * @return the length of the executable path on success (without a terminal NUL + * character), otherwise `-1` + */ +WAI_FUNCSPEC +int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length); + +/** + * Returns the path to the current module + * + * Usage: + * - first call `int length = wai_getModulePath(NULL, 0, NULL);` to retrieve + * the length of the path + * - allocate the destination buffer with `path = (char*)malloc(length + 1);` + * - call `wai_getModulePath(path, length, NULL)` again to retrieve the path + * - add a terminal NUL character with `path[length] = '\0';` + * + * @param out destination buffer, optional + * @param capacity destination buffer capacity + * @param dirname_length optional recipient for the length of the dirname part + * of the path. + * + * @return the length of the module path on success (without a terminal NUL + * character), otherwise `-1` + */ +WAI_FUNCSPEC +int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length); + +#ifdef __cplusplus +} +#endif + +#endif // #ifndef WHEREAMI_H diff --git a/engine/common/zone.c b/engine/common/zone.c index f3200ed1..4ca83f65 100644 --- a/engine/common/zone.c +++ b/engine/common/zone.c @@ -91,8 +91,8 @@ void *_Mem_Alloc( poolhandle_t poolptr, size_t size, qboolean clear, const char pool->totalsize += size; // big allocations are not clumped - pool->realsize += sizeof( memheader_t ) + size + sizeof( int ); - mem = (memheader_t *)Q_malloc( sizeof( memheader_t ) + size + sizeof( int )); + pool->realsize += sizeof( memheader_t ) + size + sizeof( size_t ); + mem = (memheader_t *)Q_malloc( sizeof( memheader_t ) + size + sizeof( size_t )); if( mem == NULL ) Sys_Error( "Mem_Alloc: out of memory (alloc at %s:%i)\n", filename, fileline ); mem->filename = filename; @@ -162,7 +162,7 @@ static void Mem_FreeBlock( memheader_t *mem, const char *filename, int fileline // memheader has been unlinked, do the actual free now pool->totalsize -= mem->size; - pool->realsize -= sizeof( memheader_t ) + mem->size + sizeof( int ); + pool->realsize -= sizeof( memheader_t ) + mem->size + sizeof( size_t ); Q_free( mem ); } diff --git a/engine/menu_int.h b/engine/menu_int.h index cdb918bf..d273d8f6 100644 --- a/engine/menu_int.h +++ b/engine/menu_int.h @@ -30,6 +30,7 @@ typedef int HIMAGE; // handle to a graphic #define PIC_NEAREST (1<<0) // disable texfilter #define PIC_KEEP_SOURCE (1<<1) // some images keep source #define PIC_NOFLIP_TGA (1<<2) // Steam background completely ignore tga attribute 0x20 +#define PIC_EXPAND_SOURCE (1<<3) // don't keep as 8-bit source, expand to RGBA // flags for COM_ParseFileSafe #define PFILE_IGNOREBRACKET (1<<0) @@ -116,7 +117,7 @@ typedef struct ui_enginefuncs_s int (*CL_CreateVisibleEntity)( int type, struct cl_entity_s *ent ); // misc handlers - void (*pfnHostError)( const char *szFmt, ... ) _format( 1 ) NORETURN; + void (*pfnHostError)( const char *szFmt, ... ) _format( 1 ); int (*pfnFileExists)( const char *filename, int gamedironly ); void (*pfnGetGameDir)( char *szGetGameDir ); diff --git a/engine/platform/android/snd_opensles.c b/engine/platform/android/snd_opensles.c index 012d9313..4cf5bbe0 100644 --- a/engine/platform/android/snd_opensles.c +++ b/engine/platform/android/snd_opensles.c @@ -209,7 +209,7 @@ qboolean SNDDMA_Init( void ) } Msg( "OpenSL ES audio initialized.\n" ); - + dma.backendName = "OpenSL ES"; return true; } @@ -254,4 +254,24 @@ void SNDDMA_BeginPainting( void ) { pthread_mutex_lock( &snddma_android_mutex ); } + +qboolean VoiceCapture_Init( void ) +{ + return false; +} + +qboolean VoiceCapture_Activate( qboolean activate ) +{ + return false; +} + +qboolean VoiceCapture_Lock( qboolean lock ) +{ + return false; +} + +void VoiceCapture_Shutdown( void ) +{ + +} #endif diff --git a/engine/platform/linux/in_evdev.c b/engine/platform/linux/in_evdev.c index 638ceddb..46075f38 100644 --- a/engine/platform/linux/in_evdev.c +++ b/engine/platform/linux/in_evdev.c @@ -343,23 +343,16 @@ void IN_EvdevFrame ( void ) { switch ( ev.code ) { - case REL_X: dx += ev.value; + case REL_X: + dx += ev.value; break; - case REL_Y: dy += ev.value; + case REL_Y: + dy += ev.value; break; - case REL_WHEEL: - if( ev.value > 0) - { - Key_Event( K_MWHEELDOWN, 1 ); - Key_Event( K_MWHEELDOWN, 0 ); - } - else - { - Key_Event( K_MWHEELUP, 1 ); - Key_Event( K_MWHEELUP, 0 ); - } + case REL_WHEEL: + IN_MWheelEvent( ev.value ); break; } } @@ -367,7 +360,7 @@ void IN_EvdevFrame ( void ) { int key = KeycodeFromEvdev( ev.code, ev.value ); - if( CVAR_TO_BOOL(evdev_keydebug) ) + if( CVAR_TO_BOOL( evdev_keydebug )) Con_Printf( "key %d %d %d\n", ev.code, key, ev.value ); Key_Event( key , ev.value ); diff --git a/engine/platform/linux/s_alsa.c b/engine/platform/linux/s_alsa.c index 5a694d39..6875a0c0 100644 --- a/engine/platform/linux/s_alsa.c +++ b/engine/platform/linux/s_alsa.c @@ -185,12 +185,12 @@ qboolean SNDDMA_Init( void ) } dma.buffer = Z_Malloc( samples * 2 ); //allocate pcm frame buffer - dma.samplepos = 0; - dma.samples = samples; dma.format.width = 2; dma.initialized = 1; + dma.backendName = "ALSA"; + snd_pcm_prepare( s_alsa.pcm_handle ); snd_pcm_writei( s_alsa.pcm_handle, dma.buffer, 2 * s_alsa.period_size ); snd_pcm_start( s_alsa.pcm_handle ); @@ -341,4 +341,24 @@ void SNDDMA_Activate( qboolean active ) } } +qboolean VoiceCapture_Init( void ) +{ + return false; +} + +qboolean VoiceCapture_Activate( qboolean activate ) +{ + return false; +} + +qboolean VoiceCapture_Lock( qboolean lock ) +{ + return false; +} + +void VoiceCapture_Shutdown( void ) +{ + +} + #endif diff --git a/engine/platform/platform.h b/engine/platform/platform.h index e764421a..0aaee2b5 100644 --- a/engine/platform/platform.h +++ b/engine/platform/platform.h @@ -37,9 +37,7 @@ double Platform_DoubleTime( void ); void Platform_Sleep( int msec ); void Platform_ShellExecute( const char *path, const char *parms ); void Platform_MessageBox( const char *title, const char *message, qboolean parentMainWindow ); -// commented out, as this is an optional feature or maybe implemented in system API directly -// see system.c -// qboolean Sys_DebuggerPresent( void ); +qboolean Sys_DebuggerPresent( void ); // optional, see Sys_DebugBreak #if XASH_ANDROID const char *Android_GetAndroidID( void ); @@ -162,4 +160,9 @@ void SNDDMA_Activate( qboolean active ); // pause audio // void SNDDMA_LockSound( void ); // unused // void SNDDMA_UnlockSound( void ); // unused +qboolean VoiceCapture_Init( void ); +void VoiceCapture_Shutdown( void ); +qboolean VoiceCapture_Activate( qboolean activate ); +qboolean VoiceCapture_Lock( qboolean lock ); + #endif // PLATFORM_H diff --git a/engine/platform/posix/lib_posix.c b/engine/platform/posix/lib_posix.c index c4026706..abfd46ad 100644 --- a/engine/platform/posix/lib_posix.c +++ b/engine/platform/posix/lib_posix.c @@ -102,7 +102,7 @@ void *COM_LoadLibrary( const char *dllname, int build_ordinals_table, qboolean d // try to find by linker(LD_LIBRARY_PATH, DYLD_LIBRARY_PATH, LD_32_LIBRARY_PATH and so on...) if( !pHandle ) { - pHandle = dlopen( dllname, RTLD_LAZY ); + pHandle = dlopen( dllname, RTLD_NOW ); if( pHandle ) return pHandle; @@ -139,7 +139,7 @@ void *COM_LoadLibrary( const char *dllname, int build_ordinals_table, qboolean d else #endif { - if( !( hInst->hInstance = dlopen( hInst->fullPath, RTLD_LAZY ) ) ) + if( !( hInst->hInstance = dlopen( hInst->fullPath, RTLD_NOW ) ) ) { COM_PushLibraryError( dlerror() ); Mem_Free( hInst ); @@ -188,12 +188,7 @@ void *COM_GetProcAddress( void *hInstance, const char *name ) void *COM_FunctionFromName( void *hInstance, const char *pName ) { - void *function; - if( !( function = COM_GetProcAddress( hInstance, pName ) ) ) - { - Con_Reportf( S_ERROR "FunctionFromName: Can't get symbol %s: %s\n", pName, dlerror()); - } - return function; + return COM_GetProcAddress( hInstance, pName ); } #ifdef XASH_DYNAMIC_DLADDR diff --git a/engine/platform/sdl/events.c b/engine/platform/sdl/events.c index 86d5cde0..515739dd 100644 --- a/engine/platform/sdl/events.c +++ b/engine/platform/sdl/events.c @@ -250,18 +250,6 @@ static void SDLash_KeyEvent( SDL_KeyboardEvent key ) Key_Event( keynum, down ); } -static void SDLash_MouseKey( int key, int down, int istouch ) -{ - if( CVAR_TO_BOOL( touch_emulate ) ) - { - Touch_KeyEvent( key, down ); - } - else if( in_mouseinitialized && !m_ignore->value && !istouch ) - { - Key_Event( key, down ); - } -} - /* ============= SDLash_MouseEvent @@ -270,14 +258,20 @@ SDLash_MouseEvent */ static void SDLash_MouseEvent( SDL_MouseButtonEvent button ) { - int down = button.state != SDL_RELEASED; - uint mstate = 0; + int down; #if SDL_VERSION_ATLEAST( 2, 0, 0 ) if( button.which == SDL_TOUCH_MOUSEID ) return; #endif + if( button.state == SDL_RELEASED ) + down = 0; + else if( button.clicks >= 2 ) + down = 2; // special state for double-click in UI + else + down = 1; + switch( button.button ) { case SDL_BUTTON_LEFT: @@ -297,10 +291,10 @@ static void SDLash_MouseEvent( SDL_MouseButtonEvent button ) break; #if ! SDL_VERSION_ATLEAST( 2, 0, 0 ) case SDL_BUTTON_WHEELUP: - Key_Event( K_MWHEELUP, down ); + IN_MWheelEvent( -1 ); break; case SDL_BUTTON_WHEELDOWN: - Key_Event( K_MWHEELDOWN, down ); + IN_MWheelEvent( 1 ); break; #endif // ! SDL_VERSION_ATLEAST( 2, 0, 0 ) default: @@ -435,9 +429,7 @@ static void SDLash_EventFilter( SDL_Event *event ) /* Mouse events */ case SDL_MOUSEMOTION: if( host.mouse_visible ) - { SDL_GetRelativeMouseState( NULL, NULL ); - } break; case SDL_MOUSEBUTTONUP: @@ -478,12 +470,8 @@ static void SDLash_EventFilter( SDL_Event *event ) break; #if SDL_VERSION_ATLEAST( 2, 0, 0 ) case SDL_MOUSEWHEEL: - { - int wheelbutton = event->wheel.y < 0 ? K_MWHEELDOWN : K_MWHEELUP; - Key_Event( wheelbutton, true ); - Key_Event( wheelbutton, false ); + IN_MWheelEvent( event->wheel.y ); break; - } /* Touch events */ case SDL_FINGERDOWN: @@ -556,7 +544,8 @@ static void SDLash_EventFilter( SDL_Event *event ) { // Swap axis to follow default axis binding: // LeftX, LeftY, RightX, RightY, TriggerRight, TriggerLeft - static int sdlControllerAxisToEngine[] = { + static int sdlControllerAxisToEngine[] = + { JOY_AXIS_SIDE, // SDL_CONTROLLER_AXIS_LEFTX, JOY_AXIS_FWD, // SDL_CONTROLLER_AXIS_LEFTY, JOY_AXIS_PITCH, // SDL_CONTROLLER_AXIS_RIGHTX, @@ -679,7 +668,7 @@ TODO: kill mouse in win32 clients too */ void Platform_PreCreateMove( void ) { - if( CVAR_TO_BOOL( m_ignore ) ) + if( CVAR_TO_BOOL( m_ignore )) { SDL_GetRelativeMouseState( NULL, NULL ); SDL_ShowCursor( SDL_TRUE ); diff --git a/engine/platform/sdl/in_sdl.c b/engine/platform/sdl/in_sdl.c index a369a90d..65282890 100644 --- a/engine/platform/sdl/in_sdl.c +++ b/engine/platform/sdl/in_sdl.c @@ -260,11 +260,11 @@ SDLash_InitCursors */ void SDLash_InitCursors( void ) { +#if SDL_VERSION_ATLEAST( 2, 0, 0 ) if( cursors.initialized ) SDLash_FreeCursors(); // load up all default cursors -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) cursors.cursors[dc_none] = NULL; cursors.cursors[dc_arrow] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); cursors.cursors[dc_ibeam] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM); @@ -278,8 +278,8 @@ void SDLash_InitCursors( void ) cursors.cursors[dc_sizeall] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEALL); cursors.cursors[dc_no] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NO); cursors.cursors[dc_hand] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND); -#endif cursors.initialized = true; +#endif } /* @@ -314,11 +314,10 @@ void Platform_SetCursorType( VGUI_DefaultCursor type ) { qboolean visible; +#if SDL_VERSION_ATLEAST( 2, 0, 0 ) if( !cursors.initialized ) return; - - if( cls.key_dest != key_game || cl.paused ) - return; +#endif switch( type ) { @@ -331,22 +330,34 @@ void Platform_SetCursorType( VGUI_DefaultCursor type ) break; } -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) - if( CVAR_TO_BOOL( touch_emulate )) + // never disable cursor in touch emulation mode + if( !visible && touch_emulate.value ) return; - if( visible && !host.mouse_visible ) + host.mouse_visible = visible; + VGui_UpdateInternalCursorState( type ); + +#if SDL_VERSION_ATLEAST( 2, 0, 0 ) + if( host.mouse_visible ) { SDL_SetCursor( cursors.cursors[type] ); SDL_ShowCursor( true ); Key_EnableTextInput( true, false ); } - else if( !visible && host.mouse_visible ) + else { SDL_ShowCursor( false ); Key_EnableTextInput( false, false ); } - host.mouse_visible = visible; +#else + if( host.mouse_visible ) + { + SDL_ShowCursor( true ); + } + else + { + SDL_ShowCursor( false ); + } #endif } diff --git a/engine/platform/sdl/s_sdl.c b/engine/platform/sdl/s_sdl.c index 8763969c..876926b3 100644 --- a/engine/platform/sdl/s_sdl.c +++ b/engine/platform/sdl/s_sdl.c @@ -18,6 +18,7 @@ GNU General Public License for more details. #if XASH_SOUND == SOUND_SDL #include "sound.h" +#include "voice.h" #include @@ -31,9 +32,11 @@ GNU General Public License for more details. #define SDL_OpenAudioDevice( a, b, c, d, e ) SDL_OpenAudio( ( c ), ( d ) ) #define SDL_CloseAudioDevice( a ) SDL_CloseAudio() #define SDL_PauseAudioDevice( a, b ) SDL_PauseAudio( ( b ) ) -#define SDLash_IsAudioError( x ) ( x ) != 0 +#define SDL_LockAudioDevice( x ) SDL_LockAudio() +#define SDL_UnlockAudioDevice( x ) SDL_UnlockAudio() +#define SDLash_IsAudioError( x ) (( x ) != 0) #else -#define SDLash_IsAudioError( x ) ( x ) == 0 +#define SDLash_IsAudioError( x ) (( x ) == 0) #endif /* @@ -43,6 +46,9 @@ so it can unlock and free the data block after it has been played. ======================================================================= */ static int sdl_dev; +static SDL_AudioDeviceID in_dev = 0; +static SDL_AudioFormat sdl_format; +static char sdl_backend_name[32]; //static qboolean snd_firsttime = true; //static qboolean primary_format_set; @@ -133,9 +139,12 @@ qboolean SNDDMA_Init( void ) dma.buffer = Z_Calloc( dma.samples * 2 ); dma.samplepos = 0; - Con_Printf( "Using SDL audio driver: %s @ %d Hz\n", SDL_GetCurrentAudioDriver( ), obtained.freq ); + sdl_format = obtained.format; + Con_Printf( "Using SDL audio driver: %s @ %d Hz\n", SDL_GetCurrentAudioDriver( ), obtained.freq ); + Q_snprintf( sdl_backend_name, sizeof( sdl_backend_name ), "SDL (%s)", SDL_GetCurrentAudioDriver( )); dma.initialized = true; + dma.backendName = sdl_backend_name; SNDDMA_Activate( true ); @@ -156,7 +165,7 @@ Makes sure dma.buffer is valid */ void SNDDMA_BeginPainting( void ) { - SDL_LockAudio( ); +// SDL_LockAudioDevice( sdl_dev ); } /* @@ -169,7 +178,7 @@ Also unlocks the dsound buffer */ void SNDDMA_Submit( void ) { - SDL_UnlockAudio( ); +// SDL_UnlockAudioDevice( sdl_dev ); } /* @@ -220,4 +229,98 @@ void SNDDMA_Activate( qboolean active ) SDL_PauseAudioDevice( sdl_dev, !active ); } + +/* +=========== +SDL_SoundInputCallback +=========== +*/ +void SDL_SoundInputCallback( void *userdata, Uint8 *stream, int len ) +{ + int size = Q_min( len, sizeof( voice.input_buffer ) - voice.input_buffer_pos ); + + // engine can't keep up, skip audio + if( !size ) + return; + + memcpy( voice.input_buffer + voice.input_buffer_pos, stream, size ); + voice.input_buffer_pos += size; +} + +/* +=========== +VoiceCapture_Init +=========== +*/ +qboolean VoiceCapture_Init( void ) +{ + SDL_AudioSpec wanted, spec; + + if( !SDLash_IsAudioError( in_dev )) + { + VoiceCapture_Shutdown(); + } + + SDL_zero( wanted ); + wanted.freq = voice.samplerate; + wanted.format = AUDIO_S16LSB; + wanted.channels = VOICE_PCM_CHANNELS; + wanted.samples = voice.frame_size; + wanted.callback = SDL_SoundInputCallback; + + in_dev = SDL_OpenAudioDevice( NULL, SDL_TRUE, &wanted, &spec, 0 ); + + if( SDLash_IsAudioError( in_dev )) + { + Con_Printf( "VoiceCapture_Init: error creating capture device (%s)\n", SDL_GetError() ); + return false; + } + + Con_Printf( S_NOTE "VoiceCapture_Init: capture device creation success (%i: %s)\n", in_dev, SDL_GetAudioDeviceName( in_dev, SDL_TRUE ) ); + return true; +} + +/* +=========== +VoiceCapture_Activate +=========== +*/ +qboolean VoiceCapture_Activate( qboolean activate ) +{ + if( SDLash_IsAudioError( in_dev )) + return false; + + SDL_PauseAudioDevice( in_dev, activate ? SDL_FALSE : SDL_TRUE ); + return true; +} + +/* +=========== +VoiceCapture_Lock +=========== +*/ +qboolean VoiceCapture_Lock( qboolean lock ) +{ + if( SDLash_IsAudioError( in_dev )) + return false; + + if( lock ) SDL_LockAudioDevice( in_dev ); + else SDL_UnlockAudioDevice( in_dev ); + + return true; +} + +/* +========== +VoiceCapture_Shutdown +========== +*/ +void VoiceCapture_Shutdown( void ) +{ + if( SDLash_IsAudioError( in_dev )) + return; + + SDL_CloseAudioDevice( in_dev ); +} + #endif // XASH_SOUND == SOUND_SDL diff --git a/engine/platform/sdl/vid_sdl.c b/engine/platform/sdl/vid_sdl.c index 4496c772..e9dd87e3 100644 --- a/engine/platform/sdl/vid_sdl.c +++ b/engine/platform/sdl/vid_sdl.c @@ -553,7 +553,9 @@ static qboolean VID_SetScreenResolution( int width, int height ) Uint32 wndFlags = 0; static string wndname; +#if !XASH_APPLE if( vid_highdpi->value ) wndFlags |= SDL_WINDOW_ALLOW_HIGHDPI; +#endif Q_strncpy( wndname, GI->title, sizeof( wndname )); want.w = width; @@ -598,7 +600,7 @@ void VID_RestoreScreenResolution( void ) #endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) } -#if XASH_WIN32 && !XASH_64BIT // ICO support only for Win32 +#if XASH_WIN32 // ICO support only for Win32 #include "SDL_syswm.h" static void WIN_SetWindowIcon( HICON ico ) { @@ -609,7 +611,7 @@ static void WIN_SetWindowIcon( HICON ico ) if( SDL_GetWindowWMInfo( host.hWnd, &wminfo ) ) { - SetClassLong( wminfo.info.win.window, GCL_HICON, (LONG)ico ); + SetClassLongPtr( wminfo.info.win.window, GCLP_HICON, (LONG)ico ); } } #endif @@ -628,6 +630,7 @@ qboolean VID_CreateWindow( int width, int height, qboolean fullscreen ) qboolean iconLoaded = false; char iconpath[MAX_STRING]; int xpos, ypos; + const char *localIcoPath; if( vid_highdpi->value ) wndFlags |= SDL_WINDOW_ALLOW_HIGHDPI; Q_strncpy( wndname, GI->title, sizeof( wndname )); @@ -686,14 +689,12 @@ qboolean VID_CreateWindow( int width, int height, qboolean fullscreen ) VID_RestoreScreenResolution(); } -#if XASH_WIN32 && !XASH_64BIT // ICO support only for Win32 - if( FS_FileExists( GI->iconpath, true ) ) +#if XASH_WIN32 // ICO support only for Win32 + if(( localIcoPath = FS_GetDiskPath( GI->iconpath, true ))) { HICON ico; - char localPath[MAX_PATH]; - Q_snprintf( localPath, sizeof( localPath ), "%s/%s", GI->gamefolder, GI->iconpath ); - ico = (HICON)LoadImage( NULL, localPath, IMAGE_ICON, 0, 0, LR_LOADFROMFILE|LR_DEFAULTSIZE ); + ico = (HICON)LoadImage( NULL, localIcoPath, IMAGE_ICON, 0, 0, LR_LOADFROMFILE|LR_DEFAULTSIZE ); if( ico ) { @@ -728,7 +729,7 @@ qboolean VID_CreateWindow( int width, int height, qboolean fullscreen ) } } -#if XASH_WIN32 && !XASH_64BIT // ICO support only for Win32 +#if XASH_WIN32 // ICO support only for Win32 if( !iconLoaded ) { WIN_SetWindowIcon( LoadIcon( host.hInst, MAKEINTRESOURCE( 101 ) ) ); diff --git a/engine/platform/stub/s_stub.c b/engine/platform/stub/s_stub.c index 509ae9b7..6bd96966 100644 --- a/engine/platform/stub/s_stub.c +++ b/engine/platform/stub/s_stub.c @@ -94,5 +94,25 @@ void SNDDMA_Shutdown( void ) } } +qboolean VoiceCapture_Init( void ) +{ + return false; +} + +qboolean VoiceCapture_Activate( qboolean activate ) +{ + return false; +} + +qboolean VoiceCapture_Lock( qboolean lock ) +{ + return false; +} + +void VoiceCapture_Shutdown( void ) +{ + +} + #endif #endif diff --git a/engine/platform/win32/lib_custom_win.c b/engine/platform/win32/lib_custom_win.c new file mode 100644 index 00000000..f4dfe4aa --- /dev/null +++ b/engine/platform/win32/lib_custom_win.c @@ -0,0 +1,478 @@ +/* +lib_custom_win.c - win32 custom dlls loader +Copyright (C) 2008 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ +#include "common.h" + +#if XASH_LIB == LIB_WIN32 && XASH_X86 +#include "lib_win.h" + +#define NUMBER_OF_DIRECTORY_ENTRIES 16 +#ifndef IMAGE_SIZEOF_BASE_RELOCATION +#define IMAGE_SIZEOF_BASE_RELOCATION ( sizeof( IMAGE_BASE_RELOCATION )) +#endif + +typedef struct +{ + PIMAGE_NT_HEADERS headers; + byte *codeBase; + void **modules; + int numModules; + int initialized; +} MEMORYMODULE, *PMEMORYMODULE; + +// Protection flags for memory pages (Executable, Readable, Writeable) +static int ProtectionFlags[2][2][2] = +{ +{ +{ PAGE_NOACCESS, PAGE_WRITECOPY }, // not executable +{ PAGE_READONLY, PAGE_READWRITE }, +}, +{ +{ PAGE_EXECUTE, PAGE_EXECUTE_WRITECOPY }, // executable +{ PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE }, +}, +}; + +typedef BOOL (WINAPI *DllEntryProc)( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved ); + +#define GET_HEADER_DICTIONARY( module, idx ) &(module)->headers->OptionalHeader.DataDirectory[idx] + +static void CopySections( const byte *data, PIMAGE_NT_HEADERS old_headers, PMEMORYMODULE module ) +{ + PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION( module->headers ); + byte *codeBase = module->codeBase; + int i, size; + byte *dest; + + for( i = 0; i < module->headers->FileHeader.NumberOfSections; i++, section++ ) + { + if( section->SizeOfRawData == 0 ) + { + // section doesn't contain data in the dll itself, but may define + // uninitialized data + size = old_headers->OptionalHeader.SectionAlignment; + + if( size > 0 ) + { + dest = (byte *)VirtualAlloc((byte *)CALCULATE_ADDRESS(codeBase, section->VirtualAddress), size, MEM_COMMIT, PAGE_READWRITE ); + section->Misc.PhysicalAddress = (DWORD)dest; + memset( dest, 0, size ); + } + // section is empty + continue; + } + + // commit memory block and copy data from dll + dest = (byte *)VirtualAlloc((byte *)CALCULATE_ADDRESS(codeBase, section->VirtualAddress), section->SizeOfRawData, MEM_COMMIT, PAGE_READWRITE ); + memcpy( dest, (byte *)CALCULATE_ADDRESS(data, section->PointerToRawData), section->SizeOfRawData ); + section->Misc.PhysicalAddress = (DWORD)dest; + } +} + +static void FreeSections( PIMAGE_NT_HEADERS old_headers, PMEMORYMODULE module ) +{ + PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(module->headers); + byte *codeBase = module->codeBase; + int i, size; + + for( i = 0; i < module->headers->FileHeader.NumberOfSections; i++, section++ ) + { + if( section->SizeOfRawData == 0 ) + { + size = old_headers->OptionalHeader.SectionAlignment; + if( size > 0 ) + { + VirtualFree((byte *)CALCULATE_ADDRESS( codeBase, section->VirtualAddress ), size, MEM_DECOMMIT ); + section->Misc.PhysicalAddress = 0; + } + continue; + } + + VirtualFree((byte *)CALCULATE_ADDRESS( codeBase, section->VirtualAddress ), section->SizeOfRawData, MEM_DECOMMIT ); + section->Misc.PhysicalAddress = 0; + } +} + +static void FinalizeSections( MEMORYMODULE *module ) +{ + PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION( module->headers ); + int i; + + // loop through all sections and change access flags + for( i = 0; i < module->headers->FileHeader.NumberOfSections; i++, section++ ) + { + DWORD protect, oldProtect, size; + int executable = (section->Characteristics & IMAGE_SCN_MEM_EXECUTE) != 0; + int readable = (section->Characteristics & IMAGE_SCN_MEM_READ) != 0; + int writeable = (section->Characteristics & IMAGE_SCN_MEM_WRITE) != 0; + + if( section->Characteristics & IMAGE_SCN_MEM_DISCARDABLE ) + { + // section is not needed any more and can safely be freed + VirtualFree((LPVOID)section->Misc.PhysicalAddress, section->SizeOfRawData, MEM_DECOMMIT); + continue; + } + + // determine protection flags based on characteristics + protect = ProtectionFlags[executable][readable][writeable]; + if( section->Characteristics & IMAGE_SCN_MEM_NOT_CACHED ) + protect |= PAGE_NOCACHE; + + // determine size of region + size = section->SizeOfRawData; + + if( size == 0 ) + { + if( section->Characteristics & IMAGE_SCN_CNT_INITIALIZED_DATA ) + size = module->headers->OptionalHeader.SizeOfInitializedData; + else if( section->Characteristics & IMAGE_SCN_CNT_UNINITIALIZED_DATA ) + size = module->headers->OptionalHeader.SizeOfUninitializedData; + } + + if( size > 0 ) + { + // change memory access flags + if( !VirtualProtect((LPVOID)section->Misc.PhysicalAddress, size, protect, &oldProtect )) + Sys_Error( "error protecting memory page\n" ); + } + } +} + +static void PerformBaseRelocation( MEMORYMODULE *module, DWORD delta ) +{ + PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY( module, IMAGE_DIRECTORY_ENTRY_BASERELOC ); + byte *codeBase = module->codeBase; + DWORD i; + + if( directory->Size > 0 ) + { + PIMAGE_BASE_RELOCATION relocation = (PIMAGE_BASE_RELOCATION)CALCULATE_ADDRESS( codeBase, directory->VirtualAddress ); + for( ; relocation->VirtualAddress > 0; ) + { + byte *dest = (byte *)CALCULATE_ADDRESS( codeBase, relocation->VirtualAddress ); + word *relInfo = (word *)((byte *)relocation + IMAGE_SIZEOF_BASE_RELOCATION ); + + for( i = 0; i<((relocation->SizeOfBlock-IMAGE_SIZEOF_BASE_RELOCATION) / 2); i++, relInfo++ ) + { + DWORD *patchAddrHL; + int type, offset; + + // the upper 4 bits define the type of relocation + type = *relInfo >> 12; + // the lower 12 bits define the offset + offset = *relInfo & 0xfff; + + switch( type ) + { + case IMAGE_REL_BASED_ABSOLUTE: + // skip relocation + break; + case IMAGE_REL_BASED_HIGHLOW: + // change complete 32 bit address + patchAddrHL = (DWORD *)CALCULATE_ADDRESS( dest, offset ); + *patchAddrHL += delta; + break; + default: + Con_Reportf( S_ERROR "PerformBaseRelocation: unknown relocation: %d\n", type ); + break; + } + } + + // advance to next relocation block + relocation = (PIMAGE_BASE_RELOCATION)CALCULATE_ADDRESS( relocation, relocation->SizeOfBlock ); + } + } +} + +FARPROC MemoryGetProcAddress( void *module, const char *name ) +{ + PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY((MEMORYMODULE *)module, IMAGE_DIRECTORY_ENTRY_EXPORT ); + byte *codeBase = ((PMEMORYMODULE)module)->codeBase; + PIMAGE_EXPORT_DIRECTORY exports; + int idx = -1; + DWORD i, *nameRef; + WORD *ordinal; + + if( directory->Size == 0 ) + { + // no export table found + return NULL; + } + + exports = (PIMAGE_EXPORT_DIRECTORY)CALCULATE_ADDRESS( codeBase, directory->VirtualAddress ); + + if( exports->NumberOfNames == 0 || exports->NumberOfFunctions == 0 ) + { + // DLL doesn't export anything + return NULL; + } + + // search function name in list of exported names + nameRef = (DWORD *)CALCULATE_ADDRESS( codeBase, exports->AddressOfNames ); + ordinal = (WORD *)CALCULATE_ADDRESS( codeBase, exports->AddressOfNameOrdinals ); + + for( i = 0; i < exports->NumberOfNames; i++, nameRef++, ordinal++ ) + { + // GetProcAddress case insensative ????? + if( !Q_stricmp( name, (const char *)CALCULATE_ADDRESS( codeBase, *nameRef ))) + { + idx = *ordinal; + break; + } + } + + if( idx == -1 ) + { + // exported symbol not found + return NULL; + } + + if((DWORD)idx > exports->NumberOfFunctions ) + { + // name <-> ordinal number don't match + return NULL; + } + + // addressOfFunctions contains the RVAs to the "real" functions + return (FARPROC)CALCULATE_ADDRESS( codeBase, *(DWORD *)CALCULATE_ADDRESS( codeBase, exports->AddressOfFunctions + (idx * 4))); +} + +static int BuildImportTable( MEMORYMODULE *module ) +{ + PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY( module, IMAGE_DIRECTORY_ENTRY_IMPORT ); + byte *codeBase = module->codeBase; + int result = 1; + + if( directory->Size > 0 ) + { + PIMAGE_IMPORT_DESCRIPTOR importDesc = (PIMAGE_IMPORT_DESCRIPTOR)CALCULATE_ADDRESS( codeBase, directory->VirtualAddress ); + + for( ; !IsBadReadPtr( importDesc, sizeof( IMAGE_IMPORT_DESCRIPTOR )) && importDesc->Name; importDesc++ ) + { + DWORD *thunkRef, *funcRef; + LPCSTR libname; + void *handle; + + libname = (LPCSTR)CALCULATE_ADDRESS( codeBase, importDesc->Name ); + handle = COM_LoadLibrary( libname, false, true ); + + if( handle == NULL ) + { + Con_Printf( S_ERROR "couldn't load library %s\n", libname ); + result = 0; + break; + } + + module->modules = (void *)Mem_Realloc( host.mempool, module->modules, (module->numModules + 1) * (sizeof( void* ))); + module->modules[module->numModules++] = handle; + + if( importDesc->OriginalFirstThunk ) + { + thunkRef = (DWORD *)CALCULATE_ADDRESS( codeBase, importDesc->OriginalFirstThunk ); + funcRef = (DWORD *)CALCULATE_ADDRESS( codeBase, importDesc->FirstThunk ); + } + else + { + // no hint table + thunkRef = (DWORD *)CALCULATE_ADDRESS( codeBase, importDesc->FirstThunk ); + funcRef = (DWORD *)CALCULATE_ADDRESS( codeBase, importDesc->FirstThunk ); + } + + for( ; *thunkRef; thunkRef++, funcRef++ ) + { + LPCSTR funcName; + + if( IMAGE_SNAP_BY_ORDINAL( *thunkRef )) + { + funcName = (LPCSTR)IMAGE_ORDINAL( *thunkRef ); + *funcRef = (DWORD)COM_GetProcAddress( handle, funcName ); + } + else + { + PIMAGE_IMPORT_BY_NAME thunkData = (PIMAGE_IMPORT_BY_NAME)CALCULATE_ADDRESS( codeBase, *thunkRef ); + funcName = (LPCSTR)&thunkData->Name; + *funcRef = (DWORD)COM_GetProcAddress( handle, funcName ); + } + + if( *funcRef == 0 ) + { + Con_Printf( S_ERROR "%s unable to find address: %s\n", libname, funcName ); + result = 0; + break; + } + } + if( !result ) break; + } + } + return result; +} + +void MemoryFreeLibrary( void *hInstance ) +{ + MEMORYMODULE *module = (MEMORYMODULE *)hInstance; + + if( module != NULL ) + { + int i; + + if( module->initialized != 0 ) + { + // notify library about detaching from process + DllEntryProc DllEntry = (DllEntryProc)CALCULATE_ADDRESS( module->codeBase, module->headers->OptionalHeader.AddressOfEntryPoint ); + (*DllEntry)((HINSTANCE)module->codeBase, DLL_PROCESS_DETACH, 0 ); + module->initialized = 0; + } + + if( module->modules != NULL ) + { + // free previously opened libraries + for( i = 0; i < module->numModules; i++ ) + { + if( module->modules[i] != NULL ) + COM_FreeLibrary( module->modules[i] ); + } + Mem_Free( module->modules ); // Mem_Realloc end + } + + FreeSections( module->headers, module ); + + if( module->codeBase != NULL ) + { + // release memory of library + VirtualFree( module->codeBase, 0, MEM_RELEASE ); + } + + HeapFree( GetProcessHeap(), 0, module ); + } +} + +void *MemoryLoadLibrary( const char *name ) +{ + MEMORYMODULE *result = NULL; + PIMAGE_DOS_HEADER dos_header; + PIMAGE_NT_HEADERS old_header; + byte *code, *headers; + DWORD locationDelta; + DllEntryProc DllEntry; + string errorstring; + qboolean successfull; + void *data = NULL; + + data = FS_LoadFile( name, NULL, false ); + + if( !data ) + { + Q_sprintf( errorstring, "couldn't load %s", name ); + goto library_error; + } + + dos_header = (PIMAGE_DOS_HEADER)data; + if( dos_header->e_magic != IMAGE_DOS_SIGNATURE ) + { + Q_sprintf( errorstring, "%s it's not a valid executable file", name ); + goto library_error; + } + + old_header = (PIMAGE_NT_HEADERS)&((const byte *)(data))[dos_header->e_lfanew]; + if( old_header->Signature != IMAGE_NT_SIGNATURE ) + { + Q_sprintf( errorstring, "%s missing PE header", name ); + goto library_error; + } + + // reserve memory for image of library + code = (byte *)VirtualAlloc((LPVOID)(old_header->OptionalHeader.ImageBase), old_header->OptionalHeader.SizeOfImage, MEM_RESERVE, PAGE_READWRITE ); + + if( code == NULL ) + { + // try to allocate memory at arbitrary position + code = (byte *)VirtualAlloc( NULL, old_header->OptionalHeader.SizeOfImage, MEM_RESERVE, PAGE_READWRITE ); + } + + if( code == NULL ) + { + Q_sprintf( errorstring, "%s can't reserve memory", name ); + goto library_error; + } + + result = (MEMORYMODULE *)HeapAlloc( GetProcessHeap(), 0, sizeof( MEMORYMODULE )); + result->codeBase = code; + result->numModules = 0; + result->modules = NULL; + result->initialized = 0; + + // XXX: is it correct to commit the complete memory region at once? + // calling DllEntry raises an exception if we don't... + VirtualAlloc( code, old_header->OptionalHeader.SizeOfImage, MEM_COMMIT, PAGE_READWRITE ); + + // commit memory for headers + headers = (byte *)VirtualAlloc( code, old_header->OptionalHeader.SizeOfHeaders, MEM_COMMIT, PAGE_READWRITE ); + + // copy PE header to code + memcpy( headers, dos_header, dos_header->e_lfanew + old_header->OptionalHeader.SizeOfHeaders ); + result->headers = (PIMAGE_NT_HEADERS)&((const byte *)(headers))[dos_header->e_lfanew]; + + // update position + result->headers->OptionalHeader.ImageBase = (DWORD)code; + + // copy sections from DLL file block to new memory location + CopySections( data, old_header, result ); + + // adjust base address of imported data + locationDelta = (DWORD)(code - old_header->OptionalHeader.ImageBase); + if( locationDelta != 0 ) PerformBaseRelocation( result, locationDelta ); + + // load required dlls and adjust function table of imports + if( !BuildImportTable( result )) + { + Q_sprintf( errorstring, "%s failed to build import table", name ); + goto library_error; + } + + // mark memory pages depending on section headers and release + // sections that are marked as "discardable" + FinalizeSections( result ); + + // get entry point of loaded library + if( result->headers->OptionalHeader.AddressOfEntryPoint != 0 ) + { + DllEntry = (DllEntryProc)CALCULATE_ADDRESS( code, result->headers->OptionalHeader.AddressOfEntryPoint ); + if( DllEntry == 0 ) + { + Q_sprintf( errorstring, "%s has no entry point", name ); + goto library_error; + } + + // notify library about attaching to process + successfull = (*DllEntry)((HINSTANCE)code, DLL_PROCESS_ATTACH, 0 ); + if( !successfull ) + { + Q_sprintf( errorstring, "can't attach library %s", name ); + goto library_error; + } + result->initialized = 1; + } + + Mem_Free( data ); // release memory + return (void *)result; +library_error: + // cleanup + if( data ) Mem_Free( data ); + MemoryFreeLibrary( result ); + Con_Printf( S_ERROR "LoadLibrary: %s\n", errorstring ); + + return NULL; +} + +#endif // XASH_LIB == LIB_WIN32 && XASH_X86 \ No newline at end of file diff --git a/engine/platform/win32/lib_win.c b/engine/platform/win32/lib_win.c index b5ee3e76..21ee1fab 100644 --- a/engine/platform/win32/lib_win.c +++ b/engine/platform/win32/lib_win.c @@ -1,5 +1,5 @@ /* -library.c - custom dlls loader +lib_win.c - win32 dynamic library loading Copyright (C) 2008 Uncle Mike This program is free software: you can redistribute it and/or modify @@ -12,481 +12,11 @@ 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_LIB == LIB_WIN32 + #include "common.h" -#include "library.h" -#include -#define CALCULATE_ADDRESS( base, offset ) ( ( DWORD )( base ) + ( DWORD )( offset ) ) - -#if XASH_X86 -/* ---------------------------------------------------------------- - - Custom dlls loader - ---------------------------------------------------------------- -*/ - -#define NUMBER_OF_DIRECTORY_ENTRIES 16 -#ifndef IMAGE_SIZEOF_BASE_RELOCATION -#define IMAGE_SIZEOF_BASE_RELOCATION ( sizeof( IMAGE_BASE_RELOCATION )) -#endif - -typedef struct -{ - PIMAGE_NT_HEADERS headers; - byte *codeBase; - void **modules; - int numModules; - int initialized; -} MEMORYMODULE, *PMEMORYMODULE; - -// Protection flags for memory pages (Executable, Readable, Writeable) -static int ProtectionFlags[2][2][2] = -{ -{ -{ PAGE_NOACCESS, PAGE_WRITECOPY }, // not executable -{ PAGE_READONLY, PAGE_READWRITE }, -}, -{ -{ PAGE_EXECUTE, PAGE_EXECUTE_WRITECOPY }, // executable -{ PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE }, -}, -}; - -typedef BOOL (WINAPI *DllEntryProc)( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved ); - -#define GET_HEADER_DICTIONARY( module, idx ) &(module)->headers->OptionalHeader.DataDirectory[idx] - -static void CopySections( const byte *data, PIMAGE_NT_HEADERS old_headers, PMEMORYMODULE module ) -{ - PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION( module->headers ); - byte *codeBase = module->codeBase; - int i, size; - byte *dest; - - for( i = 0; i < module->headers->FileHeader.NumberOfSections; i++, section++ ) - { - if( section->SizeOfRawData == 0 ) - { - // section doesn't contain data in the dll itself, but may define - // uninitialized data - size = old_headers->OptionalHeader.SectionAlignment; - - if( size > 0 ) - { - dest = (byte *)VirtualAlloc((byte *)CALCULATE_ADDRESS(codeBase, section->VirtualAddress), size, MEM_COMMIT, PAGE_READWRITE ); - section->Misc.PhysicalAddress = (DWORD)dest; - memset( dest, 0, size ); - } - // section is empty - continue; - } - - // commit memory block and copy data from dll - dest = (byte *)VirtualAlloc((byte *)CALCULATE_ADDRESS(codeBase, section->VirtualAddress), section->SizeOfRawData, MEM_COMMIT, PAGE_READWRITE ); - memcpy( dest, (byte *)CALCULATE_ADDRESS(data, section->PointerToRawData), section->SizeOfRawData ); - section->Misc.PhysicalAddress = (DWORD)dest; - } -} - -static void FreeSections( PIMAGE_NT_HEADERS old_headers, PMEMORYMODULE module ) -{ - PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(module->headers); - byte *codeBase = module->codeBase; - int i, size; - - for( i = 0; i < module->headers->FileHeader.NumberOfSections; i++, section++ ) - { - if( section->SizeOfRawData == 0 ) - { - size = old_headers->OptionalHeader.SectionAlignment; - if( size > 0 ) - { - VirtualFree((byte *)CALCULATE_ADDRESS( codeBase, section->VirtualAddress ), size, MEM_DECOMMIT ); - section->Misc.PhysicalAddress = 0; - } - continue; - } - - VirtualFree((byte *)CALCULATE_ADDRESS( codeBase, section->VirtualAddress ), section->SizeOfRawData, MEM_DECOMMIT ); - section->Misc.PhysicalAddress = 0; - } -} - -static void FinalizeSections( MEMORYMODULE *module ) -{ - PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION( module->headers ); - int i; - - // loop through all sections and change access flags - for( i = 0; i < module->headers->FileHeader.NumberOfSections; i++, section++ ) - { - DWORD protect, oldProtect, size; - int executable = (section->Characteristics & IMAGE_SCN_MEM_EXECUTE) != 0; - int readable = (section->Characteristics & IMAGE_SCN_MEM_READ) != 0; - int writeable = (section->Characteristics & IMAGE_SCN_MEM_WRITE) != 0; - - if( section->Characteristics & IMAGE_SCN_MEM_DISCARDABLE ) - { - // section is not needed any more and can safely be freed - VirtualFree((LPVOID)section->Misc.PhysicalAddress, section->SizeOfRawData, MEM_DECOMMIT); - continue; - } - - // determine protection flags based on characteristics - protect = ProtectionFlags[executable][readable][writeable]; - if( section->Characteristics & IMAGE_SCN_MEM_NOT_CACHED ) - protect |= PAGE_NOCACHE; - - // determine size of region - size = section->SizeOfRawData; - - if( size == 0 ) - { - if( section->Characteristics & IMAGE_SCN_CNT_INITIALIZED_DATA ) - size = module->headers->OptionalHeader.SizeOfInitializedData; - else if( section->Characteristics & IMAGE_SCN_CNT_UNINITIALIZED_DATA ) - size = module->headers->OptionalHeader.SizeOfUninitializedData; - } - - if( size > 0 ) - { - // change memory access flags - if( !VirtualProtect((LPVOID)section->Misc.PhysicalAddress, size, protect, &oldProtect )) - Sys_Error( "error protecting memory page\n" ); - } - } -} - -static void PerformBaseRelocation( MEMORYMODULE *module, DWORD delta ) -{ - PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY( module, IMAGE_DIRECTORY_ENTRY_BASERELOC ); - byte *codeBase = module->codeBase; - DWORD i; - - if( directory->Size > 0 ) - { - PIMAGE_BASE_RELOCATION relocation = (PIMAGE_BASE_RELOCATION)CALCULATE_ADDRESS( codeBase, directory->VirtualAddress ); - for( ; relocation->VirtualAddress > 0; ) - { - byte *dest = (byte *)CALCULATE_ADDRESS( codeBase, relocation->VirtualAddress ); - word *relInfo = (word *)((byte *)relocation + IMAGE_SIZEOF_BASE_RELOCATION ); - - for( i = 0; i<((relocation->SizeOfBlock-IMAGE_SIZEOF_BASE_RELOCATION) / 2); i++, relInfo++ ) - { - DWORD *patchAddrHL; - int type, offset; - - // the upper 4 bits define the type of relocation - type = *relInfo >> 12; - // the lower 12 bits define the offset - offset = *relInfo & 0xfff; - - switch( type ) - { - case IMAGE_REL_BASED_ABSOLUTE: - // skip relocation - break; - case IMAGE_REL_BASED_HIGHLOW: - // change complete 32 bit address - patchAddrHL = (DWORD *)CALCULATE_ADDRESS( dest, offset ); - *patchAddrHL += delta; - break; - default: - Con_Reportf( S_ERROR "PerformBaseRelocation: unknown relocation: %d\n", type ); - break; - } - } - - // advance to next relocation block - relocation = (PIMAGE_BASE_RELOCATION)CALCULATE_ADDRESS( relocation, relocation->SizeOfBlock ); - } - } -} - -static FARPROC MemoryGetProcAddress( void *module, const char *name ) -{ - PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY((MEMORYMODULE *)module, IMAGE_DIRECTORY_ENTRY_EXPORT ); - byte *codeBase = ((PMEMORYMODULE)module)->codeBase; - PIMAGE_EXPORT_DIRECTORY exports; - int idx = -1; - DWORD i, *nameRef; - WORD *ordinal; - - if( directory->Size == 0 ) - { - // no export table found - return NULL; - } - - exports = (PIMAGE_EXPORT_DIRECTORY)CALCULATE_ADDRESS( codeBase, directory->VirtualAddress ); - - if( exports->NumberOfNames == 0 || exports->NumberOfFunctions == 0 ) - { - // DLL doesn't export anything - return NULL; - } - - // search function name in list of exported names - nameRef = (DWORD *)CALCULATE_ADDRESS( codeBase, exports->AddressOfNames ); - ordinal = (WORD *)CALCULATE_ADDRESS( codeBase, exports->AddressOfNameOrdinals ); - - for( i = 0; i < exports->NumberOfNames; i++, nameRef++, ordinal++ ) - { - // GetProcAddress case insensative ????? - if( !Q_stricmp( name, (const char *)CALCULATE_ADDRESS( codeBase, *nameRef ))) - { - idx = *ordinal; - break; - } - } - - if( idx == -1 ) - { - // exported symbol not found - return NULL; - } - - if((DWORD)idx > exports->NumberOfFunctions ) - { - // name <-> ordinal number don't match - return NULL; - } - - // addressOfFunctions contains the RVAs to the "real" functions - return (FARPROC)CALCULATE_ADDRESS( codeBase, *(DWORD *)CALCULATE_ADDRESS( codeBase, exports->AddressOfFunctions + (idx * 4))); -} - -static int BuildImportTable( MEMORYMODULE *module ) -{ - PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY( module, IMAGE_DIRECTORY_ENTRY_IMPORT ); - byte *codeBase = module->codeBase; - int result = 1; - - if( directory->Size > 0 ) - { - PIMAGE_IMPORT_DESCRIPTOR importDesc = (PIMAGE_IMPORT_DESCRIPTOR)CALCULATE_ADDRESS( codeBase, directory->VirtualAddress ); - - for( ; !IsBadReadPtr( importDesc, sizeof( IMAGE_IMPORT_DESCRIPTOR )) && importDesc->Name; importDesc++ ) - { - DWORD *thunkRef, *funcRef; - LPCSTR libname; - void *handle; - - libname = (LPCSTR)CALCULATE_ADDRESS( codeBase, importDesc->Name ); - handle = COM_LoadLibrary( libname, false, true ); - - if( handle == NULL ) - { - Con_Printf( S_ERROR "couldn't load library %s\n", libname ); - result = 0; - break; - } - - module->modules = (void *)Mem_Realloc( host.mempool, module->modules, (module->numModules + 1) * (sizeof( void* ))); - module->modules[module->numModules++] = handle; - - if( importDesc->OriginalFirstThunk ) - { - thunkRef = (DWORD *)CALCULATE_ADDRESS( codeBase, importDesc->OriginalFirstThunk ); - funcRef = (DWORD *)CALCULATE_ADDRESS( codeBase, importDesc->FirstThunk ); - } - else - { - // no hint table - thunkRef = (DWORD *)CALCULATE_ADDRESS( codeBase, importDesc->FirstThunk ); - funcRef = (DWORD *)CALCULATE_ADDRESS( codeBase, importDesc->FirstThunk ); - } - - for( ; *thunkRef; thunkRef++, funcRef++ ) - { - LPCSTR funcName; - - if( IMAGE_SNAP_BY_ORDINAL( *thunkRef )) - { - funcName = (LPCSTR)IMAGE_ORDINAL( *thunkRef ); - *funcRef = (DWORD)COM_GetProcAddress( handle, funcName ); - } - else - { - PIMAGE_IMPORT_BY_NAME thunkData = (PIMAGE_IMPORT_BY_NAME)CALCULATE_ADDRESS( codeBase, *thunkRef ); - funcName = (LPCSTR)&thunkData->Name; - *funcRef = (DWORD)COM_GetProcAddress( handle, funcName ); - } - - if( *funcRef == 0 ) - { - Con_Printf( S_ERROR "%s unable to find address: %s\n", libname, funcName ); - result = 0; - break; - } - } - if( !result ) break; - } - } - return result; -} - -static void MemoryFreeLibrary( void *hInstance ) -{ - MEMORYMODULE *module = (MEMORYMODULE *)hInstance; - - if( module != NULL ) - { - int i; - - if( module->initialized != 0 ) - { - // notify library about detaching from process - DllEntryProc DllEntry = (DllEntryProc)CALCULATE_ADDRESS( module->codeBase, module->headers->OptionalHeader.AddressOfEntryPoint ); - (*DllEntry)((HINSTANCE)module->codeBase, DLL_PROCESS_DETACH, 0 ); - module->initialized = 0; - } - - if( module->modules != NULL ) - { - // free previously opened libraries - for( i = 0; i < module->numModules; i++ ) - { - if( module->modules[i] != NULL ) - COM_FreeLibrary( module->modules[i] ); - } - Mem_Free( module->modules ); // Mem_Realloc end - } - - FreeSections( module->headers, module ); - - if( module->codeBase != NULL ) - { - // release memory of library - VirtualFree( module->codeBase, 0, MEM_RELEASE ); - } - - HeapFree( GetProcessHeap(), 0, module ); - } -} - -void *MemoryLoadLibrary( const char *name ) -{ - MEMORYMODULE *result = NULL; - PIMAGE_DOS_HEADER dos_header; - PIMAGE_NT_HEADERS old_header; - byte *code, *headers; - DWORD locationDelta; - DllEntryProc DllEntry; - string errorstring; - qboolean successfull; - void *data = NULL; - - data = FS_LoadFile( name, NULL, false ); - - if( !data ) - { - Q_sprintf( errorstring, "couldn't load %s", name ); - goto library_error; - } - - dos_header = (PIMAGE_DOS_HEADER)data; - if( dos_header->e_magic != IMAGE_DOS_SIGNATURE ) - { - Q_sprintf( errorstring, "%s it's not a valid executable file", name ); - goto library_error; - } - - old_header = (PIMAGE_NT_HEADERS)&((const byte *)(data))[dos_header->e_lfanew]; - if( old_header->Signature != IMAGE_NT_SIGNATURE ) - { - Q_sprintf( errorstring, "%s missing PE header", name ); - goto library_error; - } - - // reserve memory for image of library - code = (byte *)VirtualAlloc((LPVOID)(old_header->OptionalHeader.ImageBase), old_header->OptionalHeader.SizeOfImage, MEM_RESERVE, PAGE_READWRITE ); - - if( code == NULL ) - { - // try to allocate memory at arbitrary position - code = (byte *)VirtualAlloc( NULL, old_header->OptionalHeader.SizeOfImage, MEM_RESERVE, PAGE_READWRITE ); - } - - if( code == NULL ) - { - Q_sprintf( errorstring, "%s can't reserve memory", name ); - goto library_error; - } - - result = (MEMORYMODULE *)HeapAlloc( GetProcessHeap(), 0, sizeof( MEMORYMODULE )); - result->codeBase = code; - result->numModules = 0; - result->modules = NULL; - result->initialized = 0; - - // XXX: is it correct to commit the complete memory region at once? - // calling DllEntry raises an exception if we don't... - VirtualAlloc( code, old_header->OptionalHeader.SizeOfImage, MEM_COMMIT, PAGE_READWRITE ); - - // commit memory for headers - headers = (byte *)VirtualAlloc( code, old_header->OptionalHeader.SizeOfHeaders, MEM_COMMIT, PAGE_READWRITE ); - - // copy PE header to code - memcpy( headers, dos_header, dos_header->e_lfanew + old_header->OptionalHeader.SizeOfHeaders ); - result->headers = (PIMAGE_NT_HEADERS)&((const byte *)(headers))[dos_header->e_lfanew]; - - // update position - result->headers->OptionalHeader.ImageBase = (DWORD)code; - - // copy sections from DLL file block to new memory location - CopySections( data, old_header, result ); - - // adjust base address of imported data - locationDelta = (DWORD)(code - old_header->OptionalHeader.ImageBase); - if( locationDelta != 0 ) PerformBaseRelocation( result, locationDelta ); - - // load required dlls and adjust function table of imports - if( !BuildImportTable( result )) - { - Q_sprintf( errorstring, "%s failed to build import table", name ); - goto library_error; - } - - // mark memory pages depending on section headers and release - // sections that are marked as "discardable" - FinalizeSections( result ); - - // get entry point of loaded library - if( result->headers->OptionalHeader.AddressOfEntryPoint != 0 ) - { - DllEntry = (DllEntryProc)CALCULATE_ADDRESS( code, result->headers->OptionalHeader.AddressOfEntryPoint ); - if( DllEntry == 0 ) - { - Q_sprintf( errorstring, "%s has no entry point", name ); - goto library_error; - } - - // notify library about attaching to process - successfull = (*DllEntry)((HINSTANCE)code, DLL_PROCESS_ATTACH, 0 ); - if( !successfull ) - { - Q_sprintf( errorstring, "can't attach library %s", name ); - goto library_error; - } - result->initialized = 1; - } - - Mem_Free( data ); // release memory - return (void *)result; -library_error: - // cleanup - if( data ) Mem_Free( data ); - MemoryFreeLibrary( result ); - Con_Printf( S_ERROR "LoadLibrary: %s\n", errorstring ); - - return NULL; -} -#endif +#if XASH_LIB == LIB_WIN32 +#include "lib_win.h" static DWORD GetOffsetByRVA( DWORD rva, PIMAGE_NT_HEADERS nt_header ) { @@ -764,70 +294,136 @@ table_error: return false; } -qboolean COM_CheckLibraryDirectDependency( const char *name, const char *depname, qboolean directpath ) +static const char *GetLastErrorAsString( void ) { - PIMAGE_DOS_HEADER dosHeader; - PIMAGE_NT_HEADERS peHeader; - PIMAGE_DATA_DIRECTORY importDir; + DWORD errorcode; + static string errormessage; + + errorcode = GetLastError(); + if ( !errorcode ) return ""; + + FormatMessageA( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_MAX_WIDTH_MASK, + NULL, errorcode, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), + (LPSTR)&errormessage, sizeof( errormessage ), NULL ); + + return errormessage; +} + +static PIMAGE_IMPORT_DESCRIPTOR GetImportDescriptor( const char *name, byte *data, PIMAGE_NT_HEADERS *peheader ) +{ + PIMAGE_DOS_HEADER dosHeader; + PIMAGE_NT_HEADERS peHeader; + PIMAGE_DATA_DIRECTORY importDir; PIMAGE_IMPORT_DESCRIPTOR importDesc; - string errorstring = ""; - byte *data = NULL; - dll_user_t *hInst; - hInst = FS_FindLibrary( name, directpath ); - if( !hInst ) + if ( !data ) { - return false; // nothing to load + Con_Printf( S_ERROR "%s: couldn't load %s\n", __FUNCTION__, name ); + return NULL; } - data = FS_LoadFile( name, NULL, false ); - if( !data ) + dosHeader = (PIMAGE_DOS_HEADER)data; + if ( dosHeader->e_magic != IMAGE_DOS_SIGNATURE ) { - Q_snprintf( errorstring, sizeof( errorstring ), "couldn't load %s", name ); - goto libraryerror; + Con_Printf( S_ERROR "%s: %s is not a valid executable file\n", __FUNCTION__, name ); + return NULL; } - dosHeader = ( PIMAGE_DOS_HEADER )data; - if( dosHeader->e_magic != IMAGE_DOS_SIGNATURE ) + peHeader = (PIMAGE_NT_HEADERS)( data + dosHeader->e_lfanew ); + if ( peHeader->Signature != IMAGE_NT_SIGNATURE ) { - Q_snprintf( errorstring, sizeof( errorstring ), "%s it's not a valid executable file", name ); - goto libraryerror; - } - - peHeader = ( PIMAGE_NT_HEADERS )(data + dosHeader->e_lfanew); - if( peHeader->Signature != IMAGE_NT_SIGNATURE ) - { - Q_snprintf( errorstring, sizeof( errorstring ), "%s missing PE header", name ); - goto libraryerror; + Con_Printf( S_ERROR "%s: %s is missing a PE header\n", __FUNCTION__, name ); + return NULL; } importDir = &peHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]; if( importDir->Size <= 0 ) { - Con_Printf( S_WARN "%s: %s has no dependencies. Is this library valid?\n", __FUNCTION__, name ); - goto libraryerror; + Con_Printf( S_ERROR "%s: %s has no dependencies\n", __FUNCTION__, name ); + return NULL; } - importDesc = (PIMAGE_IMPORT_DESCRIPTOR)CALCULATE_ADDRESS( data, GetOffsetByRVA(importDir->VirtualAddress, peHeader) ); - for( ; !IsBadReadPtr( importDesc, sizeof( IMAGE_IMPORT_DESCRIPTOR)) && importDesc->Name; importDesc++ ) + *peheader = peHeader; + importDesc = (PIMAGE_IMPORT_DESCRIPTOR)CALCULATE_ADDRESS( data, GetOffsetByRVA( importDir->VirtualAddress, peHeader ) ); + + return importDesc; +} + +static void ListMissingModules( dll_user_t *hInst ) +{ + PIMAGE_NT_HEADERS peHeader; + PIMAGE_IMPORT_DESCRIPTOR importDesc; + byte *data; + + if ( !hInst ) return; + + data = FS_LoadFile( hInst->dllName, NULL, false ); + if ( !data ) return; + + importDesc = GetImportDescriptor( hInst->dllName, data, &peHeader ); + if ( !importDesc ) + { + Mem_Free( data ); + return; + } + + for( ; !IsBadReadPtr( importDesc, sizeof( IMAGE_IMPORT_DESCRIPTOR ) ) && importDesc->Name; importDesc++ ) + { + HMODULE hMod; + const char *importName = (const char *)CALCULATE_ADDRESS( data, GetOffsetByRVA( importDesc->Name, peHeader ) ); + + hMod = LoadLibraryEx( importName, NULL, LOAD_LIBRARY_AS_DATAFILE ); + if ( !hMod ) + COM_PushLibraryError( va( "%s not found!", importName ) ); + else + FreeLibrary( hMod ); + } + + Mem_Free( data ); + return; +} + +qboolean COM_CheckLibraryDirectDependency( const char *name, const char *depname, qboolean directpath ) +{ + PIMAGE_NT_HEADERS peHeader; + PIMAGE_IMPORT_DESCRIPTOR importDesc; + byte *data; + dll_user_t *hInst; + qboolean ret = FALSE; + + hInst = FS_FindLibrary( name, directpath ); + if ( !hInst ) return FALSE; + + data = FS_LoadFile( name, NULL, false ); + if ( !data ) + { + COM_FreeLibrary( hInst ); + return FALSE; + } + + importDesc = GetImportDescriptor( name, data, &peHeader ); + if ( !importDesc ) + { + COM_FreeLibrary( hInst ); + Mem_Free( data ); + return FALSE; + } + + for( ; !IsBadReadPtr( importDesc, sizeof( IMAGE_IMPORT_DESCRIPTOR ) ) && importDesc->Name; importDesc++ ) { const char *importName = (const char *)CALCULATE_ADDRESS( data, GetOffsetByRVA( importDesc->Name, peHeader ) ); - Con_Reportf( "library %s has direct dependency %s\n", name, importName ); - if( !Q_stricmp( importName, depname ) ) + if ( !Q_stricmp( importName, depname ) ) { + COM_FreeLibrary( hInst ); Mem_Free( data ); - return true; + return TRUE; } } -libraryerror: - if( errorstring[0] ) - { - Con_Printf( S_ERROR "%s: %s\n", __FUNCTION__, errorstring ); - } - if( data ) Mem_Free( data ); // release memory - return false; + COM_FreeLibrary( hInst ); + Mem_Free( data ); + return FALSE; } /* @@ -841,12 +437,19 @@ void *COM_LoadLibrary( const char *dllname, int build_ordinals_table, qboolean d { dll_user_t *hInst; + COM_ResetLibraryError(); + hInst = FS_FindLibrary( dllname, directpath ); - if( !hInst ) return NULL; // nothing to load + if( !hInst ) + { + COM_PushLibraryError( va( "Failed to find library %s", dllname ) ); + return NULL; + } if( hInst->encrypted ) { - Con_Printf( S_ERROR "LoadLibrary: couldn't load encrypted library %s\n", dllname ); + COM_PushLibraryError( va( "Library %s is encrypted, cannot load", hInst->shortPath ) ); + COM_FreeLibrary( hInst ); return NULL; } @@ -863,7 +466,11 @@ void *COM_LoadLibrary( const char *dllname, int build_ordinals_table, qboolean d if( !hInst->hInstance ) { - Con_Reportf( "LoadLibrary: Loading %s - failed\n", dllname ); + COM_PushLibraryError( GetLastErrorAsString() ); + + if ( GetLastError() == ERROR_MOD_NOT_FOUND ) + ListMissingModules( hInst ); + COM_FreeLibrary( hInst ); return NULL; } @@ -873,14 +480,12 @@ void *COM_LoadLibrary( const char *dllname, int build_ordinals_table, qboolean d { if( !LibraryLoadSymbols( hInst )) { - Con_Reportf( "LoadLibrary: Loading %s - failed\n", dllname ); + COM_PushLibraryError( va( "Failed to load library %s", dllname ) ); COM_FreeLibrary( hInst ); return NULL; } } - Con_Reportf( "LoadLibrary: Loading %s - ok\n", dllname ); - return hInst; } @@ -944,7 +549,7 @@ void *COM_FunctionFromName( void *hInstance, const char *pName ) if( !Q_strcmp( pName, hInst->names[i] )) { index = hInst->ordinals[i]; - return hInst->funcs[index] + hInst->funcBase; + return (void *)( hInst->funcs[index] + hInst->funcBase ); } } diff --git a/engine/platform/win32/lib_win.h b/engine/platform/win32/lib_win.h new file mode 100644 index 00000000..429dd4f8 --- /dev/null +++ b/engine/platform/win32/lib_win.h @@ -0,0 +1,24 @@ +/* +lib_win.h - common win32 dll definitions +Copyright (C) 2022 Flying With Gauss + +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 "library.h" +#include +#include + +#define CALCULATE_ADDRESS( base, offset ) ( ( DWORD )( base ) + ( DWORD )( offset ) ) + +FARPROC MemoryGetProcAddress( void *module, const char *name ); +void MemoryFreeLibrary( void *hInstance ); +void *MemoryLoadLibrary( const char *name ); \ No newline at end of file diff --git a/engine/platform/win32/sys_win.c b/engine/platform/win32/sys_win.c index 8af51fd2..917404ea 100644 --- a/engine/platform/win32/sys_win.c +++ b/engine/platform/win32/sys_win.c @@ -56,7 +56,7 @@ void Platform_ShellExecute( const char *path, const char *parms ) void Platform_UpdateStatusLine( void ) { - int clientsCount; + int clientsCount, botsCountUnused; char szStatus[128]; static double lastTime; @@ -67,7 +67,7 @@ void Platform_UpdateStatusLine( void ) if(( sv.time - lastTime ) < 0.5f ) return; - clientsCount = SV_GetConnectedClientsCount( NULL ); + SV_GetPlayerCount( &clientsCount, &botsCountUnused ); Q_snprintf( szStatus, sizeof( szStatus ) - 1, "%.1f fps %2i/%2i on %16s", 1.f / sv.frametime, clientsCount, svs.maxclients, host.game.levelName ); #ifdef XASH_WIN32 Wcon_SetStatus( szStatus ); diff --git a/engine/ref_api.h b/engine/ref_api.h index 3c3119d3..c5215541 100644 --- a/engine/ref_api.h +++ b/engine/ref_api.h @@ -27,8 +27,12 @@ GNU General Public License for more details. #include "studio.h" #include "r_efx.h" #include "com_image.h" +#include "filesystem.h" -#define REF_API_VERSION 1 +// RefAPI changelog: +// 1. Initial release +// 2. FS functions are removed, instead we have full fs_api_t +#define REF_API_VERSION 2 #define TF_SKY (TF_SKYSIDE|TF_NOMIPMAP) @@ -252,7 +256,7 @@ typedef enum typedef struct ref_api_s { - int (*EngineGetParm)( int parm, int arg ); // generic + intptr_t (*EngineGetParm)( int parm, int arg ); // generic // cvar handlers cvar_t *(*Cvar_Get)( const char *szName, const char *szValue, int flags, const char *description ); @@ -335,7 +339,7 @@ typedef struct ref_api_s // utils void (*CL_ExtraUpdate)( void ); - void (*Host_Error)( const char *fmt, ... ) _format( 1 ) NORETURN; + void (*Host_Error)( const char *fmt, ... ) _format( 1 ); void (*COM_SetRandomSeed)( int lSeed ); float (*COM_RandomFloat)( float rmin, float rmax ); int (*COM_RandomLong)( int rmin, int rmax ); @@ -367,13 +371,6 @@ typedef struct ref_api_s void (*COM_FreeLibrary)( void *handle ); void *(*COM_GetProcAddress)( void *handle, const char *name ); - // filesystem - byte* (*COM_LoadFile)( const char *path, fs_offset_t *pLength, qboolean gamedironly ); - // use Mem_Free instead - // void (*COM_FreeFile)( void *buffer ); - int (*FS_FileExists)( const char *filename, int gamedironly ); - void (*FS_AllowDirectPaths)( qboolean enable ); - // video init // try to create window // will call GL_SetupAttributes in case of REF_GL @@ -430,6 +427,9 @@ typedef struct ref_api_s void (*pfnDrawNormalTriangles)( void ); void (*pfnDrawTransparentTriangles)( void ); render_interface_t *drawFuncs; + + // filesystem exports + fs_api_t *fsapi; } ref_api_t; struct mip_s; diff --git a/engine/server/server.h b/engine/server/server.h index 4775397f..6bb28da8 100644 --- a/engine/server/server.h +++ b/engine/server/server.h @@ -418,6 +418,8 @@ extern convar_t sv_stopspeed; extern convar_t sv_maxspeed; extern convar_t sv_wateralpha; extern convar_t sv_wateramp; +extern convar_t sv_voiceenable; +extern convar_t sv_voicequality; extern convar_t sv_stepsize; extern convar_t sv_maxvelocity; extern convar_t sv_rollangle; @@ -433,6 +435,8 @@ extern convar_t sv_consistency; extern convar_t sv_password; extern convar_t sv_uploadmax; extern convar_t sv_trace_messages; +extern convar_t sv_enttools_enable; +extern convar_t sv_enttools_maxfire; extern convar_t deathmatch; extern convar_t hostname; extern convar_t skill; @@ -470,7 +474,6 @@ void SV_SendResource( resource_t *pResource, sizebuf_t *msg ); void SV_SendResourceList( sv_client_t *cl ); void SV_AddToMaster( netadr_t from, sizebuf_t *msg ); qboolean SV_ProcessUserAgent( netadr_t from, const char *useragent ); -int SV_GetConnectedClientsCount( int *bots ); void Host_SetServerState( int state ); qboolean SV_IsSimulating( void ); void SV_FreeClients( void ); @@ -547,6 +550,7 @@ void SV_InitClientMove( void ); void SV_UpdateServerInfo( void ); void SV_EndRedirect( void ); void SV_RejectConnection( netadr_t from, const char *fmt, ... ) _format( 2 ); +void SV_GetPlayerCount( int *clients, int *bots ); // // sv_cmds.c @@ -640,8 +644,11 @@ void SV_RestartAmbientSounds( void ); void SV_RestartDecals( void ); void SV_RestartStaticEnts( void ); int pfnGetCurrentPlayer( void ); +int pfnDropToFloor( edict_t* e ); edict_t *SV_EdictNum( int n ); char *SV_Localinfo( void ); +void SV_SetModel( edict_t *ent, const char *name ); + // // sv_log.c // diff --git a/engine/server/sv_client.c b/engine/server/sv_client.c index e1215491..1476c982 100644 --- a/engine/server/sv_client.c +++ b/engine/server/sv_client.c @@ -42,6 +42,35 @@ typedef struct ucmd_s static int g_userid = 1; +/* +================= +SV_GetPlayerCount + +================= +*/ +void SV_GetPlayerCount( int *players, int *bots ) +{ + int i; + + *players = 0; + *bots = 0; + + if( !svs.clients ) + return; + + for( i = 0; i < svs.maxclients; i++ ) + { + if( svs.clients[i].state >= cs_connected ) + { + if( FBitSet( svs.clients[i].flags, FCL_FAKECLIENT )) + (*bots)++; + else + (*players)++; + } + + } +} + /* ================= SV_GetChallenge @@ -819,30 +848,26 @@ Responds with short info for broadcast scans The second parameter should be the current protocol version number. ================ */ -void SV_Info( netadr_t from ) +void SV_Info( netadr_t from, int protocolVersion ) { char string[MAX_INFO_STRING]; - int version; // ignore in single player if( svs.maxclients == 1 || !svs.initialized ) return; - version = Q_atoi( Cmd_Argv( 1 )); string[0] = '\0'; - if( version != PROTOCOL_VERSION ) + if( protocolVersion != PROTOCOL_VERSION ) { Q_snprintf( string, sizeof( string ), "%s: wrong version\n", hostname.string ); } else { - int i, count = 0; + int i, count, bots; qboolean havePassword = COM_CheckStringEmpty( sv_password.string ); - for( i = 0; i < svs.maxclients; i++ ) - if( svs.clients[i].state >= cs_connected ) - count++; + SV_GetPlayerCount( &count, &bots ); // a1ba: send protocol version to distinguish old engine and new Info_SetValueForKey( string, "p", va( "%i", PROTOCOL_VERSION ), MAX_INFO_STRING ); @@ -1021,7 +1046,7 @@ int SV_CalcPing( sv_client_t *cl ) // bots don't have a real ping if( FBitSet( cl->flags, FCL_FAKECLIENT ) || !cl->frames ) - return 5; + return 0; if( SV_UPDATE_BACKUP <= 31 ) { @@ -1291,7 +1316,7 @@ a deathmatch. */ void SV_PutClientInServer( sv_client_t *cl ) { - static byte msg_buf[0x20200]; // MAX_INIT_MSG + some space + static byte msg_buf[MAX_INIT_MSG + 0x200]; // MAX_INIT_MSG + some space edict_t *ent = cl->edict; sizebuf_t msg; @@ -2094,6 +2119,780 @@ static qboolean SV_SendBuildInfo_f( sv_client_t *cl ) return true; } +/* +================== +SV_GetCrossEnt +================== +*/ +static edict_t *SV_GetCrossEnt( edict_t *player ) +{ + edict_t *ent = EDICT_NUM(1); + edict_t *closest = NULL; + float flMaxDot = 0.94; + vec3_t forward; + vec3_t viewPos; + int i; + float maxLen = 1000; + + AngleVectors( player->v.v_angle, forward, NULL, NULL ); + VectorAdd( player->v.origin, player->v.view_ofs, viewPos ); + + // find bmodels by trace + { + trace_t trace; + vec3_t target; + + VectorMA( viewPos, 1000, forward, target ); + trace = SV_Move( viewPos, vec3_origin, vec3_origin, target, 0, player, false ); + closest = trace.ent; + VectorSubtract( viewPos, trace.endpos, target ); + maxLen = VectorLength(target) + 30; + } + + // check untraceable entities + for ( i = 1; i < svgame.numEntities; i++, ent++ ) + { + vec3_t vecLOS; + vec3_t vecOrigin; + float flDot, traceLen; + vec3_t boxSize; + trace_t trace; + vec3_t vecTrace; + + if( ent->free ) + continue; + + if( ent->v.solid == SOLID_BSP || ent->v.movetype == MOVETYPE_PUSHSTEP ) + continue; // bsp models will be found by trace later + + // do not touch following weapons + if( ent->v.movetype == MOVETYPE_FOLLOW ) + continue; + + if( ent == player ) + continue; + + VectorAdd( ent->v.absmin, ent->v.absmax, vecOrigin ); + VectorScale( vecOrigin, 0.5, vecOrigin ); + + VectorSubtract( vecOrigin, viewPos, vecLOS ); + traceLen = VectorLength(vecLOS); + + if( traceLen > maxLen ) + continue; + + VectorCopy( ent->v.size, boxSize); + VectorScale( boxSize, 0.5, boxSize ); + + if ( vecLOS[0] > boxSize[0] ) + vecLOS[0] -= boxSize[0]; + else if ( vecLOS[0] < -boxSize[0] ) + vecLOS[0] += boxSize[0]; + else + vecLOS[0] = 0; + + if ( vecLOS[1] > boxSize[1] ) + vecLOS[1] -= boxSize[1]; + else if ( vecLOS[1] < -boxSize[1] ) + vecLOS[1] += boxSize[1]; + else + vecLOS[1] = 0; + + if ( vecLOS[2] > boxSize[2] ) + vecLOS[2] -= boxSize[2]; + else if ( vecLOS[2] < -boxSize[2] ) + vecLOS[2] += boxSize[2]; + else + vecLOS[2] = 0; + VectorNormalize( vecLOS ); + + flDot = DotProduct (vecLOS , forward); + if ( flDot <= flMaxDot ) + continue; + + trace = SV_Move( viewPos, vec3_origin, vec3_origin, vecOrigin, 0, player, false ); + VectorSubtract( trace.endpos, viewPos, vecTrace ); + if( VectorLength( vecTrace ) + 30 < traceLen ) + continue; + closest = ent, flMaxDot = flDot; + } + + return closest; +} + +/* +================== +SV_EntFindSingle +================== +*/ +static edict_t *SV_EntFindSingle( sv_client_t *cl, const char *pattern ) +{ + edict_t *ent = NULL; + int i = 0; + + if( Q_isdigit( pattern ) ) + { + i = Q_atoi( pattern ); + + if( i >= svgame.numEntities ) + return NULL; + } + else if( !Q_stricmp( pattern, "!cross" ) ) + { + ent = SV_GetCrossEnt( cl->edict ); + + if( !SV_IsValidEdict( ent ) ) + return NULL; + + i = NUM_FOR_EDICT( ent ); + } + else if( pattern[0] == '!' ) // check for correct instance with !(num)_(serial) + { + const char *p = pattern + 1; + i = Q_atoi( p ); + + while( Q_isdigit( p )) p++; + + if( *p++ != '_' ) + return NULL; + + if( i >= svgame.numEntities ) + return NULL; + + ent = EDICT_NUM( i ); + + if( ent->serialnumber != Q_atoi( p ) ) + return NULL; + } + else + { + for( i = svgame.globals->maxClients + 1; i < svgame.numEntities; i++ ) + { + ent = EDICT_NUM( i ); + + if( !SV_IsValidEdict( ent ) ) + continue; + + if( Q_stricmpext( pattern, STRING( ent->v.targetname ) ) ) + break; + } + } + + ent = EDICT_NUM( i ); + + if( !SV_IsValidEdict( ent ) ) + return NULL; + + return ent; +} + +/* +=============== +SV_EntList_f + +Print list of entities to client +=============== +*/ +static qboolean SV_EntList_f( sv_client_t *cl ) +{ + vec3_t borigin; + edict_t *ent = NULL; + int i; + + for( i = 0; i < svgame.numEntities; i++ ) + { + ent = EDICT_NUM( i ); + if( !SV_IsValidEdict( ent )) + continue; + + // filter by string + if( Cmd_Argc() > 1 ) + { + if( !Q_stricmpext( Cmd_Argv( 1 ), STRING( ent->v.classname ) ) && !Q_stricmpext( Cmd_Argv( 1 ), STRING( ent->v.targetname ) ) ) + continue; + } + + VectorAdd( ent->v.absmin, ent->v.absmax, borigin ); + VectorScale( borigin, 0.5, borigin ); + + SV_ClientPrintf( cl, "%5i origin: %.f %.f %.f", i, ent->v.origin[0], ent->v.origin[1], ent->v.origin[2] ); + SV_ClientPrintf( cl, "%5i borigin: %.f %.f %.f", i, borigin[0], borigin[1], borigin[2] ); + + if( ent->v.classname ) + SV_ClientPrintf( cl, ", class: %s", STRING( ent->v.classname )); + + if( ent->v.globalname ) + SV_ClientPrintf( cl, ", global: %s", STRING( ent->v.globalname )); + + if( ent->v.targetname ) + SV_ClientPrintf( cl, ", name: %s", STRING( ent->v.targetname )); + + if( ent->v.target ) + SV_ClientPrintf( cl, ", target: %s", STRING( ent->v.target )); + + if( ent->v.model ) + SV_ClientPrintf( cl, ", model: %s", STRING( ent->v.model )); + + SV_ClientPrintf( cl, "\n" ); + } + return true; +} + +/* +=============== +SV_EntInfo_f + +Print specified entity information to client +=============== +*/ +static qboolean SV_EntInfo_f( sv_client_t *cl ) +{ + edict_t *ent = NULL; + vec3_t borigin; + + if( Cmd_Argc() != 2 ) + { + SV_ClientPrintf( cl, "Use ent_info \n" ); + return false; + } + + ent = SV_EntFindSingle( cl, Cmd_Argv( 1 ) ); + + if( !SV_IsValidEdict( ent )) + return false; + + VectorAdd( ent->v.absmin, ent->v.absmax, borigin ); + VectorScale( borigin, 0.5, borigin ); + + SV_ClientPrintf( cl, "origin: %.f %.f %.f\n", ent->v.origin[0], ent->v.origin[1], ent->v.origin[2] ); + SV_ClientPrintf( cl, "angles: %.f %.f %.f\n", ent->v.angles[0], ent->v.angles[1], ent->v.angles[2] ); + SV_ClientPrintf( cl, "borigin: %.f %.f %.f\n", borigin[0], borigin[1], borigin[2] ); + + if( ent->v.classname ) + SV_ClientPrintf( cl, "class: %s\n", STRING( ent->v.classname )); + + if( ent->v.globalname ) + SV_ClientPrintf( cl, "global: %s\n", STRING( ent->v.globalname )); + + if( ent->v.targetname ) + SV_ClientPrintf( cl, "name: %s\n", STRING( ent->v.targetname )); + + if( ent->v.target ) + SV_ClientPrintf( cl, "target: %s\n", STRING( ent->v.target )); + + if( ent->v.model ) + SV_ClientPrintf( cl, "model: %s\n", STRING( ent->v.model )); + + SV_ClientPrintf( cl, "health: %.f\n", ent->v.health ); + + if( ent->v.gravity != 1.0f ) + SV_ClientPrintf( cl, "gravity: %.2f\n", ent->v.gravity ); + + SV_ClientPrintf( cl, "movetype: %d\n", ent->v.movetype ); + SV_ClientPrintf( cl, "rendermode: %d\n", ent->v.rendermode ); + SV_ClientPrintf( cl, "renderfx: %d\n", ent->v.renderfx ); + SV_ClientPrintf( cl, "renderamt: %f\n", ent->v.renderamt ); + SV_ClientPrintf( cl, "rendercolor: %f %f %f\n", ent->v.rendercolor[0], ent->v.rendercolor[1], ent->v.rendercolor[2] ); + SV_ClientPrintf( cl, "maxspeed: %f\n", ent->v.maxspeed ); + + if( ent->v.solid ) + SV_ClientPrintf( cl, "solid: %d\n", ent->v.solid ); + + SV_ClientPrintf( cl, "flags: 0x%x\n", ent->v.flags ); + SV_ClientPrintf( cl, "spawnflags: 0x%x\n", ent->v.spawnflags ); + return true; +} + +/* +=============== +SV_EntFire_f + +Perform some actions +=============== +*/ +static qboolean SV_EntFire_f( sv_client_t *cl ) +{ + edict_t *ent = NULL; + int i = 1, count = 0; + qboolean single; // true if user specified something that match single entity + + if( Cmd_Argc() < 3 ) + { + SV_ClientPrintf( cl, "Use ent_fire []\n" + "Use ent_fire 0 help to get command list\n" ); + return false; + } + + if( ( single = Q_isdigit( Cmd_Argv( 1 ) ) ) ) + { + i = Q_atoi( Cmd_Argv( 1 ) ); + + if( i < 0 || i >= svgame.numEntities ) + return false; + + ent = EDICT_NUM( i ); + } + else if( ( single = !Q_stricmp( Cmd_Argv( 1 ), "!cross" ) ) ) + { + ent = SV_GetCrossEnt( cl->edict ); + + if (!SV_IsValidEdict(ent)) + return false; + + i = NUM_FOR_EDICT( ent ); + } + else if( ( single = ( Cmd_Argv( 1 )[0] == '!') ) ) // check for correct instance with !(num)_(serial) + { + const char *cmd = Cmd_Argv( 1 ) + 1; + i = Q_atoi( cmd ); + + while( Q_isdigit( cmd )) cmd++; + + if( *cmd++ != '_' ) + return false; + + if( i < 0 || i >= svgame.numEntities ) + return false; + + ent = EDICT_NUM( i ); + if( ent->serialnumber != Q_atoi( cmd ) ) + return false; + } + else + { + i = svgame.globals->maxClients + 1; + } + + for( ; ( i < svgame.numEntities ) && ( count < sv_enttools_maxfire.value ); i++ ) + { + ent = EDICT_NUM( i ); + if( !SV_IsValidEdict( ent )) + { + // SV_ClientPrintf( cl, PRINT_LOW, "Got invalid entity\n" ); + if( single ) + break; + continue; + } + + // if user specified not a number, try find such entity + if( !single ) + { + if( !Q_stricmpext( Cmd_Argv( 1 ), STRING( ent->v.targetname ) ) && !Q_stricmpext( Cmd_Argv( 1 ), STRING( ent->v.classname ) )) + continue; + } + + SV_ClientPrintf( cl, "entity %i\n", i ); + + count++; + + if( !Q_stricmp( Cmd_Argv( 2 ), "health" ) ) + ent->v.health = Q_atoi( Cmd_Argv ( 3 ) ); + else if( !Q_stricmp( Cmd_Argv( 2 ), "gravity" ) ) + ent->v.gravity = Q_atof( Cmd_Argv ( 3 ) ); + else if( !Q_stricmp( Cmd_Argv( 2 ), "movetype" ) ) + ent->v.movetype = Q_atoi( Cmd_Argv ( 3 ) ); + else if( !Q_stricmp( Cmd_Argv( 2 ), "solid" ) ) + ent->v.solid = Q_atoi( Cmd_Argv ( 3 ) ); + else if( !Q_stricmp( Cmd_Argv( 2 ), "rename" ) ) + ent->v.targetname = ALLOC_STRING( Cmd_Argv ( 3 ) ); + else if( !Q_stricmp( Cmd_Argv( 2 ), "settarget" ) ) + ent->v.target = ALLOC_STRING( Cmd_Argv ( 3 ) ); + else if( !Q_stricmp( Cmd_Argv( 2 ), "setmodel" ) ) + SV_SetModel( ent, Cmd_Argv( 3 ) ); + else if( !Q_stricmp( Cmd_Argv( 2 ), "set" ) ) + { + string keyname; + string value; + KeyValueData pkvd; + if( Cmd_Argc() != 5 ) + return false; + + pkvd.szClassName = (char*)STRING( ent->v.classname ); + Q_strncpy( keyname, Cmd_Argv( 3 ), sizeof( keyname )); + Q_strncpy( value, Cmd_Argv( 4 ), sizeof( value )); + pkvd.szKeyName = keyname; + pkvd.szValue = value; + pkvd.fHandled = false; + svgame.dllFuncs.pfnKeyValue( ent, &pkvd ); + + if( pkvd.fHandled ) + SV_ClientPrintf( cl, "value set successfully!\n" ); + } + else if( !Q_stricmp( Cmd_Argv( 2 ), "touch" ) ) + { + if( Cmd_Argc() == 4 ) + { + edict_t *other = SV_EntFindSingle( cl, Cmd_Argv( 3 ) ); + if( other && other->pvPrivateData ) + svgame.dllFuncs.pfnTouch( ent, other ); + } + else + svgame.dllFuncs.pfnTouch( ent, cl->edict ); + } + else if( !Q_stricmp( Cmd_Argv( 2 ), "use" ) ) + { + if( Cmd_Argc() == 4 ) + { + edict_t *other = SV_EntFindSingle( cl, Cmd_Argv( 3 ) ); + if( other && other->pvPrivateData ) + svgame.dllFuncs.pfnUse( ent, other ); + } + else + svgame.dllFuncs.pfnUse( ent, cl->edict ); + } + else if( !Q_stricmp( Cmd_Argv( 2 ), "movehere" ) ) + { + ent->v.origin[2] = cl->edict->v.origin[2] + 25; + ent->v.origin[1] = cl->edict->v.origin[1] + 100 * sin( DEG2RAD( cl->edict->v.angles[1] ) ); + ent->v.origin[0] = cl->edict->v.origin[0] + 100 * cos( DEG2RAD( cl->edict->v.angles[1] ) ); + SV_LinkEdict( ent, true ); + } + else if( !Q_stricmp( Cmd_Argv( 2 ), "drop2floor" ) ) + { + pfnDropToFloor( ent ); + } + else if( !Q_stricmp( Cmd_Argv( 2 ), "moveup" ) ) + { + float dist = 25; + if( Cmd_Argc() >= 4 ) + dist = Q_atof( Cmd_Argv( 3 ) ); + ent->v.origin[2] += dist; + if( Cmd_Argc() >= 5 ) + { + dist = Q_atof( Cmd_Argv( 4 ) ); + ent->v.origin[0] += dist * cos( DEG2RAD( cl->edict->v.angles[1] ) ); + ent->v.origin[1] += dist * sin( DEG2RAD( cl->edict->v.angles[1] ) ); + } + + } + else if( !Q_stricmp( Cmd_Argv( 2 ), "becomeowner" ) ) + { + if( Cmd_Argc() == 4 ) + ent->v.owner = SV_EntFindSingle( cl, Cmd_Argv( 3 ) ); + else + ent->v.owner = cl->edict; + } + else if( !Q_stricmp( Cmd_Argv( 2 ), "becomeenemy" ) ) + { + if( Cmd_Argc() == 4 ) + ent->v.enemy = SV_EntFindSingle( cl, Cmd_Argv( 3 ) ); + else + ent->v.enemy = cl->edict; + } + else if( !Q_stricmp( Cmd_Argv( 2 ), "becomeaiment" ) ) + { + if( Cmd_Argc() == 4 ) + ent->v.aiment= SV_EntFindSingle( cl, Cmd_Argv( 3 ) ); + else + ent->v.aiment = cl->edict; + } + else if( !Q_stricmp( Cmd_Argv( 2 ), "hullmin" ) ) + { + if( Cmd_Argc() != 6 ) + return false; + ent->v.mins[0] = Q_atof( Cmd_Argv( 3 ) ); + ent->v.mins[1] = Q_atof( Cmd_Argv( 4 ) ); + ent->v.mins[2] = Q_atof( Cmd_Argv( 5 ) ); + } + else if( !Q_stricmp( Cmd_Argv( 2 ), "hullmax" ) ) + { + if( Cmd_Argc() != 6 ) + return false; + ent->v.maxs[0] = Q_atof( Cmd_Argv( 3 ) ); + ent->v.maxs[1] = Q_atof( Cmd_Argv( 4 ) ); + ent->v.maxs[2] = Q_atof( Cmd_Argv( 5 ) ); + } + else if( !Q_stricmp( Cmd_Argv( 2 ), "rendercolor" ) ) + { + if( Cmd_Argc() != 6 ) + return false; + ent->v.rendercolor[0] = Q_atof( Cmd_Argv( 3 ) ); + ent->v.rendercolor[1] = Q_atof( Cmd_Argv( 4 ) ); + ent->v.rendercolor[2] = Q_atof( Cmd_Argv( 5 ) ); + } + else if( !Q_stricmp( Cmd_Argv( 2 ), "renderamt" ) ) + { + ent->v.renderamt = Q_atof( Cmd_Argv( 3 ) ); + } + else if( !Q_stricmp( Cmd_Argv( 2 ), "renderfx" ) ) + { + ent->v.renderfx = Q_atoi( Cmd_Argv( 3 ) ); + } + else if( !Q_stricmp( Cmd_Argv( 2 ), "rendermode" ) ) + { + ent->v.rendermode = Q_atoi( Cmd_Argv( 3 ) ); + } + else if( !Q_stricmp( Cmd_Argv( 2 ), "angles" ) ) + { + ent->v.angles[0] = Q_atof( Cmd_Argv( 3 ) ); + ent->v.angles[1] = Q_atof( Cmd_Argv( 4 ) ); + ent->v.angles[2] = Q_atof( Cmd_Argv( 5 ) ); + } + else if( !Q_stricmp( Cmd_Argv( 2 ), "setflag" ) ) + { + ent->v.flags |= 1U << Q_atoi( Cmd_Argv ( 3 ) ); + SV_ClientPrintf( cl, "flags set to 0x%x\n", ent->v.flags ); + } + else if( !Q_stricmp( Cmd_Argv( 2 ), "clearflag" ) ) + { + ent->v.flags &= ~( 1U << Q_atoi( Cmd_Argv ( 3 ) ) ); + SV_ClientPrintf( cl, "flags set to 0x%x\n", ent->v.flags ); + } + else if( !Q_stricmp( Cmd_Argv( 2 ), "setspawnflag" ) ) + { + ent->v.spawnflags |= 1U << Q_atoi( Cmd_Argv ( 3 ) ); + SV_ClientPrintf( cl, "spawnflags set to 0x%x\n", ent->v.spawnflags ); + } + else if( !Q_stricmp( Cmd_Argv( 2 ), "clearspawnflag" ) ) + { + ent->v.spawnflags &= ~( 1U << Q_atoi( Cmd_Argv ( 3 ) ) ); + SV_ClientPrintf( cl, "spawnflags set to 0x%x\n", ent->v.flags ); + } + else if( !Q_stricmp( Cmd_Argv( 2 ), "help" ) ) + { + SV_ClientPrintf( cl, "Available commands:\n" + "Set fields:\n" + " (Only set entity field, does not call any functions)\n" + " health\n" + " gravity\n" + " movetype\n" + " solid\n" + " rendermode\n" + " rendercolor (vector)\n" + " renderfx\n" + " renderamt\n" + " hullmin (vector)\n" + " hullmax (vector)\n" + "Actions\n" + " rename: set entity targetname\n" + " settarget: set entity target (only targetnames)\n" + " setmodel: set entity model\n" + " set: set by server library\n" + " See game FGD to get list.\n" + " command takes two arguments\n" + " touch: touch entity by current player.\n" + " use: use entity by current player.\n" + " movehere: place entity in player fov.\n" + " drop2floor: place entity to nearest floor surface\n" + " moveup: move entity to 25 units up\n" + "Flags:\n" + " (Set/clear specified flag bit, arg is bit number)\n" + " setflag\n" + " clearflag\n" + " setspawnflag\n" + " clearspawnflag\n" + ); + return true; + } + else + { + SV_ClientPrintf( cl, "Unknown command %s!\nUse \"ent_fire 0 help\" to list commands.\n", Cmd_Argv( 2 ) ); + return false; + } + if( single ) + break; + } + return true; +} + +/* +=============== +SV_EntSendVars +=============== +*/ +static void SV_EntSendVars( sv_client_t *cl, edict_t *ent ) +{ + if( !ent ) + return; + + MSG_WriteByte( &cl->netchan.message, svc_stufftext ); + MSG_WriteString( &cl->netchan.message, va( "set ent_last_name \"%s\"\n", STRING( ent->v.targetname ) )); + MSG_WriteByte( &cl->netchan.message, svc_stufftext ); + MSG_WriteString( &cl->netchan.message, va( "set ent_last_num %i\n", NUM_FOR_EDICT( ent ) )); + MSG_WriteByte( &cl->netchan.message, svc_stufftext ); + MSG_WriteString( &cl->netchan.message, va( "set ent_last_inst !%i_%i\n", NUM_FOR_EDICT( ent ), ent->serialnumber )); + MSG_WriteByte( &cl->netchan.message, svc_stufftext ); + MSG_WriteString( &cl->netchan.message, va( "set ent_last_origin \"%f %f %f\"\n", ent->v.origin[0], ent->v.origin[1], ent->v.origin[2])); + MSG_WriteByte( &cl->netchan.message, svc_stufftext ); + MSG_WriteString( &cl->netchan.message, va( "set ent_last_class \"%s\"\n", STRING( ent->v.classname ))); + MSG_WriteByte( &cl->netchan.message, svc_stufftext ); + MSG_WriteString( &cl->netchan.message, "ent_getvars_cb\n" ); // why do we need this? +} + +/* +=============== +SV_EntCreate_f + +Create new entity with specified name. +=============== +*/ +static qboolean SV_EntCreate_f( sv_client_t *cl ) +{ + edict_t *ent = NULL; + int i = 0; + string_t classname; + + if( Cmd_Argc() < 2 ) + { + SV_ClientPrintf( cl, "Use ent_create ...\n" ); + return false; + } + + classname = ALLOC_STRING( Cmd_Argv( 1 ) ); + + ent = SV_CreateNamedEntity( 0, classname ); + + // Xash3D extension + if( !ent && svgame.physFuncs.SV_CreateEntity ) + { + ent = SV_AllocEdict(); + ent->v.classname = classname; + if( svgame.physFuncs.SV_CreateEntity( ent, (char*)STRING( classname ) ) == -1 ) + { + if( ent && !ent->free ) + SV_FreeEdict( ent ); + ent = NULL; + } + } + + // XashXT does not implement SV_CreateEntity, use saverestore export + if( !ent && svgame.physFuncs.pfnCreateEntitiesInRestoreList ) + { + SAVERESTOREDATA data = { 0 }; + ENTITYTABLE table = { 0 }; + data.tableCount = 1; + data.pTable = &table; + table.classname = classname; + table.id = -1; + table.size = 1; + svgame.physFuncs.pfnCreateEntitiesInRestoreList( &data, 0, false ); + ent = table.pent; + } + + if( !ent ) + { + SV_ClientPrintf( cl, "Invalid entity!\n" ); + return false; + } + + // choose default origin + ent->v.origin[2] = cl->edict->v.origin[2] + 25; + ent->v.origin[1] = cl->edict->v.origin[1] + 100 * sin( DEG2RAD( cl->edict->v.angles[1] ) ); + ent->v.origin[0] = cl->edict->v.origin[0] + 100 * cos( DEG2RAD( cl->edict->v.angles[1] ) ); + + SV_LinkEdict( ent, false ); + + // apply keyvalues if supported + if( svgame.dllFuncs.pfnKeyValue ) + { + for( i = 2; i < Cmd_Argc() - 1; i++ ) + { + string keyname; + string value; + KeyValueData pkvd; + + // allow split keyvalues to prespawn and postspawn + if( !Q_strcmp( Cmd_Argv( i ), "|" ) ) + break; + + Q_strncpy( keyname, Cmd_Argv( i++ ), sizeof( keyname )); + Q_strncpy( value, Cmd_Argv( i ), sizeof( value )); + pkvd.fHandled = false; + pkvd.szClassName = (char*)STRING( ent->v.classname ); + pkvd.szKeyName = keyname; + pkvd.szValue = value; + svgame.dllFuncs.pfnKeyValue( ent, &pkvd ); + + if( pkvd.fHandled ) + SV_ClientPrintf( cl, "value \"%s\" set to \"%s\"!\n", pkvd.szKeyName, pkvd.szValue ); + } + } + + // set default targetname + if( !ent->v.targetname ) + { + string newname, clientname; + int j; + + for( j = 0; j < sizeof( cl->name ); j++ ) + { + char c = Q_tolower( cl->name[j] ); + if( c < 'a' || c > 'z' ) + c = '_'; + if( !cl->name[j] ) + { + clientname[j] = 0; + break; + } + clientname[j] = c; + } + + // generate name based on nick name and index + Q_snprintf( newname, sizeof( newname ), "%s_%i_e%i", clientname, cl->userid, NUM_FOR_EDICT( ent )); + + // i know, it may break strict aliasing rules + // but we will not lose anything in this case. + Q_strnlwr( newname, newname, sizeof( newname )); + ent->v.targetname = ALLOC_STRING( newname ); + SV_EntSendVars( cl, ent ); + } + + SV_ClientPrintf( cl, "Created %i: %s, targetname %s\n", NUM_FOR_EDICT( ent ), Cmd_Argv( 1 ), STRING( ent->v.targetname ) ); + + if( svgame.dllFuncs.pfnSpawn ) + svgame.dllFuncs.pfnSpawn( ent ); + + // now drop entity to floor. + pfnDropToFloor( ent ); + + // force think. Otherwise given weapon may crash server if player touch it before. + svgame.dllFuncs.pfnThink( ent ); + pfnDropToFloor( ent ); + + // apply postspawn keyvales if supported + if( svgame.dllFuncs.pfnKeyValue ) + { + for( i = i + 1; i < Cmd_Argc() - 1; i++ ) + { + string keyname; + string value; + KeyValueData pkvd; + + Q_strncpy( keyname, Cmd_Argv( i++ ), sizeof( keyname )); + Q_strncpy( value, Cmd_Argv( i ), sizeof( value )); + pkvd.fHandled = false; + pkvd.szClassName = (char*)STRING( ent->v.classname ); + pkvd.szKeyName = keyname; + pkvd.szValue = value; + svgame.dllFuncs.pfnKeyValue( ent, &pkvd ); + + if( pkvd.fHandled ) + SV_ClientPrintf( cl, "value \"%s\" set to \"%s\"!\n", pkvd.szKeyName, pkvd.szValue ); + } + } + return true; +} + +static qboolean SV_EntGetVars_f( sv_client_t *cl ) +{ + edict_t *ent = NULL; + + if( Cmd_Argc() != 2 ) + { + SV_ClientPrintf( cl, "Use ent_getvars \n" ); + return false; + } + + ent = SV_EntFindSingle( cl, Cmd_Argv( 1 ) ); + if( Cmd_Argc() ) + { + if( !SV_IsValidEdict( ent )) + return false; + } + + SV_EntSendVars( cl, ent ); + return true; +} ucmd_t ucmds[] = { @@ -2115,6 +2914,16 @@ ucmd_t ucmds[] = { NULL, NULL } }; +ucmd_t enttoolscmds[] = +{ +{ "ent_list", SV_EntList_f }, +{ "ent_info", SV_EntInfo_f }, +{ "ent_fire", SV_EntFire_f }, +{ "ent_create", SV_EntCreate_f }, +{ "ent_getvars", SV_EntGetVars_f }, +{ NULL, NULL } +}; + /* ================== SV_ExecuteUserCommand @@ -2137,6 +2946,24 @@ void SV_ExecuteClientCommand( sv_client_t *cl, const char *s ) } } + if( !u->name && sv_enttools_enable.value > 0.0f && !sv.background ) + { + for( u = enttoolscmds; u->name; u++ ) + { + if( !Q_strcmp( Cmd_Argv( 0 ), u->name )) + { + Con_Reportf( "enttools->%s(): %s\n", u->name, s ); + Log_Printf( "\"%s<%i><%s><>\" performed: %s\n", Info_ValueForKey( cl->userinfo, "name" ), + cl->userid, SV_GetClientIDString( cl ), NET_AdrToString( cl->netchan.remote_address ), s ); + + if( u->func ) + u->func( cl ); + + break; + } + } + } + if( !u->name && sv.state == ss_active ) { // custom client commands @@ -2165,22 +2992,11 @@ void SV_TSourceEngineQuery( netadr_t from ) { // A2S_INFO char answer[1024] = ""; - int count = 0, bots = 0; + int count, bots; int index; sizebuf_t buf; - if( svs.clients ) - { - for( index = 0; index < svs.maxclients; index++ ) - { - if( svs.clients[index].state >= cs_connected ) - { - if( FBitSet( svs.clients[index].flags, FCL_FAKECLIENT )) - bots++; - else count++; - } - } - } + SV_GetPlayerCount( &count, &bots ); MSG_Init( &buf, "TSourceEngineQuery", answer, sizeof( answer )); @@ -2257,7 +3073,7 @@ void SV_ConnectionlessPacket( netadr_t from, sizebuf_t *msg ) if( !Q_strcmp( pcmd, "ping" )) SV_Ping( from ); else if( !Q_strcmp( pcmd, "ack" )) SV_Ack( from ); - else if( !Q_strcmp( pcmd, "info" )) SV_Info( from ); + else if( !Q_strcmp( pcmd, "info" )) SV_Info( from, Q_atoi( Cmd_Argv( 1 ))); else if( !Q_strcmp( pcmd, "bandwidth" )) SV_TestBandWidth( from ); else if( !Q_strcmp( pcmd, "getchallenge" )) SV_GetChallenge( from ); else if( !Q_strcmp( pcmd, "connect" )) SV_ConnectClient( from ); @@ -2266,6 +3082,17 @@ void SV_ConnectionlessPacket( netadr_t from, sizebuf_t *msg ) else if( !Q_strcmp( pcmd, "s" )) SV_AddToMaster( from, msg ); else if( !Q_strcmp( pcmd, "T" "Source" )) SV_TSourceEngineQuery( from ); else if( !Q_strcmp( pcmd, "i" )) NET_SendPacket( NS_SERVER, 5, "\xFF\xFF\xFF\xFFj", from ); // A2A_PING + else if (!Q_strcmp( pcmd, "c" )) + { + qboolean sv_nat = Cvar_VariableInteger( "sv_nat" ); + if( sv_nat ) + { + netadr_t to; + + if( NET_StringToAdr( Cmd_Argv( 1 ), &to ) && !NET_IsReservedAdr( to )) + SV_Info( to, PROTOCOL_VERSION ); + } + } else if( svgame.dllFuncs.pfnConnectionlessPacket( &from, args, buf, &len )) { // user out of band message (must be handled in CL_ConnectionlessPacket) @@ -2536,6 +3363,65 @@ void SV_ParseCvarValue2( sv_client_t *cl, sizebuf_t *msg ) Con_Reportf( "Cvar query response: name:%s, request ID %d, cvar:%s, value:%s\n", cl->name, requestID, name, value ); } +/* +=================== +SV_ParseVoiceData +=================== +*/ +void SV_ParseVoiceData( sv_client_t *cl, sizebuf_t *msg ) +{ + char received[4096]; + sv_client_t *cur; + int i, client; + uint length, size, frames; + + cl->m_bLoopback = MSG_ReadByte( msg ); + + frames = MSG_ReadByte( msg ); + + size = MSG_ReadShort( msg ); + client = cl - svs.clients; + + if( size > sizeof( received )) + { + Con_DPrintf( "SV_ParseVoiceData: invalid incoming packet.\n" ); + SV_DropClient( cl, false ); + return; + } + + MSG_ReadBytes( msg, received, size ); + + if( !sv_voiceenable.value ) + return; + + for( i = 0, cur = svs.clients; i < svs.maxclients; i++, cur++ ) + { + if( cl != cur ) + { + if( cur->state < cs_connected ) + continue; + + if( !FBitSet( cur->listeners, BIT( client ))) + continue; + } + + length = size; + + // 6 is a number of bytes for other parts of message + if( MSG_GetNumBytesLeft( &cur->datagram ) < length + 6 ) + continue; + + if( cl == cur && !cur->m_bLoopback ) + length = 0; + + MSG_BeginServerCmd( &cur->datagram, svc_voicedata ); + MSG_WriteByte( &cur->datagram, client ); + MSG_WriteByte( &cur->datagram, frames ); + MSG_WriteShort( &cur->datagram, length ); + MSG_WriteBytes( &cur->datagram, received, length ); + } +} + /* =================== SV_ExecuteClientMessage @@ -2606,6 +3492,9 @@ void SV_ExecuteClientMessage( sv_client_t *cl, sizebuf_t *msg ) case clc_fileconsistency: SV_ParseConsistencyResponse( cl, msg ); break; + case clc_voicedata: + SV_ParseVoiceData( cl, msg ); + break; case clc_requestcvarvalue: SV_ParseCvarValue( cl, msg ); break; diff --git a/engine/server/sv_cmds.c b/engine/server/sv_cmds.c index 571c4ffc..b622d766 100644 --- a/engine/server/sv_cmds.c +++ b/engine/server/sv_cmds.c @@ -249,7 +249,7 @@ void SV_Maps_f( void ) if( Cmd_Argc() != 2 ) { - Msg( "Usage: maps \nmaps * for full listing\n" ); + Msg( S_USAGE "maps \nmaps * for full listing\n" ); return; } diff --git a/engine/server/sv_filter.c b/engine/server/sv_filter.c index d3d1228d..f6eb8df2 100644 --- a/engine/server/sv_filter.c +++ b/engine/server/sv_filter.c @@ -39,7 +39,7 @@ static void SV_RemoveID( const char *id ) for( filter = cidfilter; filter; filter = filter->next ) { - if( Q_strcmp( filter->id, id ) ) + if( Q_strcmp( filter->id, id )) { prevfilter = filter; continue; @@ -78,7 +78,7 @@ qboolean SV_CheckID( const char *id ) return false; } - if( !Q_strncmp( id, filter->id, len ) ) + if( !Q_strncmp( id, filter->id, len )) { ret = true; break; @@ -90,7 +90,7 @@ qboolean SV_CheckID( const char *id ) static void SV_BanID_f( void ) { - float time = Q_atof( Cmd_Argv( 1 ) ); + float time = Q_atof( Cmd_Argv( 1 )); const char *id = Cmd_Argv( 2 ); sv_client_t *cl = NULL; cidfilter_t *filter; @@ -100,19 +100,19 @@ static void SV_BanID_f( void ) if( !id[0] ) { - Con_Reportf( "Usage: banid <#userid or unique id>\n0 minutes for permanent ban\n" ); + Con_Reportf( S_USAGE "banid <#userid or unique id>\n0 minutes for permanent ban\n" ); return; } - if( !Q_strnicmp( id, "STEAM_", 6 ) || !Q_strnicmp( id, "VALVE_", 6 ) ) + if( !Q_strnicmp( id, "STEAM_", 6 ) || !Q_strnicmp( id, "VALVE_", 6 )) id += 6; - if( !Q_strnicmp( id, "XASH_", 5 ) ) + if( !Q_strnicmp( id, "XASH_", 5 )) id += 5; if( svs.clients ) { if( id[0] == '#' ) - cl = SV_ClientById( Q_atoi( id + 1 ) ); + cl = SV_ClientById( Q_atoi( id + 1 )); if( !cl ) { @@ -122,7 +122,7 @@ static void SV_BanID_f( void ) for( i = 0, cl1 = svs.clients; i < sv_maxclients->value; i++, cl1++ ) { - if( !Q_strncmp( id, Info_ValueForKey( cl1->useragent, "uuid" ), len ) ) + if( !Q_strncmp( id, Info_ValueForKey( cl1->useragent, "uuid" ), len )) { cl = cl1; break; @@ -152,14 +152,14 @@ static void SV_BanID_f( void ) SV_RemoveID( id ); - filter = Mem_Malloc( host.mempool, sizeof( cidfilter_t ) ); + filter = Mem_Malloc( host.mempool, sizeof( cidfilter_t )); filter->endTime = time; filter->next = cidfilter; - Q_strncpy( filter->id, id, sizeof( filter->id ) ); + Q_strncpy( filter->id, id, sizeof( filter->id )); cidfilter = filter; - if( cl && !Q_stricmp( Cmd_Argv( Cmd_Argc() - 1 ), "kick" ) ) - Cbuf_AddText( va( "kick #%d \"Kicked and banned\"\n", cl->userid ) ); + if( cl && !Q_stricmp( Cmd_Argv( Cmd_Argc() - 1 ), "kick" )) + Cbuf_AddText( va( "kick #%d \"Kicked and banned\"\n", cl->userid )); } static void SV_ListID_f( void ) @@ -197,7 +197,7 @@ static void SV_RemoveID_f( void ) if( !id[0] ) { - Con_Reportf("Usage: removeid <#slotnumber or uniqueid>\n"); + Con_Reportf( S_USAGE "removeid <#slotnumber or uniqueid>\n"); return; } @@ -211,13 +211,13 @@ static void SV_WriteID_f( void ) if( !f ) { - Con_DPrintf( S_ERROR "Could not write %s\n", Cvar_VariableString( "bannedcfgfile" ) ); + Con_DPrintf( S_ERROR "Could not write %s\n", Cvar_VariableString( "bannedcfgfile" )); return; } FS_Printf( f, "//=======================================================================\n" ); FS_Printf( f, "//\t\tCopyright Flying With Gauss Team %s ©\n", Q_timestamp( TIME_YEAR_ONLY )); - FS_Printf( f, "//\t\t %s - archive of id blacklist\n", Cvar_VariableString( "bannedcfgfile" ) ); + FS_Printf( f, "//\t\t %s - archive of id blacklist\n", Cvar_VariableString( "bannedcfgfile" )); FS_Printf( f, "//=======================================================================\n" ); for( filter = cidfilter; filter; filter = filter->next ) @@ -426,7 +426,7 @@ static void SV_AddIP_f( void ) filter.endTime = host.realtime + minutes * 60; else filter.endTime = 0; - if( !NET_StringToFilterAdr( adr, &filter.adr, &filter.prefixlen ) ) + if( !NET_StringToFilterAdr( adr, &filter.adr, &filter.prefixlen )) { Con_Printf( "Invalid IP address!\n" ); SV_AddIP_PrintUsage(); @@ -511,7 +511,7 @@ static void SV_RemoveIP_f( void ) removeAll = Cmd_Argc() == 3 && !Q_strcmp( Cmd_Argv( 2 ), "removeAll" ); - if( !NET_StringToFilterAdr( adr, &filter.adr, &filter.prefixlen ) ) + if( !NET_StringToFilterAdr( adr, &filter.adr, &filter.prefixlen )) { Con_Printf( "Invalid IP address!\n" ); SV_RemoveIP_PrintUsage(); diff --git a/engine/server/sv_game.c b/engine/server/sv_game.c index 588bc7d8..ffa37f5d 100644 --- a/engine/server/sv_game.c +++ b/engine/server/sv_game.c @@ -62,16 +62,23 @@ qboolean SV_CheckEdict( const edict_t *e, const char *file, const int line ) static edict_t *SV_PEntityOfEntIndex( const int iEntIndex, const qboolean allentities ) { - edict_t *pEdict = EDICT_NUM( iEntIndex ); - qboolean player = allentities ? iEntIndex <= svs.maxclients : iEntIndex < svs.maxclients; + if( iEntIndex >= 0 && iEntIndex < GI->max_edicts ) + { + edict_t *pEdict = EDICT_NUM( iEntIndex ); + qboolean player = allentities ? iEntIndex <= svs.maxclients : iEntIndex < svs.maxclients; - if( !SV_IsValidEdict( pEdict )) - return NULL; + if( !iEntIndex || FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) + return pEdict; // just get access to array - if( !player && !pEdict->pvPrivateData ) - return NULL; + if( SV_IsValidEdict( pEdict ) && pEdict->pvPrivateData ) + return pEdict; - return pEdict; + // g-cont: world and clients can be accessed even without private data + if( SV_IsValidEdict( pEdict ) && player ) + return pEdict; + } + + return NULL; } @@ -250,6 +257,63 @@ void SV_CopyTraceToGlobal( trace_t *trace ) else svgame.globals->trace_ent = svgame.edicts; } +/* +============== +SV_SetModel +============== +*/ +void SV_SetModel( edict_t *ent, const char *modelname ) +{ + char name[MAX_QPATH]; + qboolean found = false; + model_t *mod; + int i = 1; + + if( !SV_IsValidEdict( ent )) + { + Con_Printf( S_WARN "SV_SetModel: invalid entity %s\n", SV_ClassName( ent )); + return; + } + + if( !modelname || modelname[0] <= ' ' ) + { + Con_Printf( S_WARN "SV_SetModel: null name\n" ); + return; + } + + if( *modelname == '\\' || *modelname == '/' ) + modelname++; + + Q_strncpy( name, modelname, sizeof( name )); + COM_FixSlashes( name ); + + i = SV_ModelIndex( name ); + if( i == 0 ) + { + if( sv.state == ss_active ) + Con_Printf( S_ERROR "SV_SetModel: failed to set model %s: world model cannot be changed\n", name ); + return; + } + + if( COM_CheckString( name )) + { + ent->v.model = MAKE_STRING( sv.model_precache[i] ); + ent->v.modelindex = i; + mod = sv.models[i]; + } + else + { + // model will be cleared + ent->v.model = ent->v.modelindex = 0; + mod = NULL; + } + + // set the model size + if( mod && mod->type != mod_studio ) + SV_SetMinMaxSize( ent, mod->mins, mod->maxs, true ); + else SV_SetMinMaxSize( ent, vec3_origin, vec3_origin, true ); +} + /* ============= SV_ConvertTrace @@ -1312,61 +1376,7 @@ pfnSetModel */ void GAME_EXPORT pfnSetModel( edict_t *e, const char *m ) { - char name[MAX_QPATH]; - qboolean found = false; - model_t *mod; - int i = 1; - - if( !SV_IsValidEdict( e )) - return; - - if( *m == '\\' || *m == '/' ) m++; - Q_strncpy( name, m, sizeof( name )); - COM_FixSlashes( name ); - - if( COM_CheckString( name )) - { - // check to see if model was properly precached - for( ; i < MAX_MODELS && sv.model_precache[i][0]; i++ ) - { - if( !Q_stricmp( sv.model_precache[i], name )) - { - found = true; - break; - } - } - - if( !found ) - { - Con_Printf( S_ERROR "Failed to set model %s: was not precached\n", name ); - return; - } - } - - if( e == svgame.edicts ) - { - if( sv.state == ss_active ) - Con_Printf( S_ERROR "Failed to set model %s: world model cannot be changed\n", name ); - return; - } - - if( COM_CheckString( name )) - { - e->v.model = MAKE_STRING( sv.model_precache[i] ); - e->v.modelindex = i; - mod = sv.models[i]; - } - else - { - // model will be cleared - e->v.model = e->v.modelindex = 0; - mod = NULL; - } - - // set the model size - if( mod && mod->type != mod_studio ) - SV_SetMinMaxSize( e, mod->mins, mod->maxs, true ); - else SV_SetMinMaxSize( e, vec3_origin, vec3_origin, true ); + SV_SetModel( e, m ); } /* @@ -2673,7 +2683,7 @@ void GAME_EXPORT pfnMessageEnd( void ) return; } - sv.multicast.pData[svgame.msg_size_index] = svgame.msg_realsize; + *(word *)&sv.multicast.pData[svgame.msg_size_index] = svgame.msg_realsize; } } else if( svgame.msg[svgame.msg_index].size != -1 ) @@ -3085,12 +3095,10 @@ void SV_SetStringArrayMode( qboolean dynamic ) #endif } -#ifdef XASH_64BIT -#if !XASH_WIN32 +#if XASH_64BIT && !XASH_WIN32 && !XASH_APPLE #define USE_MMAP #include #endif -#endif /* ================== @@ -3121,14 +3129,21 @@ void SV_AllocStringPool( void ) #ifdef USE_MMAP { + uint flags; size_t pagesize = sysconf( _SC_PAGESIZE ); int arrlen = (str64.maxstringarray * 2) & ~(pagesize - 1); void *base = svgame.dllFuncs.pfnGameInit; void *start = svgame.hInstance - arrlen; +#if defined(MAP_ANON) + flags = MAP_ANON | MAP_PRIVATE; +#elif defined(MAP_ANONYMOUS) + flags = MAP_ANONYMOUS | MAP_PRIVATE; +#endif + while( start - base > INT_MIN ) { - void *mapptr = mmap((void*)((unsigned long)start & ~(pagesize - 1)), arrlen, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0 ); + void *mapptr = mmap((void*)((unsigned long)start & ~(pagesize - 1)), arrlen, PROT_READ | PROT_WRITE, flags, 0, 0 ); if( mapptr && mapptr != (void*)-1 && mapptr - base > INT_MIN && mapptr - base < INT_MAX ) { ptr = mapptr; @@ -3143,7 +3158,7 @@ void SV_AllocStringPool( void ) start = base; while( start - base < INT_MAX ) { - void *mapptr = mmap((void*)((unsigned long)start & ~(pagesize - 1)), arrlen, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0 ); + void *mapptr = mmap((void*)((unsigned long)start & ~(pagesize - 1)), arrlen, PROT_READ | PROT_WRITE, flags, 0, 0 ); if( mapptr && mapptr != (void*)-1 && mapptr - base > INT_MIN && mapptr - base < INT_MAX ) { ptr = mapptr; @@ -3368,7 +3383,9 @@ pfnPEntityOfEntIndex static edict_t *pfnPEntityOfEntIndex( int iEntIndex ) { // have to be bug-compatible with GoldSrc in this function - return SV_PEntityOfEntIndex( iEntIndex, false ); + if( host.bugcomp == BUGCOMP_GOLDSRC ) + return SV_PEntityOfEntIndex( iEntIndex, false ); + return SV_PEntityOfEntIndex( iEntIndex, true ); } /* diff --git a/engine/server/sv_init.c b/engine/server/sv_init.c index b045fdfd..ab32ad44 100644 --- a/engine/server/sv_init.c +++ b/engine/server/sv_init.c @@ -17,6 +17,8 @@ GNU General Public License for more details. #include "server.h" #include "net_encode.h" #include "library.h" +#include "voice.h" + #if XASH_LOW_MEMORY != 2 int SV_UPDATE_BACKUP = SINGLEPLAYER_BACKUP; #endif @@ -386,6 +388,18 @@ void SV_CreateResourceList( void ) } } +/* +================ +SV_WriteVoiceCodec +================ +*/ +void SV_WriteVoiceCodec( sizebuf_t *msg ) +{ + MSG_BeginServerCmd( msg, svc_voiceinit ); + MSG_WriteString( msg, VOICE_DEFAULT_CODEC ); + MSG_WriteByte( msg, (int)sv_voicequality.value ); +} + /* ================ SV_CreateBaseline @@ -404,6 +418,8 @@ void SV_CreateBaseline( void ) int delta_type; int entnum; + SV_WriteVoiceCodec( &sv.signon ); + if( FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) playermodel = SV_ModelIndex( DEFAULT_PLAYER_PATH_QUAKE ); else playermodel = SV_ModelIndex( DEFAULT_PLAYER_PATH_HALFLIFE ); @@ -761,7 +777,7 @@ void SV_SetupClients( void ) Con_Reportf( "%s alloced by server packet entities\n", Q_memprint( sizeof( entity_state_t ) * svs.num_client_entities )); // init network stuff - NET_Config(( svs.maxclients > 1 )); + NET_Config(( svs.maxclients > 1 ), true ); svgame.numEntities = svs.maxclients + 1; // clients + world ClearBits( sv_maxclients->flags, FCVAR_CHANGED ); } diff --git a/engine/server/sv_log.c b/engine/server/sv_log.c index 7c1c5d6b..db6c4dfe 100644 --- a/engine/server/sv_log.c +++ b/engine/server/sv_log.c @@ -214,7 +214,7 @@ void SV_ServerLog_f( void ) { if( Cmd_Argc() != 2 ) { - Con_Printf("usage: log < on|off >\n" ); + Con_Printf( S_USAGE "log < on|off >\n" ); if( svs.log.active ) Con_Printf( "currently logging\n" ); diff --git a/engine/server/sv_main.c b/engine/server/sv_main.c index 44b28e4d..016bcc8c 100644 --- a/engine/server/sv_main.c +++ b/engine/server/sv_main.c @@ -18,11 +18,12 @@ GNU General Public License for more details. #include "net_encode.h" #include "platform/platform.h" -#define HEARTBEAT_SECONDS 300.0f // 300 seconds +#define HEARTBEAT_SECONDS ((sv_nat.value > 0.0f) ? 60.0f : 300.0f) // 1 or 5 minutes // server cvars CVAR_DEFINE_AUTO( sv_lan, "0", 0, "server is a lan server ( no heartbeat, no authentication, no non-class C addresses, 9999.0 rate, etc." ); CVAR_DEFINE_AUTO( sv_lan_rate, "20000.0", 0, "rate for lan server" ); +CVAR_DEFINE_AUTO( sv_nat, "0", 0, "enable NAT bypass for this server" ); CVAR_DEFINE_AUTO( sv_aim, "1", FCVAR_ARCHIVE|FCVAR_SERVER, "auto aiming option" ); CVAR_DEFINE_AUTO( sv_unlag, "1", 0, "allow lag compensation on server-side" ); CVAR_DEFINE_AUTO( sv_maxunlag, "0.5", 0, "max latency value which can be interpolated (by default ping should not exceed 500 units)" ); @@ -111,6 +112,14 @@ CVAR_DEFINE_AUTO( violence_ablood, "1", 0, "draw alien blood" ); CVAR_DEFINE_AUTO( violence_hgibs, "1", 0, "show human gib entities" ); CVAR_DEFINE_AUTO( violence_agibs, "1", 0, "show alien gib entities" ); +// voice chat +CVAR_DEFINE_AUTO( sv_voiceenable, "1", FCVAR_ARCHIVE|FCVAR_SERVER, "enable voice support" ); +CVAR_DEFINE_AUTO( sv_voicequality, "3", FCVAR_ARCHIVE|FCVAR_SERVER, "voice chat quality level, from 0 to 5, higher is better" ); + +// enttools +CVAR_DEFINE_AUTO( sv_enttools_enable, "0", FCVAR_ARCHIVE|FCVAR_PROTECTED, "enable powerful and dangerous entity tools" ); +CVAR_DEFINE_AUTO( sv_enttools_maxfire, "5", FCVAR_ARCHIVE|FCVAR_PROTECTED, "limit ent_fire actions count to prevent flooding" ); + convar_t *sv_novis; // disable server culling entities by vis convar_t *sv_pausable; convar_t *timeout; // seconds without any message @@ -154,41 +163,6 @@ qboolean SV_HasActivePlayers( void ) return false; } -/* -================ -SV_GetConnectedClientsCount - -returns connected clients count (and optionally bots count) -================ -*/ -int SV_GetConnectedClientsCount(int *bots) -{ - int index; - int clients; - - clients = 0; - if( svs.clients ) - { - if( bots ) - *bots = 0; - - for( index = 0; index < svs.maxclients; index++ ) - { - if( svs.clients[index].state >= cs_connected ) - { - if( FBitSet( svs.clients[index].flags, FCL_FAKECLIENT )) - { - if( bots ) - (*bots)++; - } - else - clients++; - } - } - } - return clients; -} - /* =================== SV_UpdateMovevars @@ -713,13 +687,9 @@ Master_Add */ void Master_Add( void ) { - netadr_t adr; - - NET_Config( true ); // allow remote - - if( !NET_StringToAdr( MASTERSERVER_ADR, &adr )) - Con_Printf( "can't resolve adr: %s\n", MASTERSERVER_ADR ); - else NET_SendPacket( NS_SERVER, 2, "q\xFF", adr ); + NET_Config( true, false ); // allow remote + if( NET_SendToMasters( NS_SERVER, 2, "q\xFF" )) + svs.last_heartbeat = MAX_HEARTBEAT; } /* @@ -756,13 +726,8 @@ Informs all masters that this server is going down */ void Master_Shutdown( void ) { - netadr_t adr; - - NET_Config( true ); // allow remote - - if( !NET_StringToAdr( MASTERSERVER_ADR, &adr )) - Con_Printf( "can't resolve addr: %s\n", MASTERSERVER_ADR ); - else NET_SendPacket( NS_SERVER, 2, "\x62\x0A", adr ); + NET_Config( true, false ); // allow remote + while( NET_SendToMasters( NS_SERVER, 2, "\x62\x0A" )); } /* @@ -777,10 +742,16 @@ void SV_AddToMaster( netadr_t from, sizebuf_t *msg ) { uint challenge; char s[MAX_INFO_STRING] = "0\n"; // skip 2 bytes of header - int clients = 0, bots = 0; + int clients, bots; int len = sizeof( s ); - clients = SV_GetConnectedClientsCount( &bots ); + if( !NET_IsMasterAdr( from )) + { + Con_Printf( S_WARN "unexpected master server info query packet from %s\n", NET_AdrToString( from )); + return; + } + + SV_GetPlayerCount( &clients, &bots ); challenge = MSG_ReadUBitLong( msg, sizeof( uint ) << 3 ); Info_SetValueForKey( s, "protocol", va( "%d", PROTOCOL_VERSION ), len ); // protocol version @@ -798,6 +769,7 @@ void SV_AddToMaster( netadr_t from, sizebuf_t *msg ) Info_SetValueForKey( s, "version", va( "%s", XASH_VERSION ), len ); // server region. 255 -- all regions Info_SetValueForKey( s, "region", "255", len ); // server region. 255 -- all regions Info_SetValueForKey( s, "product", GI->gamefolder, len ); // product? Where is the difference with gamedir? + Info_SetValueForKey( s, "nat", sv_nat.string, sizeof(s) ); // Server running under NAT, use reverse connection NET_SendPacket( NS_SERVER, Q_strlen( s ), s, from ); } @@ -956,6 +928,7 @@ void SV_Init( void ) sv_hostmap = Cvar_Get( "hostmap", GI->startmap, 0, "keep name of last entered map" ); Cvar_RegisterVariable( &sv_password ); Cvar_RegisterVariable( &sv_lan ); + Cvar_RegisterVariable( &sv_nat ); Cvar_RegisterVariable( &violence_ablood ); Cvar_RegisterVariable( &violence_hblood ); Cvar_RegisterVariable( &violence_agibs ); @@ -974,7 +947,11 @@ void SV_Init( void ) Cvar_RegisterVariable( &listipcfgfile ); Cvar_RegisterVariable( &mapchangecfgfile ); + Cvar_RegisterVariable( &sv_voiceenable ); + Cvar_RegisterVariable( &sv_voicequality ); Cvar_RegisterVariable( &sv_trace_messages ); + Cvar_RegisterVariable( &sv_enttools_enable ); + Cvar_RegisterVariable( &sv_enttools_maxfire ); sv_allow_joystick = Cvar_Get( "sv_allow_joystick", "1", FCVAR_ARCHIVE, "allow connect with joystick enabled" ); sv_allow_mouse = Cvar_Get( "sv_allow_mouse", "1", FCVAR_ARCHIVE, "allow connect with mouse" ); @@ -1091,6 +1068,8 @@ void SV_Shutdown( const char *finalmsg ) // drop the client if want to load a new map if( CL_IsPlaybackDemo( )) CL_Drop(); + + SV_UnloadProgs (); return; } @@ -1106,7 +1085,7 @@ void SV_Shutdown( const char *finalmsg ) if( public_server->value && svs.maxclients != 1 ) Master_Shutdown(); - NET_Config( false ); + NET_Config( false, false ); SV_UnloadProgs (); CL_Drop(); diff --git a/engine/server/sv_move.c b/engine/server/sv_move.c index 491e0a89..48753f21 100644 --- a/engine/server/sv_move.c +++ b/engine/server/sv_move.c @@ -243,7 +243,7 @@ float SV_VecToYaw( const vec3_t src ) } else { - yaw = (int)( atan2( src[1], src[0] ) * 180.0f / M_PI_F ); + yaw = (int)( atan2( src[1], src[0] ) * 180.0 / M_PI ); if( yaw < 0 ) yaw += 360.0f; } return yaw; diff --git a/engine/server/sv_pmove.c b/engine/server/sv_pmove.c index e8e0ea29..53337e80 100644 --- a/engine/server/sv_pmove.c +++ b/engine/server/sv_pmove.c @@ -455,6 +455,8 @@ static float GAME_EXPORT pfnTraceModel( physent_t *pe, float *start, float *end, matrix4x4 matrix; hull_t *hull; + PM_InitTrace( trace, end ); + old_usehull = svgame.pmove->usehull; svgame.pmove->usehull = 2; diff --git a/engine/server/sv_world.c b/engine/server/sv_world.c index ea823974..71079e65 100644 --- a/engine/server/sv_world.c +++ b/engine/server/sv_world.c @@ -871,10 +871,7 @@ void SV_ClipMoveToEntity( edict_t *ent, const vec3_t start, vec3_t mins, vec3_t qboolean rotated, transform_bbox; matrix4x4 matrix; - memset( trace, 0, sizeof( trace_t )); - VectorCopy( end, trace->endpos ); - trace->fraction = 1.0f; - trace->allsolid = 1; + PM_InitTrace( trace, end ); model = SV_ModelHandle( ent->v.modelindex ); @@ -945,10 +942,7 @@ void SV_ClipMoveToEntity( edict_t *ent, const vec3_t start, vec3_t mins, vec3_t for( i = 0; i < hullcount; i++ ) { - memset( &trace_hitbox, 0, sizeof( trace_t )); - VectorCopy( end, trace_hitbox.endpos ); - trace_hitbox.fraction = 1.0; - trace_hitbox.allsolid = 1; + PM_InitTrace( &trace_hitbox, end ); PM_RecursiveHullCheck( &hull[i], hull[i].firstclipnode, 0.0f, 1.0f, start_l, end_l, (pmtrace_t *)&trace_hitbox ); @@ -1114,10 +1108,7 @@ or custom physics implementation void SV_CustomClipMoveToEntity( edict_t *ent, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, trace_t *trace ) { // initialize custom trace - memset( trace, 0, sizeof( trace_t )); - VectorCopy( end, trace->endpos ); - trace->allsolid = true; - trace->fraction = 1.0f; + PM_InitTrace( trace, end ); if( svgame.physFuncs.ClipMoveToEntity != NULL ) { diff --git a/engine/wscript b/engine/wscript index c1479c45..0b7ef631 100644 --- a/engine/wscript +++ b/engine/wscript @@ -48,9 +48,6 @@ def configure(conf): conf.options.NO_ASYNC_RESOLVE = True if not conf.check_cc( fragment='int main(){ int i = socket();}', lib = 'wattcpwl', mandatory=False ): conf.define('XASH_NO_NETWORK',1) - elif conf.env.DEST_OS == 'android': # Android doesn't need SDL2 - for i in ['log']: - conf.check_cc(lib = i) elif conf.options.FBDEV_SW: # unused, XASH_LINUX without XASH_SDL gives fbdev & alsa support # conf.define('XASH_FBDEV', 1) @@ -65,7 +62,7 @@ def configure(conf): else: conf.load('sdl2') if not conf.env.HAVE_SDL2: - conf.fatal('SDL2 not availiable! If you want to build dedicated server, specify --dedicated') + conf.fatal('SDL2 not available! If you want to build dedicated server, specify --dedicated') conf.define('XASH_SDL', 2) if conf.env.DEST_OS == 'haiku': @@ -106,8 +103,11 @@ def configure(conf): conf.define_cond('PSAPI_VERSION', conf.env.DEST_OS == 'win32') # will be defined as 1 def build(bld): + # public includes for renderers and utils use + bld(name = 'engine_includes', export_includes = '. common common/imagelib', use = 'filesystem_includes') + is_cxx_link = False - libs = [ 'public', 'dllemu' ] + libs = [ 'engine_includes', 'public', 'dllemu' ] # basic build: dedicated only source = bld.path.ant_glob([ @@ -165,8 +165,9 @@ def build(bld): 'client/*.c', 'client/vgui/*.c', 'client/avi/*.c']) + libs += ['opus'] - includes = ['common', 'server', 'client', 'client/vgui', 'tests', '.', '../public', '../common', '../pm_shared' ] + includes = ['server', 'client', 'client/vgui' ] if bld.env.SINGLE_BINARY: install_path = bld.env.BINDIR @@ -184,5 +185,6 @@ def build(bld): includes = includes, use = libs, install_path = install_path, - subsystem = bld.env.MSVC_SUBSYSTEM + subsystem = bld.env.MSVC_SUBSYSTEM, + rpath = '$ORIGIN' ) diff --git a/filesystem/VFileSystem009.cpp b/filesystem/VFileSystem009.cpp new file mode 100644 index 00000000..7bfee6ba --- /dev/null +++ b/filesystem/VFileSystem009.cpp @@ -0,0 +1,509 @@ +/* +VFileSystem009.h - C++ interface for filesystem_stdio +Copyright (C) 2022 Alibek Omarov + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ +#include +#include +#include +#include +#include ALLOCA_H +#include "crtlib.h" +#include "filesystem.h" +#include "filesystem_internal.h" +#include "VFileSystem009.h" + +#if __cplusplus < 201103L +#define override +#endif + +// GoldSrc Directories and ID +// GAME gamedir +// GAMECONFIG gamedir (rodir integration?) +// GAMEDOWNLOAD gamedir_downloads (gamedir/downloads for us) +// GAME_FALLBACK liblist.gam's fallback_dir +// ROOT and BASE rootdir +// PLATFORM platform +// CONFIG platform/config + +static inline qboolean IsIdGamedir( const char *id ) +{ + return !Q_strcmp( id, "GAME" ) || + !Q_strcmp( id, "GAMECONFIG" ) || + !Q_strcmp( id, "GAMEDOWNLOAD" ); +} + +static inline const char* IdToDir( const char *id ) +{ + if( !Q_strcmp( id, "GAME" )) + return GI->gamefolder; + else if( !Q_strcmp( id, "GAMEDOWNLOAD" )) + return va( "%s/downloaded", GI->gamefolder ); + else if( !Q_strcmp( id, "GAMECONFIG" )) + return fs_writedir; // full path here so it's totally our write allowed directory + else if( !Q_strcmp( id, "PLATFORM" )) + return "platform"; // stub + else if( !Q_strcmp( id, "CONFIG" )) + return "platform/config"; // stub + else // ROOT || BASE + return fs_rootdir; // give at least root directory +} + +static inline void CopyAndFixSlashes( char *p, const char *in ) +{ + Q_strcpy( p, in ); + COM_FixSlashes( p ); +} + +class CXashFS : public IVFileSystem009 +{ +private: + class CSearchState + { + public: + CSearchState( CSearchState **head, search_t *search ) : + next( *head ), search( search ), index( 0 ) + { + if( *head ) + handle = (*head)->handle + 1; + else handle = 0; + + *head = this; + } + ~CSearchState() + { + Mem_Free( search ); + } + + CSearchState *next; + search_t *search; + int index; + FileFindHandle_t handle; + }; + + CSearchState *searchHead; + + CSearchState *GetSearchStateByHandle( FileFindHandle_t handle ) + { + for( CSearchState *state = searchHead; state; state = state->next ) + { + if( state->handle == handle ) + { + return state; + } + } + + Con_DPrintf( "Can't find search state by handle %d\n", handle ); + return NULL; + } + +public: + CXashFS() : searchHead( NULL ) + { + } + + void RemoveAllSearchPaths() override + { + FS_ClearSearchPath(); + } + + void AddSearchPath( const char *path, const char *id ) override + { + char *p = (char *)alloca( Q_strlen( path ) + 1 ); + CopyAndFixSlashes( p, path ); + + FS_AddGameDirectory( p, FS_CUSTOM_PATH ); + } + + void AddSearchPathNoWrite( const char *path, const char *id ) override + { + char *p = (char *)alloca( Q_strlen( path ) + 1 ); + CopyAndFixSlashes( p, path ); + + FS_AddGameDirectory( p, FS_NOWRITE_PATH | FS_CUSTOM_PATH ); + } + + bool RemoveSearchPath( const char *id ) override + { + // TODO: + return true; + } + + void RemoveFile( const char *path, const char *id ) override + { + FS_Delete( path ); // FS_Delete is aware of slashes + } + + void CreateDirHierarchy( const char *path, const char *id ) override + { + FS_CreatePath( va( "%s/%s", IdToDir( id ), path )); // FS_CreatePath is aware of slashes + } + + bool FileExists( const char *path ) override + { + char *p = (char *)alloca( Q_strlen( path ) + 1 ); + CopyAndFixSlashes( p, path ); + + return FS_FileExists( p, false ); + } + + bool IsDirectory( const char *path ) override + { + char *p = (char *)alloca( Q_strlen( path ) + 1 ); + CopyAndFixSlashes( p, path ); + + return FS_SysFolderExists( p ); + } + + FileHandle_t Open( const char *path, const char *mode, const char *id ) override + { + char *p = (char *)alloca( Q_strlen( path ) + 1 ); + CopyAndFixSlashes( p, path ); + + file_t *fd = FS_Open( p, mode, IsIdGamedir( id ) ); + + return fd; + } + + void Close( FileHandle_t handle ) override + { + FS_Close( (file_t *)handle ); + } + + void Seek( FileHandle_t handle, int offset, FileSystemSeek_t whence ) override + { + int whence_ = SEEK_SET; + switch( whence ) + { + case FILESYSTEM_SEEK_HEAD: whence_ = SEEK_SET; break; + case FILESYSTEM_SEEK_CURRENT: whence_ = SEEK_CUR; break; + case FILESYSTEM_SEEK_TAIL: whence_ = SEEK_END; break; + } + + FS_Seek( (file_t *)handle, offset, whence_ ); + } + + unsigned int Tell( FileHandle_t handle ) override + { + return FS_Tell( (file_t *)handle ); + } + + unsigned int Size( FileHandle_t handle ) override + { + file_t *fd = (file_t *)handle; + return fd->real_length; + } + + unsigned int Size( const char *path ) override + { + char *p = (char *)alloca( Q_strlen( path ) + 1 ); + CopyAndFixSlashes( p, path ); + return FS_FileSize( p, false ); + } + + long int GetFileTime( const char *path ) override + { + char *p = (char *)alloca( Q_strlen( path ) + 1 ); + CopyAndFixSlashes( p, path ); + return FS_FileTime( p, false ); + } + + void FileTimeToString( char *p, int size, long int time ) override + { + time_t curtime = time; + char *buf = ctime( &curtime ); + Q_strncpy( p, buf, size ); + } + + bool IsOk( FileHandle_t handle ) override + { + return !FS_Eof( (file_t *)handle ); + } + + void Flush( FileHandle_t handle ) override + { + FS_Flush( (file_t *)handle ); + } + + bool EndOfFile( FileHandle_t handle ) override + { + return FS_Eof( (file_t *)handle ); + } + + int Read( void *buf, int size, FileHandle_t handle ) override + { + return FS_Read( (file_t *)handle, buf, size ); + } + + int Write( const void *buf, int size, FileHandle_t handle ) override + { + return FS_Write( (file_t *)handle, buf, size ); + } + + char *ReadLine( char *buf, int size, FileHandle_t handle ) override + { + int c = FS_Gets( (file_t *)handle, (byte*)buf, size ); + + return c >= 0 ? buf : NULL; + } + + int FPrintf( FileHandle_t handle, char *fmt, ... ) override + { + va_list ap; + int ret; + + va_start( ap, fmt ); + ret = FS_VPrintf( (file_t *)handle, fmt, ap ); + va_end( ap ); + + return ret; + } + + void * GetReadBuffer(FileHandle_t, int *size, bool) override + { + // deprecated by Valve + *size = 0; + return NULL; + } + + void ReleaseReadBuffer(FileHandle_t, void *) override + { + // deprecated by Valve + return; + } + + const char *FindFirst(const char *pattern, FileFindHandle_t *handle, const char *id) override + { + if( !handle || !pattern ) + return NULL; + + char *p = (char *)alloca( Q_strlen( pattern ) + 1 ); + CopyAndFixSlashes( p, pattern ); + search_t *search = FS_Search( p, true, IsIdGamedir( id )); + + if( !search ) + return NULL; + + CSearchState *state = new CSearchState( &searchHead, search ); + + *handle = state->handle; + + return state->search->filenames[0]; + } + + const char *FindNext( FileFindHandle_t handle ) override + { + CSearchState *state = GetSearchStateByHandle( handle ); + + if( !state ) return NULL; + + if( state->index + 1 >= state->search->numfilenames ) + return NULL; + + return state->search->filenames[++state->index]; + } + + bool FindIsDirectory( FileFindHandle_t handle ) override + { + CSearchState *state = GetSearchStateByHandle( handle ); + + if( !state ) + return false; + + if( state->index >= state->search->numfilenames ) + return false; + + return IsDirectory( state->search->filenames[state->index] ); + } + + void FindClose( FileFindHandle_t handle ) override + { + for( CSearchState *state = searchHead, **prev = NULL; + state; + *prev = state, state = state->next ) + { + if( state->handle == handle ) + { + if( prev ) + (*prev)->next = state->next; + else searchHead = state->next; + + delete state; + + return; + } + } + + Con_DPrintf( "FindClose: Can't find search state by handle %d\n", handle ); + return; + } + + const char * GetLocalPath( const char *name, char *buf, int size ) override + { + if( !name ) return NULL; + + char *p = (char *)alloca( Q_strlen( name ) + 1 ); + CopyAndFixSlashes( p, name ); + +#if !XASH_WIN32 + if( p[0] == '/' ) +#else + if( Q_strchr( p, ':' )) +#endif + { + Q_strncpy( buf, p, size ); + + return buf; + } + + + const char *fullpath = FS_GetDiskPath( p, false ); + if( !fullpath ) + return NULL; + + Q_strncpy( buf, fullpath, size ); + return buf; + } + + char *ParseFile( char *buf, char *token, bool *quoted ) override + { + qboolean qquoted; + + char *p = COM_ParseFileSafe( buf, token, PFILE_FS_TOKEN_MAX_LENGTH, 0, NULL, &qquoted ); + if( quoted ) *quoted = qquoted; + + return p; + } + + bool FullPathToRelativePath( const char *path, char *out ) override + { + if( !COM_CheckString( path )) + { + *out = 0; + return false; + } + + char *p = (char *)alloca( Q_strlen( path ) + 1 ); + CopyAndFixSlashes( p, path ); + searchpath_t *sp; + + for( sp = fs_searchpaths; sp; sp = sp->next ) + { + size_t splen = Q_strlen( sp->filename ); + + if( !Q_strnicmp( sp->filename, p, splen )) + { + Q_strcpy( out, p + splen + 1 ); + return true; + } + } + + Q_strcpy( out, p ); + return false; + } + + bool GetCurrentDirectory( char *p, int size ) override + { + Q_strncpy( p, fs_rootdir, size ); + + return true; + } + + void PrintOpenedFiles() override + { + // we don't track this yet + return; + } + + void SetWarningFunc(void (*)(const char *, ...)) override + { + // TODO: + return; + } + + void SetWarningLevel(FileWarningLevel_t) override + { + // TODO: + return; + } + + int SetVBuf( FileHandle_t handle, char *buf, int mode, long int size ) override + { + // TODO: + return 0; + } + + void GetInterfaceVersion(char *p, int size) override + { + Q_strncpy( p, "Stdio", size ); + } + + bool AddPackFile( const char *path, const char *id ) override + { + char *p = va( "%s/%s", IdToDir( id ), path ); + CopyAndFixSlashes( p, path ); + + return !!FS_AddPak_Fullpath( p, NULL, FS_CUSTOM_PATH ); + } + + FileHandle_t OpenFromCacheForRead( const char *path , const char *mode, const char *id ) override + { + char *p = (char *)alloca( Q_strlen( path ) + 1 ); + CopyAndFixSlashes( p, path ); + + return FS_OpenReadFile( p, mode, IsIdGamedir( id )); + } + + // stubs + void Mount() override {} + void Unmount() override {} + void GetLocalCopy(const char *) override {} + void LogLevelLoadStarted(const char *) override {} + void LogLevelLoadFinished(const char *) override {} + void CancelWaitForResources(WaitForResourcesHandle_t) override {} + int HintResourceNeed(const char *, int) override { return 0; } + WaitForResourcesHandle_t WaitForResources(const char *) override { return 0; } + int PauseResourcePreloading() override { return 0; } + int ResumeResourcePreloading() override { return 0; } + bool IsAppReadyForOfflinePlay(int) override { return true; } + bool IsFileImmediatelyAvailable(const char *) override { return true; } + bool GetWaitForResourcesProgress(WaitForResourcesHandle_t, float *pProgress, bool *pOverride) override + { + if( pProgress ) *pProgress = 0; + if( pOverride ) *pOverride = true; + return false; + } +} g_VFileSystem009; + +extern "C" void EXPORT *CreateInterface( const char *interface, int *retval ) +{ + if( !Q_strcmp( interface, "VFileSystem009" )) + { + if( retval ) *retval = 0; + return &g_VFileSystem009; + } + + if( !Q_strcmp( interface, FS_API_CREATEINTERFACE_TAG )) + { + // return a copy, to disallow overriding + static fs_api_t copy = { 0 }; + + if( !copy.InitStdio ) + memcpy( ©, &g_api, sizeof( copy )); + + if( retval ) *retval = 0; + return © + } + + if( retval ) *retval = 1; + return NULL; +} diff --git a/filesystem/VFileSystem009.h b/filesystem/VFileSystem009.h new file mode 100644 index 00000000..a0b73123 --- /dev/null +++ b/filesystem/VFileSystem009.h @@ -0,0 +1,153 @@ +/* +VFileSystem009.h - C++ interface for filesystem_stdio +Copyright (C) 2022 Alibek Omarov + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef VFILESYSTEM009_H +#define VFILESYSTEM009_H + +// exported from dwarf +typedef enum { + FILESYSTEM_SEEK_HEAD = 0, + FILESYSTEM_SEEK_CURRENT = 1, + FILESYSTEM_SEEK_TAIL = 2, +} FileSystemSeek_t; /* size: 4 */ + +typedef enum { + FILESYSTEM_WARNING_QUIET = 0, + FILESYSTEM_WARNING_REPORTUNCLOSED = 1, + FILESYSTEM_WARNING_REPORTUSAGE = 2, + FILESYSTEM_WARNING_REPORTALLACCESSES = 3, +} FileWarningLevel_t; /* size: 4 */ + +typedef void * FileHandle_t; /* size: 4 */ +typedef int FileFindHandle_t; /* size: 4 */ +typedef int WaitForResourcesHandle_t; /* size: 4 */ + +class IBaseInterface +{ +public: + virtual ~IBaseInterface() {} +}; + +class IVFileSystem009 : public IBaseInterface +{ +public: + virtual void Mount() = 0; /* linkage=_ZN11IFileSystem5MountEv */ + + virtual void Unmount() = 0; /* linkage=_ZN11IFileSystem7UnmountEv */ + + virtual void RemoveAllSearchPaths() = 0; /* linkage=_ZN11IFileSystem20RemoveAllSearchPathsEv */ + + virtual void AddSearchPath(const char *, const char *) = 0; /* linkage=_ZN11IFileSystem13AddSearchPathEPKcS1_ */ + + virtual bool RemoveSearchPath(const char *) = 0; /* linkage=_ZN11IFileSystem16RemoveSearchPathEPKc */ + + virtual void RemoveFile(const char *, const char *) = 0; /* linkage=_ZN11IFileSystem10RemoveFileEPKcS1_ */ + + virtual void CreateDirHierarchy(const char *, const char *) = 0; /* linkage=_ZN11IFileSystem18CreateDirHierarchyEPKcS1_ */ + + virtual bool FileExists(const char *) = 0; /* linkage=_ZN11IFileSystem10FileExistsEPKc */ + + virtual bool IsDirectory(const char *) = 0; /* linkage=_ZN11IFileSystem11IsDirectoryEPKc */ + + virtual FileHandle_t Open(const char *, const char *, const char *) = 0; /* linkage=_ZN11IFileSystem4OpenEPKcS1_S1_ */ + + virtual void Close(FileHandle_t) = 0; /* linkage=_ZN11IFileSystem5CloseEPv */ + + virtual void Seek(FileHandle_t, int, FileSystemSeek_t) = 0; /* linkage=_ZN11IFileSystem4SeekEPvi16FileSystemSeek_t */ + + virtual unsigned int Tell(FileHandle_t) = 0; /* linkage=_ZN11IFileSystem4TellEPv */ + + virtual unsigned int Size(FileHandle_t) = 0; /* linkage=_ZN11IFileSystem4SizeEPv */ + + virtual unsigned int Size(const char *) = 0; /* linkage=_ZN11IFileSystem4SizeEPKc */ + + virtual long int GetFileTime(const char *) = 0; /* linkage=_ZN11IFileSystem11GetFileTimeEPKc */ + + virtual void FileTimeToString(char *, int, long int) = 0; /* linkage=_ZN11IFileSystem16FileTimeToStringEPcil */ + + virtual bool IsOk(FileHandle_t) = 0; /* linkage=_ZN11IFileSystem4IsOkEPv */ + + virtual void Flush(FileHandle_t) = 0; /* linkage=_ZN11IFileSystem5FlushEPv */ + + virtual bool EndOfFile(FileHandle_t) = 0; /* linkage=_ZN11IFileSystem9EndOfFileEPv */ + + virtual int Read(void *, int, FileHandle_t) = 0; /* linkage=_ZN11IFileSystem4ReadEPviS0_ */ + + virtual int Write(const void *, int, FileHandle_t) = 0; /* linkage=_ZN11IFileSystem5WriteEPKviPv */ + + virtual char * ReadLine(char *, int, FileHandle_t) = 0; /* linkage=_ZN11IFileSystem8ReadLineEPciPv */ + + virtual int FPrintf(FileHandle_t, char *, ...) = 0; /* linkage=_ZN11IFileSystem7FPrintfEPvPcz */ + + virtual void * GetReadBuffer(FileHandle_t, int *, bool) = 0; /* linkage=_ZN11IFileSystem13GetReadBufferEPvPib */ + + virtual void ReleaseReadBuffer(FileHandle_t, void *) = 0; /* linkage=_ZN11IFileSystem17ReleaseReadBufferEPvS0_ */ + + virtual const char * FindFirst(const char *, FileFindHandle_t *, const char *) = 0; /* linkage=_ZN11IFileSystem9FindFirstEPKcPiS1_ */ + + virtual const char * FindNext(FileFindHandle_t) = 0; /* linkage=_ZN11IFileSystem8FindNextEi */ + + virtual bool FindIsDirectory(FileFindHandle_t) = 0; /* linkage=_ZN11IFileSystem15FindIsDirectoryEi */ + + virtual void FindClose(FileFindHandle_t) = 0; /* linkage=_ZN11IFileSystem9FindCloseEi */ + + virtual void GetLocalCopy(const char *) = 0; /* linkage=_ZN11IFileSystem12GetLocalCopyEPKc */ + + virtual const char * GetLocalPath(const char *, char *, int) = 0; /* linkage=_ZN11IFileSystem12GetLocalPathEPKcPci */ + + virtual char * ParseFile(char *, char *, bool *) = 0; /* linkage=_ZN11IFileSystem9ParseFileEPcS0_Pb */ + + virtual bool FullPathToRelativePath(const char *, char *) = 0; /* linkage=_ZN11IFileSystem22FullPathToRelativePathEPKcPc */ + + virtual bool GetCurrentDirectory(char *, int) = 0; /* linkage=_ZN11IFileSystem19GetCurrentDirectoryEPci */ + + virtual void PrintOpenedFiles() = 0; /* linkage=_ZN11IFileSystem16PrintOpenedFilesEv */ + + virtual void SetWarningFunc(void (*)(const char *, ...)) = 0; /* linkage=_ZN11IFileSystem14SetWarningFuncEPFvPKczE */ + + virtual void SetWarningLevel(FileWarningLevel_t) = 0; /* linkage=_ZN11IFileSystem15SetWarningLevelE18FileWarningLevel_t */ + + virtual void LogLevelLoadStarted(const char *) = 0; /* linkage=_ZN11IFileSystem19LogLevelLoadStartedEPKc */ + + virtual void LogLevelLoadFinished(const char *) = 0; /* linkage=_ZN11IFileSystem20LogLevelLoadFinishedEPKc */ + + virtual int HintResourceNeed(const char *, int) = 0; /* linkage=_ZN11IFileSystem16HintResourceNeedEPKci */ + + virtual int PauseResourcePreloading() = 0; /* linkage=_ZN11IFileSystem23PauseResourcePreloadingEv */ + + virtual int ResumeResourcePreloading() = 0; /* linkage=_ZN11IFileSystem24ResumeResourcePreloadingEv */ + + virtual int SetVBuf(FileHandle_t, char *, int, long int) = 0; /* linkage=_ZN11IFileSystem7SetVBufEPvPcil */ + + virtual void GetInterfaceVersion(char *, int) = 0; /* linkage=_ZN11IFileSystem19GetInterfaceVersionEPci */ + + virtual bool IsFileImmediatelyAvailable(const char *) = 0; /* linkage=_ZN11IFileSystem26IsFileImmediatelyAvailableEPKc */ + + virtual WaitForResourcesHandle_t WaitForResources(const char *) = 0; /* linkage=_ZN11IFileSystem16WaitForResourcesEPKc */ + + virtual bool GetWaitForResourcesProgress(WaitForResourcesHandle_t, float *, bool *) = 0; /* linkage=_ZN11IFileSystem27GetWaitForResourcesProgressEiPfPb */ + + virtual void CancelWaitForResources(WaitForResourcesHandle_t) = 0; /* linkage=_ZN11IFileSystem22CancelWaitForResourcesEi */ + + virtual bool IsAppReadyForOfflinePlay(int) = 0; /* linkage=_ZN11IFileSystem24IsAppReadyForOfflinePlayEi */ + + virtual bool AddPackFile(const char *, const char *) = 0; /* linkage=_ZN11IFileSystem11AddPackFileEPKcS1_ */ + + virtual FileHandle_t OpenFromCacheForRead(const char *, const char *, const char *) = 0; /* linkage=_ZN11IFileSystem20OpenFromCacheForReadEPKcS1_S1_ */ + + virtual void AddSearchPathNoWrite(const char *, const char *) = 0; /* linkage=_ZN11IFileSystem20AddSearchPathNoWriteEPKcS1_ */ +}; + +#endif // VFILESYSTEM009_H diff --git a/engine/common/filesystem.c b/filesystem/filesystem.c similarity index 56% rename from engine/common/filesystem.c rename to filesystem/filesystem.c index 0438b153..bbb445d5 100644 --- a/engine/common/filesystem.c +++ b/filesystem/filesystem.c @@ -27,131 +27,34 @@ GNU General Public License for more details. #include #include #endif -#include "miniz.h" // header-only zlib replacement -#include "common.h" -#include "wadfile.h" +#include +#include +#if XASH_LINUX +#include +#endif +#include "port.h" +#include "const.h" +#include "crtlib.h" +#include "crclib.h" #include "filesystem.h" -#include "library.h" +#include "filesystem_internal.h" #include "xash3d_mathlib.h" -#include "protocol.h" +#include "common/com_strings.h" +#include "common/protocol.h" #define FILE_COPY_SIZE (1024 * 1024) -#define FILE_BUFF_SIZE (2048) -// PAK errors -#define PAK_LOAD_OK 0 -#define PAK_LOAD_COULDNT_OPEN 1 -#define PAK_LOAD_BAD_HEADER 2 -#define PAK_LOAD_BAD_FOLDERS 3 -#define PAK_LOAD_TOO_MANY_FILES 4 -#define PAK_LOAD_NO_FILES 5 -#define PAK_LOAD_CORRUPTED 6 +fs_globals_t FI; +qboolean fs_ext_path = false; // attempt to read\write from ./ or ../ pathes +poolhandle_t fs_mempool; +searchpath_t *fs_searchpaths = NULL; // chain +char fs_rodir[MAX_SYSPATH]; +char fs_rootdir[MAX_SYSPATH]; +char fs_writedir[MAX_SYSPATH]; // path that game allows to overwrite, delete and rename files (and create new of course) -// WAD errors -#define WAD_LOAD_OK 0 -#define WAD_LOAD_COULDNT_OPEN 1 -#define WAD_LOAD_BAD_HEADER 2 -#define WAD_LOAD_BAD_FOLDERS 3 -#define WAD_LOAD_TOO_MANY_FILES 4 -#define WAD_LOAD_NO_FILES 5 -#define WAD_LOAD_CORRUPTED 6 - -// ZIP errors -#define ZIP_LOAD_OK 0 -#define ZIP_LOAD_COULDNT_OPEN 1 -#define ZIP_LOAD_BAD_HEADER 2 -#define ZIP_LOAD_BAD_FOLDERS 3 -#define ZIP_LOAD_NO_FILES 5 -#define ZIP_LOAD_CORRUPTED 6 - -typedef struct stringlist_s -{ - // maxstrings changes as needed, causing reallocation of strings[] array - int maxstrings; - int numstrings; - char **strings; -} stringlist_t; - -typedef struct wadtype_s -{ - const char *ext; - signed char type; -} wadtype_t; - -struct file_s -{ - int handle; // file descriptor - int ungetc; // single stored character from ungetc, cleared to EOF when read - fs_offset_t real_length; // uncompressed file size (for files opened in "read" mode) - fs_offset_t position; // current position in the file - fs_offset_t offset; // offset into the package (0 if external file) - time_t filetime; // pak, wad or real filetime - // contents buffer - fs_offset_t buff_ind, buff_len; // buffer current index and length - byte buff[FILE_BUFF_SIZE]; // intermediate buffer -#ifdef XASH_REDUCE_FD - const char *backup_path; - fs_offset_t backup_position; - uint backup_options; -#endif -}; - -struct wfile_s -{ - string filename; - int infotableofs; - int numlumps; - poolhandle_t mempool; // W_ReadLump temp buffers - file_t *handle; - dlumpinfo_t *lumps; - time_t filetime; -}; - -typedef struct pack_s -{ - string filename; - int handle; - int numfiles; - time_t filetime; // common for all packed files - dpackfile_t *files; -} pack_t; - -typedef struct zipfile_s -{ - char name[MAX_SYSPATH]; - fs_offset_t offset; // offset of local file header - fs_offset_t size; //original file size - fs_offset_t compressed_size; // compressed file size - unsigned short flags; -} zipfile_t; - -typedef struct zip_s -{ - string filename; - int handle; - int numfiles; - time_t filetime; - zipfile_t *files; -} zip_t; - -typedef struct searchpath_s -{ - string filename; - pack_t *pack; - wfile_t *wad; - zip_t *zip; - int flags; - struct searchpath_s *next; -} searchpath_t; - -static poolhandle_t fs_mempool; -static searchpath_t *fs_searchpaths = NULL; // chain static searchpath_t fs_directpath; // static direct path static char fs_basedir[MAX_SYSPATH]; // base game directory static char fs_gamedir[MAX_SYSPATH]; // game current directory -static char fs_writedir[MAX_SYSPATH]; // path that game allows to overwrite, delete and rename files (and create new of course) - -static qboolean fs_ext_path = false; // attempt to read\write from ./ or ../ pathes #if !XASH_WIN32 static qboolean fs_caseinsensitive = true; // try to search missing files #endif @@ -159,7 +62,6 @@ static qboolean fs_caseinsensitive = true; // try to search missing files #ifdef XASH_REDUCE_FD static file_t *fs_last_readfile; static zip_t *fs_last_zip; -static pack_t *fs_last_pak; static void FS_EnsureOpenFile( file_t *file ) { @@ -183,21 +85,6 @@ static void FS_EnsureOpenFile( file_t *file ) } } -static void FS_EnsureOpenZip( zip_t *zip ) -{ - if( fs_last_zip == zip ) - return; - - if( fs_last_zip && (fs_last_zip->handle != -1) ) - { - close( fs_last_zip->handle ); - fs_last_zip->handle = -1; - } - fs_last_zip = zip; - if( zip && (zip->handle == -1) ) - zip->handle = open( zip->filename, O_RDONLY|O_BINARY ); -} - static void FS_BackupFileName( file_t *file, const char *path, uint options ) { if( path == NULL ) @@ -217,21 +104,10 @@ static void FS_BackupFileName( file_t *file, const char *path, uint options ) #else static void FS_EnsureOpenFile( file_t *file ) {} -static void FS_EnsureOpenZip( zip_t *zip ) {} static void FS_BackupFileName( file_t *file, const char *path, uint options ) {} #endif static void FS_InitMemory( void ); -static searchpath_t *FS_FindFile( const char *name, int *index, qboolean gamedironly ); -static dlumpinfo_t *W_FindLump( wfile_t *wad, const char *name, const signed char matchtype ); -static dpackfile_t *FS_AddFileToPack( const char* name, pack_t *pack, fs_offset_t offset, fs_offset_t size ); -void Zip_Close( zip_t *zip ); -static byte *W_LoadFile( const char *path, fs_offset_t *filesizeptr, qboolean gamedironly ); -static wfile_t *W_Open( const char *filename, int *errorcode ); -static qboolean FS_SysFolderExists( const char *path ); -static int FS_SysFileTime( const char *filename ); -static signed char W_TypeFromExt( const char *lumpname ); -static const char *W_ExtFromType( signed char lumptype ); static void FS_Purge( file_t* file ); /* @@ -265,7 +141,7 @@ static void stringlistfreecontents( stringlist_t *list ) list->strings = NULL; } -static void stringlistappend( stringlist_t *list, char *text ) +void stringlistappend( stringlist_t *list, char *text ) { size_t textlen; @@ -374,7 +250,7 @@ FS_FixFileCase emulate WIN32 FS behaviour when opening local file ================== */ -static const char *FS_FixFileCase( const char *path ) +const char *FS_FixFileCase( const char *path ) { #if defined __DOS__ & !defined __WATCOM_LFN__ // not fix, but convert to 8.3 CAPS and rotate slashes @@ -428,7 +304,7 @@ static const char *FS_FixFileCase( const char *path ) } /* android has too slow directory scanning, - so drop out some not useful cases */ + so drop out some not useful cases */ if( fname - path2 > 4 ) { char *point; @@ -472,7 +348,7 @@ FS_PathToWideChar Converts input UTF-8 string to wide char string. ==================== */ -const wchar_t *FS_PathToWideChar( const char *path ) +static const wchar_t *FS_PathToWideChar( const char *path ) { static wchar_t pathBuffer[MAX_PATH]; MultiByteToWideChar( CP_UTF8, 0, path, -1, pathBuffer, MAX_PATH ); @@ -480,49 +356,6 @@ const wchar_t *FS_PathToWideChar( const char *path ) } #endif -/* -==================== -FS_AddFileToPack - -Add a file to the list of files contained into a package -==================== -*/ -static dpackfile_t *FS_AddFileToPack( const char *name, pack_t *pack, fs_offset_t offset, fs_offset_t size ) -{ - int left, right, middle; - dpackfile_t *pfile; - - // look for the slot we should put that file into (binary search) - left = 0; - right = pack->numfiles - 1; - - while( left <= right ) - { - int diff; - - middle = (left + right) / 2; - diff = Q_stricmp( pack->files[middle].name, name ); - - // If we found the file, there's a problem - if( !diff ) Con_Reportf( S_WARN "package %s contains the file %s several times\n", pack->filename, name ); - - // If we're too far in the list - if( diff > 0 ) right = middle - 1; - else left = middle + 1; - } - - // We have to move the right of the list by one slot to free the one we need - pfile = &pack->files[left]; - memmove( pfile + 1, pfile, (pack->numfiles - left) * sizeof( *pfile )); - pack->numfiles++; - - Q_strncpy( pfile->name, name, sizeof( pfile->name )); - pfile->filepos = offset; - pfile->filelen = size; - - return pfile; -} - /* ============ FS_CreatePath @@ -547,703 +380,7 @@ void FS_CreatePath( char *path ) } } -/* -============ -FS_Path_f -debug info -============ -*/ -void FS_Path_f( void ) -{ - searchpath_t *s; - - Con_Printf( "Current search path:\n" ); - - for( s = fs_searchpaths; s; s = s->next ) - { - if( s->pack ) Con_Printf( "%s (%i files)", s->pack->filename, s->pack->numfiles ); - else if( s->wad ) Con_Printf( "%s (%i files)", s->wad->filename, s->wad->numlumps ); - else if( s->zip ) Con_Printf( "%s (%i files)", s->zip->filename, s->zip->numfiles ); - else Con_Printf( "%s", s->filename ); - - if( s->flags & FS_GAMERODIR_PATH ) Con_Printf( " ^2rodir^7" ); - if( s->flags & FS_GAMEDIR_PATH ) Con_Printf( " ^2gamedir^7" ); - if( s->flags & FS_CUSTOM_PATH ) Con_Printf( " ^2custom^7" ); - if( s->flags & FS_NOWRITE_PATH ) Con_Printf( " ^2nowrite^7" ); - if( s->flags & FS_STATIC_PATH ) Con_Printf( " ^2static^7" ); - - Con_Printf( "\n" ); - } -} - -/* -============ -FS_ClearPath_f - -only for debug targets -============ -*/ -void FS_ClearPaths_f( void ) -{ - FS_ClearSearchPath(); -} - -/* -================= -FS_LoadPackPAK - -Takes an explicit (not game tree related) path to a pak file. - -Loads the header and directory, adding the files at the beginning -of the list so they override previous pack files. -================= -*/ -pack_t *FS_LoadPackPAK( const char *packfile, int *error ) -{ - dpackheader_t header; - int packhandle; - int i, numpackfiles; - pack_t *pack; - dpackfile_t *info; - fs_size_t c; - - packhandle = open( packfile, O_RDONLY|O_BINARY ); - -#if !XASH_WIN32 - if( packhandle < 0 ) - { - const char *fpackfile = FS_FixFileCase( packfile ); - if( fpackfile != packfile ) - packhandle = open( fpackfile, O_RDONLY|O_BINARY ); - } -#endif - - if( packhandle < 0 ) - { - Con_Reportf( "%s couldn't open: %s\n", packfile, strerror( errno )); - if( error ) *error = PAK_LOAD_COULDNT_OPEN; - return NULL; - } - - c = read( packhandle, (void *)&header, sizeof( header )); - - if( c != sizeof( header ) || header.ident != IDPACKV1HEADER ) - { - Con_Reportf( "%s is not a packfile. Ignored.\n", packfile ); - if( error ) *error = PAK_LOAD_BAD_HEADER; - close( packhandle ); - return NULL; - } - - if( header.dirlen % sizeof( dpackfile_t )) - { - Con_Reportf( S_ERROR "%s has an invalid directory size. Ignored.\n", packfile ); - if( error ) *error = PAK_LOAD_BAD_FOLDERS; - close( packhandle ); - return NULL; - } - - numpackfiles = header.dirlen / sizeof( dpackfile_t ); - - if( numpackfiles > MAX_FILES_IN_PACK ) - { - Con_Reportf( S_ERROR "%s has too many files ( %i ). Ignored.\n", packfile, numpackfiles ); - if( error ) *error = PAK_LOAD_TOO_MANY_FILES; - close( packhandle ); - return NULL; - } - - if( numpackfiles <= 0 ) - { - Con_Reportf( "%s has no files. Ignored.\n", packfile ); - if( error ) *error = PAK_LOAD_NO_FILES; - close( packhandle ); - return NULL; - } - - info = (dpackfile_t *)Mem_Malloc( fs_mempool, sizeof( *info ) * numpackfiles ); - lseek( packhandle, header.dirofs, SEEK_SET ); - - if( header.dirlen != read( packhandle, (void *)info, header.dirlen )) - { - Con_Reportf( "%s is an incomplete PAK, not loading\n", packfile ); - if( error ) *error = PAK_LOAD_CORRUPTED; - close( packhandle ); - Mem_Free( info ); - return NULL; - } - - pack = (pack_t *)Mem_Calloc( fs_mempool, sizeof( pack_t )); - Q_strncpy( pack->filename, packfile, sizeof( pack->filename )); - pack->files = (dpackfile_t *)Mem_Calloc( fs_mempool, numpackfiles * sizeof( dpackfile_t )); - pack->filetime = FS_SysFileTime( packfile ); - pack->handle = packhandle; - pack->numfiles = 0; - - // parse the directory - for( i = 0; i < numpackfiles; i++ ) - FS_AddFileToPack( info[i].name, pack, info[i].filepos, info[i].filelen ); - -#ifdef XASH_REDUCE_FD - // will reopen when needed - close( pack->handle ); - pack->handle = -1; -#endif - - if( error ) *error = PAK_LOAD_OK; - Mem_Free( info ); - - return pack; -} - -/* -============ -FS_SortZip -============ -*/ -static int FS_SortZip( const void *a, const void *b ) -{ - return Q_stricmp( ( ( zipfile_t* )a )->name, ( ( zipfile_t* )b )->name ); -} - -/* -============ -FS_LoadZip -============ -*/ -static zip_t *FS_LoadZip( const char *zipfile, int *error ) -{ - int numpackfiles = 0, i; - zip_cdf_header_t header_cdf; - zip_header_eocd_t header_eocd; - uint32_t signature; - fs_offset_t filepos = 0, length; - zipfile_t *info = NULL; - char filename_buffer[MAX_SYSPATH]; - zip_t *zip = (zip_t *)Mem_Calloc( fs_mempool, sizeof( *zip )); - fs_size_t c; - - zip->handle = open( zipfile, O_RDONLY|O_BINARY ); - -#if !XASH_WIN32 - if( zip->handle < 0 ) - { - const char *fzipfile = FS_FixFileCase( zipfile ); - if( fzipfile != zipfile ) - zip->handle = open( fzipfile, O_RDONLY|O_BINARY ); - } -#endif - - if( zip->handle < 0 ) - { - Con_Reportf( S_ERROR "%s couldn't open\n", zipfile ); - - if( error ) - *error = ZIP_LOAD_COULDNT_OPEN; - - Zip_Close( zip ); - return NULL; - } - - length = lseek( zip->handle, 0, SEEK_END ); - - if( length > UINT_MAX ) - { - Con_Reportf( S_ERROR "%s bigger than 4GB.\n", zipfile ); - - if( error ) - *error = ZIP_LOAD_COULDNT_OPEN; - - Zip_Close( zip ); - return NULL; - } - - lseek( zip->handle, 0, SEEK_SET ); - - c = read( zip->handle, &signature, sizeof( signature ) ); - - if( c != sizeof( signature ) || signature == ZIP_HEADER_EOCD ) - { - Con_Reportf( S_WARN "%s has no files. Ignored.\n", zipfile ); - - if( error ) - *error = ZIP_LOAD_NO_FILES; - - Zip_Close( zip ); - return NULL; - } - - if( signature != ZIP_HEADER_LF ) - { - Con_Reportf( S_ERROR "%s is not a zip file. Ignored.\n", zipfile ); - - if( error ) - *error = ZIP_LOAD_BAD_HEADER; - - Zip_Close( zip ); - return NULL; - } - - // Find oecd - lseek( zip->handle, 0, SEEK_SET ); - filepos = length; - - while ( filepos > 0 ) - { - lseek( zip->handle, filepos, SEEK_SET ); - c = read( zip->handle, &signature, sizeof( signature ) ); - - if( c == sizeof( signature ) && signature == ZIP_HEADER_EOCD ) - break; - - filepos -= sizeof( char ); // step back one byte - } - - if( ZIP_HEADER_EOCD != signature ) - { - Con_Reportf( S_ERROR "cannot find EOCD in %s. Zip file corrupted.\n", zipfile ); - - if( error ) - *error = ZIP_LOAD_BAD_HEADER; - - Zip_Close( zip ); - return NULL; - } - - c = read( zip->handle, &header_eocd, sizeof( header_eocd ) ); - - if( c != sizeof( header_eocd )) - { - Con_Reportf( S_ERROR "invalid EOCD header in %s. Zip file corrupted.\n", zipfile ); - - if( error ) - *error = ZIP_LOAD_BAD_HEADER; - - Zip_Close( zip ); - return NULL; - } - - // Move to CDF start - lseek( zip->handle, header_eocd.central_directory_offset, SEEK_SET ); - - // Calc count of files in archive - info = (zipfile_t *)Mem_Calloc( fs_mempool, sizeof( *info ) * header_eocd.total_central_directory_record ); - - for( i = 0; i < header_eocd.total_central_directory_record; i++ ) - { - c = read( zip->handle, &header_cdf, sizeof( header_cdf ) ); - - if( c != sizeof( header_cdf ) || header_cdf.signature != ZIP_HEADER_CDF ) - { - Con_Reportf( S_ERROR "CDF signature mismatch in %s. Zip file corrupted.\n", zipfile ); - - if( error ) - *error = ZIP_LOAD_BAD_HEADER; - - Mem_Free( info ); - Zip_Close( zip ); - return NULL; - } - - if( header_cdf.uncompressed_size && header_cdf.filename_len && ( header_cdf.filename_len < MAX_SYSPATH ) ) - { - memset( &filename_buffer, '\0', MAX_SYSPATH ); - c = read( zip->handle, &filename_buffer, header_cdf.filename_len ); - - if( c != header_cdf.filename_len ) - { - Con_Reportf( S_ERROR "filename length mismatch in %s. Zip file corrupted.\n", zipfile ); - - if( error ) - *error = ZIP_LOAD_CORRUPTED; - - Mem_Free( info ); - Zip_Close( zip ); - return NULL; - } - - Q_strncpy( info[numpackfiles].name, filename_buffer, MAX_SYSPATH ); - - info[numpackfiles].size = header_cdf.uncompressed_size; - info[numpackfiles].compressed_size = header_cdf.compressed_size; - info[numpackfiles].offset = header_cdf.local_header_offset; - numpackfiles++; - } - else - lseek( zip->handle, header_cdf.filename_len, SEEK_CUR ); - - if( header_cdf.extrafield_len ) - lseek( zip->handle, header_cdf.extrafield_len, SEEK_CUR ); - - if( header_cdf.file_commentary_len ) - lseek( zip->handle, header_cdf.file_commentary_len, SEEK_CUR ); - } - - // recalculate offsets - for( i = 0; i < numpackfiles; i++ ) - { - zip_header_t header; - - lseek( zip->handle, info[i].offset, SEEK_SET ); - c = read( zip->handle, &header, sizeof( header ) ); - - if( c != sizeof( header )) - { - Con_Reportf( S_ERROR "header length mismatch in %s. Zip file corrupted.\n", zipfile ); - - if( error ) - *error = ZIP_LOAD_CORRUPTED; - - Mem_Free( info ); - Zip_Close( zip ); - return NULL; - } - - info[i].flags = header.compression_flags; - info[i].offset = info[i].offset + header.filename_len + header.extrafield_len + sizeof( header ); - } - - Q_strncpy( zip->filename, zipfile, sizeof( zip->filename ) ); - zip->filetime = FS_SysFileTime( zipfile ); - zip->numfiles = numpackfiles; - zip->files = info; - - qsort( zip->files, zip->numfiles, sizeof( *zip->files ), FS_SortZip ); - -#ifdef XASH_REDUCE_FD - // will reopen when needed - close(zip->handle); - zip->handle = -1; -#endif - - if( error ) - *error = ZIP_LOAD_OK; - - return zip; -} - -void Zip_Close( zip_t *zip ) -{ - if( !zip ) - return; - - if( zip->files ) - Mem_Free( zip->files ); - - FS_EnsureOpenZip( NULL ); - - if( zip->handle >= 0 ) - close( zip->handle ); - - Mem_Free( zip ); -} - -static byte *Zip_LoadFile( const char *path, fs_offset_t *sizeptr, qboolean gamedironly ) -{ - searchpath_t *search; - int index; - zipfile_t *file = NULL; - byte *compressed_buffer = NULL, *decompressed_buffer = NULL; - int zlib_result = 0; - dword test_crc, final_crc; - z_stream decompress_stream; - size_t c; - - if( sizeptr ) *sizeptr = 0; - - search = FS_FindFile( path, &index, gamedironly ); - - if( !search || !search->zip ) - return NULL; - - file = &search->zip->files[index]; - - FS_EnsureOpenZip( search->zip ); - - if( lseek( search->zip->handle, file->offset, SEEK_SET ) == -1 ) - return NULL; - - /*if( read( search->zip->handle, &header, sizeof( header ) ) < 0 ) - return NULL; - - if( header.signature != ZIP_HEADER_LF ) - { - Con_Reportf( S_ERROR "Zip_LoadFile: %s signature error\n", file->name ); - return NULL; - }*/ - - if( file->flags == ZIP_COMPRESSION_NO_COMPRESSION ) - { - decompressed_buffer = Mem_Malloc( fs_mempool, file->size + 1 ); - decompressed_buffer[file->size] = '\0'; - - c = read( search->zip->handle, decompressed_buffer, file->size ); - if( c != file->size ) - { - Con_Reportf( S_ERROR "Zip_LoadFile: %s size doesn't match\n", file->name ); - return NULL; - } - -#if 0 - CRC32_Init( &test_crc ); - CRC32_ProcessBuffer( &test_crc, decompressed_buffer, file->size ); - - final_crc = CRC32_Final( test_crc ); - - if( final_crc != file->crc32 ) - { - Con_Reportf( S_ERROR "Zip_LoadFile: %s file crc32 mismatch\n", file->name ); - Mem_Free( decompressed_buffer ); - return NULL; - } -#endif - if( sizeptr ) *sizeptr = file->size; - - FS_EnsureOpenZip( NULL ); - return decompressed_buffer; - } - else if( file->flags == ZIP_COMPRESSION_DEFLATED ) - { - compressed_buffer = Mem_Malloc( fs_mempool, file->compressed_size + 1 ); - decompressed_buffer = Mem_Malloc( fs_mempool, file->size + 1 ); - decompressed_buffer[file->size] = '\0'; - - c = read( search->zip->handle, compressed_buffer, file->compressed_size ); - if( c != file->compressed_size ) - { - Con_Reportf( S_ERROR "Zip_LoadFile: %s compressed size doesn't match\n", file->name ); - return NULL; - } - - memset( &decompress_stream, 0, sizeof( decompress_stream ) ); - - decompress_stream.total_in = decompress_stream.avail_in = file->compressed_size; - decompress_stream.next_in = (Bytef *)compressed_buffer; - decompress_stream.total_out = decompress_stream.avail_out = file->size; - decompress_stream.next_out = (Bytef *)decompressed_buffer; - - decompress_stream.zalloc = Z_NULL; - decompress_stream.zfree = Z_NULL; - decompress_stream.opaque = Z_NULL; - - if( inflateInit2( &decompress_stream, -MAX_WBITS ) != Z_OK ) - { - Con_Printf( S_ERROR "Zip_LoadFile: inflateInit2 failed\n" ); - Mem_Free( compressed_buffer ); - Mem_Free( decompressed_buffer ); - return NULL; - } - - zlib_result = inflate( &decompress_stream, Z_NO_FLUSH ); - inflateEnd( &decompress_stream ); - - if( zlib_result == Z_OK || zlib_result == Z_STREAM_END ) - { - Mem_Free( compressed_buffer ); // finaly free compressed buffer -#if 0 - CRC32_Init( &test_crc ); - CRC32_ProcessBuffer( &test_crc, decompressed_buffer, file->size ); - - final_crc = CRC32_Final( test_crc ); - - if( final_crc != file->crc32 ) - { - Con_Reportf( S_ERROR "Zip_LoadFile: %s file crc32 mismatch\n", file->name ); - Mem_Free( decompressed_buffer ); - return NULL; - } -#endif - if( sizeptr ) *sizeptr = file->size; - - FS_EnsureOpenZip( NULL ); - return decompressed_buffer; - } - else - { - Con_Reportf( S_ERROR "Zip_LoadFile: %s : error while file decompressing. Zlib return code %d.\n", file->name, zlib_result ); - Mem_Free( compressed_buffer ); - Mem_Free( decompressed_buffer ); - return NULL; - } - - } - else - { - Con_Reportf( S_ERROR "Zip_LoadFile: %s : file compressed with unknown algorithm.\n", file->name ); - return NULL; - } - - FS_EnsureOpenZip( NULL ); - return NULL; -} - -/* -==================== -FS_AddWad_Fullpath -==================== -*/ -static qboolean FS_AddWad_Fullpath( const char *wadfile, qboolean *already_loaded, int flags ) -{ - searchpath_t *search; - wfile_t *wad = NULL; - const char *ext = COM_FileExtension( wadfile ); - int errorcode = WAD_LOAD_COULDNT_OPEN; - - for( search = fs_searchpaths; search; search = search->next ) - { - if( search->wad && !Q_stricmp( search->wad->filename, wadfile )) - { - if( already_loaded ) *already_loaded = true; - return true; // already loaded - } - } - - if( already_loaded ) - *already_loaded = false; - - if( !Q_stricmp( ext, "wad" )) - wad = W_Open( wadfile, &errorcode ); - - if( wad ) - { - search = (searchpath_t *)Mem_Calloc( fs_mempool, sizeof( searchpath_t )); - search->wad = wad; - search->next = fs_searchpaths; - search->flags |= flags; - fs_searchpaths = search; - - Con_Reportf( "Adding wadfile: %s (%i files)\n", wadfile, wad->numlumps ); - return true; - } - else - { - if( errorcode != WAD_LOAD_NO_FILES ) - Con_Reportf( S_ERROR "FS_AddWad_Fullpath: unable to load wad \"%s\"\n", wadfile ); - return false; - } -} - -/* -================ -FS_AddPak_Fullpath - -Adds the given pack to the search path. -The pack type is autodetected by the file extension. - -Returns true if the file was successfully added to the -search path or if it was already included. - -If keep_plain_dirs is set, the pack will be added AFTER the first sequence of -plain directories. -================ -*/ -static qboolean FS_AddPak_Fullpath( const char *pakfile, qboolean *already_loaded, int flags ) -{ - searchpath_t *search; - pack_t *pak = NULL; - const char *ext = COM_FileExtension( pakfile ); - int i, errorcode = PAK_LOAD_COULDNT_OPEN; - - for( search = fs_searchpaths; search; search = search->next ) - { - if( search->pack && !Q_stricmp( search->pack->filename, pakfile )) - { - if( already_loaded ) *already_loaded = true; - return true; // already loaded - } - } - - if( already_loaded ) - *already_loaded = false; - - if( !Q_stricmp( ext, "pak" )) - pak = FS_LoadPackPAK( pakfile, &errorcode ); - - if( pak ) - { - string fullpath; - - search = (searchpath_t *)Mem_Calloc( fs_mempool, sizeof( searchpath_t )); - search->pack = pak; - search->next = fs_searchpaths; - search->flags |= flags; - fs_searchpaths = search; - - Con_Reportf( "Adding pakfile: %s (%i files)\n", pakfile, pak->numfiles ); - - // time to add in search list all the wads that contains in current pakfile (if do) - for( i = 0; i < pak->numfiles; i++ ) - { - if( !Q_stricmp( COM_FileExtension( pak->files[i].name ), "wad" )) - { - Q_snprintf( fullpath, MAX_STRING, "%s/%s", pakfile, pak->files[i].name ); - FS_AddWad_Fullpath( fullpath, NULL, flags ); - } - } - - return true; - } - else - { - if( errorcode != PAK_LOAD_NO_FILES ) - Con_Reportf( S_ERROR "FS_AddPak_Fullpath: unable to load pak \"%s\"\n", pakfile ); - return false; - } -} - -static qboolean FS_AddZip_Fullpath( const char *zipfile, qboolean *already_loaded, int flags ) -{ - searchpath_t *search; - zip_t *zip = NULL; - const char *ext = COM_FileExtension( zipfile ); - int errorcode = ZIP_LOAD_COULDNT_OPEN; - - for( search = fs_searchpaths; search; search = search->next ) - { - if( search->pack && !Q_stricmp( search->pack->filename, zipfile )) - { - if( already_loaded ) *already_loaded = true; - return true; // already loaded - } - } - - if( already_loaded ) *already_loaded = false; - - if( !Q_stricmp( ext, "pk3" ) || !Q_stricmp( ext, "zip" )) - zip = FS_LoadZip( zipfile, &errorcode ); - - if( zip ) - { - string fullpath; - int i; - - search = (searchpath_t *)Mem_Calloc( fs_mempool, sizeof( searchpath_t ) ); - search->zip = zip; - search->next = fs_searchpaths; - search->flags |= flags; - fs_searchpaths = search; - - Con_Reportf( "Adding zipfile: %s (%i files)\n", zipfile, zip->numfiles ); - - // time to add in search list all the wads that contains in current pakfile (if do) - for( i = 0; i < zip->numfiles; i++ ) - { - if( !Q_stricmp( COM_FileExtension( zip->files[i].name ), "wad" )) - { - Q_snprintf( fullpath, MAX_STRING, "%s/%s", zipfile, zip->files[i].name ); - FS_AddWad_Fullpath( fullpath, NULL, flags ); - } - } - return true; - } - else - { - if( errorcode != ZIP_LOAD_NO_FILES ) - Con_Reportf( S_ERROR "FS_AddZip_Fullpath: unable to load zip \"%s\"\n", zipfile ); - return false; - } -} /* ================ @@ -1254,7 +391,7 @@ static qboolean FS_AddArchive_Fullpath( const char *file, qboolean *already_load { const char *ext = COM_FileExtension( file ); - if( !Q_stricmp( ext, "zip" ) || !Q_stricmp( ext, "pk3" )) + if( !Q_stricmp( ext, "pk3" ) ) return FS_AddZip_Fullpath( file, already_loaded, flags ); else if ( !Q_stricmp( ext, "pak" )) return FS_AddPak_Fullpath( file, already_loaded, flags ); @@ -1297,7 +434,7 @@ void FS_AddGameDirectory( const char *dir, uint flags ) // add any Zip package in the directory for( i = 0; i < list.numstrings; i++ ) { - if( !Q_stricmp( COM_FileExtension( list.strings[i] ), "zip" ) || !Q_stricmp( COM_FileExtension( list.strings[i] ), "pk3" )) + if( !Q_stricmp( COM_FileExtension( list.strings[i] ), "pk3" ) ) { Q_sprintf( fullpath, "%s%s", dir, list.strings[i] ); FS_AddZip_Fullpath( fullpath, NULL, flags ); @@ -1324,62 +461,11 @@ void FS_AddGameDirectory( const char *dir, uint flags ) search = (searchpath_t *)Mem_Calloc( fs_mempool, sizeof( searchpath_t )); Q_strncpy( search->filename, dir, sizeof ( search->filename )); search->next = fs_searchpaths; + search->type = SEARCHPATH_PLAIN; search->flags = flags; fs_searchpaths = search; } -/* -================ -FS_AddGameHierarchy -================ -*/ -void FS_AddGameHierarchy( const char *dir, uint flags ) -{ - int i; - qboolean isGameDir = flags & FS_GAMEDIR_PATH; - - GI->added = true; - - if( !COM_CheckString( dir )) - return; - - // add the common game directory - - // recursive gamedirs - // for example, czeror->czero->cstrike->valve - for( i = 0; i < SI.numgames; i++ ) - { - if( !Q_strnicmp( SI.games[i]->gamefolder, dir, 64 )) - { - Con_Reportf( "FS_AddGameHierarchy: %d %s %s\n", i, SI.games[i]->gamefolder, SI.games[i]->basedir ); - if( !SI.games[i]->added && Q_stricmp( SI.games[i]->gamefolder, SI.games[i]->basedir ) ) - { - SI.games[i]->added = true; - FS_AddGameHierarchy( SI.games[i]->basedir, flags & (~FS_GAMEDIR_PATH) ); - } - break; - } - } - - if( COM_CheckStringEmpty( host.rodir ) ) - { - // append new flags to rodir, except FS_GAMEDIR_PATH and FS_CUSTOM_PATH - uint newFlags = FS_NOWRITE_PATH | (flags & (~FS_GAMEDIR_PATH|FS_CUSTOM_PATH)); - if( isGameDir ) - newFlags |= FS_GAMERODIR_PATH; - - FS_AllowDirectPaths( true ); - FS_AddGameDirectory( va( "%s/%s/", host.rodir, dir ), newFlags ); - FS_AllowDirectPaths( false ); - } - - if( isGameDir ) - FS_AddGameDirectory( va( "%s/downloaded/", dir ), FS_NOWRITE_PATH | FS_CUSTOM_PATH ); - FS_AddGameDirectory( va( "%s/", dir ), flags ); - if( isGameDir ) - FS_AddGameDirectory( va( "%s/custom/", dir ), FS_NOWRITE_PATH | FS_CUSTOM_PATH ); -} - /* ================ FS_ClearSearchPath @@ -1402,23 +488,19 @@ void FS_ClearSearchPath( void ) } else fs_searchpaths = search->next; - if( search->pack ) + switch( search->type ) { - if( search->pack->files ) - Mem_Free( search->pack->files ); - if( search->pack->handle >= 0 ) - close( search->pack->handle ); - Mem_Free( search->pack ); - } - - if( search->wad ) - { - W_Close( search->wad ); - } - - if( search->zip ) - { - Zip_Close(search->zip); + case SEARCHPATH_PAK: + FS_ClosePAK( search->pack ); + break; + case SEARCHPATH_WAD: + FS_CloseWAD( search->wad ); + break; + case SEARCHPATH_ZIP: + FS_CloseZIP( search->zip ); + break; + default: + break; } Mem_Free( search ); @@ -1428,19 +510,18 @@ void FS_ClearSearchPath( void ) /* ==================== FS_CheckNastyPath - Return true if the path should be rejected due to one of the following: 1: path elements that are non-portable 2: path elements that would allow access to files outside the game directory, - or are just not a good idea for a mod to be using. + or are just not a good idea for a mod to be using. ==================== */ -int FS_CheckNastyPath( const char *path, qboolean isgamedir ) +int FS_CheckNastyPath (const char *path, qboolean isgamedir) { // all: never allow an empty path, as for gamedir it would access the parent directory and a non-gamedir path it is just useless if( !COM_CheckString( path )) return 2; - if( fs_ext_path ) return 0; // allow any path + if( fs_ext_path ) return 0; // allow any path // Mac: don't allow Mac-only filenames - : is a directory separator // instead of /, but we rely on / working already, so there's no reason to @@ -1449,72 +530,23 @@ int FS_CheckNastyPath( const char *path, qboolean isgamedir ) if( Q_strchr( path, ':' )) return 1; // non-portable attempt to go to root of drive // Amiga: // is parent directory - if( Q_strstr( path, "//" )) return 1; // non-portable attempt to go to parent directory + if( Q_strstr( path, "//")) return 1; // non-portable attempt to go to parent directory // all: don't allow going to parent directory (../ or /../) - if( Q_strstr( path, ".." )) return 2; // attempt to go outside the game directory + if( Q_strstr( path, "..")) return 2; // attempt to go outside the game directory // Windows and UNIXes: don't allow absolute paths - if( path[0] == '/' ) return 2; // attempt to go outside the game directory + if( path[0] == '/') return 2; // attempt to go outside the game directory // all: forbid trailing slash on gamedir - if( isgamedir && path[Q_strlen( path )-1] == '/' ) return 2; + if( isgamedir && path[Q_strlen(path)-1] == '/' ) return 2; // all: forbid leading dot on any filename for any reason - if( Q_strstr( path, "/." )) return 2; // attempt to go outside the game directory + if( Q_strstr(path, "/.")) return 2; // attempt to go outside the game directory // after all these checks we're pretty sure it's a / separated filename // and won't do much if any harm - return 0; -} - -/* -================ -FS_Rescan -================ -*/ -void FS_Rescan( void ) -{ - const char *str; - const int extrasFlags = FS_NOWRITE_PATH | FS_CUSTOM_PATH; - Con_Reportf( "FS_Rescan( %s )\n", GI->title ); - - FS_ClearSearchPath(); - -#if XASH_IOS - { - FS_AddPak_Fullpath( va( "%sextras.pak", SDL_GetBasePath() ), NULL, extrasFlags ); - FS_AddPak_Fullpath( va( "%sextras_%s.pak", SDL_GetBasePath(), GI->gamefolder ), NULL, extrasFlags ); - } -#else - str = getenv( "XASH3D_EXTRAS_PAK1" ); - if( COM_CheckString( str )) - FS_AddArchive_Fullpath( str, NULL, extrasFlags ); - - str = getenv( "XASH3D_EXTRAS_PAK2" ); - if( COM_CheckString( str )) - FS_AddArchive_Fullpath( str, NULL, extrasFlags ); -#endif - - if( Q_stricmp( GI->basedir, GI->gamefolder )) - FS_AddGameHierarchy( GI->basedir, 0 ); - if( Q_stricmp( GI->basedir, GI->falldir ) && Q_stricmp( GI->gamefolder, GI->falldir )) - FS_AddGameHierarchy( GI->falldir, 0 ); - FS_AddGameHierarchy( GI->gamefolder, FS_GAMEDIR_PATH ); - - if( FS_FileExists( va( "%s.rc", SI.basedirName ), false )) - Q_strncpy( SI.rcName, SI.basedirName, sizeof( SI.rcName )); // e.g. valve.rc - else Q_strncpy( SI.rcName, SI.exeName, sizeof( SI.rcName )); // e.g. quake.rc -} - -/* -================ -FS_Rescan_f -================ -*/ -void FS_Rescan_f( void ) -{ - FS_Rescan(); + return false; } /* @@ -1714,6 +746,24 @@ void FS_ParseGenericGameInfo( gameinfo_t *GameInfo, const char *buf, const qbool else if( !Q_stricmp( token, "gamedll_linux" )) { pfile = COM_ParseFile( pfile, GameInfo->game_dll_linux, sizeof( GameInfo->game_dll_linux )); + + // try to normalize filename only for liblist.gam + // from hl_i?86.so to hl.so + if( !isGameInfo ) + { + char *p; + COM_StripExtension( GameInfo->game_dll_linux ); + + p = Q_strrchr( GameInfo->game_dll_linux, '_' ); + + if( p && Q_stricmpext( "_i?86", p )) + { + *p = 0; + } + + COM_DefaultExtension( GameInfo->game_dll_linux, "."OS_LIB_EXT ); + } + found_linux = true; } // valid for both @@ -1868,6 +918,11 @@ void FS_ParseGenericGameInfo( gameinfo_t *GameInfo, const char *buf, const qbool pfile = COM_ParseFile( pfile, token, sizeof( token )); GameInfo->render_picbutton_text = Q_atoi( token ); } + else if( !Q_stricmp( token, "internal_vgui_support" )) + { + pfile = COM_ParseFile( pfile, token, sizeof( token )); + GameInfo->internal_vgui_support = Q_atoi( token ); + } } } @@ -1886,7 +941,7 @@ void FS_ParseGenericGameInfo( gameinfo_t *GameInfo, const char *buf, const qbool } // make sure what gamedir is really exist - if( !FS_SysFolderExists( va( "%s"PATH_SPLITTER"%s", host.rootdir, GameInfo->falldir ))) + if( !FS_SysFolderExists( va( "%s"PATH_SPLITTER"%s", fs_rootdir, GameInfo->falldir ))) GameInfo->falldir[0] = '\0'; } @@ -2011,15 +1066,15 @@ static qboolean FS_ParseGameInfo( const char *gamedir, gameinfo_t *GameInfo ) Q_snprintf( liblist_path, sizeof( liblist_path ), "%s/liblist.gam", gamedir ); // here goes some RoDir magic... - if( COM_CheckStringEmpty( host.rodir ) ) + if( COM_CheckStringEmpty( fs_rodir ) ) { string filepath_ro, liblist_ro; fs_offset_t roLibListTime, roGameInfoTime, rwGameInfoTime; FS_AllowDirectPaths( true ); - Q_snprintf( filepath_ro, sizeof( filepath_ro ), "%s/%s/gameinfo.txt", host.rodir, gamedir ); - Q_snprintf( liblist_ro, sizeof( liblist_ro ), "%s/%s/liblist.gam", host.rodir, gamedir ); + Q_snprintf( filepath_ro, sizeof( filepath_ro ), "%s/%s/gameinfo.txt", fs_rodir, gamedir ); + Q_snprintf( liblist_ro, sizeof( liblist_ro ), "%s/%s/liblist.gam", fs_rodir, gamedir ); roLibListTime = FS_SysFileTime( liblist_ro ); roGameInfoTime = FS_SysFileTime( filepath_ro ); @@ -2076,6 +1131,93 @@ static qboolean FS_ParseGameInfo( const char *gamedir, gameinfo_t *GameInfo ) return false; } +/* +================ +FS_AddGameHierarchy +================ +*/ +void FS_AddGameHierarchy( const char *dir, uint flags ) +{ + int i; + qboolean isGameDir = flags & FS_GAMEDIR_PATH; + + GI->added = true; + + if( !COM_CheckString( dir )) + return; + + // add the common game directory + + // recursive gamedirs + // for example, czeror->czero->cstrike->valve + for( i = 0; i < FI.numgames; i++ ) + { + if( !Q_strnicmp( FI.games[i]->gamefolder, dir, 64 )) + { + Con_Reportf( "FS_AddGameHierarchy: %d %s %s\n", i, FI.games[i]->gamefolder, FI.games[i]->basedir ); + if( !FI.games[i]->added && Q_stricmp( FI.games[i]->gamefolder, FI.games[i]->basedir )) + { + FI.games[i]->added = true; + FS_AddGameHierarchy( FI.games[i]->basedir, flags & (~FS_GAMEDIR_PATH) ); + } + break; + } + } + + if( COM_CheckStringEmpty( fs_rodir ) ) + { + // append new flags to rodir, except FS_GAMEDIR_PATH and FS_CUSTOM_PATH + uint newFlags = FS_NOWRITE_PATH | (flags & (~FS_GAMEDIR_PATH|FS_CUSTOM_PATH)); + if( isGameDir ) + newFlags |= FS_GAMERODIR_PATH; + + FS_AllowDirectPaths( true ); + FS_AddGameDirectory( va( "%s/%s/", fs_rodir, dir ), newFlags ); + FS_AllowDirectPaths( false ); + } + + if( isGameDir ) + FS_AddGameDirectory( va( "%s/downloaded/", dir ), FS_NOWRITE_PATH | FS_CUSTOM_PATH ); + FS_AddGameDirectory( va( "%s/", dir ), flags ); + if( isGameDir ) + FS_AddGameDirectory( va( "%s/custom/", dir ), FS_NOWRITE_PATH | FS_CUSTOM_PATH ); +} + +/* +================ +FS_Rescan +================ +*/ +void FS_Rescan( void ) +{ + const char *str; + const int extrasFlags = FS_NOWRITE_PATH | FS_CUSTOM_PATH; + Con_Reportf( "FS_Rescan( %s )\n", GI->title ); + + FS_ClearSearchPath(); + +#if XASH_IOS + { + FS_AddPak_Fullpath( va( "%sextras.pak", SDL_GetBasePath() ), NULL, extrasFlags ); + FS_AddPak_Fullpath( va( "%sextras_%s.pak", SDL_GetBasePath(), GI->gamefolder ), NULL, extrasFlags ); + } +#else + str = getenv( "XASH3D_EXTRAS_PAK1" ); + if( COM_CheckString( str )) + FS_AddArchive_Fullpath( str, NULL, extrasFlags ); + + str = getenv( "XASH3D_EXTRAS_PAK2" ); + if( COM_CheckString( str )) + FS_AddArchive_Fullpath( str, NULL, extrasFlags ); +#endif + + if( Q_stricmp( GI->basedir, GI->gamefolder )) + FS_AddGameHierarchy( GI->basedir, 0 ); + if( Q_stricmp( GI->basedir, GI->falldir ) && Q_stricmp( GI->gamefolder, GI->falldir )) + FS_AddGameHierarchy( GI->falldir, 0 ); + FS_AddGameHierarchy( GI->gamefolder, FS_GAMEDIR_PATH ); +} + /* ================ FS_LoadGameInfo @@ -2097,39 +1239,176 @@ void FS_LoadGameInfo( const char *rootfolder ) FS_ClearSearchPath(); // validate gamedir - for( i = 0; i < SI.numgames; i++ ) + for( i = 0; i < FI.numgames; i++ ) { - if( !Q_stricmp( SI.games[i]->gamefolder, fs_gamedir )) + if( !Q_stricmp( FI.games[i]->gamefolder, fs_gamedir )) break; } - if( i == SI.numgames ) + if( i == FI.numgames ) Sys_Error( "Couldn't find game directory '%s'\n", fs_gamedir ); - SI.GameInfo = SI.games[i]; - - if( !Sys_GetParmFromCmdLine( "-dll", SI.gamedll ) ) - { - SI.gamedll[0] = 0; - } - - if( !Sys_GetParmFromCmdLine( "-clientlib", SI.clientlib ) ) - { - SI.clientlib[0] = 0; - } + FI.GameInfo = FI.games[i]; FS_Rescan(); // create new filesystem - - Image_CheckPaletteQ1 (); - Host_InitDecals (); // reload decals } +/* +================== +FS_CheckForCrypt + +return true if library is crypted +================== +*/ +static qboolean FS_CheckForCrypt( const char *dllname ) +{ + file_t *f; + int key; + + f = FS_Open( dllname, "rb", false ); + if( !f ) return false; + + FS_Seek( f, 64, SEEK_SET ); // skip first 64 bytes + FS_Read( f, &key, sizeof( key )); + FS_Close( f ); + + return ( key == 0x12345678 ) ? true : false; +} + +/* +================== +FS_FindLibrary + +search for library, assume index is valid +================== +*/ +static qboolean FS_FindLibrary( const char *dllname, qboolean directpath, fs_dllinfo_t *dllInfo ) +{ + searchpath_t *search; + int index, start = 0, i, len; + + fs_ext_path = directpath; + + // check for bad exports + if( !COM_CheckString( dllname )) + return false; + + // HACKHACK remove absoulte path to valve folder + if( !Q_strnicmp( dllname, "..\\valve\\", 9 ) || !Q_strnicmp( dllname, "../valve/", 9 )) + start += 9; + + // replace all backward slashes + len = Q_strlen( dllname ); + + for( i = 0; i < len; i++ ) + { + if( dllname[i+start] == '\\' ) dllInfo->shortPath[i] = '/'; + else dllInfo->shortPath[i] = Q_tolower( dllname[i+start] ); + } + dllInfo->shortPath[i] = '\0'; + + COM_DefaultExtension( dllInfo->shortPath, "."OS_LIB_EXT ); // apply ext if forget + + search = FS_FindFile( dllInfo->shortPath, &index, false ); + + if( !search && !directpath ) + { + fs_ext_path = false; + + // trying check also 'bin' folder for indirect paths + Q_strncpy( dllInfo->shortPath, dllname, sizeof( dllInfo->shortPath )); + search = FS_FindFile( dllInfo->shortPath, &index, false ); + if( !search ) return false; // unable to find + } + + dllInfo->encrypted = FS_CheckForCrypt( dllInfo->shortPath ); + + if( index < 0 && !dllInfo->encrypted && search ) + { + Q_snprintf( dllInfo->fullPath, sizeof( dllInfo->fullPath ), + "%s%s", search->filename, dllInfo->shortPath ); + dllInfo->custom_loader = false; // we can loading from disk and use normal debugging + } + else + { + // NOTE: if search is NULL let the OS found library himself + Q_strncpy( dllInfo->fullPath, dllInfo->shortPath, sizeof( dllInfo->fullPath )); + + if( search && search->type != SEARCHPATH_PLAIN ) + { +#if XASH_WIN32 && XASH_X86 // a1ba: custom loader is non-portable (I just don't want to touch it) + Con_Printf( S_WARN "%s: loading libraries from packs is deprecated " + "and will be removed in the future\n", __FUNCTION__ ); + dllInfo->custom_loader = true; +#else + Con_Printf( S_WARN "%s: loading libraries from packs is unsupported on " + "this platform\n", __FUNCTION__ ); + dllInfo->custom_loader = false; +#endif + } + else + { + dllInfo->custom_loader = false; + } + } + fs_ext_path = false; // always reset direct paths + + return true; +} + +poolhandle_t _Mem_AllocPool( const char *name, const char *filename, int fileline ) +{ + return 0xDEADC0DE; +} + +void _Mem_FreePool( poolhandle_t *poolptr, const char *filename, int fileline ) +{ + // stub +} + +void* _Mem_Alloc( poolhandle_t poolptr, size_t size, qboolean clear, const char *filename, int fileline ) +{ + if( clear ) return calloc( 1, size ); + return malloc( size ); +} + +void* _Mem_Realloc( poolhandle_t poolptr, void *memptr, size_t size, qboolean clear, const char *filename, int fileline ) +{ + return realloc( memptr, size ); +} + +void _Mem_Free( void *data, const char *filename, int fileline ) +{ + free( data ); +} + +void _Con_Printf( const char *fmt, ... ) +{ + va_list ap; + + va_start( ap, fmt ); + vprintf( fmt, ap ); + va_end( ap ); +} + +void _Sys_Error( const char *fmt, ... ) +{ + va_list ap; + + va_start( ap, fmt ); + vfprintf( stderr, fmt, ap ); + va_end( ap ); + + exit( 1 ); +} + + /* ================ FS_Init ================ */ -void FS_Init( void ) +qboolean FS_InitStdio( qboolean caseinsensitive, const char *rootdir, const char *basedir, const char *gamedir, const char *rodir ) { stringlist_t dirs; qboolean hasBaseDir = false; @@ -2137,62 +1416,32 @@ void FS_Init( void ) int i; FS_InitMemory(); - - Cmd_AddRestrictedCommand( "fs_rescan", FS_Rescan_f, "rescan filesystem search pathes" ); - Cmd_AddRestrictedCommand( "fs_path", FS_Path_f, "show filesystem search pathes" ); - Cmd_AddRestrictedCommand( "fs_clearpaths", FS_ClearPaths_f, "clear filesystem search pathes" ); - #if !XASH_WIN32 - if( Sys_CheckParm( "-casesensitive" ) ) - fs_caseinsensitive = false; - - if( !fs_caseinsensitive ) - { - if( COM_CheckStringEmpty( host.rodir ) && !Q_strcmp( host.rodir, host.rootdir ) ) - { - Sys_Error( "RoDir and default rootdir can't point to same directory!" ); - } - } - else + fs_caseinsensitive = caseinsensitive; #endif - { - if( COM_CheckStringEmpty( host.rodir ) && !Q_stricmp( host.rodir, host.rootdir ) ) - { - Sys_Error( "RoDir and default rootdir can't point to same directory!" ); - } - } - // ignore commandlineoption "-game" for other stuff - SI.numgames = 0; - - Q_strncpy( fs_basedir, SI.basedirName, sizeof( fs_basedir )); // default dir - - if( !Sys_GetParmFromCmdLine( "-game", fs_gamedir )) - Q_strncpy( fs_gamedir, fs_basedir, sizeof( fs_gamedir )); // gamedir == basedir - - if( FS_CheckNastyPath( fs_basedir, true )) - { - // this is completely fatal... - Sys_Error( "invalid base directory \"%s\"\n", fs_basedir ); - } - - if( FS_CheckNastyPath( fs_gamedir, true )) - { - Con_Printf( S_ERROR "invalid game directory \"%s\"\n", fs_gamedir ); - Q_strncpy( fs_gamedir, fs_basedir, sizeof( fs_gamedir )); // default dir - } + Q_strncpy( fs_rootdir, rootdir, sizeof( fs_rootdir )); + Q_strncpy( fs_gamedir, gamedir, sizeof( fs_gamedir )); + Q_strncpy( fs_basedir, basedir, sizeof( fs_basedir )); + Q_strncpy( fs_rodir, rodir, sizeof( fs_rodir )); // add readonly directories first - if( COM_CheckStringEmpty( host.rodir ) ) + if( COM_CheckStringEmpty( fs_rodir )) { + if( !Q_stricmp( fs_rodir, fs_rootdir )) + { + Sys_Error( "RoDir and default rootdir can't point to same directory!" ); + return false; + } + stringlistinit( &dirs ); - listdirectory( &dirs, host.rodir, false ); + listdirectory( &dirs, fs_rodir, false ); stringlistsort( &dirs ); for( i = 0; i < dirs.numstrings; i++ ) { - char *roPath = va( "%s" PATH_SPLITTER "%s" PATH_SPLITTER, host.rodir, dirs.strings[i] ); - char *rwPath = va( "%s" PATH_SPLITTER "%s" PATH_SPLITTER, host.rootdir, dirs.strings[i] ); + char *roPath = va( "%s" PATH_SPLITTER "%s" PATH_SPLITTER, fs_rodir, dirs.strings[i] ); + char *rwPath = va( "%s" PATH_SPLITTER "%s" PATH_SPLITTER, fs_rootdir, dirs.strings[i] ); // check if it's a directory if( !FS_SysFolderExists( roPath )) @@ -2226,22 +1475,24 @@ void FS_Init( void ) } // build list of game directories here - FS_AddGameDirectory( "./", 0 ); + FS_AddGameDirectory( "./", FS_STATIC_PATH ); for( i = 0; i < dirs.numstrings; i++ ) { if( !FS_SysFolderExists( dirs.strings[i] ) || ( !Q_strcmp( dirs.strings[i], ".." ) && !fs_ext_path )) continue; - if( SI.games[SI.numgames] == NULL ) - SI.games[SI.numgames] = (gameinfo_t *)Mem_Calloc( fs_mempool, sizeof( gameinfo_t )); + if( FI.games[FI.numgames] == NULL ) + FI.games[FI.numgames] = (gameinfo_t *)Mem_Calloc( fs_mempool, sizeof( gameinfo_t )); - if( FS_ParseGameInfo( dirs.strings[i], SI.games[SI.numgames] )) - SI.numgames++; // added + if( FS_ParseGameInfo( dirs.strings[i], FI.games[FI.numgames] )) + FI.numgames++; // added } stringlistfreecontents( &dirs ); Con_Reportf( "FS_Init: done\n" ); + + return true; } void FS_AllowDirectPaths( qboolean enable ) @@ -2254,20 +1505,62 @@ void FS_AllowDirectPaths( qboolean enable ) FS_Shutdown ================ */ -void FS_Shutdown( void ) +void FS_ShutdownStdio( void ) { - int i; - + int i; // release gamedirs - for( i = 0; i < SI.numgames; i++ ) - if( SI.games[i] ) Mem_Free( SI.games[i] ); - - memset( &SI, 0, sizeof( sysinfo_t )); + for( i = 0; i < FI.numgames; i++ ) + if( FI.games[i] ) Mem_Free( FI.games[i] ); FS_ClearSearchPath(); // release all wad files too Mem_FreePool( &fs_mempool ); } +/* +============ +FS_Path_f + +debug info +============ +*/ +void FS_Path_f( void ) +{ + searchpath_t *s; + + Con_Printf( "Current search path:\n" ); + + for( s = fs_searchpaths; s; s = s->next ) + { + string info; + + switch( s->type ) + { + case SEARCHPATH_PAK: + FS_PrintPAKInfo( info, sizeof( info ), s->pack ); + break; + case SEARCHPATH_WAD: + FS_PrintWADInfo( info, sizeof( info ), s->wad ); + break; + case SEARCHPATH_ZIP: + FS_PrintZIPInfo( info, sizeof( info ), s->zip ); + break; + case SEARCHPATH_PLAIN: + Q_strncpy( info, s->filename, sizeof( info )); + break; + } + + Con_Printf( "%s", info ); + + if( s->flags & FS_GAMERODIR_PATH ) Con_Printf( " ^2rodir^7" ); + if( s->flags & FS_GAMEDIR_PATH ) Con_Printf( " ^2gamedir^7" ); + if( s->flags & FS_CUSTOM_PATH ) Con_Printf( " ^2custom^7" ); + if( s->flags & FS_NOWRITE_PATH ) Con_Printf( " ^2nowrite^7" ); + if( s->flags & FS_STATIC_PATH ) Con_Printf( " ^2static^7" ); + + Con_Printf( "\n" ); + } +} + /* ==================== FS_SysFileTime @@ -2275,7 +1568,7 @@ FS_SysFileTime Internal function used to determine filetime ==================== */ -static int FS_SysFileTime( const char *filename ) +int FS_SysFileTime( const char *filename ) { struct stat buf; @@ -2292,7 +1585,7 @@ FS_SysOpen Internal function used to create a file_t and open the relevant non-packed file on disk ==================== */ -static file_t *FS_SysOpen( const char *filepath, const char *mode ) +file_t *FS_SysOpen( const char *filepath, const char *mode ) { file_t *file; int mod, opt; @@ -2391,7 +1684,7 @@ static int FS_DuplicateHandle( const char *filename, int handle, fs_offset_t pos } */ -static file_t *FS_OpenHandle( const char *syspath, int handle, fs_offset_t offset, fs_offset_t len ) +file_t *FS_OpenHandle( const char *syspath, int handle, fs_offset_t offset, fs_offset_t len ) { file_t *file = (file_t *)Mem_Calloc( fs_mempool, sizeof( file_t )); #ifndef XASH_REDUCE_FD @@ -2422,44 +1715,6 @@ static file_t *FS_OpenHandle( const char *syspath, int handle, fs_offset_t offse return file; } -/* -=========== -FS_OpenPackedFile - -Open a packed file using its package file descriptor -=========== -*/ -file_t *FS_OpenPackedFile( pack_t *pack, int pack_ind ) -{ - dpackfile_t *pfile; - - pfile = &pack->files[pack_ind]; - - return FS_OpenHandle( pack->filename, pack->handle, pfile->filepos, pfile->filelen ); -} - -/* -=========== -FS_OpenZipFile - -Open a packed file using its package file descriptor -=========== -*/ -file_t *FS_OpenZipFile( zip_t *zip, int pack_ind ) -{ - zipfile_t *pfile; - pfile = &zip->files[pack_ind]; - - // compressed files handled in Zip_LoadFile - if( pfile->flags != ZIP_COMPRESSION_NO_COMPRESSION ) - { - Con_Printf( S_ERROR "%s: can't open compressed file %s\n", __FUNCTION__, pfile->name ); - return NULL; - } - - return FS_OpenHandle( zip->filename, zip->handle, pfile->offset, pfile->size ); -} - /* ================== FS_SysFileExists @@ -2535,10 +1790,7 @@ qboolean FS_SysFolderExists( const char *path ) struct stat buf; if( stat( path, &buf ) < 0 ) - { - Con_Reportf( S_ERROR "FS_SysFolderExists: problem while opening dir: %s\n", strerror( errno )); return false; - } return S_ISDIR( buf.st_mode ); #else @@ -2556,7 +1808,7 @@ Return the searchpath where the file was found (or NULL) and the file index in the package if relevant ==================== */ -static searchpath_t *FS_FindFile( const char *name, int *index, qboolean gamedironly ) +searchpath_t *FS_FindFile( const char *name, int *index, qboolean gamedironly ) { searchpath_t *search; char *pEnvPath; @@ -2568,107 +1820,31 @@ static searchpath_t *FS_FindFile( const char *name, int *index, qboolean gamedir continue; // is the element a pak file? - if( search->pack ) + if( search->type == SEARCHPATH_PAK ) { - int left, right, middle; - pack_t *pak; - - pak = search->pack; - - // look for the file (binary search) - left = 0; - right = pak->numfiles - 1; - while( left <= right ) + int pack_ind = FS_FindFilePAK( search->pack, name ); + if( pack_ind >= 0 ) { - int diff; - - middle = (left + right) / 2; - diff = Q_stricmp( pak->files[middle].name, name ); - - // Found it - if( !diff ) - { - if( index ) *index = middle; - return search; - } - - // if we're too far in the list - if( diff > 0 ) - right = middle - 1; - else left = middle + 1; - } - } - else if( search->wad ) - { - dlumpinfo_t *lump; - signed char type = W_TypeFromExt( name ); - qboolean anywadname = true; - string wadname, wadfolder; - string shortname; - - // quick reject by filetype - if( type == TYP_NONE ) continue; - COM_ExtractFilePath( name, wadname ); - wadfolder[0] = '\0'; - - if( COM_CheckStringEmpty( wadname ) ) - { - COM_FileBase( wadname, wadname ); - Q_strncpy( wadfolder, wadname, sizeof( wadfolder )); - COM_DefaultExtension( wadname, ".wad" ); - anywadname = false; - } - - // make wadname from wad fullpath - COM_FileBase( search->wad->filename, shortname ); - COM_DefaultExtension( shortname, ".wad" ); - - // quick reject by wadname - if( !anywadname && Q_stricmp( wadname, shortname )) - continue; - - // NOTE: we can't using long names for wad, - // because we using original wad names[16]; - COM_FileBase( name, shortname ); - - lump = W_FindLump( search->wad, shortname, type ); - - if( lump ) - { - if( index ) - *index = lump - search->wad->lumps; + if( index ) *index = pack_ind; return search; } } - else if( search->zip ) + else if( search->type == SEARCHPATH_WAD ) { - int left, right, middle; - zip_t *zip; - - zip = search->zip; - - // look for the file (binary search) - left = 0; - right = zip->numfiles - 1; - - while( left <= right ) + int pack_ind = FS_FindFileWAD( search->wad, name ); + if( pack_ind >= 0 ) { - int diff; - - middle = (left + right) / 2; - diff = Q_stricmp( zip->files[middle].name, name ); - - // Found it - if( !diff ) - { - if( index ) *index = middle; - return search; - } - - // if we're too far in the list - if( diff > 0 ) - right = middle - 1; - else left = middle + 1; + if( index ) *index = pack_ind; + return search; + } + } + else if( search->type == SEARCHPATH_ZIP ) + { + int pack_ind = FS_FindFileZIP( search->zip, name ); + if( pack_ind >= 0 ) + { + if( index ) *index = pack_ind; + return search; } } else @@ -2694,7 +1870,7 @@ static searchpath_t *FS_FindFile( const char *name, int *index, qboolean gamedir memset( search, 0, sizeof( searchpath_t )); // root folder has a more priority than netpath - Q_strncpy( search->filename, host.rootdir, sizeof( search->filename )); + Q_strncpy( search->filename, fs_rootdir, sizeof( search->filename )); Q_strcat( search->filename, PATH_SPLITTER ); Q_snprintf( netpath, MAX_SYSPATH, "%s%s", search->filename, name ); @@ -2704,26 +1880,6 @@ static searchpath_t *FS_FindFile( const char *name, int *index, qboolean gamedir *index = -1; return search; } - -#if 0 - // search for environment path - while( ( pEnvPath = getenv( "Path" ) ) ) - { - char *end = Q_strchr( pEnvPath, ';' ); - if( !end ) break; - Q_strncpy( search->filename, pEnvPath, (end - pEnvPath) + 1 ); - Q_strcat( search->filename, PATH_SPLITTER ); - Q_snprintf( netpath, MAX_SYSPATH, "%s%s", search->filename, name ); - - if( FS_SysFileExists( netpath, !( search->flags & FS_CUSTOM_PATH ) )) - { - if( index != NULL ) - *index = -1; - return search; - } - pEnvPath += (end - pEnvPath) + 1; // move pointer - } -#endif // 0 } if( index != NULL ) @@ -2751,19 +1907,23 @@ file_t *FS_OpenReadFile( const char *filename, const char *mode, qboolean gamedi if( search == NULL ) return NULL; - if( search->pack ) - return FS_OpenPackedFile( search->pack, pack_ind ); - else if( search->wad ) - return NULL; // let W_LoadFile get lump correctly - else if( search->zip ) - return FS_OpenZipFile( search->zip, pack_ind ); - else if( pack_ind < 0 ) + switch( search->type ) { - char path [MAX_SYSPATH]; + case SEARCHPATH_PAK: + return FS_OpenPackedFile( search->pack, pack_ind ); + case SEARCHPATH_WAD: + return NULL; // let W_LoadFile get lump correctly + case SEARCHPATH_ZIP: + return FS_OpenZipFile( search->zip, pack_ind ); + default: + if( pack_ind < 0 ) + { + char path [MAX_SYSPATH]; - // found in the filesystem? - Q_sprintf( path, "%s%s", search->filename, filename ); - return FS_SysOpen( path, mode ); + // found in the filesystem? + Q_sprintf( path, "%s%s", search->filename, filename ); + return FS_SysOpen( path, mode ); + } } return NULL; @@ -2831,6 +1991,32 @@ int FS_Close( file_t *file ) return 0; } +/* +==================== +FS_Flush + +flushes written data to disk +==================== +*/ +int FS_Flush( file_t *file ) +{ + if( !file ) return 0; + + // purge cached data + FS_Purge( file ); + + // sync +#if XASH_POSIX + if( fsync( file->handle ) < 0 ) + return EOF; +#else + if( _commit( file->handle ) < 0 ) + return EOF; +#endif + + return 0; +} + /* ==================== FS_Write @@ -3158,7 +2344,7 @@ FS_Purge Erases any buffered input or output data ==================== */ -void FS_Purge( file_t *file ) +static void FS_Purge( file_t *file ) { file->buff_len = 0; file->buff_ind = 0; @@ -3192,10 +2378,10 @@ byte *FS_LoadFile( const char *path, fs_offset_t *filesizeptr, qboolean gamediro } else { - buf = W_LoadFile( path, &filesize, gamedironly ); + buf = FS_LoadWADFile( path, &filesize, gamedironly ); if( !buf ) - buf = Zip_LoadFile( path, &filesize, gamedironly ); + buf = FS_LoadZIPFile( path, &filesize, gamedironly ); } @@ -3214,7 +2400,6 @@ qboolean CRC32_File( dword *crcvalue, const char *filename ) f = FS_Open( filename, "rb", false ); if( !f ) return false; - Assert( crcvalue != NULL ); CRC32_Init( crcvalue ); while( 1 ) @@ -3373,122 +2558,6 @@ const char *FS_GetDiskPath( const char *name, qboolean gamedironly ) return NULL; } -/* -================== -FS_CheckForCrypt - -return true if library is crypted -================== -*/ -qboolean FS_CheckForCrypt( const char *dllname ) -{ - file_t *f; - int key; - - f = FS_Open( dllname, "rb", false ); - if( !f ) return false; - - FS_Seek( f, 64, SEEK_SET ); // skip first 64 bytes - FS_Read( f, &key, sizeof( key )); - FS_Close( f ); - - return ( key == 0x12345678 ) ? true : false; -} - -/* -================== -FS_FindLibrary - -search for library, assume index is valid -only for internal use -================== -*/ -dll_user_t *FS_FindLibrary( const char *dllname, qboolean directpath ) -{ - string dllpath; - searchpath_t *search; - dll_user_t *hInst; - int i, index; - int start = 0; - int len; - - // check for bad exports - if( !COM_CheckString( dllname )) - return NULL; - - fs_ext_path = directpath; - - // HACKHACK remove absoulte path to valve folder - if( !Q_strnicmp( dllname, "..\\valve\\", 9 ) || !Q_strnicmp( dllname, "../valve/", 9 )) - start += 9; - - // replace all backward slashes - len = Q_strlen( dllname ); - - for( i = 0; i < len; i++ ) - { - if( dllname[i+start] == '\\' ) dllpath[i] = '/'; - else dllpath[i] = Q_tolower( dllname[i+start] ); - } - dllpath[i] = '\0'; - - COM_DefaultExtension( dllpath, "."OS_LIB_EXT ); // apply ext if forget - search = FS_FindFile( dllpath, &index, false ); - - if( !search && !directpath ) - { - fs_ext_path = false; - - // trying check also 'bin' folder for indirect paths - Q_strncpy( dllpath, dllname, sizeof( dllpath )); - search = FS_FindFile( dllpath, &index, false ); - if( !search ) return NULL; // unable to find - } - - // NOTE: for libraries we not fail even if search is NULL - // let the OS find library himself - hInst = Mem_Calloc( host.mempool, sizeof( dll_user_t )); - - // save dllname for debug purposes - Q_strncpy( hInst->dllName, dllname, sizeof( hInst->dllName )); - - // shortPath is used for LibraryLoadSymbols only - Q_strncpy( hInst->shortPath, dllpath, sizeof( hInst->shortPath )); - - hInst->encrypted = FS_CheckForCrypt( dllpath ); - - if( index < 0 && !hInst->encrypted && search ) - { - Q_snprintf( hInst->fullPath, sizeof( hInst->fullPath ), "%s%s", search->filename, dllpath ); - hInst->custom_loader = false; // we can loading from disk and use normal debugging - } - else - { - // NOTE: if search is NULL let the OS found library himself - Q_strncpy( hInst->fullPath, dllpath, sizeof( hInst->fullPath )); - - if( search && ( search->wad || search->pack || search->zip ) ) - { -#if XASH_WIN32 && XASH_X86 // a1ba: custom loader is non-portable (I just don't want to touch it) - Con_Printf( S_WARN "%s: loading libraries from packs is deprecated " - "and will be removed in the future\n", __FUNCTION__ ); - hInst->custom_loader = true; -#else - Con_Printf( S_WARN "%s: loading libraries from packs is unsupported on " - "this platform\n", __FUNCTION__ ); - hInst->custom_loader = false; -#endif - } - else - { - hInst->custom_loader = false; - } - } - fs_ext_path = false; // always reset direct paths - - return hInst; -} - /* ================== FS_FileSize @@ -3542,19 +2611,23 @@ int FS_FileTime( const char *filename, qboolean gamedironly ) search = FS_FindFile( filename, &pack_ind, gamedironly ); if( !search ) return -1; // doesn't exist - if( search->pack ) // grab pack filetime - return search->pack->filetime; - else if( search->wad ) // grab wad filetime - return search->wad->filetime; - else if( search->zip ) - return search->zip->filetime; - else if( pack_ind < 0 ) + switch( search->type ) { - // found in the filesystem? - char path [MAX_SYSPATH]; + case SEARCHPATH_PAK: + return FS_FileTimePAK( search->pack ); + case SEARCHPATH_WAD: + return FS_FileTimeWAD( search->wad ); + case SEARCHPATH_ZIP: + return FS_FileTimeZIP( search->zip ); + default: + if( pack_ind < 0 ) + { + char path [MAX_SYSPATH]; - Q_sprintf( path, "%s%s", search->filename, filename ); - return FS_SysFileTime( path ); + // found in the filesystem? + Q_sprintf( path, "%s%s", search->filename, filename ); + return FS_SysFileTime( path ); + } } return -1; // doesn't exist @@ -3686,154 +2759,18 @@ search_t *FS_Search( const char *pattern, int caseinsensitive, int gamedironly ) continue; // is the element a pak file? - if( searchpath->pack ) + if( searchpath->type == SEARCHPATH_PAK ) { // look through all the pak file elements - pak = searchpath->pack; - for( i = 0; i < pak->numfiles; i++ ) - { - Q_strncpy( temp, pak->files[i].name, sizeof( temp )); - while( temp[0] ) - { - if( matchpattern( temp, (char *)pattern, true )) - { - for( resultlistindex = 0; resultlistindex < resultlist.numstrings; resultlistindex++ ) - { - if( !Q_strcmp( resultlist.strings[resultlistindex], temp )) - break; - } - - if( resultlistindex == resultlist.numstrings ) - stringlistappend( &resultlist, temp ); - } - - // strip off one path element at a time until empty - // this way directories are added to the listing if they match the pattern - slash = Q_strrchr( temp, '/' ); - backslash = Q_strrchr( temp, '\\' ); - colon = Q_strrchr( temp, ':' ); - separator = temp; - if( separator < slash ) - separator = slash; - if( separator < backslash ) - separator = backslash; - if( separator < colon ) - separator = colon; - *((char *)separator) = 0; - } - } + FS_SearchPAK( &resultlist, searchpath->pack, pattern ); } - else if( searchpath->zip ) + else if( searchpath->type == SEARCHPATH_ZIP ) { - zip = searchpath->zip; - for( i = 0; i < zip->numfiles; i++ ) - { - Q_strncpy( temp, zip->files[i].name, sizeof(temp) ); - while( temp[0] ) - { - if( matchpattern( temp, (char *)pattern, true )) - { - for( resultlistindex = 0; resultlistindex < resultlist.numstrings; resultlistindex++ ) - { - if( !Q_strcmp( resultlist.strings[resultlistindex], temp )) - break; - } - - if( resultlistindex == resultlist.numstrings ) - stringlistappend( &resultlist, temp ); - } - - // strip off one path element at a time until empty - // this way directories are added to the listing if they match the pattern - slash = Q_strrchr( temp, '/' ); - backslash = Q_strrchr( temp, '\\' ); - colon = Q_strrchr( temp, ':' ); - separator = temp; - if( separator < slash ) - separator = slash; - if( separator < backslash ) - separator = backslash; - if( separator < colon ) - separator = colon; - *((char *)separator) = 0; - } - } + FS_SearchZIP( &resultlist, searchpath->zip, pattern ); } - else if( searchpath->wad ) + else if( searchpath->type == SEARCHPATH_WAD ) { - string wadpattern, wadname, temp2; - signed char type = W_TypeFromExt( pattern ); - qboolean anywadname = true; - string wadfolder; - - // quick reject by filetype - if( type == TYP_NONE ) continue; - COM_ExtractFilePath( pattern, wadname ); - COM_FileBase( pattern, wadpattern ); - wadfolder[0] = '\0'; - - if( COM_CheckStringEmpty( wadname )) - { - COM_FileBase( wadname, wadname ); - Q_strncpy( wadfolder, wadname, sizeof( wadfolder )); - COM_DefaultExtension( wadname, ".wad" ); - anywadname = false; - } - - // make wadname from wad fullpath - COM_FileBase( searchpath->wad->filename, temp2 ); - COM_DefaultExtension( temp2, ".wad" ); - - // quick reject by wadname - if( !anywadname && Q_stricmp( wadname, temp2 )) - continue; - - // look through all the wad file elements - wad = searchpath->wad; - - for( i = 0; i < wad->numlumps; i++ ) - { - // if type not matching, we already have no chance ... - if( type != TYP_ANY && wad->lumps[i].type != type ) - continue; - - // build the lumpname with image suffix (if present) - Q_strncpy( temp, wad->lumps[i].name, sizeof( temp )); - - while( temp[0] ) - { - if( matchpattern( temp, wadpattern, true )) - { - for( resultlistindex = 0; resultlistindex < resultlist.numstrings; resultlistindex++ ) - { - if( !Q_strcmp( resultlist.strings[resultlistindex], temp )) - break; - } - - if( resultlistindex == resultlist.numstrings ) - { - // build path: wadname/lumpname.ext - Q_snprintf( temp2, sizeof(temp2), "%s/%s", wadfolder, temp ); - COM_DefaultExtension( temp2, va(".%s", W_ExtFromType( wad->lumps[i].type ))); - stringlistappend( &resultlist, temp2 ); - } - } - - // strip off one path element at a time until empty - // this way directories are added to the listing if they match the pattern - slash = Q_strrchr( temp, '/' ); - backslash = Q_strrchr( temp, '\\' ); - colon = Q_strrchr( temp, ':' ); - separator = temp; - if( separator < slash ) - separator = slash; - if( separator < backslash ) - separator = backslash; - if( separator < colon ) - separator = colon; - *((char *)separator) = 0; - } - } + FS_SearchWAD( &resultlist, searchpath->wad, pattern ); } else { @@ -3902,376 +2839,124 @@ void FS_InitMemory( void ) fs_searchpaths = NULL; } -/* -============================================================================= - -WADSYSTEM PRIVATE ROUTINES - -============================================================================= -*/ -// associate extension with wad type -static const wadtype_t wad_types[7] = +fs_interface_t g_engfuncs = { -{ "pal", TYP_PALETTE }, // palette -{ "dds", TYP_DDSTEX }, // DDS image -{ "lmp", TYP_GFXPIC }, // quake1, hl pic -{ "fnt", TYP_QFONT }, // hl qfonts -{ "mip", TYP_MIPTEX }, // hl/q1 mip -{ "txt", TYP_SCRIPT }, // scripts -{ NULL, TYP_NONE } + _Con_Printf, + _Con_Printf, + _Con_Printf, + _Sys_Error, + _Mem_AllocPool, + _Mem_FreePool, + _Mem_Alloc, + _Mem_Realloc, + _Mem_Free }; -/* -=========== -W_TypeFromExt - -Extracts file type from extension -=========== -*/ -static signed char W_TypeFromExt( const char *lumpname ) +static qboolean FS_InitInterface( int version, fs_interface_t *engfuncs ) { - const char *ext = COM_FileExtension( lumpname ); - const wadtype_t *type; - - // we not known about filetype, so match only by filename - if( !Q_strcmp( ext, "*" ) || !Q_strcmp( ext, "" )) - return TYP_ANY; - - for( type = wad_types; type->ext; type++ ) + // to be extended in future interface revisions + if( version != FS_API_VERSION ) { - if( !Q_stricmp( ext, type->ext )) - return type->type; + Con_Printf( S_ERROR "filesystem optional interface version mismatch: expected %d, got %d\n", + FS_API_VERSION, version ); + return false; } - return TYP_NONE; + + if( engfuncs->_Con_Printf ) + g_engfuncs._Con_Printf = engfuncs->_Con_Printf; + + if( engfuncs->_Con_DPrintf ) + g_engfuncs._Con_DPrintf = engfuncs->_Con_DPrintf; + + if( engfuncs->_Con_Reportf ) + g_engfuncs._Con_Reportf = engfuncs->_Con_Reportf; + + if( engfuncs->_Sys_Error ) + g_engfuncs._Sys_Error = engfuncs->_Sys_Error; + + if( engfuncs->_Mem_AllocPool && engfuncs->_Mem_FreePool ) + { + g_engfuncs._Mem_AllocPool = engfuncs->_Mem_AllocPool; + g_engfuncs._Mem_FreePool = engfuncs->_Mem_FreePool; + + Con_Reportf( "filesystem_stdio: custom pool allocation functions found\n" ); + } + + if( engfuncs->_Mem_Alloc && engfuncs->_Mem_Realloc && engfuncs->_Mem_Free ) + { + g_engfuncs._Mem_Alloc = engfuncs->_Mem_Alloc; + g_engfuncs._Mem_Realloc = engfuncs->_Mem_Realloc; + g_engfuncs._Mem_Free = engfuncs->_Mem_Free; + + Con_Reportf( "filesystem_stdio: custom memory allocation functions found\n" ); + } + + return true; } -/* -=========== -W_ExtFromType - -Convert type to extension -=========== -*/ -static const char *W_ExtFromType( signed char lumptype ) +fs_api_t g_api = { - const wadtype_t *type; + FS_InitStdio, + FS_ShutdownStdio, - // we not known aboyt filetype, so match only by filename - if( lumptype == TYP_NONE || lumptype == TYP_ANY ) - return ""; + // search path utils + FS_Rescan, + FS_ClearSearchPath, + FS_AllowDirectPaths, + FS_AddGameDirectory, + FS_AddGameHierarchy, + FS_Search, + FS_SetCurrentDirectory, + FS_FindLibrary, + FS_Path_f, - for( type = wad_types; type->ext; type++ ) - { - if( lumptype == type->type ) - return type->ext; - } - return ""; -} + // gameinfo utils + FS_LoadGameInfo, -/* -=========== -W_FindLump + // file ops + FS_Open, + FS_Write, + FS_Read, + FS_Seek, + FS_Tell, + FS_Eof, + FS_Flush, + FS_Close, + FS_Gets, + FS_UnGetc, + FS_Getc, + FS_VPrintf, + FS_Printf, + FS_Print, + FS_FileLength, + FS_FileCopy, -Serach for already existed lump -=========== -*/ -static dlumpinfo_t *W_FindLump( wfile_t *wad, const char *name, const signed char matchtype ) + // file buffer ops + FS_LoadFile, + FS_LoadDirectFile, + FS_WriteFile, + + // file hashing + CRC32_File, + MD5_HashFile, + + // filesystem ops + FS_FileExists, + FS_FileTime, + FS_FileSize, + FS_Rename, + FS_Delete, + FS_SysFileExists, + FS_GetDiskPath, +}; + +int EXPORT GetFSAPI( int version, fs_api_t *api, fs_globals_t **globals, fs_interface_t *engfuncs ) { - int left, right; + if( !FS_InitInterface( version, engfuncs )) + return 0; - if( !wad || !wad->lumps || matchtype == TYP_NONE ) - return NULL; + memcpy( api, &g_api, sizeof( *api )); + *globals = &FI; - // look for the file (binary search) - left = 0; - right = wad->numlumps - 1; - - while( left <= right ) - { - int middle = (left + right) / 2; - int diff = Q_stricmp( wad->lumps[middle].name, name ); - - if( !diff ) - { - if(( matchtype == TYP_ANY ) || ( matchtype == wad->lumps[middle].type )) - return &wad->lumps[middle]; // found - else if( wad->lumps[middle].type < matchtype ) - diff = 1; - else if( wad->lumps[middle].type > matchtype ) - diff = -1; - else break; // not found - } - - // if we're too far in the list - if( diff > 0 ) right = middle - 1; - else left = middle + 1; - } - - return NULL; -} - -/* -==================== -W_AddFileToWad - -Add a file to the list of files contained into a package -and sort LAT in alpha-bethical order -==================== -*/ -static dlumpinfo_t *W_AddFileToWad( const char *name, wfile_t *wad, dlumpinfo_t *newlump ) -{ - int left, right; - dlumpinfo_t *plump; - - // look for the slot we should put that file into (binary search) - left = 0; - right = wad->numlumps - 1; - - while( left <= right ) - { - int middle = ( left + right ) / 2; - int diff = Q_stricmp( wad->lumps[middle].name, name ); - - if( !diff ) - { - if( wad->lumps[middle].type < newlump->type ) - diff = 1; - else if( wad->lumps[middle].type > newlump->type ) - diff = -1; - else Con_Reportf( S_WARN "Wad %s contains the file %s several times\n", wad->filename, name ); - } - - // If we're too far in the list - if( diff > 0 ) right = middle - 1; - else left = middle + 1; - } - - // we have to move the right of the list by one slot to free the one we need - plump = &wad->lumps[left]; - memmove( plump + 1, plump, ( wad->numlumps - left ) * sizeof( *plump )); - wad->numlumps++; - - *plump = *newlump; - memcpy( plump->name, name, sizeof( plump->name )); - - return plump; -} - -/* -=========== -W_ReadLump - -reading lump into temp buffer -=========== -*/ -byte *W_ReadLump( wfile_t *wad, dlumpinfo_t *lump, fs_offset_t *lumpsizeptr ) -{ - size_t oldpos, size = 0; - byte *buf; - - // assume error - if( lumpsizeptr ) *lumpsizeptr = 0; - - // no wads loaded - if( !wad || !lump ) return NULL; - - oldpos = FS_Tell( wad->handle ); // don't forget restore original position - - if( FS_Seek( wad->handle, lump->filepos, SEEK_SET ) == -1 ) - { - Con_Reportf( S_ERROR "W_ReadLump: %s is corrupted\n", lump->name ); - FS_Seek( wad->handle, oldpos, SEEK_SET ); - return NULL; - } - - buf = (byte *)Mem_Malloc( wad->mempool, lump->disksize ); - size = FS_Read( wad->handle, buf, lump->disksize ); - - if( size < lump->disksize ) - { - Con_Reportf( S_WARN "W_ReadLump: %s is probably corrupted\n", lump->name ); - FS_Seek( wad->handle, oldpos, SEEK_SET ); - Mem_Free( buf ); - return NULL; - } - - if( lumpsizeptr ) *lumpsizeptr = lump->disksize; - FS_Seek( wad->handle, oldpos, SEEK_SET ); - - return buf; -} - -/* -============================================================================= - -WADSYSTEM PUBLIC BASE FUNCTIONS - -============================================================================= -*/ -/* -=========== -W_Open - -open the wad for reading & writing -=========== -*/ -wfile_t *W_Open( const char *filename, int *error ) -{ - wfile_t *wad = (wfile_t *)Mem_Calloc( fs_mempool, sizeof( wfile_t )); - const char *basename; - int i, lumpcount; - dlumpinfo_t *srclumps; - size_t lat_size; - dwadinfo_t header; - - // NOTE: FS_Open is load wad file from the first pak in the list (while fs_ext_path is false) - if( fs_ext_path ) basename = filename; - else basename = COM_FileWithoutPath( filename ); - - wad->handle = FS_Open( basename, "rb", false ); - - // HACKHACK: try to open WAD by full path for RoDir, when searchpaths are not ready - if( COM_CheckStringEmpty( host.rodir ) && fs_ext_path && wad->handle == NULL ) - wad->handle = FS_SysOpen( filename, "rb" ); - - if( wad->handle == NULL ) - { - Con_Reportf( S_ERROR "W_Open: couldn't open %s\n", filename ); - if( error ) *error = WAD_LOAD_COULDNT_OPEN; - W_Close( wad ); - return NULL; - } - - // copy wad name - Q_strncpy( wad->filename, filename, sizeof( wad->filename )); - wad->filetime = FS_SysFileTime( filename ); - wad->mempool = Mem_AllocPool( filename ); - - if( FS_Read( wad->handle, &header, sizeof( dwadinfo_t )) != sizeof( dwadinfo_t )) - { - Con_Reportf( S_ERROR "W_Open: %s can't read header\n", filename ); - if( error ) *error = WAD_LOAD_BAD_HEADER; - W_Close( wad ); - return NULL; - } - - if( header.ident != IDWAD2HEADER && header.ident != IDWAD3HEADER ) - { - Con_Reportf( S_ERROR "W_Open: %s is not a WAD2 or WAD3 file\n", filename ); - if( error ) *error = WAD_LOAD_BAD_HEADER; - W_Close( wad ); - return NULL; - } - - lumpcount = header.numlumps; - - if( lumpcount >= MAX_FILES_IN_WAD ) - { - Con_Reportf( S_WARN "W_Open: %s is full (%i lumps)\n", filename, lumpcount ); - if( error ) *error = WAD_LOAD_TOO_MANY_FILES; - } - else if( lumpcount <= 0 ) - { - Con_Reportf( S_ERROR "W_Open: %s has no lumps\n", filename ); - if( error ) *error = WAD_LOAD_NO_FILES; - W_Close( wad ); - return NULL; - } - else if( error ) *error = WAD_LOAD_OK; - - wad->infotableofs = header.infotableofs; // save infotableofs position - - if( FS_Seek( wad->handle, wad->infotableofs, SEEK_SET ) == -1 ) - { - Con_Reportf( S_ERROR "W_Open: %s can't find lump allocation table\n", filename ); - if( error ) *error = WAD_LOAD_BAD_FOLDERS; - W_Close( wad ); - return NULL; - } - - lat_size = lumpcount * sizeof( dlumpinfo_t ); - - // NOTE: lumps table can be reallocated for O_APPEND mode - srclumps = (dlumpinfo_t *)Mem_Malloc( wad->mempool, lat_size ); - - if( FS_Read( wad->handle, srclumps, lat_size ) != lat_size ) - { - Con_Reportf( S_ERROR "W_ReadLumpTable: %s has corrupted lump allocation table\n", wad->filename ); - if( error ) *error = WAD_LOAD_CORRUPTED; - Mem_Free( srclumps ); - W_Close( wad ); - return NULL; - } - - // starting to add lumps - wad->lumps = (dlumpinfo_t *)Mem_Calloc( wad->mempool, lat_size ); - wad->numlumps = 0; - - // sort lumps for binary search - for( i = 0; i < lumpcount; i++ ) - { - char name[16]; - int k; - - // cleanup lumpname - Q_strnlwr( srclumps[i].name, name, sizeof( srclumps[i].name )); - - // check for '*' symbol issues (quake1) - k = Q_strlen( Q_strrchr( name, '*' )); - if( k ) name[Q_strlen( name ) - k] = '!'; - - // check for Quake 'conchars' issues (only lmp loader really allows to read this lame pic) - if( srclumps[i].type == 68 && !Q_stricmp( srclumps[i].name, "conchars" )) - srclumps[i].type = TYP_GFXPIC; - - W_AddFileToWad( name, wad, &srclumps[i] ); - } - - // release source lumps - Mem_Free( srclumps ); - - // and leave the file open - return wad; -} - -/* -=========== -W_Close - -finalize wad or just close -=========== -*/ -void W_Close( wfile_t *wad ) -{ - if( !wad ) return; - - Mem_FreePool( &wad->mempool ); - if( wad->handle != NULL ) - FS_Close( wad->handle ); - Mem_Free( wad ); // free himself -} - -/* -============================================================================= - -FILESYSTEM IMPLEMENTATION - -============================================================================= -*/ -/* -=========== -W_LoadFile - -loading lump into the tmp buffer -=========== -*/ -static byte *W_LoadFile( const char *path, fs_offset_t *lumpsizeptr, qboolean gamedironly ) -{ - searchpath_t *search; - int index; - - search = FS_FindFile( path, &index, gamedironly ); - if( search && search->wad ) - return W_ReadLump( search->wad, &search->wad->lumps[index], lumpsizeptr ); - return NULL; + return FS_API_VERSION; } diff --git a/filesystem/filesystem.h b/filesystem/filesystem.h new file mode 100644 index 00000000..e9b0a2df --- /dev/null +++ b/filesystem/filesystem.h @@ -0,0 +1,211 @@ +/* +filesystem.h - engine FS +Copyright (C) 2007 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef FILESYSTEM_H +#define FILESYSTEM_H + +#include +#include +#include +#include "xash3d_types.h" +#include "const.h" +#include "com_model.h" + +#ifdef __cplusplus +extern "C" +{ +#endif // __cplusplus + +#define FS_API_VERSION 1 // not stable yet! +#define FS_API_CREATEINTERFACE_TAG "XashFileSystem001" // follow FS_API_VERSION!!! + +// search path flags +enum +{ + FS_STATIC_PATH = BIT( 0 ), // FS_ClearSearchPath will be ignore this path + FS_NOWRITE_PATH = BIT( 1 ), // default behavior - last added gamedir set as writedir. This flag disables it + FS_GAMEDIR_PATH = BIT( 2 ), // just a marker for gamedir path + FS_CUSTOM_PATH = BIT( 3 ), // gamedir but with custom/mod data + FS_GAMERODIR_PATH = BIT( 4 ), // gamedir but read-only + + FS_GAMEDIRONLY_SEARCH_FLAGS = FS_GAMEDIR_PATH | FS_CUSTOM_PATH | FS_GAMERODIR_PATH +}; + +typedef struct +{ + int numfilenames; + char **filenames; + char *filenamesbuffer; +} search_t; + +typedef struct gameinfo_s +{ + // filesystem info + char gamefolder[MAX_QPATH]; // used for change game '-game x' + char basedir[MAX_QPATH]; // base game directory (like 'id1' for Quake or 'valve' for Half-Life) + char falldir[MAX_QPATH]; // used as second basedir + char startmap[MAX_QPATH];// map to start singleplayer game + char trainmap[MAX_QPATH];// map to start hazard course (if specified) + char title[64]; // Game Main Title + float version; // game version (optional) + + // .dll pathes + char dll_path[MAX_QPATH]; // e.g. "bin" or "cl_dlls" + char game_dll[MAX_QPATH]; // custom path for game.dll + + // .ico path + char iconpath[MAX_QPATH]; // "game.ico" by default + + // about mod info + string game_url; // link to a developer's site + string update_url; // link to updates page + char type[MAX_QPATH]; // single, toolkit, multiplayer etc + char date[MAX_QPATH]; + size_t size; + + int gamemode; + qboolean secure; // prevent to console acess + qboolean nomodels; // don't let player to choose model (use player.mdl always) + qboolean noskills; // disable skill menu selection + qboolean render_picbutton_text; // use font renderer to render WON buttons + qboolean internal_vgui_support; // skip loading VGUI, pass ingame UI support API to client + + char sp_entity[32]; // e.g. info_player_start + char mp_entity[32]; // e.g. info_player_deathmatch + char mp_filter[32]; // filtering multiplayer-maps + + char ambientsound[NUM_AMBIENTS][MAX_QPATH]; // quake ambient sounds + + int max_edicts; // min edicts is 600, max edicts is 8196 + int max_tents; // min temp ents is 300, max is 2048 + int max_beams; // min beams is 64, max beams is 512 + int max_particles; // min particles is 4096, max particles is 32768 + + char game_dll_linux[64]; // custom path for game.dll + char game_dll_osx[64]; // custom path for game.dll + + qboolean added; +} gameinfo_t; + +typedef enum +{ + GAME_NORMAL, + GAME_SINGLEPLAYER_ONLY, + GAME_MULTIPLAYER_ONLY +} gametype_t; + +typedef struct fs_dllinfo_t +{ + string fullPath; + string shortPath; + qboolean encrypted; + qboolean custom_loader; +} fs_dllinfo_t; + +typedef struct fs_globals_t +{ + gameinfo_t *GameInfo; // current GameInfo + gameinfo_t *games[MAX_MODS]; // environment games (founded at each engine start) + int numgames; +} fs_globals_t; + +typedef void (*fs_event_callback_t)( const char *path ); + + +typedef struct fs_api_t +{ + qboolean (*InitStdio)( qboolean caseinsensitive, const char *rootdir, const char *basedir, const char *gamedir, const char *rodir ); + void (*ShutdownStdio)( void ); + + // search path utils + void (*Rescan)( void ); + void (*ClearSearchPath)( void ); + void (*AllowDirectPaths)( qboolean enable ); + void (*AddGameDirectory)( const char *dir, uint flags ); + void (*AddGameHierarchy)( const char *dir, uint flags ); + search_t *(*Search)( const char *pattern, int caseinsensitive, int gamedironly ); + int (*SetCurrentDirectory)( const char *path ); + qboolean (*FindLibrary)( const char *dllname, qboolean directpath, fs_dllinfo_t *dllinfo ); + void (*Path_f)( void ); + + // gameinfo utils + void (*LoadGameInfo)( const char *rootfolder ); + + // file ops + file_t *(*Open)( const char *filepath, const char *mode, qboolean gamedironly ); + fs_offset_t (*Write)( file_t *file, const void *data, size_t datasize ); + fs_offset_t (*Read)( file_t *file, void *buffer, size_t buffersize ); + int (*Seek)( file_t *file, fs_offset_t offset, int whence ); + fs_offset_t (*Tell)( file_t *file ); + qboolean (*Eof)( file_t *file ); + int (*Flush)( file_t *file ); + int (*Close)( file_t *file ); + int (*Gets)( file_t *file, byte *string, size_t bufsize ); + int (*UnGetc)( file_t *file, byte c ); + int (*Getc)( file_t *file ); + int (*VPrintf)( file_t *file, const char *format, va_list ap ); + int (*Printf)( file_t *file, const char *format, ... ) _format( 2 ); + int (*Print)( file_t *file, const char *msg ); + fs_offset_t (*FileLength)( file_t *f ); + qboolean (*FileCopy)( file_t *pOutput, file_t *pInput, int fileSize ); + + // file buffer ops + byte *(*LoadFile)( const char *path, fs_offset_t *filesizeptr, qboolean gamedironly ); + byte *(*LoadDirectFile)( const char *path, fs_offset_t *filesizeptr ); + qboolean (*WriteFile)( const char *filename, const void *data, fs_offset_t len ); + + // file hashing + qboolean (*CRC32_File)( dword *crcvalue, const char *filename ); + qboolean (*MD5_HashFile)( byte digest[16], const char *pszFileName, uint seed[4] ); + + // filesystem ops + int (*FileExists)( const char *filename, int gamedironly ); + int (*FileTime)( const char *filename, qboolean gamedironly ); + fs_offset_t (*FileSize)( const char *filename, qboolean gamedironly ); + qboolean (*Rename)( const char *oldname, const char *newname ); + qboolean (*Delete)( const char *path ); + qboolean (*SysFileExists)( const char *path, qboolean casesensitive ); + const char *(*GetDiskPath)( const char *name, qboolean gamedironly ); + + // file watcher + void (*WatchFrame)( void ); // engine will read all events and call appropriate callbacks + qboolean (*AddWatch)( const char *path, fs_event_callback_t callback ); +} fs_api_t; + +typedef struct fs_interface_t +{ + // logging + void (*_Con_Printf)( const char *fmt, ... ) _format( 1 ); // typical console allowed messages + void (*_Con_DPrintf)( const char *fmt, ... ) _format( 1 ); // -dev 1 + void (*_Con_Reportf)( const char *fmt, ... ) _format( 1 ); // -dev 2 + + void (*_Sys_Error)( const char *fmt, ... ) _format( 1 ); + + // memory + poolhandle_t (*_Mem_AllocPool)( const char *name, const char *filename, int fileline ); + void (*_Mem_FreePool)( poolhandle_t *poolptr, const char *filename, int fileline ); + void *(*_Mem_Alloc)( poolhandle_t poolptr, size_t size, qboolean clear, const char *filename, int fileline ); + void *(*_Mem_Realloc)( poolhandle_t poolptr, void *memptr, size_t size, qboolean clear, const char *filename, int fileline ); + void (*_Mem_Free)( void *data, const char *filename, int fileline ); +} fs_interface_t; + +typedef int (*FSAPI)( int version, fs_api_t *api, fs_globals_t **globals, fs_interface_t *interface ); +#define GET_FS_API "GetFSAPI" + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif//FILESYSTEM_H diff --git a/filesystem/filesystem_internal.h b/filesystem/filesystem_internal.h new file mode 100644 index 00000000..99250189 --- /dev/null +++ b/filesystem/filesystem_internal.h @@ -0,0 +1,215 @@ +/* +filesystem.h - engine FS +Copyright (C) 2007 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef FILESYSTEM_INTERNAL_H +#define FILESYSTEM_INTERNAL_H + +#include "xash3d_types.h" +#include "filesystem.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +typedef struct zip_s zip_t; +typedef struct pack_s pack_t; +typedef struct wfile_s wfile_t; + +#define FILE_BUFF_SIZE (2048) + +struct file_s +{ + int handle; // file descriptor + int ungetc; // single stored character from ungetc, cleared to EOF when read + fs_offset_t real_length; // uncompressed file size (for files opened in "read" mode) + fs_offset_t position; // current position in the file + fs_offset_t offset; // offset into the package (0 if external file) + time_t filetime; // pak, wad or real filetime + // contents buffer + fs_offset_t buff_ind, buff_len; // buffer current index and length + byte buff[FILE_BUFF_SIZE]; // intermediate buffer +#ifdef XASH_REDUCE_FD + const char *backup_path; + fs_offset_t backup_position; + uint backup_options; +#endif +}; + +enum +{ + SEARCHPATH_PLAIN = 0, + SEARCHPATH_PAK, + SEARCHPATH_WAD, + SEARCHPATH_ZIP +}; + +typedef struct stringlist_s +{ + // maxstrings changes as needed, causing reallocation of strings[] array + int maxstrings; + int numstrings; + char **strings; +} stringlist_t; + +typedef struct searchpath_s +{ + string filename; + int type; + int flags; + union + { + pack_t *pack; + wfile_t *wad; + zip_t *zip; + }; + struct searchpath_s *next; +} searchpath_t; + +extern fs_globals_t FI; +extern searchpath_t *fs_searchpaths; +extern poolhandle_t fs_mempool; +extern fs_interface_t g_engfuncs; +extern qboolean fs_ext_path; +extern char fs_rodir[MAX_SYSPATH]; +extern char fs_rootdir[MAX_SYSPATH]; +extern char fs_writedir[MAX_SYSPATH]; +extern fs_api_t g_api; + +#define GI FI.GameInfo + +#define Mem_Malloc( pool, size ) g_engfuncs._Mem_Alloc( pool, size, false, __FILE__, __LINE__ ) +#define Mem_Calloc( pool, size ) g_engfuncs._Mem_Alloc( pool, size, true, __FILE__, __LINE__ ) +#define Mem_Realloc( pool, ptr, size ) g_engfuncs._Mem_Realloc( pool, ptr, size, true, __FILE__, __LINE__ ) +#define Mem_Free( mem ) g_engfuncs._Mem_Free( mem, __FILE__, __LINE__ ) +#define Mem_AllocPool( name ) g_engfuncs._Mem_AllocPool( name, __FILE__, __LINE__ ) +#define Mem_FreePool( pool ) g_engfuncs._Mem_FreePool( pool, __FILE__, __LINE__ ) + +#define Con_Printf (*g_engfuncs._Con_Printf) +#define Con_DPrintf (*g_engfuncs._Con_DPrintf) +#define Con_Reportf (*g_engfuncs._Con_Reportf) +#define Sys_Error (*g_engfuncs._Sys_Error) + +// +// filesystem.c +// +qboolean FS_InitStdio( qboolean caseinsensitive, const char *rootdir, const char *basedir, const char *gamedir, const char *rodir ); +void FS_ShutdownStdio( void ); + +// search path utils +void FS_Rescan( void ); +void FS_ClearSearchPath( void ); +void FS_AllowDirectPaths( qboolean enable ); +void FS_AddGameDirectory( const char *dir, uint flags ); +void FS_AddGameHierarchy( const char *dir, uint flags ); +search_t *FS_Search( const char *pattern, int caseinsensitive, int gamedironly ); +int FS_SetCurrentDirectory( const char *path ); +void FS_Path_f( void ); + +// gameinfo utils +void FS_LoadGameInfo( const char *rootfolder ); + +// file ops +file_t *FS_Open( const char *filepath, const char *mode, qboolean gamedironly ); +fs_offset_t FS_Write( file_t *file, const void *data, size_t datasize ); +fs_offset_t FS_Read( file_t *file, void *buffer, size_t buffersize ); +int FS_Seek( file_t *file, fs_offset_t offset, int whence ); +fs_offset_t FS_Tell( file_t *file ); +qboolean FS_Eof( file_t *file ); +int FS_Flush( file_t *file ); +int FS_Close( file_t *file ); +int FS_Gets( file_t *file, byte *string, size_t bufsize ); +int FS_UnGetc( file_t *file, byte c ); +int FS_Getc( file_t *file ); +int FS_VPrintf( file_t *file, const char *format, va_list ap ); +int FS_Printf( file_t *file, const char *format, ... ) _format( 2 ); +int FS_Print( file_t *file, const char *msg ); +fs_offset_t FS_FileLength( file_t *f ); +qboolean FS_FileCopy( file_t *pOutput, file_t *pInput, int fileSize ); + +// file buffer ops +byte *FS_LoadFile( const char *path, fs_offset_t *filesizeptr, qboolean gamedironly ); +byte *FS_LoadDirectFile( const char *path, fs_offset_t *filesizeptr ); +qboolean FS_WriteFile( const char *filename, const void *data, fs_offset_t len ); + +// file hashing +qboolean CRC32_File( dword *crcvalue, const char *filename ); +qboolean MD5_HashFile( byte digest[16], const char *pszFileName, uint seed[4] ); + +// filesystem ops +int FS_FileExists( const char *filename, int gamedironly ); +int FS_FileTime( const char *filename, qboolean gamedironly ); +fs_offset_t FS_FileSize( const char *filename, qboolean gamedironly ); +qboolean FS_Rename( const char *oldname, const char *newname ); +qboolean FS_Delete( const char *path ); +qboolean FS_SysFileExists( const char *path, qboolean casesensitive ); +const char *FS_GetDiskPath( const char *name, qboolean gamedironly ); +void stringlistappend( stringlist_t *list, char *text ); +void FS_CreatePath( char *path ); +qboolean FS_SysFolderExists( const char *path ); +file_t *FS_OpenReadFile( const char *filename, const char *mode, qboolean gamedironly ); + +int FS_SysFileTime( const char *filename ); +file_t *FS_OpenHandle( const char *syspath, int handle, fs_offset_t offset, fs_offset_t len ); +file_t *FS_SysOpen( const char *filepath, const char *mode ); +const char *FS_FixFileCase( const char *path ); +searchpath_t *FS_FindFile( const char *name, int *index, qboolean gamedironly ); + +// +// pak.c +// +int FS_FileTimePAK( pack_t *pack ); +int FS_FindFilePAK( pack_t *pack, const char *name ); +void FS_PrintPAKInfo( char *dst, size_t size, pack_t *pack ); +void FS_ClosePAK( pack_t *pack ); +void FS_SearchPAK( stringlist_t *list, pack_t *pack, const char *pattern ); +file_t *FS_OpenPackedFile( pack_t *pack, int pack_ind ); +qboolean FS_AddPak_Fullpath( const char *pakfile, qboolean *already_loaded, int flags ); + +// +// wad.c +// +int FS_FileTimeWAD( wfile_t *wad ); +int FS_FindFileWAD( wfile_t *wad, const char *name ); +void FS_PrintWADInfo( char *dst, size_t size, wfile_t *wad ); +void FS_CloseWAD( wfile_t *wad ); +void FS_SearchWAD( stringlist_t *list, wfile_t *wad, const char *pattern ); +byte *FS_LoadWADFile( const char *path, fs_offset_t *sizeptr, qboolean gamedironly ); +qboolean FS_AddWad_Fullpath( const char *wadfile, qboolean *already_loaded, int flags ); + +// +// watch.c +// +qboolean FS_WatchInitialize( void ); +int FS_AddWatch( const char *path, fs_event_callback_t callback ); +void FS_WatchFrame( void ); + +// +// zip.c +// +int FS_FileTimeZIP( zip_t *zip ); +int FS_FindFileZIP( zip_t *zip, const char *name ); +void FS_PrintZIPInfo( char *dst, size_t size, zip_t *zip ); +void FS_CloseZIP( zip_t *zip ); +void FS_SearchZIP( stringlist_t *list, zip_t *zip, const char *pattern ); +byte *FS_LoadZIPFile( const char *path, fs_offset_t *sizeptr, qboolean gamedironly ); +file_t *FS_OpenZipFile( zip_t *zip, int pack_ind ); +qboolean FS_AddZip_Fullpath( const char *zipfile, qboolean *already_loaded, int flags ); + +#ifdef __cplusplus +} +#endif + +#endif // FILESYSTEM_INTERNAL_H diff --git a/filesystem/fscallback.h b/filesystem/fscallback.h new file mode 100644 index 00000000..bebfdd29 --- /dev/null +++ b/filesystem/fscallback.h @@ -0,0 +1,80 @@ +/* +fscallback.h - common filesystem callbacks +Copyright (C) 2022 Alibek Omarov + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ +#ifndef FSCALLBACK_H +#define FSCALLBACK_H + +#include "filesystem.h" + +extern fs_api_t g_fsapi; +extern fs_globals_t *FI; + +#define GI FI->GameInfo +#define FS_Gamedir() GI->gamefolder +#define FS_Title() GI->title + +#define FS_InitStdio (*g_fsapi.InitStdio) +#define FS_ShutdownStdio (*g_fsapi.ShutdownStdio) + +// search path utils +#define FS_Rescan (*g_fsapi.Rescan) +#define FS_ClearSearchPath (*g_fsapi.ClearSearchPath) +#define FS_AllowDirectPaths (*g_fsapi.AllowDirectPaths) +#define FS_AddGameDirectory (*g_fsapi.AddGameDirectory) +#define FS_AddGameHierarchy (*g_fsapi.AddGameHierarchy) +#define FS_Search (*g_fsapi.Search) +#define FS_SetCurrentDirectory (*g_fsapi.SetCurrentDirectory) +#define FS_Path_f (*g_fsapi.Path_f) + +// gameinfo utils +#define FS_LoadGameInfo (*g_fsapi.LoadGameInfo) + +// file ops +#define FS_Open (*g_fsapi.Open) +#define FS_Write (*g_fsapi.Write) +#define FS_Read (*g_fsapi.Read) +#define FS_Seek (*g_fsapi.Seek) +#define FS_Tell (*g_fsapi.Tell) +#define FS_Eof (*g_fsapi.Eof) +#define FS_Flush (*g_fsapi.Flush) +#define FS_Close (*g_fsapi.Close) +#define FS_Gets (*g_fsapi.Gets) +#define FS_UnGetc (*g_fsapi.UnGetc) +#define FS_Getc (*g_fsapi.Getc) +#define FS_VPrintf (*g_fsapi.VPrintf) +#define FS_Printf (*g_fsapi.Printf) +#define FS_Print (*g_fsapi.Print) +#define FS_FileLength (*g_fsapi.FileLength) +#define FS_FileCopy (*g_fsapi.FileCopy) + +// file buffer ops +#define FS_LoadFile (*g_fsapi.LoadFile) +#define FS_LoadDirectFile (*g_fsapi.LoadDirectFile) +#define FS_WriteFile (*g_fsapi.WriteFile) + +// file hashing +#define CRC32_File (*g_fsapi.CRC32_File) +#define MD5_HashFile (*g_fsapi.MD5_HashFile) + +// filesystem ops +#define FS_FileExists (*g_fsapi.FileExists) +#define FS_FileTime (*g_fsapi.FileTime) +#define FS_FileSize (*g_fsapi.FileSize) +#define FS_Rename (*g_fsapi.Rename) +#define FS_Delete (*g_fsapi.Delete) +#define FS_SysFileExists (*g_fsapi.SysFileExists) +#define FS_GetDiskPath (*g_fsapi.GetDiskPath) + + +#endif // FSCALLBACK_H diff --git a/filesystem/pak.c b/filesystem/pak.c new file mode 100644 index 00000000..1cc0037a --- /dev/null +++ b/filesystem/pak.c @@ -0,0 +1,397 @@ +/* +pak.c - PAK support for filesystem +Copyright (C) 2007 Uncle Mike +Copyright (C) 2022 Alibek Omarov + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "build.h" +#include +#include +#include +#if XASH_POSIX +#include +#endif +#include +#include +#include "port.h" +#include "filesystem_internal.h" +#include "crtlib.h" +#include "common/com_strings.h" + +/* +======================================================================== +PAK FILES + +The .pak files are just a linear collapse of a directory tree +======================================================================== +*/ +// header +#define IDPACKV1HEADER (('K'<<24)+('C'<<16)+('A'<<8)+'P') // little-endian "PACK" + +#define MAX_FILES_IN_PACK 65536 // pak + +typedef struct +{ + int ident; + int dirofs; + int dirlen; +} dpackheader_t; + +typedef struct +{ + char name[56]; // total 64 bytes + int filepos; + int filelen; +} dpackfile_t; + +// PAK errors +#define PAK_LOAD_OK 0 +#define PAK_LOAD_COULDNT_OPEN 1 +#define PAK_LOAD_BAD_HEADER 2 +#define PAK_LOAD_BAD_FOLDERS 3 +#define PAK_LOAD_TOO_MANY_FILES 4 +#define PAK_LOAD_NO_FILES 5 +#define PAK_LOAD_CORRUPTED 6 + +struct pack_s +{ + string filename; + int handle; + int numfiles; + time_t filetime; // common for all packed files + dpackfile_t *files; +}; + +/* +==================== +FS_AddFileToPack + +Add a file to the list of files contained into a package +==================== +*/ +static dpackfile_t *FS_AddFileToPack( const char *name, pack_t *pack, fs_offset_t offset, fs_offset_t size ) +{ + int left, right, middle; + dpackfile_t *pfile; + + // look for the slot we should put that file into (binary search) + left = 0; + right = pack->numfiles - 1; + + while( left <= right ) + { + int diff; + + middle = (left + right) / 2; + diff = Q_stricmp( pack->files[middle].name, name ); + + // If we found the file, there's a problem + if( !diff ) Con_Reportf( S_WARN "package %s contains the file %s several times\n", pack->filename, name ); + + // If we're too far in the list + if( diff > 0 ) right = middle - 1; + else left = middle + 1; + } + + // We have to move the right of the list by one slot to free the one we need + pfile = &pack->files[left]; + memmove( pfile + 1, pfile, (pack->numfiles - left) * sizeof( *pfile )); + pack->numfiles++; + + Q_strncpy( pfile->name, name, sizeof( pfile->name )); + pfile->filepos = offset; + pfile->filelen = size; + + return pfile; +} + +/* +================= +FS_LoadPackPAK + +Takes an explicit (not game tree related) path to a pak file. + +Loads the header and directory, adding the files at the beginning +of the list so they override previous pack files. +================= +*/ +static pack_t *FS_LoadPackPAK( const char *packfile, int *error ) +{ + dpackheader_t header; + int packhandle; + int i, numpackfiles; + pack_t *pack; + dpackfile_t *info; + fs_size_t c; + + packhandle = open( packfile, O_RDONLY|O_BINARY ); + +#if !XASH_WIN32 + if( packhandle < 0 ) + { + const char *fpackfile = FS_FixFileCase( packfile ); + if( fpackfile != packfile ) + packhandle = open( fpackfile, O_RDONLY|O_BINARY ); + } +#endif + + if( packhandle < 0 ) + { + Con_Reportf( "%s couldn't open: %s\n", packfile, strerror( errno )); + if( error ) *error = PAK_LOAD_COULDNT_OPEN; + return NULL; + } + + c = read( packhandle, (void *)&header, sizeof( header )); + + if( c != sizeof( header ) || header.ident != IDPACKV1HEADER ) + { + Con_Reportf( "%s is not a packfile. Ignored.\n", packfile ); + if( error ) *error = PAK_LOAD_BAD_HEADER; + close( packhandle ); + return NULL; + } + + if( header.dirlen % sizeof( dpackfile_t )) + { + Con_Reportf( S_ERROR "%s has an invalid directory size. Ignored.\n", packfile ); + if( error ) *error = PAK_LOAD_BAD_FOLDERS; + close( packhandle ); + return NULL; + } + + numpackfiles = header.dirlen / sizeof( dpackfile_t ); + + if( numpackfiles > MAX_FILES_IN_PACK ) + { + Con_Reportf( S_ERROR "%s has too many files ( %i ). Ignored.\n", packfile, numpackfiles ); + if( error ) *error = PAK_LOAD_TOO_MANY_FILES; + close( packhandle ); + return NULL; + } + + if( numpackfiles <= 0 ) + { + Con_Reportf( "%s has no files. Ignored.\n", packfile ); + if( error ) *error = PAK_LOAD_NO_FILES; + close( packhandle ); + return NULL; + } + + info = (dpackfile_t *)Mem_Malloc( fs_mempool, sizeof( *info ) * numpackfiles ); + lseek( packhandle, header.dirofs, SEEK_SET ); + + if( header.dirlen != read( packhandle, (void *)info, header.dirlen )) + { + Con_Reportf( "%s is an incomplete PAK, not loading\n", packfile ); + if( error ) *error = PAK_LOAD_CORRUPTED; + close( packhandle ); + Mem_Free( info ); + return NULL; + } + + pack = (pack_t *)Mem_Calloc( fs_mempool, sizeof( pack_t )); + Q_strncpy( pack->filename, packfile, sizeof( pack->filename )); + pack->files = (dpackfile_t *)Mem_Calloc( fs_mempool, numpackfiles * sizeof( dpackfile_t )); + pack->filetime = FS_SysFileTime( packfile ); + pack->handle = packhandle; + pack->numfiles = 0; + + // parse the directory + for( i = 0; i < numpackfiles; i++ ) + FS_AddFileToPack( info[i].name, pack, info[i].filepos, info[i].filelen ); + +#ifdef XASH_REDUCE_FD + // will reopen when needed + close( pack->handle ); + pack->handle = -1; +#endif + + if( error ) *error = PAK_LOAD_OK; + Mem_Free( info ); + + return pack; +} + +/* +=========== +FS_OpenPackedFile + +Open a packed file using its package file descriptor +=========== +*/ +file_t *FS_OpenPackedFile( pack_t *pack, int pack_ind ) +{ + dpackfile_t *pfile; + + pfile = &pack->files[pack_ind]; + + return FS_OpenHandle( pack->filename, pack->handle, pfile->filepos, pfile->filelen ); +} + +/* +================ +FS_AddPak_Fullpath + +Adds the given pack to the search path. +The pack type is autodetected by the file extension. + +Returns true if the file was successfully added to the +search path or if it was already included. + +If keep_plain_dirs is set, the pack will be added AFTER the first sequence of +plain directories. +================ +*/ +qboolean FS_AddPak_Fullpath( const char *pakfile, qboolean *already_loaded, int flags ) +{ + searchpath_t *search; + pack_t *pak = NULL; + const char *ext = COM_FileExtension( pakfile ); + int i, errorcode = PAK_LOAD_COULDNT_OPEN; + + for( search = fs_searchpaths; search; search = search->next ) + { + if( search->type == SEARCHPATH_PAK && !Q_stricmp( search->pack->filename, pakfile )) + { + if( already_loaded ) *already_loaded = true; + return true; // already loaded + } + } + + if( already_loaded ) + *already_loaded = false; + + if( !Q_stricmp( ext, "pak" )) + pak = FS_LoadPackPAK( pakfile, &errorcode ); + + if( pak ) + { + string fullpath; + + search = (searchpath_t *)Mem_Calloc( fs_mempool, sizeof( searchpath_t )); + search->pack = pak; + search->type = SEARCHPATH_PAK; + search->next = fs_searchpaths; + search->flags |= flags; + fs_searchpaths = search; + + Con_Reportf( "Adding pakfile: %s (%i files)\n", pakfile, pak->numfiles ); + + // time to add in search list all the wads that contains in current pakfile (if do) + for( i = 0; i < pak->numfiles; i++ ) + { + if( !Q_stricmp( COM_FileExtension( pak->files[i].name ), "wad" )) + { + Q_snprintf( fullpath, MAX_STRING, "%s/%s", pakfile, pak->files[i].name ); + FS_AddWad_Fullpath( fullpath, NULL, flags ); + } + } + + return true; + } + else + { + if( errorcode != PAK_LOAD_NO_FILES ) + Con_Reportf( S_ERROR "FS_AddPak_Fullpath: unable to load pak \"%s\"\n", pakfile ); + return false; + } +} + +int FS_FindFilePAK( pack_t *pack, const char *name ) +{ + int left, right, middle; + + // look for the file (binary search) + left = 0; + right = pack->numfiles - 1; + while( left <= right ) + { + int diff; + + middle = (left + right) / 2; + diff = Q_stricmp( pack->files[middle].name, name ); + + // Found it + if( !diff ) + { + return middle; + } + + // if we're too far in the list + if( diff > 0 ) + right = middle - 1; + else left = middle + 1; + } + + return -1; +} + +void FS_SearchPAK( stringlist_t *list, pack_t *pack, const char *pattern ) +{ + string temp; + const char *slash, *backslash, *colon, *separator; + int j, i; + + for( i = 0; i < pack->numfiles; i++ ) + { + Q_strncpy( temp, pack->files[i].name, sizeof( temp )); + while( temp[0] ) + { + if( matchpattern( temp, pattern, true )) + { + for( j = 0; j < list->numstrings; j++ ) + { + if( !Q_strcmp( list->strings[j], temp )) + break; + } + + if( j == list->numstrings ) + stringlistappend( list, temp ); + } + + // strip off one path element at a time until empty + // this way directories are added to the listing if they match the pattern + slash = Q_strrchr( temp, '/' ); + backslash = Q_strrchr( temp, '\\' ); + colon = Q_strrchr( temp, ':' ); + separator = temp; + if( separator < slash ) + separator = slash; + if( separator < backslash ) + separator = backslash; + if( separator < colon ) + separator = colon; + *((char *)separator) = 0; + } + } +} + +int FS_FileTimePAK( pack_t *pack ) +{ + return pack->filetime; +} + +void FS_PrintPAKInfo( char *dst, size_t size, pack_t *pack ) +{ + Q_snprintf( dst, size, "%s (%i files)", pack->filename, pack->numfiles ); +} + +void FS_ClosePAK( pack_t *pack ) +{ + if( pack->files ) + Mem_Free( pack->files ); + if( pack->handle >= 0 ) + close( pack->handle ); + Mem_Free( pack ); +} diff --git a/filesystem/wad.c b/filesystem/wad.c new file mode 100644 index 00000000..646476d3 --- /dev/null +++ b/filesystem/wad.c @@ -0,0 +1,636 @@ +/* +wad.c - WAD support for filesystem +Copyright (C) 2007 Uncle Mike +Copyright (C) 2022 Alibek Omarov + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include +#include +#include +#if XASH_POSIX +#include +#endif +#include +#include +#include "port.h" +#include "filesystem_internal.h" +#include "crtlib.h" +#include "common/com_strings.h" +#include "wadfile.h" + +/* +======================================================================== +.WAD archive format (WhereAllData - WAD) + +List of compressed files, that can be identify only by TYPE_* + + +header: dwadinfo_t[dwadinfo_t] +file_1: byte[dwadinfo_t[num]->disksize] +file_2: byte[dwadinfo_t[num]->disksize] +file_3: byte[dwadinfo_t[num]->disksize] +... +file_n: byte[dwadinfo_t[num]->disksize] +infotable dlumpinfo_t[dwadinfo_t->numlumps] +======================================================================== +*/ +#define WAD3_NAMELEN 16 +#define HINT_NAMELEN 5 // e.g. _mask, _norm +#define MAX_FILES_IN_WAD 65535 // real limit as above <2Gb size not a lumpcount + +#include "const.h" + +typedef struct +{ + int ident; // should be WAD3 + int numlumps; // num files + int infotableofs; // LUT offset +} dwadinfo_t; + +typedef struct +{ + int filepos; // file offset in WAD + int disksize; // compressed or uncompressed + int size; // uncompressed + signed char type; // TYP_* + signed char attribs; // file attribs + signed char pad0; + signed char pad1; + char name[WAD3_NAMELEN]; // must be null terminated +} dlumpinfo_t; + +struct wfile_s +{ + string filename; + int infotableofs; + int numlumps; + poolhandle_t mempool; // W_ReadLump temp buffers + file_t *handle; + dlumpinfo_t *lumps; + time_t filetime; +}; + +// WAD errors +#define WAD_LOAD_OK 0 +#define WAD_LOAD_COULDNT_OPEN 1 +#define WAD_LOAD_BAD_HEADER 2 +#define WAD_LOAD_BAD_FOLDERS 3 +#define WAD_LOAD_TOO_MANY_FILES 4 +#define WAD_LOAD_NO_FILES 5 +#define WAD_LOAD_CORRUPTED 6 + +typedef struct wadtype_s +{ + const char *ext; + signed char type; +} wadtype_t; + +// associate extension with wad type +static const wadtype_t wad_types[7] = +{ +{ "pal", TYP_PALETTE }, // palette +{ "dds", TYP_DDSTEX }, // DDS image +{ "lmp", TYP_GFXPIC }, // quake1, hl pic +{ "fnt", TYP_QFONT }, // hl qfonts +{ "mip", TYP_MIPTEX }, // hl/q1 mip +{ "txt", TYP_SCRIPT }, // scripts +{ NULL, TYP_NONE } +}; + +/* +=========== +W_TypeFromExt + +Extracts file type from extension +=========== +*/ +static signed char W_TypeFromExt( const char *lumpname ) +{ + const char *ext = COM_FileExtension( lumpname ); + const wadtype_t *type; + + // we not known about filetype, so match only by filename + if( !Q_strcmp( ext, "*" ) || !Q_strcmp( ext, "" )) + return TYP_ANY; + + for( type = wad_types; type->ext; type++ ) + { + if( !Q_stricmp( ext, type->ext )) + return type->type; + } + return TYP_NONE; +} + +/* +=========== +W_ExtFromType + +Convert type to extension +=========== +*/ +static const char *W_ExtFromType( signed char lumptype ) +{ + const wadtype_t *type; + + // we not known aboyt filetype, so match only by filename + if( lumptype == TYP_NONE || lumptype == TYP_ANY ) + return ""; + + for( type = wad_types; type->ext; type++ ) + { + if( lumptype == type->type ) + return type->ext; + } + return ""; +} + +/* +==================== +W_AddFileToWad + +Add a file to the list of files contained into a package +and sort LAT in alpha-bethical order +==================== +*/ +static dlumpinfo_t *W_AddFileToWad( const char *name, wfile_t *wad, dlumpinfo_t *newlump ) +{ + int left, right; + dlumpinfo_t *plump; + + // look for the slot we should put that file into (binary search) + left = 0; + right = wad->numlumps - 1; + + while( left <= right ) + { + int middle = ( left + right ) / 2; + int diff = Q_stricmp( wad->lumps[middle].name, name ); + + if( !diff ) + { + if( wad->lumps[middle].type < newlump->type ) + diff = 1; + else if( wad->lumps[middle].type > newlump->type ) + diff = -1; + else Con_Reportf( S_WARN "Wad %s contains the file %s several times\n", wad->filename, name ); + } + + // If we're too far in the list + if( diff > 0 ) right = middle - 1; + else left = middle + 1; + } + + // we have to move the right of the list by one slot to free the one we need + plump = &wad->lumps[left]; + memmove( plump + 1, plump, ( wad->numlumps - left ) * sizeof( *plump )); + wad->numlumps++; + + *plump = *newlump; + memcpy( plump->name, name, sizeof( plump->name )); + + return plump; +} + +/* +=========== +FS_CloseWAD + +finalize wad or just close +=========== +*/ +void FS_CloseWAD( wfile_t *wad ) +{ + Mem_FreePool( &wad->mempool ); + if( wad->handle != NULL ) + FS_Close( wad->handle ); + Mem_Free( wad ); // free himself +} + +/* +=========== +W_Open + +open the wad for reading & writing +=========== +*/ +static wfile_t *W_Open( const char *filename, int *error ) +{ + wfile_t *wad = (wfile_t *)Mem_Calloc( fs_mempool, sizeof( wfile_t )); + const char *basename; + int i, lumpcount; + dlumpinfo_t *srclumps; + size_t lat_size; + dwadinfo_t header; + + // NOTE: FS_Open is load wad file from the first pak in the list (while fs_ext_path is false) + if( fs_ext_path ) basename = filename; + else basename = COM_FileWithoutPath( filename ); + + wad->handle = FS_Open( basename, "rb", false ); + + // HACKHACK: try to open WAD by full path for RoDir, when searchpaths are not ready + if( COM_CheckStringEmpty( fs_rodir ) && fs_ext_path && wad->handle == NULL ) + wad->handle = FS_SysOpen( filename, "rb" ); + + if( wad->handle == NULL ) + { + Con_Reportf( S_ERROR "W_Open: couldn't open %s\n", filename ); + if( error ) *error = WAD_LOAD_COULDNT_OPEN; + FS_CloseWAD( wad ); + return NULL; + } + + // copy wad name + Q_strncpy( wad->filename, filename, sizeof( wad->filename )); + wad->filetime = FS_SysFileTime( filename ); + wad->mempool = Mem_AllocPool( filename ); + + if( FS_Read( wad->handle, &header, sizeof( dwadinfo_t )) != sizeof( dwadinfo_t )) + { + Con_Reportf( S_ERROR "W_Open: %s can't read header\n", filename ); + if( error ) *error = WAD_LOAD_BAD_HEADER; + FS_CloseWAD( wad ); + return NULL; + } + + if( header.ident != IDWAD2HEADER && header.ident != IDWAD3HEADER ) + { + Con_Reportf( S_ERROR "W_Open: %s is not a WAD2 or WAD3 file\n", filename ); + if( error ) *error = WAD_LOAD_BAD_HEADER; + FS_CloseWAD( wad ); + return NULL; + } + + lumpcount = header.numlumps; + + if( lumpcount >= MAX_FILES_IN_WAD ) + { + Con_Reportf( S_WARN "W_Open: %s is full (%i lumps)\n", filename, lumpcount ); + if( error ) *error = WAD_LOAD_TOO_MANY_FILES; + } + else if( lumpcount <= 0 ) + { + Con_Reportf( S_ERROR "W_Open: %s has no lumps\n", filename ); + if( error ) *error = WAD_LOAD_NO_FILES; + FS_CloseWAD( wad ); + return NULL; + } + else if( error ) *error = WAD_LOAD_OK; + + wad->infotableofs = header.infotableofs; // save infotableofs position + + if( FS_Seek( wad->handle, wad->infotableofs, SEEK_SET ) == -1 ) + { + Con_Reportf( S_ERROR "W_Open: %s can't find lump allocation table\n", filename ); + if( error ) *error = WAD_LOAD_BAD_FOLDERS; + FS_CloseWAD( wad ); + return NULL; + } + + lat_size = lumpcount * sizeof( dlumpinfo_t ); + + // NOTE: lumps table can be reallocated for O_APPEND mode + srclumps = (dlumpinfo_t *)Mem_Malloc( wad->mempool, lat_size ); + + if( FS_Read( wad->handle, srclumps, lat_size ) != lat_size ) + { + Con_Reportf( S_ERROR "W_ReadLumpTable: %s has corrupted lump allocation table\n", wad->filename ); + if( error ) *error = WAD_LOAD_CORRUPTED; + Mem_Free( srclumps ); + FS_CloseWAD( wad ); + return NULL; + } + + // starting to add lumps + wad->lumps = (dlumpinfo_t *)Mem_Calloc( wad->mempool, lat_size ); + wad->numlumps = 0; + + // sort lumps for binary search + for( i = 0; i < lumpcount; i++ ) + { + char name[16]; + int k; + + // cleanup lumpname + Q_strnlwr( srclumps[i].name, name, sizeof( srclumps[i].name )); + + // check for '*' symbol issues (quake1) + k = Q_strlen( Q_strrchr( name, '*' )); + if( k ) name[Q_strlen( name ) - k] = '!'; + + // check for Quake 'conchars' issues (only lmp loader really allows to read this lame pic) + if( srclumps[i].type == 68 && !Q_stricmp( srclumps[i].name, "conchars" )) + srclumps[i].type = TYP_GFXPIC; + + W_AddFileToWad( name, wad, &srclumps[i] ); + } + + // release source lumps + Mem_Free( srclumps ); + + // and leave the file open + return wad; +} + +/* +==================== +FS_AddWad_Fullpath +==================== +*/ +qboolean FS_AddWad_Fullpath( const char *wadfile, qboolean *already_loaded, int flags ) +{ + searchpath_t *search; + wfile_t *wad = NULL; + const char *ext = COM_FileExtension( wadfile ); + int errorcode = WAD_LOAD_COULDNT_OPEN; + + for( search = fs_searchpaths; search; search = search->next ) + { + if( search->type == SEARCHPATH_WAD && !Q_stricmp( search->wad->filename, wadfile )) + { + if( already_loaded ) *already_loaded = true; + return true; // already loaded + } + } + + if( already_loaded ) + *already_loaded = false; + + if( !Q_stricmp( ext, "wad" )) + wad = W_Open( wadfile, &errorcode ); + + if( wad ) + { + search = (searchpath_t *)Mem_Calloc( fs_mempool, sizeof( searchpath_t )); + search->wad = wad; + search->type = SEARCHPATH_WAD; + search->next = fs_searchpaths; + search->flags |= flags; + fs_searchpaths = search; + + Con_Reportf( "Adding wadfile: %s (%i files)\n", wadfile, wad->numlumps ); + return true; + } + else + { + if( errorcode != WAD_LOAD_NO_FILES ) + Con_Reportf( S_ERROR "FS_AddWad_Fullpath: unable to load wad \"%s\"\n", wadfile ); + return false; + } +} + +/* +============================================================================= + +WADSYSTEM PRIVATE ROUTINES + +============================================================================= +*/ + +/* +=========== +W_FindLump + +Serach for already existed lump +=========== +*/ +static dlumpinfo_t *W_FindLump( wfile_t *wad, const char *name, const signed char matchtype ) +{ + int left, right; + + if( !wad || !wad->lumps || matchtype == TYP_NONE ) + return NULL; + + // look for the file (binary search) + left = 0; + right = wad->numlumps - 1; + + while( left <= right ) + { + int middle = (left + right) / 2; + int diff = Q_stricmp( wad->lumps[middle].name, name ); + + if( !diff ) + { + if(( matchtype == TYP_ANY ) || ( matchtype == wad->lumps[middle].type )) + return &wad->lumps[middle]; // found + else if( wad->lumps[middle].type < matchtype ) + diff = 1; + else if( wad->lumps[middle].type > matchtype ) + diff = -1; + else break; // not found + } + + // if we're too far in the list + if( diff > 0 ) right = middle - 1; + else left = middle + 1; + } + + return NULL; +} + +/* +=========== +W_ReadLump + +reading lump into temp buffer +=========== +*/ +static byte *W_ReadLump( wfile_t *wad, dlumpinfo_t *lump, fs_offset_t *lumpsizeptr ) +{ + size_t oldpos, size = 0; + byte *buf; + + // assume error + if( lumpsizeptr ) *lumpsizeptr = 0; + + // no wads loaded + if( !wad || !lump ) return NULL; + + oldpos = FS_Tell( wad->handle ); // don't forget restore original position + + if( FS_Seek( wad->handle, lump->filepos, SEEK_SET ) == -1 ) + { + Con_Reportf( S_ERROR "W_ReadLump: %s is corrupted\n", lump->name ); + FS_Seek( wad->handle, oldpos, SEEK_SET ); + return NULL; + } + + buf = (byte *)Mem_Malloc( wad->mempool, lump->disksize ); + size = FS_Read( wad->handle, buf, lump->disksize ); + + if( size < lump->disksize ) + { + Con_Reportf( S_WARN "W_ReadLump: %s is probably corrupted\n", lump->name ); + FS_Seek( wad->handle, oldpos, SEEK_SET ); + Mem_Free( buf ); + return NULL; + } + + if( lumpsizeptr ) *lumpsizeptr = lump->disksize; + FS_Seek( wad->handle, oldpos, SEEK_SET ); + + return buf; +} + +/* +=========== +FS_LoadWADFile + +loading lump into the tmp buffer +=========== +*/ +byte *FS_LoadWADFile( const char *path, fs_offset_t *lumpsizeptr, qboolean gamedironly ) +{ + searchpath_t *search; + int index; + + search = FS_FindFile( path, &index, gamedironly ); + if( search && search->type == SEARCHPATH_WAD ) + return W_ReadLump( search->wad, &search->wad->lumps[index], lumpsizeptr ); + return NULL; +} + +int FS_FileTimeWAD( wfile_t *wad ) +{ + return wad->filetime; +} + +void FS_PrintWADInfo( char *dst, size_t size, wfile_t *wad ) +{ + Q_snprintf( dst, size, "%s (%i files)", wad->filename, wad->numlumps ); +} + +int FS_FindFileWAD( wfile_t *wad, const char *name ) +{ + dlumpinfo_t *lump; + signed char type = W_TypeFromExt( name ); + qboolean anywadname = true; + string wadname, wadfolder; + string shortname; + + // quick reject by filetype + if( type == TYP_NONE ) + return -1; + + COM_ExtractFilePath( name, wadname ); + wadfolder[0] = '\0'; + + if( COM_CheckStringEmpty( wadname ) ) + { + COM_FileBase( wadname, wadname ); + Q_strncpy( wadfolder, wadname, sizeof( wadfolder )); + COM_DefaultExtension( wadname, ".wad" ); + anywadname = false; + } + + // make wadname from wad fullpath + COM_FileBase( wad->filename, shortname ); + COM_DefaultExtension( shortname, ".wad" ); + + // quick reject by wadname + if( !anywadname && Q_stricmp( wadname, shortname )) + return -1; + + // NOTE: we can't using long names for wad, + // because we using original wad names[16]; + COM_FileBase( name, shortname ); + + lump = W_FindLump( wad, shortname, type ); + + if( lump ) + { + return lump - wad->lumps; + } + + return -1; + +} + +void FS_SearchWAD( stringlist_t *list, wfile_t *wad, const char *pattern ) +{ + string wadpattern, wadname, temp2; + signed char type = W_TypeFromExt( pattern ); + qboolean anywadname = true; + string wadfolder, temp; + int j, i; + const char *slash, *backslash, *colon, *separator; + + // quick reject by filetype + if( type == TYP_NONE ) + return; + + COM_ExtractFilePath( pattern, wadname ); + COM_FileBase( pattern, wadpattern ); + wadfolder[0] = '\0'; + + if( COM_CheckStringEmpty( wadname )) + { + COM_FileBase( wadname, wadname ); + Q_strncpy( wadfolder, wadname, sizeof( wadfolder )); + COM_DefaultExtension( wadname, ".wad" ); + anywadname = false; + } + + // make wadname from wad fullpath + COM_FileBase( wad->filename, temp2 ); + COM_DefaultExtension( temp2, ".wad" ); + + // quick reject by wadname + if( !anywadname && Q_stricmp( wadname, temp2 )) + return; + + for( i = 0; i < wad->numlumps; i++ ) + { + // if type not matching, we already have no chance ... + if( type != TYP_ANY && wad->lumps[i].type != type ) + continue; + + // build the lumpname with image suffix (if present) + Q_strncpy( temp, wad->lumps[i].name, sizeof( temp )); + + while( temp[0] ) + { + if( matchpattern( temp, wadpattern, true )) + { + for( j = 0; j < list->numstrings; j++ ) + { + if( !Q_strcmp( list->strings[j], temp )) + break; + } + + if( j == list->numstrings ) + { + // build path: wadname/lumpname.ext + Q_snprintf( temp2, sizeof(temp2), "%s/%s", wadfolder, temp ); + COM_DefaultExtension( temp2, va(".%s", W_ExtFromType( wad->lumps[i].type ))); + stringlistappend( list, temp2 ); + } + } + + // strip off one path element at a time until empty + // this way directories are added to the listing if they match the pattern + slash = Q_strrchr( temp, '/' ); + backslash = Q_strrchr( temp, '\\' ); + colon = Q_strrchr( temp, ':' ); + separator = temp; + if( separator < slash ) + separator = slash; + if( separator < backslash ) + separator = backslash; + if( separator < colon ) + separator = colon; + *((char *)separator) = 0; + } + } +} diff --git a/filesystem/watch.c b/filesystem/watch.c new file mode 100644 index 00000000..1d4cf7ea --- /dev/null +++ b/filesystem/watch.c @@ -0,0 +1,117 @@ +#if 0 +#include "build.h" +#if XASH_LINUX +#include +#include +#include +#endif +#include "filesystem_internal.h" +#include "common/com_strings.h" + +#define MAX_FS_WATCHES 256 + +struct +{ +#if XASH_LINUX + int fd; + int count; + struct + { + fs_event_callback_t callback; + int fd; + } watch[MAX_FS_WATCHES]; +#endif // XASH_LINUX +} fsnotify; + +#if XASH_LINUX +static qboolean FS_InotifyInit( void ) +{ + int fd; + + if(( fd = inotify_init1( IN_NONBLOCK )) < 0 ) + { + Con_Printf( S_ERROR "inotify_init1 failed: %s", strerror( errno )); + return false; + } + + fsnotify.fd = fd; + return true; +} + +static qboolean FS_InotifyWasInit( void ) +{ + return fsnotify.fd >= 0; +} +#endif + +/* +=============== +FS_AddWatch + +Adds on-disk path to filesystem watcher list +Every file modification will call back +=============== +*/ +int FS_AddWatch( const char *path, fs_event_callback_t callback ) +{ +#if XASH_LINUX + int fd; + const uint mask = IN_CREATE | IN_DELETE | IN_MODIFY; + + if( !FS_InotifyWasInit() && !FS_InotifyInit()) + return false; + + if(( fd = inotify_add_watch( fsnotify.fd, path, mask )) < 0 ) + { + Con_Printf( S_ERROR "inotify_add_watch failed: %s", strerror( errno )); + return false; + } + + fsnotify.watch[fsnotify.count].fd = fd; + fsnotify.watch[fsnotify.count].callback = callback; + + return true; +#else + return false; +#endif +} + +/* +=============== +FS_WatchFrame + +Polls any changes and runs call backs +=============== +*/ +void FS_WatchFrame( void ) +{ +#if XASH_LINUX + int i; + + for( i = 0; i < fsnotify.count; i++ ) + { + struct inotify_event events; + } + +#endif +} + +/* +=============== +FS_WatchInitialize + +initializes filesystem watcher subsystem +=============== +*/ +qboolean FS_WatchInitialize( void ) +{ +#if XASH_LINUX + fsnotify.fd = -1; // only call inotify init when requested + fsnotify.count = 0; + + return true; +#else + return false; +#endif +} +#endif // 0 diff --git a/filesystem/wscript b/filesystem/wscript new file mode 100644 index 00000000..2b8d8a8b --- /dev/null +++ b/filesystem/wscript @@ -0,0 +1,24 @@ +#!/usr/bin/env python + +def options(opt): + pass + +def configure(conf): + nortti = { + 'msvc': ['/GR-'], + 'default': ['-fno-rtti', '-fno-exceptions'] + } + conf.env.append_unique('CXXFLAGS', conf.get_flags_by_compiler(nortti, conf.env.COMPILER_CC)) + + if conf.env.DEST_OS != 'android': + if conf.env.cxxshlib_PATTERN.startswith('lib'): + conf.env.cxxshlib_PATTERN = conf.env.cxxshlib_PATTERN[3:] + +def build(bld): + bld(name = 'filesystem_includes', export_includes = '.') + bld.shlib(target = 'filesystem_stdio', + features = 'cxx', + source = bld.path.ant_glob(['*.c', '*.cpp']), + use = 'filesystem_includes public', + install_path = bld.env.LIBDIR, + subsystem = bld.env.MSVC_SUBSYSTEM) diff --git a/filesystem/zip.c b/filesystem/zip.c new file mode 100644 index 00000000..829d1553 --- /dev/null +++ b/filesystem/zip.c @@ -0,0 +1,680 @@ +/* +zip.c - ZIP support for filesystem +Copyright (C) 2019 Mr0maks +Copyright (C) 2022 Alibek Omarov + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include +#include +#include +#if XASH_POSIX +#include +#endif +#include +#include +#include STDINT_H +#include "port.h" +#include "filesystem_internal.h" +#include "crtlib.h" +#include "common/com_strings.h" +#include "miniz.h" + +#define ZIP_HEADER_LF (('K'<<8)+('P')+(0x03<<16)+(0x04<<24)) +#define ZIP_HEADER_SPANNED ((0x08<<24)+(0x07<<16)+('K'<<8)+'P') + +#define ZIP_HEADER_CDF ((0x02<<24)+(0x01<<16)+('K'<<8)+'P') +#define ZIP_HEADER_EOCD ((0x06<<24)+(0x05<<16)+('K'<<8)+'P') + +#define ZIP_COMPRESSION_NO_COMPRESSION 0 +#define ZIP_COMPRESSION_DEFLATED 8 + +#define ZIP_ZIP64 0xffffffff + +#pragma pack( push, 1 ) +typedef struct zip_header_s +{ + unsigned int signature; // little endian ZIP_HEADER + unsigned short version; // version of pkzip need to unpack + unsigned short flags; // flags (16 bits == 16 flags) + unsigned short compression_flags; // compression flags (bits) + unsigned int dos_date; // file modification time and file modification date + unsigned int crc32; //crc32 + unsigned int compressed_size; + unsigned int uncompressed_size; + unsigned short filename_len; + unsigned short extrafield_len; +} zip_header_t; + +/* + in zip64 comp and uncompr size == 0xffffffff remeber this + compressed and uncompress filesize stored in extra field +*/ + +typedef struct zip_header_extra_s +{ + unsigned int signature; // ZIP_HEADER_SPANNED + unsigned int crc32; + unsigned int compressed_size; + unsigned int uncompressed_size; +} zip_header_extra_t; + +typedef struct zip_cdf_header_s +{ + unsigned int signature; + unsigned short version; + unsigned short version_need; + unsigned short generalPurposeBitFlag; + unsigned short flags; + unsigned short modification_time; + unsigned short modification_date; + unsigned int crc32; + unsigned int compressed_size; + unsigned int uncompressed_size; + unsigned short filename_len; + unsigned short extrafield_len; + unsigned short file_commentary_len; + unsigned short disk_start; + unsigned short internal_attr; + unsigned int external_attr; + unsigned int local_header_offset; +} zip_cdf_header_t; + +typedef struct zip_header_eocd_s +{ + unsigned short disk_number; + unsigned short start_disk_number; + unsigned short number_central_directory_record; + unsigned short total_central_directory_record; + unsigned int size_of_central_directory; + unsigned int central_directory_offset; + unsigned short commentary_len; +} zip_header_eocd_t; +#pragma pack( pop ) + +// ZIP errors +#define ZIP_LOAD_OK 0 +#define ZIP_LOAD_COULDNT_OPEN 1 +#define ZIP_LOAD_BAD_HEADER 2 +#define ZIP_LOAD_BAD_FOLDERS 3 +#define ZIP_LOAD_NO_FILES 5 +#define ZIP_LOAD_CORRUPTED 6 + +typedef struct zipfile_s +{ + char name[MAX_SYSPATH]; + fs_offset_t offset; // offset of local file header + fs_offset_t size; //original file size + fs_offset_t compressed_size; // compressed file size + unsigned short flags; +} zipfile_t; + +struct zip_s +{ + string filename; + int handle; + int numfiles; + time_t filetime; + zipfile_t *files; +}; + +#ifdef XASH_REDUCE_FD +static void FS_EnsureOpenZip( zip_t *zip ) +{ + if( fs_last_zip == zip ) + return; + + if( fs_last_zip && (fs_last_zip->handle != -1) ) + { + close( fs_last_zip->handle ); + fs_last_zip->handle = -1; + } + fs_last_zip = zip; + if( zip && (zip->handle == -1) ) + zip->handle = open( zip->filename, O_RDONLY|O_BINARY ); +} +#else +static void FS_EnsureOpenZip( zip_t *zip ) {} +#endif + +void FS_CloseZIP( zip_t *zip ) +{ + if( zip->files ) + Mem_Free( zip->files ); + + FS_EnsureOpenZip( NULL ); + + if( zip->handle >= 0 ) + close( zip->handle ); + + Mem_Free( zip ); +} + +/* +============ +FS_SortZip +============ +*/ +static int FS_SortZip( const void *a, const void *b ) +{ + return Q_stricmp( ( ( zipfile_t* )a )->name, ( ( zipfile_t* )b )->name ); +} + +/* +============ +FS_LoadZip +============ +*/ +static zip_t *FS_LoadZip( const char *zipfile, int *error ) +{ + int numpackfiles = 0, i; + zip_cdf_header_t header_cdf; + zip_header_eocd_t header_eocd; + uint32_t signature; + fs_offset_t filepos = 0, length; + zipfile_t *info = NULL; + char filename_buffer[MAX_SYSPATH]; + zip_t *zip = (zip_t *)Mem_Calloc( fs_mempool, sizeof( *zip )); + fs_size_t c; + + zip->handle = open( zipfile, O_RDONLY|O_BINARY ); + +#if !XASH_WIN32 + if( zip->handle < 0 ) + { + const char *fzipfile = FS_FixFileCase( zipfile ); + if( fzipfile != zipfile ) + zip->handle = open( fzipfile, O_RDONLY|O_BINARY ); + } +#endif + + if( zip->handle < 0 ) + { + Con_Reportf( S_ERROR "%s couldn't open\n", zipfile ); + + if( error ) + *error = ZIP_LOAD_COULDNT_OPEN; + + FS_CloseZIP( zip ); + return NULL; + } + + length = lseek( zip->handle, 0, SEEK_END ); + + if( length > UINT_MAX ) + { + Con_Reportf( S_ERROR "%s bigger than 4GB.\n", zipfile ); + + if( error ) + *error = ZIP_LOAD_COULDNT_OPEN; + + FS_CloseZIP( zip ); + return NULL; + } + + lseek( zip->handle, 0, SEEK_SET ); + + c = read( zip->handle, &signature, sizeof( signature ) ); + + if( c != sizeof( signature ) || signature == ZIP_HEADER_EOCD ) + { + Con_Reportf( S_WARN "%s has no files. Ignored.\n", zipfile ); + + if( error ) + *error = ZIP_LOAD_NO_FILES; + + FS_CloseZIP( zip ); + return NULL; + } + + if( signature != ZIP_HEADER_LF ) + { + Con_Reportf( S_ERROR "%s is not a zip file. Ignored.\n", zipfile ); + + if( error ) + *error = ZIP_LOAD_BAD_HEADER; + + FS_CloseZIP( zip ); + return NULL; + } + + // Find oecd + lseek( zip->handle, 0, SEEK_SET ); + filepos = length; + + while ( filepos > 0 ) + { + lseek( zip->handle, filepos, SEEK_SET ); + c = read( zip->handle, &signature, sizeof( signature ) ); + + if( c == sizeof( signature ) && signature == ZIP_HEADER_EOCD ) + break; + + filepos -= sizeof( char ); // step back one byte + } + + if( ZIP_HEADER_EOCD != signature ) + { + Con_Reportf( S_ERROR "cannot find EOCD in %s. Zip file corrupted.\n", zipfile ); + + if( error ) + *error = ZIP_LOAD_BAD_HEADER; + + FS_CloseZIP( zip ); + return NULL; + } + + c = read( zip->handle, &header_eocd, sizeof( header_eocd ) ); + + if( c != sizeof( header_eocd )) + { + Con_Reportf( S_ERROR "invalid EOCD header in %s. Zip file corrupted.\n", zipfile ); + + if( error ) + *error = ZIP_LOAD_BAD_HEADER; + + FS_CloseZIP( zip ); + return NULL; + } + + // Move to CDF start + lseek( zip->handle, header_eocd.central_directory_offset, SEEK_SET ); + + // Calc count of files in archive + info = (zipfile_t *)Mem_Calloc( fs_mempool, sizeof( *info ) * header_eocd.total_central_directory_record ); + + for( i = 0; i < header_eocd.total_central_directory_record; i++ ) + { + c = read( zip->handle, &header_cdf, sizeof( header_cdf ) ); + + if( c != sizeof( header_cdf ) || header_cdf.signature != ZIP_HEADER_CDF ) + { + Con_Reportf( S_ERROR "CDF signature mismatch in %s. Zip file corrupted.\n", zipfile ); + + if( error ) + *error = ZIP_LOAD_BAD_HEADER; + + Mem_Free( info ); + FS_CloseZIP( zip ); + return NULL; + } + + if( header_cdf.uncompressed_size && header_cdf.filename_len && ( header_cdf.filename_len < MAX_SYSPATH ) ) + { + memset( &filename_buffer, '\0', MAX_SYSPATH ); + c = read( zip->handle, &filename_buffer, header_cdf.filename_len ); + + if( c != header_cdf.filename_len ) + { + Con_Reportf( S_ERROR "filename length mismatch in %s. Zip file corrupted.\n", zipfile ); + + if( error ) + *error = ZIP_LOAD_CORRUPTED; + + Mem_Free( info ); + FS_CloseZIP( zip ); + return NULL; + } + + Q_strncpy( info[numpackfiles].name, filename_buffer, MAX_SYSPATH ); + + info[numpackfiles].size = header_cdf.uncompressed_size; + info[numpackfiles].compressed_size = header_cdf.compressed_size; + info[numpackfiles].offset = header_cdf.local_header_offset; + numpackfiles++; + } + else + lseek( zip->handle, header_cdf.filename_len, SEEK_CUR ); + + if( header_cdf.extrafield_len ) + lseek( zip->handle, header_cdf.extrafield_len, SEEK_CUR ); + + if( header_cdf.file_commentary_len ) + lseek( zip->handle, header_cdf.file_commentary_len, SEEK_CUR ); + } + + // recalculate offsets + for( i = 0; i < numpackfiles; i++ ) + { + zip_header_t header; + + lseek( zip->handle, info[i].offset, SEEK_SET ); + c = read( zip->handle, &header, sizeof( header ) ); + + if( c != sizeof( header )) + { + Con_Reportf( S_ERROR "header length mismatch in %s. Zip file corrupted.\n", zipfile ); + + if( error ) + *error = ZIP_LOAD_CORRUPTED; + + Mem_Free( info ); + FS_CloseZIP( zip ); + return NULL; + } + + info[i].flags = header.compression_flags; + info[i].offset = info[i].offset + header.filename_len + header.extrafield_len + sizeof( header ); + } + + Q_strncpy( zip->filename, zipfile, sizeof( zip->filename ) ); + zip->filetime = FS_SysFileTime( zipfile ); + zip->numfiles = numpackfiles; + zip->files = info; + + qsort( zip->files, zip->numfiles, sizeof( *zip->files ), FS_SortZip ); + +#ifdef XASH_REDUCE_FD + // will reopen when needed + close(zip->handle); + zip->handle = -1; +#endif + + if( error ) + *error = ZIP_LOAD_OK; + + return zip; +} + +/* +=========== +FS_OpenZipFile + +Open a packed file using its package file descriptor +=========== +*/ +file_t *FS_OpenZipFile( zip_t *zip, int pack_ind ) +{ + zipfile_t *pfile; + pfile = &zip->files[pack_ind]; + + // compressed files handled in Zip_LoadFile + if( pfile->flags != ZIP_COMPRESSION_NO_COMPRESSION ) + { + Con_Printf( S_ERROR "%s: can't open compressed file %s\n", __FUNCTION__, pfile->name ); + return NULL; + } + + return FS_OpenHandle( zip->filename, zip->handle, pfile->offset, pfile->size ); +} + +byte *FS_LoadZIPFile( const char *path, fs_offset_t *sizeptr, qboolean gamedironly ) +{ + searchpath_t *search; + int index; + zipfile_t *file = NULL; + byte *compressed_buffer = NULL, *decompressed_buffer = NULL; + int zlib_result = 0; + dword test_crc, final_crc; + z_stream decompress_stream; + size_t c; + + if( sizeptr ) *sizeptr = 0; + + search = FS_FindFile( path, &index, gamedironly ); + + if( !search || search->type != SEARCHPATH_ZIP ) + return NULL; + + file = &search->zip->files[index]; + + FS_EnsureOpenZip( search->zip ); + + if( lseek( search->zip->handle, file->offset, SEEK_SET ) == -1 ) + return NULL; + + /*if( read( search->zip->handle, &header, sizeof( header ) ) < 0 ) + return NULL; + + if( header.signature != ZIP_HEADER_LF ) + { + Con_Reportf( S_ERROR "Zip_LoadFile: %s signature error\n", file->name ); + return NULL; + }*/ + + if( file->flags == ZIP_COMPRESSION_NO_COMPRESSION ) + { + decompressed_buffer = Mem_Malloc( fs_mempool, file->size + 1 ); + decompressed_buffer[file->size] = '\0'; + + c = read( search->zip->handle, decompressed_buffer, file->size ); + if( c != file->size ) + { + Con_Reportf( S_ERROR "Zip_LoadFile: %s size doesn't match\n", file->name ); + return NULL; + } + +#if 0 + CRC32_Init( &test_crc ); + CRC32_ProcessBuffer( &test_crc, decompressed_buffer, file->size ); + + final_crc = CRC32_Final( test_crc ); + + if( final_crc != file->crc32 ) + { + Con_Reportf( S_ERROR "Zip_LoadFile: %s file crc32 mismatch\n", file->name ); + Mem_Free( decompressed_buffer ); + return NULL; + } +#endif + if( sizeptr ) *sizeptr = file->size; + + FS_EnsureOpenZip( NULL ); + return decompressed_buffer; + } + else if( file->flags == ZIP_COMPRESSION_DEFLATED ) + { + compressed_buffer = Mem_Malloc( fs_mempool, file->compressed_size + 1 ); + decompressed_buffer = Mem_Malloc( fs_mempool, file->size + 1 ); + decompressed_buffer[file->size] = '\0'; + + c = read( search->zip->handle, compressed_buffer, file->compressed_size ); + if( c != file->compressed_size ) + { + Con_Reportf( S_ERROR "Zip_LoadFile: %s compressed size doesn't match\n", file->name ); + return NULL; + } + + memset( &decompress_stream, 0, sizeof( decompress_stream ) ); + + decompress_stream.total_in = decompress_stream.avail_in = file->compressed_size; + decompress_stream.next_in = (Bytef *)compressed_buffer; + decompress_stream.total_out = decompress_stream.avail_out = file->size; + decompress_stream.next_out = (Bytef *)decompressed_buffer; + + decompress_stream.zalloc = Z_NULL; + decompress_stream.zfree = Z_NULL; + decompress_stream.opaque = Z_NULL; + + if( inflateInit2( &decompress_stream, -MAX_WBITS ) != Z_OK ) + { + Con_Printf( S_ERROR "Zip_LoadFile: inflateInit2 failed\n" ); + Mem_Free( compressed_buffer ); + Mem_Free( decompressed_buffer ); + return NULL; + } + + zlib_result = inflate( &decompress_stream, Z_NO_FLUSH ); + inflateEnd( &decompress_stream ); + + if( zlib_result == Z_OK || zlib_result == Z_STREAM_END ) + { + Mem_Free( compressed_buffer ); // finaly free compressed buffer +#if 0 + CRC32_Init( &test_crc ); + CRC32_ProcessBuffer( &test_crc, decompressed_buffer, file->size ); + + final_crc = CRC32_Final( test_crc ); + + if( final_crc != file->crc32 ) + { + Con_Reportf( S_ERROR "Zip_LoadFile: %s file crc32 mismatch\n", file->name ); + Mem_Free( decompressed_buffer ); + return NULL; + } +#endif + if( sizeptr ) *sizeptr = file->size; + + FS_EnsureOpenZip( NULL ); + return decompressed_buffer; + } + else + { + Con_Reportf( S_ERROR "Zip_LoadFile: %s : error while file decompressing. Zlib return code %d.\n", file->name, zlib_result ); + Mem_Free( compressed_buffer ); + Mem_Free( decompressed_buffer ); + return NULL; + } + + } + else + { + Con_Reportf( S_ERROR "Zip_LoadFile: %s : file compressed with unknown algorithm.\n", file->name ); + return NULL; + } + + FS_EnsureOpenZip( NULL ); + return NULL; +} + + +qboolean FS_AddZip_Fullpath( const char *zipfile, qboolean *already_loaded, int flags ) +{ + searchpath_t *search; + zip_t *zip = NULL; + const char *ext = COM_FileExtension( zipfile ); + int errorcode = ZIP_LOAD_COULDNT_OPEN; + + for( search = fs_searchpaths; search; search = search->next ) + { + if( search->type == SEARCHPATH_ZIP && !Q_stricmp( search->zip->filename, zipfile )) + { + if( already_loaded ) *already_loaded = true; + return true; // already loaded + } + } + + if( already_loaded ) *already_loaded = false; + + if( !Q_stricmp( ext, "pk3" ) ) + zip = FS_LoadZip( zipfile, &errorcode ); + + if( zip ) + { + string fullpath; + int i; + + search = (searchpath_t *)Mem_Calloc( fs_mempool, sizeof( searchpath_t ) ); + search->zip = zip; + search->type = SEARCHPATH_ZIP; + search->next = fs_searchpaths; + search->flags |= flags; + fs_searchpaths = search; + + Con_Reportf( "Adding zipfile: %s (%i files)\n", zipfile, zip->numfiles ); + + // time to add in search list all the wads that contains in current pakfile (if do) + for( i = 0; i < zip->numfiles; i++ ) + { + if( !Q_stricmp( COM_FileExtension( zip->files[i].name ), "wad" )) + { + Q_snprintf( fullpath, MAX_STRING, "%s/%s", zipfile, zip->files[i].name ); + FS_AddWad_Fullpath( fullpath, NULL, flags ); + } + } + return true; + } + else + { + if( errorcode != ZIP_LOAD_NO_FILES ) + Con_Reportf( S_ERROR "FS_AddZip_Fullpath: unable to load zip \"%s\"\n", zipfile ); + return false; + } +} + +int FS_FileTimeZIP( zip_t *zip ) +{ + return zip->filetime; +} + +void FS_PrintZIPInfo( char *dst, size_t size, zip_t *zip ) +{ + Q_snprintf( dst, size, "%s (%i files)", zip->filename, zip->numfiles ); +} + +int FS_FindFileZIP( zip_t *zip, const char *name ) +{ + int left, right, middle; + + // look for the file (binary search) + left = 0; + right = zip->numfiles - 1; + while( left <= right ) + { + int diff; + + middle = (left + right) / 2; + diff = Q_stricmp( zip->files[middle].name, name ); + + // Found it + if( !diff ) + return middle; + + // if we're too far in the list + if( diff > 0 ) + right = middle - 1; + else left = middle + 1; + } + + return -1; +} + +void FS_SearchZIP( stringlist_t *list, zip_t *zip, const char *pattern ) +{ + string temp; + const char *slash, *backslash, *colon, *separator; + int j, i; + + for( i = 0; i < zip->numfiles; i++ ) + { + Q_strncpy( temp, zip->files[i].name, sizeof( temp )); + while( temp[0] ) + { + if( matchpattern( temp, pattern, true )) + { + for( j = 0; j < list->numstrings; j++ ) + { + if( !Q_strcmp( list->strings[j], temp )) + break; + } + + if( j == list->numstrings ) + stringlistappend( list, temp ); + } + + // strip off one path element at a time until empty + // this way directories are added to the listing if they match the pattern + slash = Q_strrchr( temp, '/' ); + backslash = Q_strrchr( temp, '\\' ); + colon = Q_strrchr( temp, ':' ); + separator = temp; + if( separator < slash ) + separator = slash; + if( separator < backslash ) + separator = backslash; + if( separator < colon ) + separator = colon; + *((char *)separator) = 0; + } + } +} + diff --git a/game_launch/game.cpp b/game_launch/game.cpp index 7b637989..704c9f16 100644 --- a/game_launch/game.cpp +++ b/game_launch/game.cpp @@ -14,24 +14,24 @@ GNU General Public License for more details. */ #include "port.h" +#include "build.h" #include #include #include #include -#if defined(__APPLE__) || defined(__unix__) || defined(__HAIKU__) - #define XASHLIB "libxash." OS_LIB_EXT -#elif _WIN32 - #if !__MINGW32__ && _MSC_VER >= 1200 - #define USE_WINMAIN - #endif - #define XASHLIB "xash.dll" - #define dlerror() GetStringLastError() - #include // CommandLineToArgvW -#endif +#if XASH_POSIX +#define XASHLIB "libxash." OS_LIB_EXT +#define LoadLibrary( x ) dlopen( x, RTLD_NOW ) +#define GetProcAddress( x, y ) dlsym( x, y ) +#define FreeLibrary( x ) dlclose( x ) +#elif XASH_WIN32 +#include // CommandLineToArgvW +#define XASHLIB "xash.dll" +#define SDL2LIB "SDL2.dll" +#define dlerror() GetStringLastError() -#ifdef WIN32 extern "C" { // Enable NVIDIA High Performance Graphics while using Integrated Graphics. @@ -40,10 +40,14 @@ __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; // Enable AMD High Performance Graphics while using Integrated Graphics. __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; } +#else +#error // port me! #endif #define E_GAME "XASH3D_GAME" // default env dir to start from -#define GAME_PATH "valve" // default dir to start from +#ifndef XASH_GAMEDIR +#define XASH_GAMEDIR "valve" +#endif typedef void (*pfnChangeGame)( const char *progname ); typedef int (*pfnInit)( int argc, char **argv, const char *progname, int bChangeGame, pfnChangeGame func ); @@ -54,7 +58,7 @@ static pfnShutdown Xash_Shutdown = NULL; static char szGameDir[128]; // safe place to keep gamedir static int szArgc; static char **szArgv; -static HINSTANCE hEngine; +static HINSTANCE hEngine; static void Xash_Error( const char *szFmt, ... ) { @@ -70,6 +74,7 @@ static void Xash_Error( const char *szFmt, ... ) #else fprintf( stderr, "Xash Error: %s\n", buffer ); #endif + exit( 1 ); } @@ -88,6 +93,19 @@ static const char *GetStringLastError() static void Sys_LoadEngine( void ) { +#if XASH_WIN32 + HMODULE hSdl; + + if (( hSdl = LoadLibraryEx( SDL2LIB, NULL, LOAD_LIBRARY_AS_DATAFILE )) == NULL ) + { + Xash_Error("Unable to load the " SDL2LIB ": %s", dlerror() ); + } + else + { + FreeLibrary( hSdl ); + } +#endif + if(( hEngine = LoadLibrary( XASHLIB )) == NULL ) { Xash_Error("Unable to load the " XASHLIB ": %s", dlerror() ); @@ -107,12 +125,15 @@ static void Sys_UnloadEngine( void ) if( Xash_Shutdown ) Xash_Shutdown( ); if( hEngine ) FreeLibrary( hEngine ); + hEngine = NULL; Xash_Main = NULL; Xash_Shutdown = NULL; } static void Sys_ChangeGame( const char *progname ) { + // a1ba: may never be called within engine + // if platform supports execv() function if( !progname || !progname[0] ) Xash_Error( "Sys_ChangeGame: NULL gamedir" ); @@ -121,9 +142,8 @@ static void Sys_ChangeGame( const char *progname ) strncpy( szGameDir, progname, sizeof( szGameDir ) - 1 ); - Sys_UnloadEngine (); + Sys_UnloadEngine(); Sys_LoadEngine (); - Xash_Main( szArgc, szArgv, szGameDir, 1, Sys_ChangeGame ); } @@ -131,26 +151,26 @@ _inline int Sys_Start( void ) { int ret; pfnChangeGame changeGame = NULL; + const char *game = getenv( E_GAME ); + + if( !game ) + game = XASH_GAMEDIR; + + strncpy( szGameDir, game, sizeof( szGameDir ) - 1 ); Sys_LoadEngine(); -#ifndef XASH_DISABLE_MENU_CHANGEGAME if( Xash_Shutdown ) changeGame = Sys_ChangeGame; -#endif - const char *game = getenv( E_GAME ); - if( !game ) - game = GAME_PATH; - - ret = Xash_Main( szArgc, szArgv, game, 0, changeGame ); + ret = Xash_Main( szArgc, szArgv, szGameDir, 0, changeGame ); Sys_UnloadEngine(); return ret; } -#ifndef USE_WINMAIN +#if !XASH_WIN32 int main( int argc, char **argv ) { szArgc = argc; @@ -159,7 +179,6 @@ int main( int argc, char **argv ) return Sys_Start(); } #else -//#pragma comment(lib, "shell32.lib") int __stdcall WinMain( HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR cmdLine, int nShow ) { LPWSTR* lpArgv; @@ -170,8 +189,10 @@ int __stdcall WinMain( HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR cmdLine, int for( i = 0; i < szArgc; ++i ) { - int size = wcslen(lpArgv[i]) + 1; - szArgv[i] = ( char* )malloc( size ); + size_t size = wcslen(lpArgv[i]) + 1; + + // just in case, allocate some more memory + szArgv[i] = ( char * )malloc( size * sizeof( wchar_t )); wcstombs( szArgv[i], lpArgv[i], size ); } szArgv[szArgc] = 0; diff --git a/game_launch/wscript b/game_launch/wscript index 4b9ad831..138d6911 100644 --- a/game_launch/wscript +++ b/game_launch/wscript @@ -21,9 +21,8 @@ def configure(conf): conf.define_cond('XASH_DISABLE_MENU_CHANGEGAME', conf.options.DISABLE_MENU_CHANGEGAME) def build(bld): + libs = ['sdk_includes'] source = ['game.cpp'] - includes = '. ../common ../public' - libs = [] if bld.env.DEST_OS != 'win32': libs += [ 'DL' ] @@ -31,13 +30,11 @@ def build(bld): libs += ['USER32', 'SHELL32'] source += ['game.rc'] - bld( - source = source, + bld(source = source, target = 'xash3d', # hl.exe features = 'c cxx cxxprogram', - includes = includes, use = libs, - rpath = '.', + rpath = '$ORIGIN', install_path = bld.env.BINDIR, subsystem = bld.env.MSVC_SUBSYSTEM ) diff --git a/mainui b/mainui deleted file mode 160000 index 0a28db38..00000000 --- a/mainui +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0a28db384b9572f2aefbc335fe5255e65f9f7b7f diff --git a/engine/common/build.c b/public/build.c similarity index 98% rename from engine/common/build.c rename to public/build.c index c4ddaeeb..b9662c53 100644 --- a/engine/common/build.c +++ b/public/build.c @@ -13,7 +13,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ -#include "common.h" +#include "crtlib.h" static const char *date = __DATE__ ; static const char *mon[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; @@ -95,6 +95,8 @@ const char *Q_buildos( void ) osname = "DOS4GW"; #elif XASH_HAIKU osname = "haiku"; +#elif XASH_SERENITY + osname = "serenityos"; #else #error "Place your operating system name here! If this is a mistake, try to fix conditions above and report a bug" #endif diff --git a/public/build.h b/public/build.h index 6e1f326d..57a7735f 100644 --- a/public/build.h +++ b/public/build.h @@ -74,6 +74,7 @@ For more information, please refer to #undef XASH_RISCV_DOUBLEFP #undef XASH_RISCV_SINGLEFP #undef XASH_RISCV_SOFTFP +#undef XASH_SERENITY #undef XASH_WIN32 #undef XASH_WIN64 #undef XASH_X86 @@ -125,6 +126,9 @@ For more information, please refer to #elif defined __HAIKU__ #define XASH_HAIKU 1 #define XASH_POSIX 1 +#elif defined __serenity__ + #define XASH_SERENITY 1 + #define XASH_POSIX 1 #else #error "Place your operating system name here! If this is a mistake, try to fix conditions above and report a bug" #endif diff --git a/public/crtlib.c b/public/crtlib.c index 73bafdf4..6dba1ec4 100644 --- a/public/crtlib.c +++ b/public/crtlib.c @@ -317,94 +317,6 @@ void Q_atov( float *vec, const char *str, size_t siz ) } } -char *Q_strchr( const char *s, char c ) -{ - size_t len = Q_strlen( s ); - - while( len-- ) - { - if( *++s == c ) - return (char *)s; - } - return 0; -} - -char *Q_strrchr( const char *s, char c ) -{ - size_t len = Q_strlen( s ); - - s += len; - - while( len-- ) - { - if( *--s == c ) - return (char *)s; - } - return 0; -} - -int Q_strnicmp( const char *s1, const char *s2, int n ) -{ - int c1, c2; - - if( s1 == NULL ) - { - if( s2 == NULL ) - return 0; - else return -1; - } - else if( s2 == NULL ) - { - return 1; - } - - do { - c1 = *s1++; - c2 = *s2++; - - if( !n-- ) return 0; // strings are equal until end point - - if( c1 != c2 ) - { - if( c1 >= 'a' && c1 <= 'z' ) c1 -= ('a' - 'A'); - if( c2 >= 'a' && c2 <= 'z' ) c2 -= ('a' - 'A'); - if( c1 != c2 ) return c1 < c2 ? -1 : 1; - } - } while( c1 ); - - // strings are equal - return 0; -} - -int Q_strncmp( const char *s1, const char *s2, int n ) -{ - int c1, c2; - - if( s1 == NULL ) - { - if( s2 == NULL ) - return 0; - else return -1; - } - else if( s2 == NULL ) - { - return 1; - } - - do { - c1 = *s1++; - c2 = *s2++; - - // strings are equal until end point - if( !n-- ) return 0; - if( c1 != c2 ) return c1 < c2 ? -1 : 1; - - } while( c1 ); - - // strings are equal - return 0; -} - static qboolean Q_starcmp( const char *pattern, const char *text ) { char c, c1; @@ -426,12 +338,15 @@ static qboolean Q_starcmp( const char *pattern, const char *text ) } } -qboolean Q_stricmpext( const char *pattern, const char *text ) +qboolean Q_strnicmpext( const char *pattern, const char *text, size_t minimumlength ) { + size_t i = 0; char c; while(( c = *pattern++ ) != '\0' ) { + i++; + switch( c ) { case '?': @@ -449,7 +364,12 @@ qboolean Q_stricmpext( const char *pattern, const char *text ) return false; } } - return ( *text == '\0' ); + return ( *text == '\0' ) || i == minimumlength; +} + +qboolean Q_stricmpext( const char *pattern, const char *text ) +{ + return Q_strnicmpext( pattern, text, ~((size_t)0) ); } const char* Q_timestamp( int format ) @@ -496,31 +416,7 @@ const char* Q_timestamp( int format ) return timestamp; } -char *Q_strstr( const char *string, const char *string2 ) -{ - int c; - size_t len; - - if( !string || !string2 ) return NULL; - - c = *string2; - len = Q_strlen( string2 ); - - while( string ) - { - for( ; *string && *string != c; string++ ); - - if( *string ) - { - if( !Q_strncmp( string, string2, len )) - break; - string++; - } - else return NULL; - } - return (char *)string; -} - +#if !defined( HAVE_STRCASESTR ) char *Q_stristr( const char *string, const char *string2 ) { int c; @@ -545,6 +441,7 @@ char *Q_stristr( const char *string, const char *string2 ) } return (char *)string; } +#endif // !defined( HAVE_STRCASESTR ) int Q_vsnprintf( char *buffer, size_t buffersize, const char *format, va_list args ) { @@ -615,6 +512,17 @@ char *Q_strpbrk(const char *s, const char *accept) return NULL; } +void COM_StripColors( const char *in, char *out ) +{ + while ( *in ) + { + if ( IsColorString( in ) ) + in += 2; + else *out++ = *in++; + } + *out = '\0'; +} + uint Q_hashkey( const char *string, uint hashSize, qboolean caseinsensitive ) { uint i, hashKey = 0; @@ -919,6 +827,23 @@ void COM_RemoveLineFeed( char *str ) } } +/* +============ +COM_FixSlashes + +Changes all '/' characters into '\' characters, in place. +============ +*/ +void COM_FixSlashes( char *pname ) +{ + while( *pname ) + { + if( *pname == '\\' ) + *pname = '/'; + pname++; + } +} + /* ============ COM_PathSlashFix @@ -989,11 +914,14 @@ COM_ParseFile text parser ============== */ -char *_COM_ParseFileSafe( char *data, char *token, const int size, unsigned int flags, int *plen ) +char *COM_ParseFileSafe( char *data, char *token, const int size, unsigned int flags, int *plen, qboolean *quoted ) { int c, len = 0; qboolean overflow = false; + if( quoted ) + *quoted = false; + if( !token || !size ) { if( plen ) *plen = 0; @@ -1027,6 +955,9 @@ skipwhite: // handle quoted strings specially if( c == '\"' ) { + if( quoted ) + *quoted = true; + data++; while( 1 ) { diff --git a/public/crtlib.h b/public/crtlib.h index 5e23423f..f3013a8f 100644 --- a/public/crtlib.h +++ b/public/crtlib.h @@ -16,9 +16,15 @@ GNU General Public License for more details. #ifndef STDLIB_H #define STDLIB_H -#include #include +#include #include "build.h" +#include "xash3d_types.h" + +#ifdef __cplusplus +extern "C" +{ +#endif // timestamp modes enum @@ -36,6 +42,16 @@ enum #define PFILE_IGNOREBRACKET (1<<0) #define PFILE_HANDLECOLON (1<<1) #define PFILE_TOKEN_MAX_LENGTH 1024 +#define PFILE_FS_TOKEN_MAX_LENGTH 512 + +// +// build.c +// +int Q_buildnum( void ); +int Q_buildnum_compat( void ); +const char *Q_buildos( void ); +const char *Q_buildarch( void ); +const char *Q_buildcommit( void ); // // crtlib.c @@ -58,21 +74,17 @@ qboolean Q_isspace( const char *str ); int Q_atoi( const char *str ); float Q_atof( const char *str ); void Q_atov( float *vec, const char *str, size_t siz ); -char *Q_strchr( const char *s, char c ); -char *Q_strrchr( const char *s, char c ); -#define Q_stricmp( s1, s2 ) Q_strnicmp( s1, s2, 99999 ) -int Q_strnicmp( const char *s1, const char *s2, int n ); -#define Q_strcmp( s1, s2 ) Q_strncmp( s1, s2, 99999 ) -int Q_strncmp( const char *s1, const char *s2, int n ); -qboolean Q_stricmpext( const char *s1, const char *s2 ); +#define Q_strchr strchr +#define Q_strrchr strrchr +qboolean Q_stricmpext( const char *pattern, const char *text ); +qboolean Q_strnicmpext( const char *pattern, const char *text, size_t minimumlen ); const char *Q_timestamp( int format ); -char *Q_stristr( const char *string, const char *string2 ); -char *Q_strstr( const char *string, const char *string2 ); #define Q_vsprintf( buffer, format, args ) Q_vsnprintf( buffer, 99999, format, args ) int Q_vsnprintf( char *buffer, size_t buffersize, const char *format, va_list args ); int Q_snprintf( char *buffer, size_t buffersize, const char *format, ... ) _format( 3 ); int Q_sprintf( char *buffer, const char *format, ... ) _format( 2 ); char *Q_strpbrk(const char *s, const char *accept); +void COM_StripColors( const char *in, char *out ); #define Q_memprint( val ) Q_pretifymem( val, 2 ) char *Q_pretifymem( float value, int digitsafterdecimal ); char *va( const char *format, ... ) _format( 1 ); @@ -84,14 +96,73 @@ void COM_ExtractFilePath( const char *path, char *dest ); const char *COM_FileWithoutPath( const char *in ); void COM_StripExtension( char *path ); void COM_RemoveLineFeed( char *str ); +void COM_FixSlashes( char *pname ); void COM_PathSlashFix( char *path ); char COM_Hex2Char( uint8_t hex ); void COM_Hex2String( uint8_t hex, char *str ); +// return 0 on empty or null string, 1 otherwise #define COM_CheckString( string ) ( ( !string || !*string ) ? 0 : 1 ) #define COM_CheckStringEmpty( string ) ( ( !*string ) ? 0 : 1 ) -char *_COM_ParseFileSafe( char *data, char *token, const int size, unsigned int flags, int *len ); -#define COM_ParseFile( data, token, size ) _COM_ParseFileSafe( data, token, size, 0, NULL ) +char *COM_ParseFileSafe( char *data, char *token, const int size, unsigned int flags, int *len, qboolean *quoted ); +#define COM_ParseFile( data, token, size ) COM_ParseFileSafe( data, token, size, 0, NULL, NULL ) int matchpattern( const char *in, const char *pattern, qboolean caseinsensitive ); int matchpattern_with_separator( const char *in, const char *pattern, qboolean caseinsensitive, const char *separators, qboolean wildcard_least_one ); +// libc implementations +static inline int Q_strcmp( const char *s1, const char *s2 ) +{ + return unlikely(!s1) ? + ( !s2 ? 0 : -1 ) : + ( unlikely(!s2) ? 1 : strcmp( s1, s2 )); +} + +static inline int Q_strncmp( const char *s1, const char *s2, size_t n ) +{ + return unlikely(!s1) ? + ( !s2 ? 0 : -1 ) : + ( unlikely(!s2) ? 1 : strncmp( s1, s2, n )); +} + +static inline char *Q_strstr( const char *s1, const char *s2 ) +{ + return unlikely( !s1 || !s2 ) ? NULL : (char*)strstr( s1, s2 ); +} + +// libc extensions, be careful + +#if XASH_WIN32 +#define strcasecmp stricmp +#define strncasecmp strnicmp +#endif // XASH_WIN32 + +static inline int Q_stricmp( const char *s1, const char *s2 ) +{ + return unlikely(!s1) ? + ( !s2 ? 0 : -1 ) : + ( unlikely(!s2) ? 1 : strcasecmp( s1, s2 )); +} + +static inline int Q_strnicmp( const char *s1, const char *s2, size_t n ) +{ + return unlikely(!s1) ? + ( !s2 ? 0 : -1 ) : + ( unlikely(!s2) ? 1 : strncasecmp( s1, s2, n )); +} + +#if defined( HAVE_STRCASESTR ) +#if XASH_WIN32 +#define strcasestr stristr +#endif +static inline char *Q_stristr( const char *s1, const char *s2 ) +{ + return unlikely( !s1 || !s2 ) ? NULL : (char *)strcasestr( s1, s2 ); +} +#else // defined( HAVE_STRCASESTR ) +char *Q_stristr( const char *s1, const char *s2 ); +#endif // defined( HAVE_STRCASESTR ) + +#ifdef __cplusplus +} +#endif + #endif//STDLIB_H diff --git a/engine/common/miniz.h b/public/miniz.h similarity index 92% rename from engine/common/miniz.h rename to public/miniz.h index cef9453d..55f8a778 100644 --- a/engine/common/miniz.h +++ b/public/miniz.h @@ -1,4 +1,7 @@ -/* miniz.c 2.1.0 - public domain deflate/inflate, zlib-subset, ZIP reading/writing/appending, PNG writing +#ifndef MINIZ_EXPORT +#define MINIZ_EXPORT +#endif +/* miniz.c 3.0.0 - public domain deflate/inflate, zlib-subset, ZIP reading/writing/appending, PNG writing See "unlicense" statement at the end of this file. Rich Geldreich , last updated Oct. 13, 2013 Implements RFC 1950: http://www.ietf.org/rfc/rfc1950.txt and RFC 1951: http://www.ietf.org/rfc/rfc1951.txt @@ -95,7 +98,7 @@ possibility that the archive's central directory could be lost with this method if anything goes wrong, though. - ZIP archive support limitations: - No zip64 or spanning support. Extraction functions can only handle unencrypted, stored or deflated files. + No spanning support. Extraction functions can only handle unencrypted, stored or deflated files. Requires streams capable of seeking. * This is a header file library, like stb_image.c. To get only a header file, either cut and paste the @@ -114,10 +117,8 @@ - - -/* Defines to completely disable specific portions of miniz.c: - If all macros here are defined the only functionality remaining will be CRC-32, adler-32, tinfl, and tdefl. */ +/* Defines to completely disable specific portions of miniz.c: + If all macros here are defined the only functionality remaining will be CRC-32 and adler-32. */ /* Define MINIZ_NO_STDIO to disable all usage and any functions which rely on stdio for file I/O. */ #define MINIZ_NO_STDIO @@ -127,6 +128,12 @@ /* The current downside is the times written to your archives will be from 1979. */ #define MINIZ_NO_TIME +/* Define MINIZ_NO_DEFLATE_APIS to disable all compression API's. */ +/*#define MINIZ_NO_DEFLATE_APIS */ + +/* Define MINIZ_NO_INFLATE_APIS to disable all decompression API's. */ +/*#define MINIZ_NO_INFLATE_APIS */ + /* Define MINIZ_NO_ARCHIVE_APIS to disable all ZIP archive API's. */ #define MINIZ_NO_ARCHIVE_APIS @@ -139,12 +146,20 @@ /* Define MINIZ_NO_ZLIB_COMPATIBLE_NAME to disable zlib names, to prevent conflicts against stock zlib. */ /*#define MINIZ_NO_ZLIB_COMPATIBLE_NAMES */ -/* Define MINIZ_NO_MALLOC to disable all calls to malloc, free, and realloc. +/* Define MINIZ_NO_MALLOC to disable all calls to malloc, free, and realloc. Note if MINIZ_NO_MALLOC is defined then the user must always provide custom user alloc/free/realloc callbacks to the zlib and archive API's, and a few stand-alone helper API's which don't provide custom user functions (such as tdefl_compress_mem_to_heap() and tinfl_decompress_mem_to_heap()) won't work. */ /*#define MINIZ_NO_MALLOC */ +#ifdef MINIZ_NO_INFLATE_APIS +#define MINIZ_NO_ARCHIVE_APIS +#endif + +#ifdef MINIZ_NO_DEFLATE_APIS +#define MINIZ_NO_ARCHIVE_WRITING_APIS +#endif + #if defined(__TINYC__) && (defined(__linux) || defined(__linux__)) /* TODO: Work around "error: include file 'sys\utime.h' when compiling with tcc on Linux */ #define MINIZ_NO_TIME @@ -163,13 +178,35 @@ #define MINIZ_X86_OR_X64_CPU 0 #endif -#if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || MINIZ_X86_OR_X64_CPU +/* Set MINIZ_LITTLE_ENDIAN only if not set */ +#if !defined(MINIZ_LITTLE_ENDIAN) +#if defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) + +#if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) /* Set MINIZ_LITTLE_ENDIAN to 1 if the processor is little endian. */ #define MINIZ_LITTLE_ENDIAN 1 #else #define MINIZ_LITTLE_ENDIAN 0 #endif +#else + +#if MINIZ_X86_OR_X64_CPU +#define MINIZ_LITTLE_ENDIAN 1 +#else +#define MINIZ_LITTLE_ENDIAN 0 +#endif + +#endif +#endif + +/* Using unaligned loads and stores causes errors when using UBSan */ +#if defined(__has_feature) +#if __has_feature(undefined_behavior_sanitizer) +#define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 0 +#endif +#endif + /* Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES only if not set */ #if !defined(MINIZ_USE_UNALIGNED_LOADS_AND_STORES) #if MINIZ_X86_OR_X64_CPU @@ -198,15 +235,15 @@ extern "C" { typedef unsigned long mz_ulong; /* mz_free() internally uses the MZ_FREE() macro (which by default calls free() unless you've modified the MZ_MALLOC macro) to release a block allocated from the heap. */ -void mz_free(void *p); +MINIZ_EXPORT void mz_free(void *p); #define MZ_ADLER32_INIT (1) /* mz_adler32() returns the initial adler-32 value to use when called with ptr==NULL. */ -mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len); +MINIZ_EXPORT mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len); #define MZ_CRC32_INIT (0) /* mz_crc32() returns the initial CRC-32 value to use when called with ptr==NULL. */ -mz_ulong mz_crc32(mz_ulong crc, const unsigned char *ptr, size_t buf_len); +MINIZ_EXPORT mz_ulong mz_crc32(mz_ulong crc, const unsigned char *ptr, size_t buf_len); /* Compression strategies. */ enum @@ -222,7 +259,7 @@ enum #define MZ_DEFLATED 8 /* Heap allocation callbacks. -Note that mz_alloc_func parameter types purpsosely differ from zlib's: items/size is size_t, not unsigned long. */ +Note that mz_alloc_func parameter types purposely differ from zlib's: items/size is size_t, not unsigned long. */ typedef void *(*mz_alloc_func)(void *opaque, size_t items, size_t size); typedef void (*mz_free_func)(void *opaque, void *address); typedef void *(*mz_realloc_func)(void *opaque, void *address, size_t items, size_t size); @@ -238,10 +275,10 @@ enum MZ_DEFAULT_COMPRESSION = -1 }; -#define MZ_VERSION "10.1.0" -#define MZ_VERNUM 0xA100 -#define MZ_VER_MAJOR 10 -#define MZ_VER_MINOR 1 +#define MZ_VERSION "11.0.0" +#define MZ_VERNUM 0xB000 +#define MZ_VER_MAJOR 11 +#define MZ_VER_MINOR 0 #define MZ_VER_REVISION 0 #define MZ_VER_SUBREVISION 0 @@ -304,7 +341,9 @@ typedef struct mz_stream_s typedef mz_stream *mz_streamp; /* Returns the version string of miniz.c. */ -const char *mz_version(void); +MINIZ_EXPORT const char *mz_version(void); + +#ifndef MINIZ_NO_DEFLATE_APIS /* mz_deflateInit() initializes a compressor with default options: */ /* Parameters: */ @@ -317,17 +356,17 @@ const char *mz_version(void); /* MZ_STREAM_ERROR if the stream is bogus. */ /* MZ_PARAM_ERROR if the input parameters are bogus. */ /* MZ_MEM_ERROR on out of memory. */ -int mz_deflateInit(mz_streamp pStream, int level); +MINIZ_EXPORT int mz_deflateInit(mz_streamp pStream, int level); /* mz_deflateInit2() is like mz_deflate(), except with more control: */ /* Additional parameters: */ /* method must be MZ_DEFLATED */ /* window_bits must be MZ_DEFAULT_WINDOW_BITS (to wrap the deflate stream with zlib header/adler-32 footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate/no header or footer) */ /* mem_level must be between [1, 9] (it's checked but ignored by miniz.c) */ -int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy); +MINIZ_EXPORT int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy); /* Quickly resets a compressor without having to reallocate anything. Same as calling mz_deflateEnd() followed by mz_deflateInit()/mz_deflateInit2(). */ -int mz_deflateReset(mz_streamp pStream); +MINIZ_EXPORT int mz_deflateReset(mz_streamp pStream); /* mz_deflate() compresses the input to output, consuming as much of the input and producing as much output as possible. */ /* Parameters: */ @@ -339,34 +378,38 @@ int mz_deflateReset(mz_streamp pStream); /* MZ_STREAM_ERROR if the stream is bogus. */ /* MZ_PARAM_ERROR if one of the parameters is invalid. */ /* MZ_BUF_ERROR if no forward progress is possible because the input and/or output buffers are empty. (Fill up the input buffer or free up some output space and try again.) */ -int mz_deflate(mz_streamp pStream, int flush); +MINIZ_EXPORT int mz_deflate(mz_streamp pStream, int flush); /* mz_deflateEnd() deinitializes a compressor: */ /* Return values: */ /* MZ_OK on success. */ /* MZ_STREAM_ERROR if the stream is bogus. */ -int mz_deflateEnd(mz_streamp pStream); +MINIZ_EXPORT int mz_deflateEnd(mz_streamp pStream); /* mz_deflateBound() returns a (very) conservative upper bound on the amount of data that could be generated by deflate(), assuming flush is set to only MZ_NO_FLUSH or MZ_FINISH. */ -mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len); +MINIZ_EXPORT mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len); /* Single-call compression functions mz_compress() and mz_compress2(): */ /* Returns MZ_OK on success, or one of the error codes from mz_deflate() on failure. */ -int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len); -int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level); +MINIZ_EXPORT int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len); +MINIZ_EXPORT int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level); /* mz_compressBound() returns a (very) conservative upper bound on the amount of data that could be generated by calling mz_compress(). */ -mz_ulong mz_compressBound(mz_ulong source_len); +MINIZ_EXPORT mz_ulong mz_compressBound(mz_ulong source_len); + +#endif /*#ifndef MINIZ_NO_DEFLATE_APIS*/ + +#ifndef MINIZ_NO_INFLATE_APIS /* Initializes a decompressor. */ -int mz_inflateInit(mz_streamp pStream); +MINIZ_EXPORT int mz_inflateInit(mz_streamp pStream); /* mz_inflateInit2() is like mz_inflateInit() with an additional option that controls the window size and whether or not the stream has been wrapped with a zlib header/footer: */ /* window_bits must be MZ_DEFAULT_WINDOW_BITS (to parse zlib header/footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate). */ -int mz_inflateInit2(mz_streamp pStream, int window_bits); +MINIZ_EXPORT int mz_inflateInit2(mz_streamp pStream, int window_bits); /* Quickly resets a compressor without having to reallocate anything. Same as calling mz_inflateEnd() followed by mz_inflateInit()/mz_inflateInit2(). */ -int mz_inflateReset(mz_streamp pStream); +MINIZ_EXPORT int mz_inflateReset(mz_streamp pStream); /* Decompresses the input stream to the output, consuming only as much of the input as needed, and writing as much to the output as possible. */ /* Parameters: */ @@ -382,17 +425,19 @@ int mz_inflateReset(mz_streamp pStream); /* MZ_PARAM_ERROR if one of the parameters is invalid. */ /* MZ_BUF_ERROR if no forward progress is possible because the input buffer is empty but the inflater needs more input to continue, or if the output buffer is not large enough. Call mz_inflate() again */ /* with more input data, or with more room in the output buffer (except when using single call decompression, described above). */ -int mz_inflate(mz_streamp pStream, int flush); +MINIZ_EXPORT int mz_inflate(mz_streamp pStream, int flush); /* Deinitializes a decompressor. */ -int mz_inflateEnd(mz_streamp pStream); +MINIZ_EXPORT int mz_inflateEnd(mz_streamp pStream); /* Single-call decompression. */ /* Returns MZ_OK on success, or one of the error codes from mz_inflate() on failure. */ -int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len); +MINIZ_EXPORT int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len); +MINIZ_EXPORT int mz_uncompress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong *pSource_len); +#endif /*#ifndef MINIZ_NO_INFLATE_APIS*/ /* Returns a string description of the specified error code, or NULL if the error code is invalid. */ -const char *mz_error(int err); +MINIZ_EXPORT const char *mz_error(int err); /* Redefine zlib-compatible names to miniz equivalents, so miniz.c can be used as a drop-in replacement for the subset of zlib that miniz.c supports. */ /* Define MINIZ_NO_ZLIB_COMPATIBLE_NAMES to disable zlib-compatibility if you use zlib in the same project. */ @@ -440,6 +485,8 @@ typedef void *const voidpc; #define free_func mz_free_func #define internal_state mz_internal_state #define z_stream mz_stream + +#ifndef MINIZ_NO_DEFLATE_APIS #define deflateInit mz_deflateInit #define deflateInit2 mz_deflateInit2 #define deflateReset mz_deflateReset @@ -449,12 +496,18 @@ typedef void *const voidpc; #define compress mz_compress #define compress2 mz_compress2 #define compressBound mz_compressBound +#endif /*#ifndef MINIZ_NO_DEFLATE_APIS*/ + +#ifndef MINIZ_NO_INFLATE_APIS #define inflateInit mz_inflateInit #define inflateInit2 mz_inflateInit2 #define inflateReset mz_inflateReset #define inflate mz_inflate #define inflateEnd mz_inflateEnd #define uncompress mz_uncompress +#define uncompress2 mz_uncompress2 +#endif /*#ifndef MINIZ_NO_INFLATE_APIS*/ + #define crc32 mz_crc32 #define adler32 mz_adler32 #define MAX_WBITS 15 @@ -475,12 +528,19 @@ typedef void *const voidpc; #ifdef __cplusplus } #endif + + + + + #pragma once #include #include #include #include + + /* ------------------- Types and macros */ typedef unsigned char mz_uint8; typedef signed short mz_int16; @@ -511,7 +571,8 @@ typedef int mz_bool; #ifdef MINIZ_NO_TIME typedef struct mz_dummy_time_t_tag { - int m_dummy; + mz_uint32 m_dummy1; + mz_uint32 m_dummy2; } mz_dummy_time_t; #define MZ_TIME_T mz_dummy_time_t #else @@ -533,6 +594,8 @@ typedef struct mz_dummy_time_t_tag #define MZ_MAX(a, b) (((a) > (b)) ? (a) : (b)) #define MZ_MIN(a, b) (((a) < (b)) ? (a) : (b)) #define MZ_CLEAR_OBJ(obj) memset(&(obj), 0, sizeof(obj)) +#define MZ_CLEAR_ARR(obj) memset((obj), 0, sizeof(obj)) +#define MZ_CLEAR_PTR(obj) memset((obj), 0, sizeof(*obj)) #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN #define MZ_READ_LE16(p) *((const mz_uint16 *)(p)) @@ -556,9 +619,9 @@ typedef struct mz_dummy_time_t_tag extern "C" { #endif -extern void *miniz_def_alloc_func(void *opaque, size_t items, size_t size); -extern void miniz_def_free_func(void *opaque, void *address); -extern void *miniz_def_realloc_func(void *opaque, void *address, size_t items, size_t size); +extern MINIZ_EXPORT void *miniz_def_alloc_func(void *opaque, size_t items, size_t size); +extern MINIZ_EXPORT void miniz_def_free_func(void *opaque, void *address); +extern MINIZ_EXPORT void *miniz_def_realloc_func(void *opaque, void *address, size_t items, size_t size); #define MZ_UINT16_MAX (0xFFFFU) #define MZ_UINT32_MAX (0xFFFFFFFFU) @@ -569,6 +632,8 @@ extern void *miniz_def_realloc_func(void *opaque, void *address, size_t items, s #pragma once +#ifndef MINIZ_NO_DEFLATE_APIS + #ifdef __cplusplus extern "C" { #endif @@ -616,11 +681,11 @@ enum /* Function returns a pointer to the compressed data, or NULL on failure. */ /* *pOut_len will be set to the compressed data's size, which could be larger than src_buf_len on uncompressible data. */ /* The caller must free() the returned block when it's no longer needed. */ -void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); +MINIZ_EXPORT void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); /* tdefl_compress_mem_to_mem() compresses a block in memory to another block in memory. */ /* Returns 0 on failure. */ -size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags); +MINIZ_EXPORT size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags); /* Compresses an image to a compressed PNG file in memory. */ /* On entry: */ @@ -632,14 +697,14 @@ size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void /* Function returns a pointer to the compressed data, or NULL on failure. */ /* *pLen_out will be set to the size of the PNG image file. */ /* The caller must mz_free() the returned heap block (which will typically be larger than *pLen_out) when it's no longer needed. */ -void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip); -void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out); +MINIZ_EXPORT void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip); +MINIZ_EXPORT void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out); /* Output stream interface. The compressor uses this interface to write compressed data. It'll typically be called TDEFL_OUT_BUF_SIZE at a time. */ typedef mz_bool (*tdefl_put_buf_func_ptr)(const void *pBuf, int len, void *pUser); /* tdefl_compress_mem_to_output() compresses a block to an output stream. The above helpers use this function internally. */ -mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); +MINIZ_EXPORT mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); enum { @@ -727,39 +792,43 @@ typedef struct /* pBut_buf_func: If NULL, output data will be supplied to the specified callback. In this case, the user should call the tdefl_compress_buffer() API for compression. */ /* If pBut_buf_func is NULL the user should always call the tdefl_compress() API. */ /* flags: See the above enums (TDEFL_HUFFMAN_ONLY, TDEFL_WRITE_ZLIB_HEADER, etc.) */ -tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); +MINIZ_EXPORT tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); /* Compresses a block of data, consuming as much of the specified input buffer as possible, and writing as much compressed data to the specified output buffer as possible. */ -tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush); +MINIZ_EXPORT tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush); /* tdefl_compress_buffer() is only usable when the tdefl_init() is called with a non-NULL tdefl_put_buf_func_ptr. */ /* tdefl_compress_buffer() always consumes the entire input buffer. */ -tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush); +MINIZ_EXPORT tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush); -tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d); -mz_uint32 tdefl_get_adler32(tdefl_compressor *d); +MINIZ_EXPORT tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d); +MINIZ_EXPORT mz_uint32 tdefl_get_adler32(tdefl_compressor *d); /* Create tdefl_compress() flags given zlib-style compression parameters. */ /* level may range from [0,10] (where 10 is absolute max compression, but may be much slower on some files) */ /* window_bits may be -15 (raw deflate) or 15 (zlib) */ /* strategy may be either MZ_DEFAULT_STRATEGY, MZ_FILTERED, MZ_HUFFMAN_ONLY, MZ_RLE, or MZ_FIXED */ -mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy); +MINIZ_EXPORT mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy); #ifndef MINIZ_NO_MALLOC /* Allocate the tdefl_compressor structure in C so that */ /* non-C language bindings to tdefl_ API don't need to worry about */ /* structure size and allocation mechanism. */ -tdefl_compressor *tdefl_compressor_alloc(void); -void tdefl_compressor_free(tdefl_compressor *pComp); +MINIZ_EXPORT tdefl_compressor *tdefl_compressor_alloc(void); +MINIZ_EXPORT void tdefl_compressor_free(tdefl_compressor *pComp); #endif #ifdef __cplusplus } #endif + +#endif /*#ifndef MINIZ_NO_DEFLATE_APIS*/ #pragma once /* ------------------- Low-level Decompression API Definitions */ +#ifndef MINIZ_NO_INFLATE_APIS + #ifdef __cplusplus extern "C" { #endif @@ -784,17 +853,17 @@ enum /* Function returns a pointer to the decompressed data, or NULL on failure. */ /* *pOut_len will be set to the decompressed data's size, which could be larger than src_buf_len on uncompressible data. */ /* The caller must call mz_free() on the returned block when it's no longer needed. */ -void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); +MINIZ_EXPORT void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); /* tinfl_decompress_mem_to_mem() decompresses a block in memory to another block in memory. */ /* Returns TINFL_DECOMPRESS_MEM_TO_MEM_FAILED on failure, or the number of bytes written on success. */ #define TINFL_DECOMPRESS_MEM_TO_MEM_FAILED ((size_t)(-1)) -size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags); +MINIZ_EXPORT size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags); /* tinfl_decompress_mem_to_callback() decompresses a block in memory to an internal 32KB buffer, and a user provided callback function will be called to flush the buffer. */ /* Returns 1 on success or 0 on failure. */ typedef int (*tinfl_put_buf_func_ptr)(const void *pBuf, int len, void *pUser); -int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); +MINIZ_EXPORT int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); struct tinfl_decompressor_tag; typedef struct tinfl_decompressor_tag tinfl_decompressor; @@ -803,8 +872,8 @@ typedef struct tinfl_decompressor_tag tinfl_decompressor; /* Allocate the tinfl_decompressor structure in C so that */ /* non-C language bindings to tinfl_ API don't need to worry about */ /* structure size and allocation mechanism. */ -tinfl_decompressor *tinfl_decompressor_alloc(void); -void tinfl_decompressor_free(tinfl_decompressor *pDecomp); +MINIZ_EXPORT tinfl_decompressor *tinfl_decompressor_alloc(void); +MINIZ_EXPORT void tinfl_decompressor_free(tinfl_decompressor *pDecomp); #endif /* Max size of LZ dictionary. */ @@ -855,7 +924,7 @@ typedef enum { /* Main low-level decompressor coroutine function. This is the only function actually needed for decompression. All the other functions are just high-level helpers for improved usability. */ /* This is a universal API, i.e. it can be used as a building block to build any desired higher level decompression API. In the limit case, it can be called once per every byte input or output. */ -tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags); +MINIZ_EXPORT tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags); /* Internal/private bits follow. */ enum @@ -868,12 +937,6 @@ enum TINFL_FAST_LOOKUP_SIZE = 1 << TINFL_FAST_LOOKUP_BITS }; -typedef struct -{ - mz_uint8 m_code_size[TINFL_MAX_HUFF_SYMBOLS_0]; - mz_int16 m_look_up[TINFL_FAST_LOOKUP_SIZE], m_tree[TINFL_MAX_HUFF_SYMBOLS_0 * 2]; -} tinfl_huff_table; - #if MINIZ_HAS_64BIT_REGISTERS #define TINFL_USE_64BIT_BITBUF 1 #else @@ -893,7 +956,13 @@ struct tinfl_decompressor_tag mz_uint32 m_state, m_num_bits, m_zhdr0, m_zhdr1, m_z_adler32, m_final, m_type, m_check_adler32, m_dist, m_counter, m_num_extra, m_table_sizes[TINFL_MAX_HUFF_TABLES]; tinfl_bit_buf_t m_bit_buf; size_t m_dist_from_out_buf_start; - tinfl_huff_table m_tables[TINFL_MAX_HUFF_TABLES]; + mz_int16 m_look_up[TINFL_MAX_HUFF_TABLES][TINFL_FAST_LOOKUP_SIZE]; + mz_int16 m_tree_0[TINFL_MAX_HUFF_SYMBOLS_0 * 2]; + mz_int16 m_tree_1[TINFL_MAX_HUFF_SYMBOLS_1 * 2]; + mz_int16 m_tree_2[TINFL_MAX_HUFF_SYMBOLS_2 * 2]; + mz_uint8 m_code_size_0[TINFL_MAX_HUFF_SYMBOLS_0]; + mz_uint8 m_code_size_1[TINFL_MAX_HUFF_SYMBOLS_1]; + mz_uint8 m_code_size_2[TINFL_MAX_HUFF_SYMBOLS_2]; mz_uint8 m_raw_header[4], m_len_codes[TINFL_MAX_HUFF_SYMBOLS_0 + TINFL_MAX_HUFF_SYMBOLS_1 + 137]; }; @@ -901,6 +970,8 @@ struct tinfl_decompressor_tag } #endif +#endif /*#ifndef MINIZ_NO_INFLATE_APIS*/ + #ifndef MINIZ_HEADER_FILE_ONLY /************************************************************************** * @@ -986,6 +1057,12 @@ mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len) } return ~crcu32; } +#elif defined(USE_EXTERNAL_MZCRC) +/* If USE_EXTERNAL_CRC is defined, an external module will export the + * mz_crc32() symbol for us to use, e.g. an SSE-accelerated version. + * Depending on the impl, it may be necessary to ~ the input/output crc values. + */ +mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len); #else /* Faster, but larger CPU cache footprint. */ @@ -1061,17 +1138,17 @@ void mz_free(void *p) MZ_FREE(p); } -void *miniz_def_alloc_func(void *opaque, size_t items, size_t size) +MINIZ_EXPORT void *miniz_def_alloc_func(void *opaque, size_t items, size_t size) { (void)opaque, (void)items, (void)size; return MZ_MALLOC(items * size); } -void miniz_def_free_func(void *opaque, void *address) +MINIZ_EXPORT void miniz_def_free_func(void *opaque, void *address) { (void)opaque, (void)address; MZ_FREE(address); } -void *miniz_def_realloc_func(void *opaque, void *address, size_t items, size_t size) +MINIZ_EXPORT void *miniz_def_realloc_func(void *opaque, void *address, size_t items, size_t size) { (void)opaque, (void)address, (void)items, (void)size; return MZ_REALLOC(address, items * size); @@ -1084,6 +1161,8 @@ const char *mz_version(void) #ifndef MINIZ_NO_ZLIB_APIS +#ifndef MINIZ_NO_DEFLATE_APIS + int mz_deflateInit(mz_streamp pStream, int level) { return mz_deflateInit2(pStream, level, MZ_DEFLATED, MZ_DEFAULT_WINDOW_BITS, 9, MZ_DEFAULT_STRATEGY); @@ -1218,7 +1297,7 @@ int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char memset(&stream, 0, sizeof(stream)); /* In case mz_ulong is 64-bits (argh I hate longs). */ - if ((source_len | *pDest_len) > 0xFFFFFFFFU) + if ((mz_uint64)(source_len | *pDest_len) > 0xFFFFFFFFU) return MZ_PARAM_ERROR; stream.next_in = pSource; @@ -1251,6 +1330,10 @@ mz_ulong mz_compressBound(mz_ulong source_len) return mz_deflateBound(NULL, source_len); } +#endif /*#ifndef MINIZ_NO_DEFLATE_APIS*/ + +#ifndef MINIZ_NO_INFLATE_APIS + typedef struct { tinfl_decompressor m_decomp; @@ -1450,19 +1533,18 @@ int mz_inflateEnd(mz_streamp pStream) } return MZ_OK; } - -int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len) +int mz_uncompress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong *pSource_len) { mz_stream stream; int status; memset(&stream, 0, sizeof(stream)); /* In case mz_ulong is 64-bits (argh I hate longs). */ - if ((source_len | *pDest_len) > 0xFFFFFFFFU) + if ((mz_uint64)(*pSource_len | *pDest_len) > 0xFFFFFFFFU) return MZ_PARAM_ERROR; stream.next_in = pSource; - stream.avail_in = (mz_uint32)source_len; + stream.avail_in = (mz_uint32)*pSource_len; stream.next_out = pDest; stream.avail_out = (mz_uint32)*pDest_len; @@ -1471,6 +1553,7 @@ int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char return status; status = mz_inflate(&stream, MZ_FINISH); + *pSource_len = *pSource_len - stream.avail_in; if (status != MZ_STREAM_END) { mz_inflateEnd(&stream); @@ -1481,6 +1564,13 @@ int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char return mz_inflateEnd(&stream); } +int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len) +{ + return mz_uncompress2(pDest, pDest_len, pSource, &source_len); +} + +#endif /*#ifndef MINIZ_NO_INFLATE_APIS*/ + const char *mz_error(int err) { static struct @@ -1558,6 +1648,7 @@ const char *mz_error(int err) +#ifndef MINIZ_NO_DEFLATE_APIS #ifdef __cplusplus extern "C" { @@ -1637,7 +1728,7 @@ static tdefl_sym_freq *tdefl_radix_sort_syms(mz_uint num_syms, tdefl_sym_freq *p { mz_uint32 total_passes = 2, pass_shift, pass, i, hist[256 * 2]; tdefl_sym_freq *pCur_syms = pSyms0, *pNew_syms = pSyms1; - MZ_CLEAR_OBJ(hist); + MZ_CLEAR_ARR(hist); for (i = 0; i < num_syms; i++) { mz_uint freq = pSyms0[i].m_key; @@ -1755,7 +1846,7 @@ static void tdefl_optimize_huffman_table(tdefl_compressor *d, int table_num, int { int i, j, l, num_codes[1 + TDEFL_MAX_SUPPORTED_HUFF_CODESIZE]; mz_uint next_code[TDEFL_MAX_SUPPORTED_HUFF_CODESIZE + 1]; - MZ_CLEAR_OBJ(num_codes); + MZ_CLEAR_ARR(num_codes); if (static_table) { for (i = 0; i < table_len; i++) @@ -1781,8 +1872,8 @@ static void tdefl_optimize_huffman_table(tdefl_compressor *d, int table_num, int tdefl_huffman_enforce_max_code_size(num_codes, num_used_syms, code_size_limit); - MZ_CLEAR_OBJ(d->m_huff_code_sizes[table_num]); - MZ_CLEAR_OBJ(d->m_huff_codes[table_num]); + MZ_CLEAR_ARR(d->m_huff_code_sizes[table_num]); + MZ_CLEAR_ARR(d->m_huff_codes[table_num]); for (i = 1, j = num_used_syms; i <= code_size_limit; i++) for (l = num_codes[i]; l > 0; l--) d->m_huff_code_sizes[table_num][pSyms[--j].m_sym_index] = (mz_uint8)(i); @@ -1868,7 +1959,7 @@ static void tdefl_optimize_huffman_table(tdefl_compressor *d, int table_num, int } \ } -static mz_uint8 s_tdefl_packed_code_size_syms_swizzle[] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; +static const mz_uint8 s_tdefl_packed_code_size_syms_swizzle[] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; static void tdefl_start_dynamic_block(tdefl_compressor *d) { @@ -2006,7 +2097,8 @@ static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d) if (flags & 1) { mz_uint s0, s1, n0, n1, sym, num_extra_bits; - mz_uint match_len = pLZ_codes[0], match_dist = *(const mz_uint16 *)(pLZ_codes + 1); + mz_uint match_len = pLZ_codes[0]; + mz_uint match_dist = (pLZ_codes[1] | (pLZ_codes[2] << 8)); pLZ_codes += 3; MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); @@ -2051,7 +2143,7 @@ static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d) if (pOutput_buf >= d->m_pOutput_buf_end) return MZ_FALSE; - *(mz_uint64 *)pOutput_buf = bit_buffer; + memcpy(pOutput_buf, &bit_buffer, sizeof(mz_uint64)); pOutput_buf += (bits_in >> 3); bit_buffer >>= (bits_in & ~7); bits_in &= 7; @@ -2133,6 +2225,8 @@ static mz_bool tdefl_compress_block(tdefl_compressor *d, mz_bool static_block) return tdefl_compress_lz_codes(d); } +static const mz_uint s_tdefl_num_probes[11]; + static int tdefl_flush_block(tdefl_compressor *d, int flush) { mz_uint saved_bit_buf, saved_bits_in; @@ -2153,8 +2247,27 @@ static int tdefl_flush_block(tdefl_compressor *d, int flush) if ((d->m_flags & TDEFL_WRITE_ZLIB_HEADER) && (!d->m_block_index)) { - TDEFL_PUT_BITS(0x78, 8); - TDEFL_PUT_BITS(0x01, 8); + const mz_uint8 cmf = 0x78; + mz_uint8 flg, flevel = 3; + mz_uint header, i, mz_un = sizeof(s_tdefl_num_probes) / sizeof(mz_uint); + + /* Determine compression level by reversing the process in tdefl_create_comp_flags_from_zip_params() */ + for (i = 0; i < mz_un; i++) + if (s_tdefl_num_probes[i] == (d->m_flags & 0xFFF)) break; + + if (i < 2) + flevel = 0; + else if (i < 6) + flevel = 1; + else if (i == 6) + flevel = 2; + + header = cmf << 8 | (flevel << 6); + header += 31 - (header % 31); + flg = header & 0xFF; + + TDEFL_PUT_BITS(cmf, 8); + TDEFL_PUT_BITS(flg, 8); } TDEFL_PUT_BITS(flush == TDEFL_FINISH, 1); @@ -2607,9 +2720,7 @@ static MZ_FORCEINLINE void tdefl_record_match(tdefl_compressor *d, mz_uint match s0 = s_tdefl_small_dist_sym[match_dist & 511]; s1 = s_tdefl_large_dist_sym[(match_dist >> 8) & 127]; d->m_huff_count[1][(match_dist < 512) ? s0 : s1]++; - - if (match_len >= TDEFL_MIN_MATCH_LEN) - d->m_huff_count[0][s_tdefl_len_sym[match_len - TDEFL_MIN_MATCH_LEN]]++; + d->m_huff_count[0][s_tdefl_len_sym[match_len - TDEFL_MIN_MATCH_LEN]]++; } static mz_bool tdefl_compress_normal(tdefl_compressor *d) @@ -2627,7 +2738,7 @@ static mz_bool tdefl_compress_normal(tdefl_compressor *d) mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK, ins_pos = d->m_lookahead_pos + d->m_lookahead_size - 2; mz_uint hash = (d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] << TDEFL_LZ_HASH_SHIFT) ^ d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK]; mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(src_buf_left, TDEFL_MAX_MATCH_LEN - d->m_lookahead_size); - const mz_uint8 *pSrc_end = pSrc + num_bytes_to_process; + const mz_uint8 *pSrc_end = pSrc ? pSrc + num_bytes_to_process : NULL; src_buf_left -= num_bytes_to_process; d->m_lookahead_size += num_bytes_to_process; while (pSrc != pSrc_end) @@ -2837,8 +2948,8 @@ tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pI d->m_finished = (flush == TDEFL_FINISH); if (flush == TDEFL_FULL_FLUSH) { - MZ_CLEAR_OBJ(d->m_hash); - MZ_CLEAR_OBJ(d->m_next); + MZ_CLEAR_ARR(d->m_hash); + MZ_CLEAR_ARR(d->m_next); d->m_dict_size = 0; } } @@ -2861,11 +2972,12 @@ tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_fun d->m_greedy_parsing = (flags & TDEFL_GREEDY_PARSING_FLAG) != 0; d->m_max_probes[1] = 1 + (((flags & 0xFFF) >> 2) + 2) / 3; if (!(flags & TDEFL_NONDETERMINISTIC_PARSING_FLAG)) - MZ_CLEAR_OBJ(d->m_hash); + MZ_CLEAR_ARR(d->m_hash); d->m_lookahead_pos = d->m_lookahead_size = d->m_dict_size = d->m_total_lz_bytes = d->m_lz_code_buf_dict_pos = d->m_bits_in = 0; d->m_output_flush_ofs = d->m_output_flush_remaining = d->m_finished = d->m_block_index = d->m_bit_buffer = d->m_wants_to_finish = 0; d->m_pLZ_code_buf = d->m_lz_code_buf + 1; d->m_pLZ_flags = d->m_lz_code_buf; + *d->m_pLZ_flags = 0; d->m_num_flags_left = 8; d->m_pOutput_buf = d->m_output_buf; d->m_pOutput_buf_end = d->m_output_buf; @@ -2881,7 +2993,7 @@ tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_fun d->m_src_buf_left = 0; d->m_out_buf_ofs = 0; if (!(flags & TDEFL_NONDETERMINISTIC_PARSING_FLAG)) - MZ_CLEAR_OBJ(d->m_dict); + MZ_CLEAR_ARR(d->m_dict); memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0); memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1); return TDEFL_STATUS_OKAY; @@ -3091,7 +3203,7 @@ void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, /* Allocate the tdefl_compressor and tinfl_decompressor structures in C so that */ /* non-C language bindings to tdefL_ and tinfl_ API don't need to worry about */ /* structure size and allocation mechanism. */ -tdefl_compressor *tdefl_compressor_alloc( void ) +tdefl_compressor *tdefl_compressor_alloc(void) { return (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); } @@ -3109,6 +3221,8 @@ void tdefl_compressor_free(tdefl_compressor *pComp) #ifdef __cplusplus } #endif + +#endif /*#ifndef MINIZ_NO_DEFLATE_APIS*/ /************************************************************************** * * Copyright 2013-2014 RAD Game Tools and Valve Software @@ -3137,6 +3251,8 @@ void tdefl_compressor_free(tdefl_compressor *pComp) +#ifndef MINIZ_NO_INFLATE_APIS + #ifdef __cplusplus extern "C" { #endif @@ -3217,10 +3333,10 @@ extern "C" { /* It reads just enough bytes from the input stream that are needed to decode the next Huffman code (and absolutely no more). It works by trying to fully decode a */ /* Huffman code by using whatever bits are currently present in the bit buffer. If this fails, it reads another byte, and tries again until it succeeds or until the */ /* bit buffer contains >=15 bits (deflate's max. Huffman code size). */ -#define TINFL_HUFF_BITBUF_FILL(state_index, pHuff) \ +#define TINFL_HUFF_BITBUF_FILL(state_index, pLookUp, pTree) \ do \ { \ - temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]; \ + temp = pLookUp[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]; \ if (temp >= 0) \ { \ code_len = temp >> 9; \ @@ -3232,7 +3348,7 @@ extern "C" { code_len = TINFL_FAST_LOOKUP_BITS; \ do \ { \ - temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; \ + temp = pTree[~temp + ((bit_buf >> code_len++) & 1)]; \ } while ((temp < 0) && (num_bits >= (code_len + 1))); \ if (temp >= 0) \ break; \ @@ -3248,7 +3364,7 @@ extern "C" { /* The slow path is only executed at the very end of the input buffer. */ /* v1.16: The original macro handled the case at the very end of the passed-in input buffer, but we also need to handle the case where the user passes in 1+zillion bytes */ /* following the deflate data and our non-conservative read-ahead path won't kick in here on this code. This is much trickier. */ -#define TINFL_HUFF_DECODE(state_index, sym, pHuff) \ +#define TINFL_HUFF_DECODE(state_index, sym, pLookUp, pTree) \ do \ { \ int temp; \ @@ -3257,7 +3373,7 @@ extern "C" { { \ if ((pIn_buf_end - pIn_buf_cur) < 2) \ { \ - TINFL_HUFF_BITBUF_FILL(state_index, pHuff); \ + TINFL_HUFF_BITBUF_FILL(state_index, pLookUp, pTree); \ } \ else \ { \ @@ -3266,14 +3382,14 @@ extern "C" { num_bits += 16; \ } \ } \ - if ((temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) \ + if ((temp = pLookUp[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) \ code_len = temp >> 9, temp &= 511; \ else \ { \ code_len = TINFL_FAST_LOOKUP_BITS; \ do \ { \ - temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; \ + temp = pTree[~temp + ((bit_buf >> code_len++) & 1)]; \ } while (temp < 0); \ } \ sym = temp; \ @@ -3282,20 +3398,33 @@ extern "C" { } \ MZ_MACRO_END +static void tinfl_clear_tree(tinfl_decompressor *r) +{ + if (r->m_type == 0) + MZ_CLEAR_ARR(r->m_tree_0); + else if (r->m_type == 1) + MZ_CLEAR_ARR(r->m_tree_1); + else + MZ_CLEAR_ARR(r->m_tree_2); +} + tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags) { - static const int s_length_base[31] = { 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0 }; - static const int s_length_extra[31] = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 0, 0 }; - static const int s_dist_base[32] = { 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577, 0, 0 }; - static const int s_dist_extra[32] = { 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13 }; + static const mz_uint16 s_length_base[31] = { 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0 }; + static const mz_uint8 s_length_extra[31] = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 0, 0 }; + static const mz_uint16 s_dist_base[32] = { 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577, 0, 0 }; + static const mz_uint8 s_dist_extra[32] = { 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13 }; static const mz_uint8 s_length_dezigzag[19] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; - static const int s_min_table_sizes[3] = { 257, 1, 4 }; + static const mz_uint16 s_min_table_sizes[3] = { 257, 1, 4 }; + + mz_int16 *pTrees[3]; + mz_uint8 *pCode_sizes[3]; tinfl_status status = TINFL_STATUS_FAILED; mz_uint32 num_bits, dist, counter, num_extra; tinfl_bit_buf_t bit_buf; const mz_uint8 *pIn_buf_cur = pIn_buf_next, *const pIn_buf_end = pIn_buf_next + *pIn_buf_size; - mz_uint8 *pOut_buf_cur = pOut_buf_next, *const pOut_buf_end = pOut_buf_next + *pOut_buf_size; + mz_uint8 *pOut_buf_cur = pOut_buf_next, *const pOut_buf_end = pOut_buf_next ? pOut_buf_next + *pOut_buf_size : NULL; size_t out_buf_size_mask = (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF) ? (size_t)-1 : ((pOut_buf_next - pOut_buf_start) + *pOut_buf_size) - 1, dist_from_out_buf_start; /* Ensure the output buffer's size is a power of 2, unless the output buffer is large enough to hold the entire output file (in which case it doesn't matter). */ @@ -3305,6 +3434,13 @@ tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_nex return TINFL_STATUS_BAD_PARAM; } + pTrees[0] = r->m_tree_0; + pTrees[1] = r->m_tree_1; + pTrees[2] = r->m_tree_2; + pCode_sizes[0] = r->m_code_size_0; + pCode_sizes[1] = r->m_code_size_1; + pCode_sizes[2] = r->m_code_size_2; + num_bits = r->m_num_bits; bit_buf = r->m_bit_buf; dist = r->m_dist; @@ -3321,7 +3457,7 @@ tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_nex TINFL_GET_BYTE(2, r->m_zhdr1); counter = (((r->m_zhdr0 * 256 + r->m_zhdr1) % 31 != 0) || (r->m_zhdr1 & 32) || ((r->m_zhdr0 & 15) != 8)); if (!(decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) - counter |= (((1U << (8U + (r->m_zhdr0 >> 4))) > 32768U) || ((out_buf_size_mask + 1) < (size_t)(1U << (8U + (r->m_zhdr0 >> 4))))); + counter |= (((1U << (8U + (r->m_zhdr0 >> 4))) > 32768U) || ((out_buf_size_mask + 1) < (size_t)((size_t)1 << (8U + (r->m_zhdr0 >> 4))))); if (counter) { TINFL_CR_RETURN_FOREVER(36, TINFL_STATUS_FAILED); @@ -3382,11 +3518,11 @@ tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_nex { if (r->m_type == 1) { - mz_uint8 *p = r->m_tables[0].m_code_size; + mz_uint8 *p = r->m_code_size_0; mz_uint i; r->m_table_sizes[0] = 288; r->m_table_sizes[1] = 32; - TINFL_MEMSET(r->m_tables[1].m_code_size, 5, 32); + TINFL_MEMSET(r->m_code_size_1, 5, 32); for (i = 0; i <= 143; ++i) *p++ = 8; for (; i <= 255; ++i) @@ -3403,26 +3539,30 @@ tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_nex TINFL_GET_BITS(11, r->m_table_sizes[counter], "\05\05\04"[counter]); r->m_table_sizes[counter] += s_min_table_sizes[counter]; } - MZ_CLEAR_OBJ(r->m_tables[2].m_code_size); + MZ_CLEAR_ARR(r->m_code_size_2); for (counter = 0; counter < r->m_table_sizes[2]; counter++) { mz_uint s; TINFL_GET_BITS(14, s, 3); - r->m_tables[2].m_code_size[s_length_dezigzag[counter]] = (mz_uint8)s; + r->m_code_size_2[s_length_dezigzag[counter]] = (mz_uint8)s; } r->m_table_sizes[2] = 19; } for (; (int)r->m_type >= 0; r->m_type--) { int tree_next, tree_cur; - tinfl_huff_table *pTable; + mz_int16 *pLookUp; + mz_int16 *pTree; + mz_uint8 *pCode_size; mz_uint i, j, used_syms, total, sym_index, next_code[17], total_syms[16]; - pTable = &r->m_tables[r->m_type]; - MZ_CLEAR_OBJ(total_syms); - MZ_CLEAR_OBJ(pTable->m_look_up); - MZ_CLEAR_OBJ(pTable->m_tree); + pLookUp = r->m_look_up[r->m_type]; + pTree = pTrees[r->m_type]; + pCode_size = pCode_sizes[r->m_type]; + MZ_CLEAR_ARR(total_syms); + TINFL_MEMSET(pLookUp, 0, sizeof(r->m_look_up[0])); + tinfl_clear_tree(r); for (i = 0; i < r->m_table_sizes[r->m_type]; ++i) - total_syms[pTable->m_code_size[i]]++; + total_syms[pCode_size[i]]++; used_syms = 0, total = 0; next_code[0] = next_code[1] = 0; for (i = 1; i <= 15; ++i) @@ -3436,7 +3576,7 @@ tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_nex } for (tree_next = -1, sym_index = 0; sym_index < r->m_table_sizes[r->m_type]; ++sym_index) { - mz_uint rev_code = 0, l, cur_code, code_size = pTable->m_code_size[sym_index]; + mz_uint rev_code = 0, l, cur_code, code_size = pCode_size[sym_index]; if (!code_size) continue; cur_code = next_code[code_size]++; @@ -3447,14 +3587,14 @@ tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_nex mz_int16 k = (mz_int16)((code_size << 9) | sym_index); while (rev_code < TINFL_FAST_LOOKUP_SIZE) { - pTable->m_look_up[rev_code] = k; + pLookUp[rev_code] = k; rev_code += (1 << code_size); } continue; } - if (0 == (tree_cur = pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)])) + if (0 == (tree_cur = pLookUp[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)])) { - pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)] = (mz_int16)tree_next; + pLookUp[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)] = (mz_int16)tree_next; tree_cur = tree_next; tree_next -= 2; } @@ -3462,24 +3602,24 @@ tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_nex for (j = code_size; j > (TINFL_FAST_LOOKUP_BITS + 1); j--) { tree_cur -= ((rev_code >>= 1) & 1); - if (!pTable->m_tree[-tree_cur - 1]) + if (!pTree[-tree_cur - 1]) { - pTable->m_tree[-tree_cur - 1] = (mz_int16)tree_next; + pTree[-tree_cur - 1] = (mz_int16)tree_next; tree_cur = tree_next; tree_next -= 2; } else - tree_cur = pTable->m_tree[-tree_cur - 1]; + tree_cur = pTree[-tree_cur - 1]; } tree_cur -= ((rev_code >>= 1) & 1); - pTable->m_tree[-tree_cur - 1] = (mz_int16)sym_index; + pTree[-tree_cur - 1] = (mz_int16)sym_index; } if (r->m_type == 2) { for (counter = 0; counter < (r->m_table_sizes[0] + r->m_table_sizes[1]);) { mz_uint s; - TINFL_HUFF_DECODE(16, dist, &r->m_tables[2]); + TINFL_HUFF_DECODE(16, dist, r->m_look_up[2], r->m_tree_2); if (dist < 16) { r->m_len_codes[counter++] = (mz_uint8)dist; @@ -3499,8 +3639,8 @@ tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_nex { TINFL_CR_RETURN_FOREVER(21, TINFL_STATUS_FAILED); } - TINFL_MEMCPY(r->m_tables[0].m_code_size, r->m_len_codes, r->m_table_sizes[0]); - TINFL_MEMCPY(r->m_tables[1].m_code_size, r->m_len_codes + r->m_table_sizes[0], r->m_table_sizes[1]); + TINFL_MEMCPY(r->m_code_size_0, r->m_len_codes, r->m_table_sizes[0]); + TINFL_MEMCPY(r->m_code_size_1, r->m_len_codes + r->m_table_sizes[0], r->m_table_sizes[1]); } } for (;;) @@ -3510,7 +3650,7 @@ tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_nex { if (((pIn_buf_end - pIn_buf_cur) < 4) || ((pOut_buf_end - pOut_buf_cur) < 2)) { - TINFL_HUFF_DECODE(23, counter, &r->m_tables[0]); + TINFL_HUFF_DECODE(23, counter, r->m_look_up[0], r->m_tree_0); if (counter >= 256) break; while (pOut_buf_cur >= pOut_buf_end) @@ -3538,14 +3678,14 @@ tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_nex num_bits += 16; } #endif - if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) + if ((sym2 = r->m_look_up[0][bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) code_len = sym2 >> 9; else { code_len = TINFL_FAST_LOOKUP_BITS; do { - sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; + sym2 = r->m_tree_0[~sym2 + ((bit_buf >> code_len++) & 1)]; } while (sym2 < 0); } counter = sym2; @@ -3562,14 +3702,14 @@ tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_nex num_bits += 16; } #endif - if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) + if ((sym2 = r->m_look_up[0][bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) code_len = sym2 >> 9; else { code_len = TINFL_FAST_LOOKUP_BITS; do { - sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; + sym2 = r->m_tree_0[~sym2 + ((bit_buf >> code_len++) & 1)]; } while (sym2 < 0); } bit_buf >>= code_len; @@ -3598,7 +3738,7 @@ tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_nex counter += extra_bits; } - TINFL_HUFF_DECODE(26, dist, &r->m_tables[1]); + TINFL_HUFF_DECODE(26, dist, r->m_look_up[1], r->m_tree_1); num_extra = s_dist_extra[dist]; dist = s_dist_base[dist]; if (num_extra) @@ -3609,7 +3749,7 @@ tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_nex } dist_from_out_buf_start = pOut_buf_cur - pOut_buf_start; - if ((dist > dist_from_out_buf_start) && (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) + if ((dist == 0 || dist > dist_from_out_buf_start || dist_from_out_buf_start == 0) && (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) { TINFL_CR_RETURN_FOREVER(37, TINFL_STATUS_FAILED); } @@ -3683,7 +3823,7 @@ tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_nex --pIn_buf_cur; num_bits -= 8; } - bit_buf &= (tinfl_bit_buf_t)((((mz_uint64)1) << num_bits) - (mz_uint64)1); + bit_buf &= ~(~(tinfl_bit_buf_t)0 << num_bits); MZ_ASSERT(!num_bits); /* if this assert fires then we've read beyond the end of non-deflate/zlib streams with following data (such as gzip streams). */ if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) @@ -3715,7 +3855,7 @@ common_exit: } } r->m_num_bits = num_bits; - r->m_bit_buf = bit_buf & (tinfl_bit_buf_t)((((mz_uint64)1) << num_bits) - (mz_uint64)1); + r->m_bit_buf = bit_buf & ~(~(tinfl_bit_buf_t)0 << num_bits); r->m_dist = dist; r->m_counter = counter; r->m_num_extra = num_extra; @@ -3810,6 +3950,7 @@ int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, size_t in_buf_ofs = 0, dict_ofs = 0; if (!pDict) return TINFL_STATUS_FAILED; + memset(pDict,0,TINFL_LZ_DICT_SIZE); tinfl_init(&decomp); for (;;) { @@ -3832,7 +3973,7 @@ int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, } #ifndef MINIZ_NO_MALLOC -tinfl_decompressor *tinfl_decompressor_alloc( void ) +tinfl_decompressor *tinfl_decompressor_alloc(void) { tinfl_decompressor *pDecomp = (tinfl_decompressor *)MZ_MALLOC(sizeof(tinfl_decompressor)); if (pDecomp) @@ -3850,4 +3991,5 @@ void tinfl_decompressor_free(tinfl_decompressor *pDecomp) } #endif -#endif // MINIZ_HEADER_FILE_ONLY +#endif /*#ifndef MINIZ_NO_INFLATE_APIS*/ +#endif /*#ifndef MINIZ_HEADER_FILE_ONLY*/ diff --git a/public/wscript b/public/wscript index d2ad2ea8..cc20f0c5 100644 --- a/public/wscript +++ b/public/wscript @@ -16,15 +16,9 @@ def configure(conf): return def build(bld): - source = bld.path.ant_glob(['*.c']) - libs = [] - includes = [ '.', '../common', '../engine' ] - - bld.stlib( - source = source, + bld(name = 'sdk_includes', export_includes = '. ../common ../pm_shared ../engine') + bld.stlib(source = bld.path.ant_glob('*.c'), target = 'public', features = 'c', - includes = includes, - use = libs, - subsystem = bld.env.MSVC_SUBSYSTEM - ) + use = 'sdk_includes', + subsystem = bld.env.MSVC_SUBSYSTEM) diff --git a/public/xash3d_mathlib.h b/public/xash3d_mathlib.h index 985e5d1f..363090d2 100644 --- a/public/xash3d_mathlib.h +++ b/public/xash3d_mathlib.h @@ -34,18 +34,18 @@ GNU General Public License for more details. #define ROLL 2 #ifndef M_PI -#define M_PI (float)3.14159265358979323846 +#define M_PI (double)3.14159265358979323846 #endif #ifndef M_PI2 -#define M_PI2 ((float)(M_PI * 2)) +#define M_PI2 ((double)(M_PI * 2)) #endif #define M_PI_F ((float)(M_PI)) #define M_PI2_F ((float)(M_PI2)) -#define RAD2DEG( x ) ((float)(x) * (float)(180.f / M_PI_F)) -#define DEG2RAD( x ) ((float)(x) * (float)(M_PI_F / 180.f)) +#define RAD2DEG( x ) ((double)(x) * (double)(180.0 / M_PI)) +#define DEG2RAD( x ) ((double)(x) * (double)(M_PI / 180.0)) #define NUMVERTEXNORMALS 162 diff --git a/ref_gl/exports.txt b/ref/gl/exports.txt similarity index 100% rename from ref_gl/exports.txt rename to ref/gl/exports.txt diff --git a/ref_gl/gl_alias.c b/ref/gl/gl_alias.c similarity index 99% rename from ref_gl/gl_alias.c rename to ref/gl/gl_alias.c index 77b27b77..d9766755 100644 --- a/ref_gl/gl_alias.c +++ b/ref/gl/gl_alias.c @@ -486,7 +486,7 @@ void *Mod_LoadSingleSkin( daliasskintype_t *pskintype, int skinnum, int size ) Q_snprintf( name, sizeof( name ), "%s:frame%i", loadmodel->name, skinnum ); Q_snprintf( lumaname, sizeof( lumaname ), "%s:luma%i", loadmodel->name, skinnum ); Q_snprintf( checkname, sizeof( checkname ), "%s_%i.tga", loadmodel->name, skinnum ); - if( !gEngfuncs.FS_FileExists( checkname, false ) || ( pic = gEngfuncs.FS_LoadImage( checkname, NULL, 0 )) == NULL ) + if( !gEngfuncs.fsapi->FileExists( checkname, false ) || ( pic = gEngfuncs.FS_LoadImage( checkname, NULL, 0 )) == NULL ) pic = Mod_CreateSkinData( loadmodel, (byte *)(pskintype + 1), m_pAliasHeader->skinwidth, m_pAliasHeader->skinheight ); m_pAliasHeader->gl_texturenum[skinnum][0] = diff --git a/ref_gl/gl_backend.c b/ref/gl/gl_backend.c similarity index 99% rename from ref_gl/gl_backend.c rename to ref/gl/gl_backend.c index 88881f9f..da530405 100644 --- a/ref_gl/gl_backend.c +++ b/ref/gl/gl_backend.c @@ -478,7 +478,7 @@ qboolean VID_ScreenShot( const char *filename, int shot_type ) case VID_SCREENSHOT: break; case VID_SNAPSHOT: - gEngfuncs.FS_AllowDirectPaths( true ); + gEngfuncs.fsapi->AllowDirectPaths( true ); break; case VID_LEVELSHOT: flags |= IMAGE_RESAMPLE; @@ -509,7 +509,7 @@ qboolean VID_ScreenShot( const char *filename, int shot_type ) // write image result = gEngfuncs.FS_SaveImage( filename, r_shot ); - gEngfuncs.FS_AllowDirectPaths( false ); // always reset after store screenshot + gEngfuncs.fsapi->AllowDirectPaths( false ); // always reset after store screenshot gEngfuncs.FS_FreeImage( r_shot ); return result; diff --git a/ref_gl/gl_beams.c b/ref/gl/gl_beams.c similarity index 99% rename from ref_gl/gl_beams.c rename to ref/gl/gl_beams.c index 215eb890..7886297f 100644 --- a/ref_gl/gl_beams.c +++ b/ref/gl/gl_beams.c @@ -19,7 +19,6 @@ GNU General Public License for more details. #include "entity_types.h" #include "triangleapi.h" #include "customentity.h" -#include "cl_tent.h" #include "pm_local.h" #include "studio.h" @@ -1197,7 +1196,7 @@ void R_BeamDrawCustomEntity( cl_entity_t *ent ) g = ent->curstate.rendercolor.g / 255.0f; b = ent->curstate.rendercolor.b / 255.0f; - R_BeamSetup( &beam, ent->origin, ent->angles, ent->curstate.modelindex, 0, ent->curstate.scale, amp, blend, ent->curstate.animtime ); + R_BeamSetup( &beam, ent->origin, ent->curstate.angles, ent->curstate.modelindex, 0, ent->curstate.scale, amp, blend, ent->curstate.animtime ); R_BeamSetAttributes( &beam, r, g, b, ent->curstate.framerate, ent->curstate.frame ); beam.pFollowModel = NULL; diff --git a/ref_gl/gl_context.c b/ref/gl/gl_context.c similarity index 100% rename from ref_gl/gl_context.c rename to ref/gl/gl_context.c diff --git a/ref_gl/gl_cull.c b/ref/gl/gl_cull.c similarity index 100% rename from ref_gl/gl_cull.c rename to ref/gl/gl_cull.c diff --git a/ref_gl/gl_dbghulls.c b/ref/gl/gl_dbghulls.c similarity index 100% rename from ref_gl/gl_dbghulls.c rename to ref/gl/gl_dbghulls.c diff --git a/ref_gl/gl_decals.c b/ref/gl/gl_decals.c similarity index 99% rename from ref_gl/gl_decals.c rename to ref/gl/gl_decals.c index c9e70983..2b3f82b8 100644 --- a/ref_gl/gl_decals.c +++ b/ref/gl/gl_decals.c @@ -14,7 +14,6 @@ GNU General Public License for more details. */ #include "gl_local.h" -#include "cl_tent.h" #define DECAL_OVERLAP_DISTANCE 2 #define DECAL_DISTANCE 4 // too big values produce more clipped polygons diff --git a/ref_gl/gl_draw.c b/ref/gl/gl_draw.c similarity index 100% rename from ref_gl/gl_draw.c rename to ref/gl/gl_draw.c diff --git a/ref_gl/gl_export.h b/ref/gl/gl_export.h similarity index 100% rename from ref_gl/gl_export.h rename to ref/gl/gl_export.h diff --git a/ref_gl/gl_frustum.c b/ref/gl/gl_frustum.c similarity index 100% rename from ref_gl/gl_frustum.c rename to ref/gl/gl_frustum.c diff --git a/ref_gl/gl_frustum.h b/ref/gl/gl_frustum.h similarity index 100% rename from ref_gl/gl_frustum.h rename to ref/gl/gl_frustum.h diff --git a/ref_gl/gl_image.c b/ref/gl/gl_image.c similarity index 99% rename from ref_gl/gl_image.c rename to ref/gl/gl_image.c index 1b5ed033..658b0240 100644 --- a/ref_gl/gl_image.c +++ b/ref/gl/gl_image.c @@ -1908,6 +1908,11 @@ void GL_ProcessTexture( int texnum, float gamma, int topColor, int bottomColor ) // all the operations makes over the image copy not an original pic = gEngfuncs.FS_CopyImage( image->original ); + + // we need to expand image into RGBA buffer + if( pic->type == PF_INDEXED_24 || pic->type == PF_INDEXED_32 ) + flags |= IMAGE_FORCE_RGBA; + gEngfuncs.Image_Process( &pic, topColor, bottomColor, flags, 0.0f ); GL_UploadTexture( image, pic ); diff --git a/ref_gl/gl_local.h b/ref/gl/gl_local.h similarity index 100% rename from ref_gl/gl_local.h rename to ref/gl/gl_local.h diff --git a/ref_gl/gl_opengl.c b/ref/gl/gl_opengl.c similarity index 100% rename from ref_gl/gl_opengl.c rename to ref/gl/gl_opengl.c diff --git a/ref_gl/gl_rlight.c b/ref/gl/gl_rlight.c similarity index 100% rename from ref_gl/gl_rlight.c rename to ref/gl/gl_rlight.c diff --git a/ref_gl/gl_rmain.c b/ref/gl/gl_rmain.c similarity index 100% rename from ref_gl/gl_rmain.c rename to ref/gl/gl_rmain.c diff --git a/ref_gl/gl_rmath.c b/ref/gl/gl_rmath.c similarity index 100% rename from ref_gl/gl_rmath.c rename to ref/gl/gl_rmath.c diff --git a/ref_gl/gl_rmisc.c b/ref/gl/gl_rmisc.c similarity index 98% rename from ref_gl/gl_rmisc.c rename to ref/gl/gl_rmisc.c index c402e337..230e66b9 100644 --- a/ref_gl/gl_rmisc.c +++ b/ref/gl/gl_rmisc.c @@ -29,7 +29,7 @@ static void R_ParseDetailTextures( const char *filename ) texture_t *tex; int i; - afile = gEngfuncs.COM_LoadFile( filename, NULL, false ); + afile = gEngfuncs.fsapi->LoadFile( filename, NULL, false ); if( !afile ) return; pfile = (char *)afile; diff --git a/ref_gl/gl_rpart.c b/ref/gl/gl_rpart.c similarity index 99% rename from ref_gl/gl_rpart.c rename to ref/gl/gl_rpart.c index fb4f6cac..00954af1 100644 --- a/ref_gl/gl_rpart.c +++ b/ref/gl/gl_rpart.c @@ -19,7 +19,6 @@ GNU General Public License for more details. #include "entity_types.h" #include "triangleapi.h" #include "pm_local.h" -#include "cl_tent.h" #include "studio.h" static float gTracerSize[11] = { 1.5f, 0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f }; diff --git a/ref_gl/gl_rsurf.c b/ref/gl/gl_rsurf.c similarity index 100% rename from ref_gl/gl_rsurf.c rename to ref/gl/gl_rsurf.c diff --git a/ref_gl/gl_sprite.c b/ref/gl/gl_sprite.c similarity index 99% rename from ref_gl/gl_sprite.c rename to ref/gl/gl_sprite.c index ceffbb5a..006fe135 100644 --- a/ref_gl/gl_sprite.c +++ b/ref/gl/gl_sprite.c @@ -18,7 +18,6 @@ GNU General Public License for more details. #include "sprite.h" #include "studio.h" #include "entity_types.h" -#include "cl_tent.h" // it's a Valve default value for LoadMapSprite (probably must be power of two) #define MAPSPRITE_SIZE 128 diff --git a/ref_gl/gl_studio.c b/ref/gl/gl_studio.c similarity index 99% rename from ref_gl/gl_studio.c rename to ref/gl/gl_studio.c index bb52c262..910dd07b 100644 --- a/ref_gl/gl_studio.c +++ b/ref/gl/gl_studio.c @@ -20,8 +20,6 @@ GNU General Public License for more details. #include "triangleapi.h" #include "studio.h" #include "pm_local.h" -#include "cl_tent.h" -//#include "client.h" #include "pmtrace.h" #define EVENT_CLIENT 5000 // less than this value it's a server-side studio events @@ -2699,7 +2697,7 @@ static model_t *R_StudioSetupPlayerModel( int index ) Q_snprintf( state->modelname, sizeof( state->modelname ), "models/player/%s/%s.mdl", info->model, info->model ); - if( gEngfuncs.FS_FileExists( state->modelname, false )) + if( gEngfuncs.fsapi->FileExists( state->modelname, false )) state->model = gEngfuncs.Mod_ForName( state->modelname, false, true ); else state->model = NULL; diff --git a/ref_gl/gl_triapi.c b/ref/gl/gl_triapi.c similarity index 100% rename from ref_gl/gl_triapi.c rename to ref/gl/gl_triapi.c diff --git a/ref_gl/gl_vgui.c b/ref/gl/gl_vgui.c similarity index 100% rename from ref_gl/gl_vgui.c rename to ref/gl/gl_vgui.c diff --git a/ref_gl/gl_warp.c b/ref/gl/gl_warp.c similarity index 95% rename from ref_gl/gl_warp.c rename to ref/gl/gl_warp.c index 32359d89..3144324d 100644 --- a/ref_gl/gl_warp.c +++ b/ref/gl/gl_warp.c @@ -60,11 +60,7 @@ float r_turbsin[] = #include "warpsin.h" }; -#define SKYBOX_MISSED 0 -#define SKYBOX_HLSTYLE 1 -#define SKYBOX_Q1STYLE 2 - -static int CheckSkybox( const char *name ) +static qboolean CheckSkybox( const char *name, char out[6][MAX_STRING] ) { const char *skybox_ext[3] = { "dds", "tga", "bmp" }; int i, j, num_checked_sides; @@ -73,32 +69,40 @@ static int CheckSkybox( const char *name ) // search for skybox images for( i = 0; i < 3; i++ ) { + // check HL-style skyboxes num_checked_sides = 0; for( j = 0; j < 6; j++ ) { // build side name sidename = va( "%s%s.%s", name, r_skyBoxSuffix[j], skybox_ext[i] ); - if( gEngfuncs.FS_FileExists( sidename, false )) + if( gEngfuncs.fsapi->FileExists( sidename, false )) + { + Q_strncpy( out[j], sidename, sizeof( out[j] )); num_checked_sides++; - + } } if( num_checked_sides == 6 ) - return SKYBOX_HLSTYLE; // image exists + return true; // image exists + // check Q1-style skyboxes + num_checked_sides = 0; for( j = 0; j < 6; j++ ) { // build side name sidename = va( "%s_%s.%s", name, r_skyBoxSuffix[j], skybox_ext[i] ); - if( gEngfuncs.FS_FileExists( sidename, false )) + if( gEngfuncs.fsapi->FileExists( sidename, false )) + { + Q_strncpy( out[j], sidename, sizeof( out[j] )); num_checked_sides++; + } } if( num_checked_sides == 6 ) - return SKYBOX_Q1STYLE; // images exists + return true; // images exists } - return SKYBOX_MISSED; + return false; } void DrawSkyPolygon( int nump, vec3_t vecs ) @@ -411,8 +415,9 @@ R_SetupSky void R_SetupSky( const char *skyboxname ) { char loadname[MAX_STRING]; - char sidename[MAX_STRING]; - int i, result, len; + char sidenames[6][MAX_STRING]; + int i, len; + qboolean result; if( !COM_CheckString( skyboxname )) { @@ -428,10 +433,10 @@ void R_SetupSky( const char *skyboxname ) if( loadname[len - 1] == '_' ) loadname[len - 1] = '\0'; - result = CheckSkybox( loadname ); + result = CheckSkybox( loadname, sidenames ); // to prevent infinite recursion if default skybox was missed - if( result == SKYBOX_MISSED && Q_stricmp( loadname, DEFAULT_SKYBOX_PATH )) + if( !result && Q_stricmp( loadname, DEFAULT_SKYBOX_PATH )) { gEngfuncs.Con_Reportf( S_WARN "missed or incomplete skybox '%s'\n", skyboxname ); R_SetupSky( "desert" ); // force to default @@ -444,12 +449,11 @@ void R_SetupSky( const char *skyboxname ) for( i = 0; i < 6; i++ ) { - if( result == SKYBOX_HLSTYLE ) - Q_snprintf( sidename, sizeof( sidename ), "%s%s", loadname, r_skyBoxSuffix[i] ); - else Q_snprintf( sidename, sizeof( sidename ), "%s_%s", loadname, r_skyBoxSuffix[i] ); + tr.skyboxTextures[i] = GL_LoadTexture( sidenames[i], NULL, 0, TF_CLAMP|TF_SKY ); + + if( !tr.skyboxTextures[i] ) + break; - tr.skyboxTextures[i] = GL_LoadTexture( sidename, NULL, 0, TF_CLAMP|TF_SKY ); - if( !tr.skyboxTextures[i] ) break; gEngfuncs.Con_DPrintf( "%s%s%s", skyboxname, r_skyBoxSuffix[i], i != 5 ? ", " : ". " ); } diff --git a/ref/gl/wscript b/ref/gl/wscript new file mode 100644 index 00000000..b8678e46 --- /dev/null +++ b/ref/gl/wscript @@ -0,0 +1,74 @@ +#! /usr/bin/env python +# encoding: utf-8 +# mittorn, 2018 + +from waflib import Logs +import os + +top = '.' + +def options(opt): + grp = opt.add_option_group('ref_gl options') + + grp.add_option('--enable-static-gl', action='store_true', dest='GL_STATIC', default=False, + help = 'enable direct linking to opengl [default: %default]') + + # stub + return + +def configure(conf): + # check for dedicated server build + if conf.options.DEDICATED: + return + + conf.define_cond('SUPPORT_BSP2_FORMAT', conf.options.SUPPORT_BSP2_FORMAT) + + conf.env.GL_STATIC = conf.options.GL_STATIC + if conf.env.GL_STATIC: + conf.check(lib='GL') + + conf.define('REF_DLL', 1) + if conf.env.DEST_OS2 == 'android': + conf.check_cc(lib='log') + +def build(bld): + libs = [ 'engine_includes', 'public', 'M' ] + + source = bld.path.ant_glob(['*.c']) + includes = '.' + + targets = { + 'ref_gl': { + 'enable': bld.env.GL, + 'libs': ['GL'] if bld.env.GL_STATIC else [], + 'defines': ['XASH_GL_STATIC'] if bld.env.GL_STATIC else [], + }, + 'ref_gles1': { + 'enable': bld.env.NANOGL, + 'libs': ['DL', 'nanogl', 'LOG'], + 'defines': ['XASH_NANOGL'], + }, + 'ref_gles2': { + 'enable': bld.env.GLWES, + 'libs': ['DL', 'gl-wes-v2', 'LOG'], + 'defines': ['XASH_WES'], + }, + 'ref_gl4es': { + 'enable': bld.env.GL4ES, + 'libs': ['DL', 'gl4es', 'LOG'], + 'defines': ['XASH_GL_STATIC', 'XASH_GL4ES'], + }, + } + + for k,v in targets.items(): + if not v['enable']: + continue + + bld.shlib(source = source, + target = k, + features = 'c', + includes = includes, + use = libs + v['libs'], + defines = v['defines'], + install_path = bld.env.LIBDIR, + subsystem = bld.env.MSVC_SUBSYSTEM) diff --git a/ref_soft/adivtab.h b/ref/soft/adivtab.h similarity index 100% rename from ref_soft/adivtab.h rename to ref/soft/adivtab.h diff --git a/ref_soft/r_aclip.c b/ref/soft/r_aclip.c similarity index 100% rename from ref_soft/r_aclip.c rename to ref/soft/r_aclip.c diff --git a/ref_soft/r_beams.c b/ref/soft/r_beams.c similarity index 99% rename from ref_soft/r_beams.c rename to ref/soft/r_beams.c index 0e69868d..49142cb2 100644 --- a/ref_soft/r_beams.c +++ b/ref/soft/r_beams.c @@ -19,9 +19,8 @@ GNU General Public License for more details. #include "entity_types.h" #include "triangleapi.h" #include "customentity.h" -#include "cl_tent.h" #include "pm_local.h" - +#include "triangleapi.h" #include "studio.h" #define NOISE_DIVISIONS 64 // don't touch - many tripmines cause the crash when it equal 128 @@ -1210,7 +1209,7 @@ void R_BeamDrawCustomEntity( cl_entity_t *ent ) g = ent->curstate.rendercolor.g / 255.0f; b = ent->curstate.rendercolor.b / 255.0f; - R_BeamSetup( &beam, ent->origin, ent->angles, ent->curstate.modelindex, 0, ent->curstate.scale, amp, blend, ent->curstate.animtime ); + R_BeamSetup( &beam, ent->origin, ent->curstate.angles, ent->curstate.modelindex, 0, ent->curstate.scale, amp, blend, ent->curstate.animtime ); R_BeamSetAttributes( &beam, r, g, b, ent->curstate.framerate, ent->curstate.frame ); beam.pFollowModel = NULL; diff --git a/ref_soft/r_bsp.c b/ref/soft/r_bsp.c similarity index 100% rename from ref_soft/r_bsp.c rename to ref/soft/r_bsp.c diff --git a/ref_soft/r_context.c b/ref/soft/r_context.c similarity index 100% rename from ref_soft/r_context.c rename to ref/soft/r_context.c diff --git a/ref_soft/r_decals.c b/ref/soft/r_decals.c similarity index 99% rename from ref_soft/r_decals.c rename to ref/soft/r_decals.c index 49fa6111..e3d4805a 100644 --- a/ref_soft/r_decals.c +++ b/ref/soft/r_decals.c @@ -14,7 +14,6 @@ GNU General Public License for more details. */ #include "r_local.h" -//#include "cl_tent.h" #define DECAL_OVERLAP_DISTANCE 2 #define DECAL_DISTANCE 4 // too big values produce more clipped polygons diff --git a/ref_soft/r_draw.c b/ref/soft/r_draw.c similarity index 100% rename from ref_soft/r_draw.c rename to ref/soft/r_draw.c diff --git a/ref_soft/r_edge.c b/ref/soft/r_edge.c similarity index 100% rename from ref_soft/r_edge.c rename to ref/soft/r_edge.c diff --git a/ref_soft/r_glblit.c b/ref/soft/r_glblit.c similarity index 99% rename from ref_soft/r_glblit.c rename to ref/soft/r_glblit.c index 6f73b7f2..99d609b1 100644 --- a/ref_soft/r_glblit.c +++ b/ref/soft/r_glblit.c @@ -1,6 +1,6 @@ #include "r_local.h" #define APIENTRY_LINKAGE static -#include "../ref_gl/gl_export.h" +#include "../gl/gl_export.h" struct swblit_s { diff --git a/ref_soft/r_image.c b/ref/soft/r_image.c similarity index 99% rename from ref_soft/r_image.c rename to ref/soft/r_image.c index a374d401..247e2e1e 100644 --- a/ref_soft/r_image.c +++ b/ref/soft/r_image.c @@ -1186,6 +1186,11 @@ void GAME_EXPORT GL_ProcessTexture( int texnum, float gamma, int topColor, int b // all the operations makes over the image copy not an original pic = gEngfuncs.FS_CopyImage( image->original ); + + // we need to expand image into RGBA buffer + if( pic->type == PF_INDEXED_24 || pic->type == PF_INDEXED_32 ) + flags |= IMAGE_FORCE_RGBA; + gEngfuncs.Image_Process( &pic, topColor, bottomColor, flags, 0.0f ); GL_UploadTexture( image, pic ); diff --git a/ref_soft/r_light.c b/ref/soft/r_light.c similarity index 100% rename from ref_soft/r_light.c rename to ref/soft/r_light.c diff --git a/ref_soft/r_local.h b/ref/soft/r_local.h similarity index 99% rename from ref_soft/r_local.h rename to ref/soft/r_local.h index 4546544a..3ee559cc 100644 --- a/ref_soft/r_local.h +++ b/ref/soft/r_local.h @@ -670,6 +670,7 @@ void TriEnd( void ); void TriTexCoord2f( float u, float v ); void TriVertex3fv( const float *v ); void TriVertex3f( float x, float y, float z ); +void TriColor4f( float r, float g, float b, float a ); void _TriColor4f( float r, float g, float b, float a ); void TriColor4ub( byte r, byte g, byte b, byte a ); void _TriColor4ub( byte r, byte g, byte b, byte a ); diff --git a/ref_soft/r_main.c b/ref/soft/r_main.c similarity index 100% rename from ref_soft/r_main.c rename to ref/soft/r_main.c diff --git a/ref_soft/r_math.c b/ref/soft/r_math.c similarity index 100% rename from ref_soft/r_math.c rename to ref/soft/r_math.c diff --git a/ref_soft/r_misc.c b/ref/soft/r_misc.c similarity index 100% rename from ref_soft/r_misc.c rename to ref/soft/r_misc.c diff --git a/ref_soft/r_part.c b/ref/soft/r_part.c similarity index 99% rename from ref_soft/r_part.c rename to ref/soft/r_part.c index c71cb7bf..790b843e 100644 --- a/ref_soft/r_part.c +++ b/ref/soft/r_part.c @@ -19,7 +19,6 @@ GNU General Public License for more details. #include "entity_types.h" #include "triangleapi.h" #include "pm_local.h" -#include "cl_tent.h" #include "studio.h" static float gTracerSize[11] = { 1.5f, 0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f }; diff --git a/ref_soft/r_polyse.c b/ref/soft/r_polyse.c similarity index 100% rename from ref_soft/r_polyse.c rename to ref/soft/r_polyse.c diff --git a/ref_soft/r_rast.c b/ref/soft/r_rast.c similarity index 100% rename from ref_soft/r_rast.c rename to ref/soft/r_rast.c diff --git a/ref_soft/r_scan.c b/ref/soft/r_scan.c similarity index 100% rename from ref_soft/r_scan.c rename to ref/soft/r_scan.c diff --git a/ref_soft/r_sprite.c b/ref/soft/r_sprite.c similarity index 99% rename from ref_soft/r_sprite.c rename to ref/soft/r_sprite.c index adb063a3..5aa0f74d 100644 --- a/ref_soft/r_sprite.c +++ b/ref/soft/r_sprite.c @@ -18,7 +18,6 @@ GNU General Public License for more details. #include "sprite.h" #include "studio.h" #include "entity_types.h" -//#include "cl_tent.h" // it's a Valve default value for LoadMapSprite (probably must be power of two) #define MAPSPRITE_SIZE 128 diff --git a/ref_soft/r_studio.c b/ref/soft/r_studio.c similarity index 99% rename from ref_soft/r_studio.c rename to ref/soft/r_studio.c index 3dad15b6..aa413bae 100644 --- a/ref_soft/r_studio.c +++ b/ref/soft/r_studio.c @@ -20,8 +20,6 @@ GNU General Public License for more details. #include "triangleapi.h" #include "studio.h" #include "pm_local.h" -//#include "cl_tent.h" -//#include "client.h" #include "pmtrace.h" #define EVENT_CLIENT 5000 // less than this value it's a server-side studio events @@ -2462,7 +2460,7 @@ static model_t *R_StudioSetupPlayerModel( int index ) Q_snprintf( state->modelname, sizeof( state->modelname ), "models/player/%s/%s.mdl", info->model, info->model ); - if( gEngfuncs.FS_FileExists( state->modelname, false )) + if( gEngfuncs.fsapi->FileExists( state->modelname, false )) state->model = gEngfuncs.Mod_ForName( state->modelname, false, true ); else state->model = NULL; diff --git a/ref_soft/r_surf.c b/ref/soft/r_surf.c similarity index 100% rename from ref_soft/r_surf.c rename to ref/soft/r_surf.c diff --git a/ref_soft/r_trialias.c b/ref/soft/r_trialias.c similarity index 100% rename from ref_soft/r_trialias.c rename to ref/soft/r_trialias.c diff --git a/ref_soft/r_triapi.c b/ref/soft/r_triapi.c similarity index 100% rename from ref_soft/r_triapi.c rename to ref/soft/r_triapi.c diff --git a/ref_soft/r_vgui.c b/ref/soft/r_vgui.c similarity index 100% rename from ref_soft/r_vgui.c rename to ref/soft/r_vgui.c diff --git a/ref_soft/wscript b/ref/soft/wscript similarity index 65% rename from ref_soft/wscript rename to ref/soft/wscript index 93933140..e5cf7ca2 100644 --- a/ref_soft/wscript +++ b/ref/soft/wscript @@ -25,25 +25,11 @@ def build(bld): if bld.env.DEDICATED: return - libs = [ 'public', 'M' ] - - source = bld.path.ant_glob(['*.c']) - - includes = ['.', - '../engine', - '../engine/common', - '../engine/server', - '../engine/client', - '../public', - '../common', - '../pm_shared' ] - - bld.shlib( - source = source, + bld.shlib(source = bld.path.ant_glob(['*.c']), target = 'ref_soft', features = 'c', - includes = includes, - use = libs, + includes = '.', + use = 'engine_includes public M', install_path = bld.env.LIBDIR, subsystem = bld.env.MSVC_SUBSYSTEM ) diff --git a/ref_gl/gl4es b/ref_gl/gl4es deleted file mode 160000 index 36a5cd54..00000000 --- a/ref_gl/gl4es +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 36a5cd54e7ff6cad15ce6d1924c63bf5cf64c1f1 diff --git a/ref_gl/wscript b/ref_gl/wscript deleted file mode 100644 index af60caea..00000000 --- a/ref_gl/wscript +++ /dev/null @@ -1,127 +0,0 @@ -#! /usr/bin/env python -# encoding: utf-8 -# mittorn, 2018 - -from waflib import Logs -import os - -top = '.' - -def options(opt): - grp = opt.add_option_group('ref_gl options') - - grp.add_option('--enable-static-gl', action='store_true', dest='GL_STATIC', default=False, - help = 'enable direct linking to opengl [default: %default]') - - grp.add_option('--enable-gles1', action='store_true', dest='NANOGL', default=False, - help = 'enable gles1 renderer [default: %default]') - - grp.add_option('--enable-gles2', action='store_true', dest='GLWES', default=False, - help = 'enable gles2 renderer [default: %default]') - - grp.add_option('--enable-gl4es', action='store_true', dest='GL4ES', default=False, - help = 'enable gles2 renderer [default: %default]') - - grp.add_option('--disable-gl', action='store_false', dest='GL', default=True, - help = 'disable opengl [default: %default]') - - # stub - return - -def configure(conf): - # check for dedicated server build - if conf.options.DEDICATED: - return - - conf.define_cond('SUPPORT_BSP2_FORMAT', conf.options.SUPPORT_BSP2_FORMAT) - - conf.env.NANOGL = conf.options.NANOGL - conf.env.GLWES = conf.options.GLWES - conf.env.GL4ES = conf.options.GL4ES - conf.env.GL = conf.options.GL - - if conf.env.NANOGL: - conf.add_subproject('nanogl') - if conf.env.GLWES: - conf.add_subproject('gl-wes-v2') - - conf.env.GL_STATIC = conf.options.GL_STATIC - if conf.env.GL_STATIC: - conf.check(lib='GL') - - conf.define('REF_DLL', 1) - if conf.env.DEST_OS2 == 'android': - conf.check_cc(lib='log') - -def build(bld): - libs = [ 'public', 'M' ] - - source = bld.path.ant_glob(['*.c']) - - includes = ['.', - '../engine', - '../engine/common', - '../engine/server', - '../engine/client', - '../public', - '../common', - '../pm_shared' ] - - if bld.env.GL: - bld.shlib( - source = source, - target = 'ref_gl', - features = 'c', - includes = includes, - use = libs + (['GL'] if bld.env.GL_STATIC else []), - defines = ['XASH_GL_STATIC'] if bld.env.GL_STATIC else [], - install_path = bld.env.LIBDIR, - subsystem = bld.env.MSVC_SUBSYSTEM - ) - - if bld.env.NANOGL: - bld.add_subproject('nanogl') - bld.shlib( - source = source, - target = 'ref_gles1', - features = 'c', - includes = includes, - use = libs + ['DL', 'nanogl'], - defines = ['XASH_NANOGL'], - install_path = bld.env.LIBDIR, - subsystem = bld.env.MSVC_SUBSYSTEM) - - if bld.env.GLWES: - bld.add_subproject('gl-wes-v2') - bld.shlib( - source = source, - target = 'ref_gles2', - features = 'c', - includes = includes, - use = libs + ['DL', 'gl-wes-v2'], - defines = ['XASH_WES'], - install_path = bld.env.LIBDIR, - subsystem = bld.env.MSVC_SUBSYSTEM) - - if bld.env.GL4ES: - gl4es_srcdir = bld.path.find_node('gl4es/src') - - bld.stlib( - source = gl4es_srcdir.ant_glob(['gl/*.c', 'gl/*/*.c', 'glx/hardext.c']), - target = 'gl4es', - features = 'c', - includes = ['gl4es/src', 'gl4es/src/gl', 'gl4es/src/glx', 'gl4es/include'], - defines = ['NOX11', 'NO_GBM', 'NO_INIT_CONSTRUCTOR', 'DEFAULT_ES=2', 'NOEGL', 'EXTERNAL_GETPROCADDRESS=GL4ES_GetProcAddress', 'NO_LOADER', 'STATICLIB'], - cflags = ['-w', '-fvisibility=hidden', '-std=gnu99'], - use = libs, - subsystem = bld.env.MSVC_SUBSYSTEM) - - bld.shlib( - source = source, - target = 'ref_gl4es', - features = 'c', - includes = includes, - use = libs + ['DL', 'gl4es', 'LOG'], - defines = ['XASH_GL4ES', 'XASH_GL_STATIC'], - install_path = bld.env.LIBDIR, - subsystem = bld.env.MSVC_SUBSYSTEM) diff --git a/scripts/gha/build_linux.sh b/scripts/gha/build_linux.sh index c238e8d6..3ed86cae 100755 --- a/scripts/gha/build_linux.sh +++ b/scripts/gha/build_linux.sh @@ -6,6 +6,10 @@ APP=xash3d-fwgs APPDIR=$APP.AppDir APPIMAGE=$APP-$ARCH.AppImage +DS=xashds-linux +DSDIR=$DS-$ARCH +DSTARGZ=$DS-$ARCH.tar.gz + build_sdl2() { cd "$BUILDDIR"/SDL2_src || die @@ -62,12 +66,9 @@ build_appimage() ./waf install --destdir="$APPDIR" || die - # Generate extras.pak - python3 scripts/makepak.py xash-extras/ "$APPDIR/extras.pak" - cp SDL2_linux/lib/libSDL2-2.0.so.0 "$APPDIR/" if [ "$ARCH" = "i386" ]; then - cp vgui-dev/lib/vgui.so "$APPDIR/" + cp 3rdparty/vgui_support/vgui-dev/lib/vgui.so "$APPDIR/" fi cat > "$APPDIR"/AppRun << 'EOF' @@ -79,7 +80,7 @@ fi echo "Xash3D FWGS installed as AppImage." echo "Base directory is $XASH3D_BASEDIR. Set XASH3D_BASEDIR environment variable to override this" -export XASH3D_EXTRAS_PAK1="${APPDIR}"/extras.pak +export XASH3D_EXTRAS_PAK1="${APPDIR}"/valve/extras.pk3 ${DEBUGGER} "${APPDIR}"/xash3d "$@" exit $? EOF @@ -103,14 +104,23 @@ EOF ./appimagetool.AppImage "$APPDIR" "$APPIMAGE" } +build_dedicated_tarball() +{ + cd "$BUILDDIR" || die + + ./waf install --destdir=$DSDIR || die + + tar -czvf $DSTARGZ $DSDIR +} + mkdir -p artifacts/ rm -rf build # clean-up build directory build_engine dedicated -mv build/engine/xash artifacts/xashds-linux-$ARCH +build_dedicated_tarball +mv $DSTARGZ artifacts/ -rm -rf build build_sdl2 -build_engine full +build_engine full # don't rebuild some common parts twice build_appimage mv $APPIMAGE artifacts/ diff --git a/scripts/gha/build_motomagx.sh b/scripts/gha/build_motomagx.sh index c103bd42..fda2c356 100755 --- a/scripts/gha/build_motomagx.sh +++ b/scripts/gha/build_motomagx.sh @@ -23,14 +23,11 @@ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$LIBDIR1:$LIBDIR2:$LIBDIR3 export HOME=$mypath export SDL_QT_INVERT_ROTATION=1 export SWAP_PATH=$HOME/xash.swap -export XASH3D_EXTRAS_PAK1=$HOME/extras.pak cd $mypath sleep 1 exec $mypath/xash -dev $@ EOF -python3 scripts/makepak.py xash-extras/ Xash/extras.pak - mkdir -p artifacts/ 7z a -t7z artifacts/xash3d-fwgs-magx.7z -m0=lzma2 -mx=9 -mfb=64 -md=32m -ms=on -r Xash/ diff --git a/scripts/gha/build_win32.sh b/scripts/gha/build_win32.sh index e52f6ddf..2416ac2c 100755 --- a/scripts/gha/build_win32.sh +++ b/scripts/gha/build_win32.sh @@ -11,22 +11,19 @@ fi # NOTE: to build with other version use --msvc_version during configuration # NOTE: sometimes you may need to add WinSDK to %PATH% -./waf.bat configure -s "SDL2_VC" -T "debug" --enable-utils --prefix=`pwd` $AMD64 || die +./waf.bat configure -s "SDL2_VC" -T "release" --enable-utils --prefix=`pwd` $AMD64 || die ./waf.bat build -v || die ./waf.bat install || die if [ "$ARCH" = "i386" ]; then cp SDL2_VC/lib/x86/SDL2.dll . # Install SDL2 - cp vgui-dev/lib/win32_vc6/vgui.dll . + cp 3rdparty/vgui_support/vgui-dev/lib/win32_vc6/vgui.dll . elif [ "$ARCH" = "amd64" ]; then cp SDL2_VC/lib/x64/SDL2.dll . else die fi -mkdir valve/ -python3 scripts/makepak.py xash-extras/ valve/extras.pak - mkdir -p artifacts/ 7z a -t7z artifacts/xash3d-fwgs-win32-$ARCH.7z -m0=lzma2 -mx=9 -mfb=64 -md=32m -ms=on \ *.dll *.exe *.pdb activities.txt \ diff --git a/scripts/gha/deps_android.sh b/scripts/gha/deps_android.sh index 75e70979..59b5c549 100755 --- a/scripts/gha/deps_android.sh +++ b/scripts/gha/deps_android.sh @@ -15,9 +15,9 @@ popd echo "Download all needed tools and NDK" yes | sdk/tools/bin/sdkmanager --licenses > /dev/null 2>/dev/null # who even reads licenses? :) sdk/tools/bin/sdkmanager --install build-tools\;29.0.1 platform-tools platforms\;android-29 > /dev/null 2>/dev/null -wget https://dl.google.com/android/repository/android-ndk-r23b-linux.zip -qO ndk.zip > /dev/null || exit 1 +wget https://dl.google.com/android/repository/android-ndk-r25-linux.zip -qO ndk.zip > /dev/null || exit 1 unzip -q ndk.zip || exit 1 -mv android-ndk-r23b sdk/ndk-bundle || exit 1 +mv android-ndk-r25 sdk/ndk-bundle || exit 1 echo "Download Xash3D FWGS Android source" git clone --depth 1 https://github.com/FWGS/xash3d-android-project -b waf android || exit 1 diff --git a/scripts/waifulib/compiler_optimizations.py b/scripts/waifulib/compiler_optimizations.py index 15b70e7a..3d653770 100644 --- a/scripts/waifulib/compiler_optimizations.py +++ b/scripts/waifulib/compiler_optimizations.py @@ -51,7 +51,7 @@ LINKFLAGS = { CFLAGS = { 'common': { # disable thread-safe local static initialization for C++11 code, as it cause crashes on Windows XP - 'msvc': ['/D_USING_V110_SDK71_', '/FS', '/Zc:threadSafeInit-', '/MT'], + 'msvc': ['/D_USING_V110_SDK71_', '/FS', '/Zc:threadSafeInit-', '/MT', '/MP', '/Zc:__cplusplus'], 'clang': ['-g', '-gdwarf-2', '-fvisibility=hidden', '-fno-threadsafe-statics'], 'gcc': ['-g', '-fvisibility=hidden'], 'owcc': ['-fno-short-enum', '-ffloat-store', '-g3'] diff --git a/scripts/waifulib/xcompile.py b/scripts/waifulib/xcompile.py index 0abf53f1..981a081d 100644 --- a/scripts/waifulib/xcompile.py +++ b/scripts/waifulib/xcompile.py @@ -20,12 +20,12 @@ import os import sys ANDROID_NDK_ENVVARS = ['ANDROID_NDK_HOME', 'ANDROID_NDK'] -ANDROID_NDK_SUPPORTED = [10, 19, 20, 23] +ANDROID_NDK_SUPPORTED = [10, 19, 20, 23, 25] ANDROID_NDK_HARDFP_MAX = 11 # latest version that supports hardfp ANDROID_NDK_GCC_MAX = 17 # latest NDK that ships with GCC ANDROID_NDK_UNIFIED_SYSROOT_MIN = 15 ANDROID_NDK_SYSROOT_FLAG_MAX = 19 # latest NDK that need --sysroot flag -ANDROID_NDK_API_MIN = { 10: 3, 19: 16, 20: 16, 23: 16 } # minimal API level ndk revision supports +ANDROID_NDK_API_MIN = { 10: 3, 19: 16, 20: 16, 23: 16, 25: 19 } # minimal API level ndk revision supports ANDROID_STPCPY_API_MIN = 21 # stpcpy() introduced in SDK 21 ANDROID_64BIT_API_MIN = 21 # minimal API level that supports 64-bit targets @@ -349,13 +349,13 @@ class Android: return ldflags def options(opt): - android = opt.add_option_group('Android options') - android.add_option('--android', action='store', dest='ANDROID_OPTS', default=None, + xc = opt.add_option_group('Cross compile options') + xc.add_option('--android', action='store', dest='ANDROID_OPTS', default=None, help='enable building for android, format: --android=,,, example: --android=armeabi-v7a-hard,4.9,9') - - magx = opt.add_option_group('MotoMAGX options') - magx.add_option('--enable-magx', action = 'store_true', dest = 'MAGX', default = False, - help = 'enable targetting for MotoMAGX phones [default: %default]') + xc.add_option('--enable-magx', action='store_true', dest='MAGX', default=False, + help='enable building for Motorola MAGX [default: %default]') + xc.add_option('--enable-msvc-wine', action='store_true', dest='MSVC_WINE', default=False, + help='enable building with MSVC using Wine [default: %default]') def configure(conf): if conf.options.ANDROID_OPTS: @@ -389,17 +389,28 @@ def configure(conf): conf.msg('... C/C++ flags', ' '.join(android.cflags()).replace(android.ndk_home, '$NDK/')) conf.msg('... link flags', ' '.join(android.linkflags()).replace(android.ndk_home, '$NDK/')) conf.msg('... ld flags', ' '.join(android.ldflags()).replace(android.ndk_home, '$NDK/')) - - # conf.env.ANDROID_OPTS = android - conf.env.DEST_OS2 = 'android' elif conf.options.MAGX: # useless to change toolchain path, as toolchain meant to be placed in this path toolchain_path = '/opt/toolchains/motomagx/arm-eabi2/lib/' conf.env.INCLUDES_MAGX = [toolchain_path + i for i in ['ezx-z6/include', 'qt-2.3.8/include']] conf.env.LIBPATH_MAGX = [toolchain_path + i for i in ['ezx-z6/lib', 'qt-2.3.8/lib']] conf.env.LINKFLAGS_MAGX = ['-Wl,-rpath-link=' + i for i in conf.env.LIBPATH_MAGX] + elif conf.options.MSVC_WINE: + try: + toolchain_path = conf.environ['MSVC_WINE_PATH'] + except KeyError: + conf.fatal('Set MSVC_WINE_PATH environment variable to the MSVC toolchain root!') + + conf.environ['CC'] = conf.environ['CXX'] = os.path.join(toolchain_path, 'bin', conf.env.MSVC_TARGETS[0], 'cl') + conf.environ['LINK_CXX'] = os.path.join(toolchain_path, 'bin', conf.env.MSVC_TARGETS[0], 'link') + conf.environ['AR'] = os.path.join(toolchain_path, 'bin', conf.env.MSVC_TARGETS[0], 'lib') + conf.environ['WINRC'] = os.path.join(toolchain_path, 'bin', conf.env.MSVC_TARGETS[0], 'rc') + conf.env.DEST_OS = 'win32' + conf.env.DEST_CPU = conf.env.MSVC_TARGETS[0] + conf.env.COMPILER_CXX = conf.env.COMPILER_CC = 'msvc' conf.env.MAGX = conf.options.MAGX + conf.env.MSVC_WINE = conf.options.MSVC_WINE MACRO_TO_DESTOS = OrderedDict({ '__ANDROID__' : 'android' }) for k in c_config.MACRO_TO_DESTOS: MACRO_TO_DESTOS[k] = c_config.MACRO_TO_DESTOS[k] # ordering is important @@ -433,11 +444,17 @@ compiler_cxx_configure = getattr(compiler_cxx, 'configure') compiler_c_configure = getattr(compiler_c, 'configure') def patch_compiler_cxx_configure(conf): - compiler_cxx_configure(conf) + if not conf.env.MSVC_WINE: + compiler_cxx_configure(conf) + else: + conf.load('msvc', funs='no_autodetect') post_compiler_cxx_configure(conf) def patch_compiler_c_configure(conf): - compiler_c_configure(conf) + if not conf.env.MSVC_WINE: + compiler_c_configure(conf) + else: + conf.load('msvc', funs='no_autodetect') post_compiler_c_configure(conf) setattr(compiler_cxx, 'configure', patch_compiler_cxx_configure) diff --git a/scripts/waifulib/zip.py b/scripts/waifulib/zip.py new file mode 100644 index 00000000..6e903ba7 --- /dev/null +++ b/scripts/waifulib/zip.py @@ -0,0 +1,59 @@ +#! /usr/bin/env python +# encoding: utf-8 + +from waflib import TaskGen, Task, Logs, Utils +import zipfile + +class ziparchive(Task.Task): + color = 'YELLOW' + + def __str__(self): + tgt_str = ' '.join([a.path_from(a.ctx.launch_node()) for a in self.outputs]) + count = len(self.inputs) + return '%s: %d files -> %s' % (self.__class__.__name__, count, tgt_str) + + def keyword(self): + return 'Creating' + + def run(self): + outfile = self.outputs[0].path_from(self.outputs[0].ctx.launch_node()) + comp = zipfile.ZIP_STORED if self.compresslevel == 0 else zipfile.ZIP_DEFLATED + + with zipfile.ZipFile(outfile, mode='w', compression=comp) as zf: + for src in self.inputs: + infile = src.path_from(src.ctx.launch_node()) + arcfile = src.path_from(self.relative_to) + + Logs.debug('%s: %s <- %s as %s', self.__class__.__name__, outfile, infile, arcfile) + zf.write(infile, arcfile) + +@TaskGen.feature('zip') +def create_zip_archive(self): + compresslevel = getattr(self, 'compresslevel', 6) # 6 is zip default + if compresslevel < 0 or compresslevel > 9: + self.bld.fatal('Invalid compress level') + + files = getattr(self, 'files', None) + if not files: + self.bld.fatal('No files to archive') + + relative_to = getattr(self, 'relative_to', None) + if not relative_to: + self.bld.fatal('No relative directory supplied') + + self.path.get_bld().mkdir() + target = self.path.get_bld().make_node(self.name) + + tsk = self.create_task('ziparchive', files, target) + + setattr(tsk, 'compresslevel', compresslevel) + setattr(tsk, 'relative_to', relative_to) + + try: + inst_to = self.install_path + self.install_task = self.add_install_files( + install_to=inst_to, install_from=target, + chmod=Utils.O644, task=tsk) + + except AttributeError: + pass diff --git a/utils/mdldec/mdldec.c b/utils/mdldec/mdldec.c index d902d400..c137be93 100644 --- a/utils/mdldec/mdldec.c +++ b/utils/mdldec/mdldec.c @@ -164,9 +164,9 @@ static qboolean LoadMDL( const char *modelname ) if( destdir[0] != '\0' ) { - if( !IsFileExists( destdir ) ) + if( !MakeDirectory( destdir ) ) { - fprintf( stderr, "ERROR: Couldn't find directory %s\n", destdir ); + fprintf( stderr, "ERROR: Couldn't create directory %s\n", destdir ); return false; } @@ -175,7 +175,7 @@ static qboolean LoadMDL( const char *modelname ) else COM_ExtractFilePath( modelname, destdir ); - len -= 4; // path length without extension + len -= ( sizeof( ".mdl" ) - 1 ); // path length without extension if( !model_hdr->numtextures ) { diff --git a/utils/mdldec/utils.c b/utils/mdldec/utils.c index 739390d9..d43fe266 100644 --- a/utils/mdldec/utils.c +++ b/utils/mdldec/utils.c @@ -17,24 +17,39 @@ GNU General Public License for more details. #include #include #include +#include #include "xash3d_types.h" +#include "port.h" #include "crtlib.h" #include "utils.h" /* ============ -IsFileExists +MakeDirectory ============ */ -qboolean IsFileExists( const char *filename ) +qboolean MakeDirectory( const char *path ) { - struct stat st; - int ret; + if( -1 == _mkdir( path )) + { + if( errno == EEXIST ) + { + // TODO: when filesystem library will be ready + // use FS_SysFolderExists here or replace this whole function + // with FS_CreatePath +#if XASH_WIN32 + DWORD dwFlags = GetFileAttributes( path ); - ret = stat( filename, &st ); + return ( dwFlags != -1 ) && ( dwFlags & FILE_ATTRIBUTE_DIRECTORY ); +#else + struct stat buf; - if( ret == -1 ) + if( !stat( path, &buf )) + return S_ISDIR( buf.st_mode ); +#endif + } return false; + } return true; } @@ -44,7 +59,7 @@ qboolean IsFileExists( const char *filename ) GetFileSize ============ */ -off_t GetFileSize( FILE *fp ) +off_t GetSizeOfFile( FILE *fp ) { struct stat st; int fd; @@ -71,7 +86,7 @@ byte *LoadFile( const char *filename ) if( !fp ) return NULL; - size = GetFileSize( fp ); + size = GetSizeOfFile( fp ); buf = malloc( size ); diff --git a/utils/mdldec/utils.h b/utils/mdldec/utils.h index b2e9958f..08d07202 100644 --- a/utils/mdldec/utils.h +++ b/utils/mdldec/utils.h @@ -16,8 +16,8 @@ GNU General Public License for more details. #ifndef UTILS_H #define UTILS_H -qboolean IsFileExists( const char *filename ); -off_t GetFileSize( FILE *fp ); +qboolean MakeDirectory( const char *path ); +off_t GetSizeOfFile( FILE *fp ); byte *LoadFile( const char *filename ); #endif // UTILS_H diff --git a/utils/mdldec/wscript b/utils/mdldec/wscript index 48619987..72b310d0 100644 --- a/utils/mdldec/wscript +++ b/utils/mdldec/wscript @@ -16,16 +16,11 @@ def build(bld): if bld.env.DISABLE_UTILS_MDLDEC: return - source = bld.path.ant_glob('*.c') - includes = '. ../../common ../../engine ../../engine/common ../../engine/common/imagelib ../../public' - libs = [ 'public', 'M' ] - - bld( - source = source, + bld(source = bld.path.ant_glob('*.c'), target = 'mdldec', features = 'c cprogram', - includes = includes, - use = libs, + includes = '.', + use = 'engine_includes public M', install_path = bld.env.BINDIR, subsystem = bld.env.CONSOLE_SUBSYSTEM ) diff --git a/vgui-dev b/vgui-dev deleted file mode 160000 index 93573075..00000000 --- a/vgui-dev +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 93573075afe885618ea15831e72d44bdacd65bfb diff --git a/vgui_support/Android.mk b/vgui_support/Android.mk deleted file mode 100644 index 78c7db35..00000000 --- a/vgui_support/Android.mk +++ /dev/null @@ -1,23 +0,0 @@ -LOCAL_PATH := $(call my-dir) - -include $(CLEAR_VARS) - -LOCAL_MODULE = vgui_support - -include $(XASH3D_CONFIG) - -LOCAL_CFLAGS = -fsigned-char -DVGUI_TOUCH_SCROLL -DNO_STL -DXASH_GLES -DINTERNAL_VGUI_SUPPORT -Wall -Wextra -Wno-unused-parameter -Wno-unused-variable - -LOCAL_CPPFLAGS = -frtti -fno-exceptions -Wno-write-strings -Wno-invalid-offsetof -std=gnu++98 - -LOCAL_C_INCLUDES := \ - $(LOCAL_PATH)/../common \ - $(LOCAL_PATH)/../engine \ - $(HLSDK_PATH)/utils/vgui/include \ - $(VGUI_DIR)/include - -LOCAL_SRC_FILES := vgui_clip.cpp vgui_font.cpp vgui_input.cpp vgui_int.cpp vgui_surf.cpp - -LOCAL_STATIC_LIBRARIES := vgui - -include $(BUILD_STATIC_LIBRARY) diff --git a/vgui_support/Makefile.linux b/vgui_support/Makefile.linux deleted file mode 100644 index 8031d116..00000000 --- a/vgui_support/Makefile.linux +++ /dev/null @@ -1,29 +0,0 @@ -CC ?= gcc -m32 -CXX ?= g++ -m32 -std=gnu++98 -CFLAGS ?= -O2 -ggdb -fPIC -TOPDIR = $(PWD)/.. -VGUI_DIR ?= ./vgui-dev -INCLUDES = -I../common -I../engine -I../engine/common -I../engine/client -I../engine/client/vgui -I../pm_shared -INCLUDES += -I$(VGUI_DIR)/include/ -DEFINES = -DNO_STL -%.o : %.cpp - $(CXX) $(CFLAGS) $(INCLUDES) $(DEFINES) -c $< -o $@ - -SRCS = $(wildcard *.cpp) -OBJS = $(SRCS:.cpp=.o) - -libvgui_support.so : $(OBJS) - $(CXX) $(LDFLAGS) -o libvgui_support.so -shared $(OBJS) vgui.so - -.PHONY: depend clean - -clean : - $(RM) $(OBJS) libvgui_support.so - -depend: Makefile.dep - -Makefile.dep: $(SRCS) - rm -f ./Makefile.dep - $(CC) $(CFLAGS) $(INCLUDES) $(DEFINES) -MM $^>>./Makefile.dep - -include Makefile.dep diff --git a/vgui_support/cl.bat b/vgui_support/cl.bat deleted file mode 100644 index c0f59c0d..00000000 --- a/vgui_support/cl.bat +++ /dev/null @@ -1,5 +0,0 @@ - set MSVCDir=Z:\path\to\msvc - set INCLUDE=%MSVCDir%\include - set LIB=%MSVCDir%\lib - set PATH=%MSVCDir%\bin;%PATH% -cl.exe %* \ No newline at end of file diff --git a/vgui_support/vgui_clip.cpp b/vgui_support/vgui_clip.cpp deleted file mode 100644 index 67085577..00000000 --- a/vgui_support/vgui_clip.cpp +++ /dev/null @@ -1,124 +0,0 @@ -/* -vgui_clip.cpp - clip in 2D space -Copyright (C) 2011 Uncle Mike - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -In addition, as a special exception, the author gives permission -to link the code of this program with VGUI library developed by -Valve, L.L.C ("Valve"). You must obey the GNU General Public License -in all respects for all of the code used other than VGUI library. -If you modify this file, you may extend this exception to your -version of the file, but you are not obligated to do so. If -you do not wish to do so, delete this exception statement -from your version. - -*/ - -#include "vgui_main.h" -#include "wrect.h" - -//----------------------------------------------------------------------------- -// For simulated scissor tests... -//----------------------------------------------------------------------------- -static wrect_t g_ScissorRect; -static qboolean g_bScissor = false; -namespace vgui_support { -//----------------------------------------------------------------------------- -// Enable/disable scissoring... -//----------------------------------------------------------------------------- -void EnableScissor( qboolean enable ) -{ - g_bScissor = enable; -} - -void SetScissorRect( int left, int top, int right, int bottom ) -{ - // Check for a valid rectangle... - assert( left <= right ); - assert( top <= bottom ); - - g_ScissorRect.left = left; - g_ScissorRect.top = top; - g_ScissorRect.right = right; - g_ScissorRect.bottom = bottom; -} - -//----------------------------------------------------------------------------- -// Purpose: Used for clipping, produces an interpolated texture coordinate -//----------------------------------------------------------------------------- -inline float InterpTCoord( float val, float mins, float maxs, float tMin, float tMax ) -{ - float flPercent; - - if( mins != maxs ) - flPercent = (float)(val - mins) / (maxs - mins); - else flPercent = 0.5f; - - return tMin + (tMax - tMin) * flPercent; -} - -//----------------------------------------------------------------------------- -// Purpose: Does a scissor clip of the input rectangle. -// Returns false if it is completely clipped off. -//----------------------------------------------------------------------------- -qboolean ClipRect( const vpoint_t &inUL, const vpoint_t &inLR, vpoint_t *pOutUL, vpoint_t *pOutLR ) -{ - if( g_bScissor ) - { - // pick whichever left side is larger - if( g_ScissorRect.left > inUL.point[0] ) - pOutUL->point[0] = g_ScissorRect.left; - else - pOutUL->point[0] = inUL.point[0]; - - // pick whichever right side is smaller - if( g_ScissorRect.right <= inLR.point[0] ) - pOutLR->point[0] = g_ScissorRect.right; - else - pOutLR->point[0] = inLR.point[0]; - - // pick whichever top side is larger - if( g_ScissorRect.top > inUL.point[1] ) - pOutUL->point[1] = g_ScissorRect.top; - else - pOutUL->point[1] = inUL.point[1]; - - // pick whichever bottom side is smaller - if( g_ScissorRect.bottom <= inLR.point[1] ) - pOutLR->point[1] = g_ScissorRect.bottom; - else - pOutLR->point[1] = inLR.point[1]; - - // Check for non-intersecting - if(( pOutUL->point[0] > pOutLR->point[0] ) || ( pOutUL->point[1] > pOutLR->point[1] )) - { - return false; - } - - pOutUL->coord[0] = InterpTCoord(pOutUL->point[0], - inUL.point[0], inLR.point[0], inUL.coord[0], inLR.coord[0] ); - pOutLR->coord[0] = InterpTCoord(pOutLR->point[0], - inUL.point[0], inLR.point[0], inUL.coord[0], inLR.coord[0] ); - - pOutUL->coord[1] = InterpTCoord(pOutUL->point[1], - inUL.point[1], inLR.point[1], inUL.coord[1], inLR.coord[1] ); - pOutLR->coord[1] = InterpTCoord(pOutLR->point[1], - inUL.point[1], inLR.point[1], inUL.coord[1], inLR.coord[1] ); - } - else - { - *pOutUL = inUL; - *pOutLR = inLR; - } - return true; -} -} diff --git a/vgui_support/vgui_input.cpp b/vgui_support/vgui_input.cpp deleted file mode 100644 index 0a6ee74b..00000000 --- a/vgui_support/vgui_input.cpp +++ /dev/null @@ -1,91 +0,0 @@ -/* -vgui_input.cpp - handle kb & mouse -Copyright (C) 2011 Uncle Mike - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -In addition, as a special exception, the author gives permission -to link the code of this program with VGUI library developed by -Valve, L.L.C ("Valve"). You must obey the GNU General Public License -in all respects for all of the code used other than VGUI library. -If you modify this file, you may extend this exception to your -version of the file, but you are not obligated to do so. If -you do not wish to do so, delete this exception statement -from your version. - -*/ - -#define OEMRESOURCE // for OCR_* cursor junk - -#include "vgui_main.h" - -namespace vgui_support { -void VGUI_Key(VGUI_KeyAction action, VGUI_KeyCode code) -{ - App *pApp = App::getInstance(); - if(!surface) - return; - switch( action ) - { - case KA_PRESSED: - pApp->internalKeyPressed( (KeyCode) code, surface ); - break; - case KA_RELEASED: - pApp->internalKeyReleased( (KeyCode) code, surface ); - break; - case KA_TYPED: - pApp->internalKeyTyped( (KeyCode) code, surface ); - break; - } - //fprintf(stdout,"vgui_support: VGUI key action %d %d\n", action, code); - //fflush(stdout); -} - -void VGUI_Mouse(VGUI_MouseAction action, int code) -{ - App *pApp = App::getInstance(); - if(!surface) - return; - switch( action ) - { - case MA_PRESSED: - pApp->internalMousePressed( (MouseCode) code, surface ); - break; - case MA_RELEASED: - pApp->internalMouseReleased( (MouseCode) code, surface ); - break; - case MA_DOUBLE: - pApp->internalMouseDoublePressed( (MouseCode) code, surface ); - break; - case MA_WHEEL: - //fprintf(stdout, "vgui_support: VGUI mouse wheeled %d %d\n", action, code); - pApp->internalMouseWheeled( code, surface ); - break; - } - //fprintf(stdout, "vgui_support: VGUI mouse action %d %d\n", action, code); - //fflush(stdout); -} - -void VGUI_MouseMove(int x, int y) -{ - App *pApp = App::getInstance(); - //fprintf(stdout, "vgui_support: VGUI mouse move %d %d %p\n", x, y, surface); - //fflush(stdout); - if(!surface) - return; - pApp->internalCursorMoved( x, y, surface ); -} - -void VGUI_TextInput(const char *text) -{ - // stub -} -} diff --git a/vgui_support/vgui_int.cpp b/vgui_support/vgui_int.cpp deleted file mode 100644 index 1506767a..00000000 --- a/vgui_support/vgui_int.cpp +++ /dev/null @@ -1,123 +0,0 @@ -/* -vgui_int.c - vgui dll interaction -Copyright (C) 2011 Uncle Mike - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -In addition, as a special exception, the author gives permission -to link the code of this program with VGUI library developed by -Valve, L.L.C ("Valve"). You must obey the GNU General Public License -in all respects for all of the code used other than VGUI library. -If you modify this file, you may extend this exception to your -version of the file, but you are not obligated to do so. If -you do not wish to do so, delete this exception statement -from your version. - -*/ - -#include "vgui_main.h" -#include "xash3d_types.h" -namespace vgui_support { - -vguiapi_t *g_api; - -Panel *rootpanel = NULL; -CEngineSurface *surface = NULL; -CEngineApp staticApp; - -void VGui_Startup( int width, int height ) -{ - if( rootpanel ) - { - rootpanel->setSize( width, height ); - return; - } - - rootpanel = new Panel; - rootpanel->setSize( width, height ); - rootpanel->setPaintBorderEnabled( false ); - rootpanel->setPaintBackgroundEnabled( false ); - rootpanel->setVisible( true ); - rootpanel->setCursor( new Cursor( Cursor::dc_none )); - - staticApp.start(); - staticApp.setMinimumTickMillisInterval( 0 ); - - surface = new CEngineSurface( rootpanel ); - rootpanel->setSurfaceBaseTraverse( surface ); - - - //ASSERT( rootpanel->getApp() != NULL ); - //ASSERT( rootpanel->getSurfaceBase() != NULL ); - - g_api->DrawInit (); -} - -void VGui_Shutdown( void ) -{ - staticApp.stop(); - - delete rootpanel; - delete surface; - - rootpanel = NULL; - surface = NULL; -} - -void VGui_Paint( void ) -{ - int w, h; - - //if( cls.state != ca_active || !rootpanel ) - // return; - if( !g_api->IsInGame() || !rootpanel ) - return; - - // setup the base panel to cover the screen - Panel *pVPanel = surface->getEmbeddedPanel(); - if( !pVPanel ) return; - //SDL_GetWindowSize(host.hWnd, &w, &h); - //host.input_enabled = rootpanel->isVisible(); - rootpanel->getSize(w, h); - EnableScissor( true ); - - staticApp.externalTick (); - - pVPanel->setBounds( 0, 0, w, h ); - pVPanel->repaint(); - - // paint everything - pVPanel->paintTraverse(); - - EnableScissor( false ); -} -void *VGui_GetPanel( void ) -{ - return (void *)rootpanel; -} -} - -#ifdef INTERNAL_VGUI_SUPPORT -#define InitAPI InitVGUISupportAPI -#endif - -extern "C" EXPORT void InitAPI(vguiapi_t * api) -{ - g_api = api; - g_api->Startup = VGui_Startup; - g_api->Shutdown = VGui_Shutdown; - g_api->GetPanel = VGui_GetPanel; - g_api->Paint = VGui_Paint; - g_api->Mouse = VGUI_Mouse; - g_api->MouseMove = VGUI_MouseMove; - g_api->Key = VGUI_Key; - g_api->TextInput = VGUI_TextInput; -} diff --git a/vgui_support/vgui_main.h b/vgui_support/vgui_main.h deleted file mode 100644 index 5819531d..00000000 --- a/vgui_support/vgui_main.h +++ /dev/null @@ -1,156 +0,0 @@ -/* -vgui_main.h - vgui main header -Copyright (C) 2011 Uncle Mike - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -In addition, as a special exception, the author gives permission -to link the code of this program with VGUI library developed by -Valve, L.L.C ("Valve"). You must obey the GNU General Public License -in all respects for all of the code used other than VGUI library. -If you modify this file, you may extend this exception to your -version of the file, but you are not obligated to do so. If -you do not wish to do so, delete this exception statement -from your version. - -*/ -#ifndef VGUI_MAIN_H -#define VGUI_MAIN_H - -#ifdef _WIN32 -#include -#else -#include -#endif - -#include - -#include "vgui_api.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace vgui_support -{ -extern vguiapi_t *g_api; - -using namespace vgui; - -struct PaintStack -{ - Panel *m_pPanel; - int iTranslateX; - int iTranslateY; - int iScissorLeft; - int iScissorRight; - int iScissorTop; - int iScissorBottom; -}; - -class CEngineSurface : public SurfaceBase -{ -private: - - // point translation for current panel - int _translateX; - int _translateY; - - // the size of the window to draw into - int _surfaceExtents[4]; - - void SetupPaintState( const PaintStack *paintState ); - void InitVertex( vpoint_t &vertex, int x, int y, float u, float v ); -public: - CEngineSurface( Panel *embeddedPanel ); - ~CEngineSurface(); -public: - virtual Panel *getEmbeddedPanel( void ); - virtual bool setFullscreenMode( int wide, int tall, int bpp ); - virtual void setWindowedMode( void ); - virtual void setTitle( const char *title ) { } - virtual void createPopup( Panel* embeddedPanel ) { } - virtual bool isWithin( int x, int y ) { return true; } - virtual bool hasFocus( void ); - // now it's not abstract class, yay - virtual void GetMousePos(int &x, int &y) { - g_api->GetCursorPos(&x, &y); - } - void drawPrintChar(int x, int y, int wide, int tall, float s0, float t0, float s1, float t1, int color[]); -protected: - virtual int createNewTextureID( void ); - virtual void drawSetColor( int r, int g, int b, int a ); - virtual void drawSetTextColor( int r, int g, int b, int a ); - virtual void drawFilledRect( int x0, int y0, int x1, int y1 ); - virtual void drawOutlinedRect( int x0,int y0,int x1,int y1 ); - virtual void drawSetTextFont( Font *font ); - virtual void drawSetTextPos( int x, int y ); - virtual void drawPrintText( const char* text, int textLen ); - virtual void drawSetTextureRGBA( int id, const char* rgba, int wide, int tall ); - virtual void drawSetTexture( int id ); - virtual void drawTexturedRect( int x0, int y0, int x1, int y1 ); - virtual bool createPlat( void ) { return false; } - virtual bool recreateContext( void ) { return false; } - virtual void setCursor( Cursor* cursor ); - virtual void pushMakeCurrent( Panel* panel, bool useInsets ); - virtual void popMakeCurrent( Panel* panel ); - - // not used in engine instance - virtual void enableMouseCapture( bool state ) { } - virtual void invalidate( Panel *panel ) { } - virtual void setAsTopMost( bool state ) { } - virtual void applyChanges( void ) { } - virtual void swapBuffers( void ) { } -protected: - Cursor* _hCurrentCursor; - int _drawTextPos[2]; - int _drawColor[4]; - int _drawTextColor[4]; - friend class App; - friend class Panel; -}; - -// initialize VGUI::App as external (part of engine) -class CEngineApp : public App -{ -public: - CEngineApp( bool externalMain = true ) : App( externalMain ) { } - virtual void main( int argc, char* argv[] ) { } // stub -}; - -// -// vgui_input.cpp -// -void *VGui_GetPanel( void ); -void VGui_Paint( void ); -void VGUI_Mouse(VGUI_MouseAction action, int code); -void VGUI_Key(VGUI_KeyAction action, VGUI_KeyCode code); -void VGUI_MouseMove(int x, int y); -void VGUI_TextInput(const char *text); - -// -// vgui_clip.cpp -// -void EnableScissor( qboolean enable ); -void SetScissorRect( int left, int top, int right, int bottom ); -qboolean ClipRect( const vpoint_t &inUL, const vpoint_t &inLR, vpoint_t *pOutUL, vpoint_t *pOutLR ); - -extern CEngineSurface *surface; -extern Panel *root; -} -using namespace vgui_support; -#endif//VGUI_MAIN_H diff --git a/vgui_support/vgui_surf.cpp b/vgui_support/vgui_surf.cpp deleted file mode 100644 index 21802dab..00000000 --- a/vgui_support/vgui_surf.cpp +++ /dev/null @@ -1,440 +0,0 @@ -/* -vgui_surf.cpp - main vgui layer -Copyright (C) 2011 Uncle Mike - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -In addition, as a special exception, the author gives permission -to link the code of this program with VGUI library developed by -Valve, L.L.C ("Valve"). You must obey the GNU General Public License -in all respects for all of the code used other than VGUI library. -If you modify this file, you may extend this exception to your -version of the file, but you are not obligated to do so. If -you do not wish to do so, delete this exception statement -from your version. - -*/ - -#include -#include "vgui_main.h" - -#define MAX_PAINT_STACK 16 -#define FONT_SIZE 512 -#define FONT_PAGES 8 - -struct FontInfo -{ - int id; - int pageCount; - int pageForChar[256]; - int bindIndex[FONT_PAGES]; - float texCoord[256][FONT_PAGES]; - int contextCount; -}; - -static int staticContextCount = 0; -static char staticRGBA[FONT_SIZE * FONT_SIZE * 4]; -static Font* staticFont = NULL; -static FontInfo* staticFontInfo; -static Dar staticFontInfoDar; -static PaintStack paintStack[MAX_PAINT_STACK]; -static int staticPaintStackPos = 0; - -#define ColorIndex( c )((( c ) - '0' ) & 7 ) - -CEngineSurface :: CEngineSurface( Panel *embeddedPanel ):SurfaceBase( embeddedPanel ) -{ - _embeddedPanel = embeddedPanel; - _drawColor[0] = _drawColor[1] = _drawColor[2] = _drawColor[3] = 255; - _drawTextColor[0] = _drawTextColor[1] = _drawTextColor[2] = _drawTextColor[3] = 255; - - _surfaceExtents[0] = _surfaceExtents[1] = 0; - //_surfaceExtents[2] = menu.globals->scrWidth; - //_surfaceExtents[3] = menu.globals->scrHeight; - embeddedPanel->getSize(_surfaceExtents[2], _surfaceExtents[3]); - _drawTextPos[0] = _drawTextPos[1] = 0; - _hCurrentCursor = null; - - staticFont = NULL; - staticFontInfo = NULL; - staticFontInfoDar.setCount( 0 ); - staticPaintStackPos = 0; - staticContextCount++; - - _translateX = _translateY = 0; -} - -CEngineSurface :: ~CEngineSurface( void ) -{ - g_api->DrawShutdown (); -} - -Panel *CEngineSurface :: getEmbeddedPanel( void ) -{ - return _embeddedPanel; -} - -bool CEngineSurface :: hasFocus( void ) -{ - // What differs when window does not has focus? - //return host.state != HOST_NOFOCUS; - return true; -} - -void CEngineSurface :: setCursor( Cursor *cursor ) -{ - _currentCursor = cursor; - - if( cursor ) - { - g_api->CursorSelect( (VGUI_DefaultCursor)cursor->getDefaultCursor() ); - } -} - -void CEngineSurface :: SetupPaintState( const PaintStack *paintState ) -{ - _translateX = paintState->iTranslateX; - _translateY = paintState->iTranslateY; - SetScissorRect( paintState->iScissorLeft, paintState->iScissorTop, - paintState->iScissorRight, paintState->iScissorBottom ); -} - -void CEngineSurface :: InitVertex( vpoint_t &vertex, int x, int y, float u, float v ) -{ - vertex.point[0] = x + _translateX; - vertex.point[1] = y + _translateY; - vertex.coord[0] = u; - vertex.coord[1] = v; -} - -int CEngineSurface :: createNewTextureID( void ) -{ - return g_api->GenerateTexture(); -} - -void CEngineSurface :: drawSetColor( int r, int g, int b, int a ) -{ - _drawColor[0] = r; - _drawColor[1] = g; - _drawColor[2] = b; - _drawColor[3] = a; -} - -void CEngineSurface :: drawSetTextColor( int r, int g, int b, int a ) -{ - _drawTextColor[0] = r; - _drawTextColor[1] = g; - _drawTextColor[2] = b; - _drawTextColor[3] = a; -} - -void CEngineSurface :: drawFilledRect( int x0, int y0, int x1, int y1 ) -{ - vpoint_t rect[2]; - vpoint_t clippedRect[2]; - - if( _drawColor[3] >= 255 ) return; - - InitVertex( rect[0], x0, y0, 0, 0 ); - InitVertex( rect[1], x1, y1, 0, 0 ); - - // fully clipped? - if( !ClipRect( rect[0], rect[1], &clippedRect[0], &clippedRect[1] )) - return; - - g_api->SetupDrawingRect( _drawColor ); - g_api->EnableTexture( false ); - g_api->DrawQuad( &clippedRect[0], &clippedRect[1] ); - g_api->EnableTexture( true ); -} - -void CEngineSurface :: drawOutlinedRect( int x0, int y0, int x1, int y1 ) -{ - if( _drawColor[3] >= 255 ) return; - - drawFilledRect( x0, y0, x1, y0 + 1 ); // top - drawFilledRect( x0, y1 - 1, x1, y1 ); // bottom - drawFilledRect( x0, y0 + 1, x0 + 1, y1 - 1 ); // left - drawFilledRect( x1 - 1, y0 + 1, x1, y1 - 1 ); // right -} - -void CEngineSurface :: drawSetTextFont( Font *font ) -{ - staticFont = font; - - if( font ) - { - bool buildFont = false; - - staticFontInfo = NULL; - - for( int i = 0; i < staticFontInfoDar.getCount(); i++ ) - { - if( staticFontInfoDar[i]->id == font->getId( )) - { - staticFontInfo = staticFontInfoDar[i]; - if( staticFontInfo->contextCount != staticContextCount ) - buildFont = true; - } - } - - if( !staticFontInfo || buildFont ) - { - staticFontInfo = new FontInfo; - staticFontInfo->id = 0; - staticFontInfo->pageCount = 0; - staticFontInfo->bindIndex[0] = 0; - staticFontInfo->bindIndex[1] = 0; - staticFontInfo->bindIndex[2] = 0; - staticFontInfo->bindIndex[3] = 0; - memset( staticFontInfo->pageForChar, 0, sizeof( staticFontInfo->pageForChar )); - staticFontInfo->contextCount = -1; - staticFontInfo->id = staticFont->getId(); - staticFontInfoDar.putElement( staticFontInfo ); - staticFontInfo->contextCount = staticContextCount; - - int currentPage = 0; - int x = 0, y = 0; - - memset( staticRGBA, 0, sizeof( staticRGBA )); - - for( int i = 0; i < 256; i++ ) - { - int abcA, abcB, abcC; - staticFont->getCharABCwide( i, abcA, abcB, abcC ); - - int wide = abcB; - - if( isspace( i )) continue; - - int tall = staticFont->getTall(); - - if( x + wide + 1 > FONT_SIZE ) - { - x = 0; - y += tall + 1; - } - - if( y + tall + 1 > FONT_SIZE ) - { - if( !staticFontInfo->bindIndex[currentPage] ) - staticFontInfo->bindIndex[currentPage] = createNewTextureID(); - drawSetTextureRGBA( staticFontInfo->bindIndex[currentPage], staticRGBA, FONT_SIZE, FONT_SIZE ); - currentPage++; - - if( currentPage == FONT_PAGES ) - break; - - memset( staticRGBA, 0, sizeof( staticRGBA )); - x = y = 0; - } - - staticFont->getCharRGBA( i, x, y, FONT_SIZE, FONT_SIZE, (byte *)staticRGBA ); - staticFontInfo->pageForChar[i] = currentPage; - staticFontInfo->texCoord[i][0] = (float)((double)x / (double)FONT_SIZE ); - staticFontInfo->texCoord[i][1] = (float)((double)y / (double)FONT_SIZE ); - staticFontInfo->texCoord[i][2] = (float)((double)(x + wide)/(double)FONT_SIZE ); - staticFontInfo->texCoord[i][3] = (float)((double)(y + tall)/(double)FONT_SIZE ); - x += wide + 1; - } - - if( currentPage != FONT_PAGES ) - { - if( !staticFontInfo->bindIndex[currentPage] ) - staticFontInfo->bindIndex[currentPage] = createNewTextureID(); - drawSetTextureRGBA( staticFontInfo->bindIndex[currentPage], staticRGBA, FONT_SIZE, FONT_SIZE ); - } - staticFontInfo->pageCount = currentPage + 1; - } - } -} - -void CEngineSurface :: drawSetTextPos( int x, int y ) -{ - _drawTextPos[0] = x; - _drawTextPos[1] = y; -} - -void CEngineSurface :: drawPrintChar( int x, int y, int wide, int tall, float s0, float t0, float s1, float t1, int color[4] ) -{ - vpoint_t ul, lr; - - ul.point[0] = x; - ul.point[1] = y; - lr.point[0] = x + wide; - lr.point[1] = y + tall; - - // gets at the texture coords for this character in its texture page - ul.coord[0] = s0; - ul.coord[1] = t0; - lr.coord[0] = s1; - lr.coord[1] = t1; - - vpoint_t clippedRect[2]; - - if( !ClipRect( ul, lr, &clippedRect[0], &clippedRect[1] )) - return; - - g_api->SetupDrawingImage( color ); - g_api->DrawQuad( &clippedRect[0], &clippedRect[1] ); // draw the letter -} - -void CEngineSurface :: drawPrintText( const char* text, int textLen ) -{ - //return; - static bool hasColor = 0; - static int numColor = 7; - - if( !text || !staticFont || _drawTextColor[3] >= 255 ) - return; - - int x = _drawTextPos[0] + _translateX; - int y = _drawTextPos[1] + _translateY; - - int tall = staticFont->getTall(); - - int j, iTotalWidth = 0; - int curTextColor[4]; - - // HACKHACK: allow color strings in VGUI - if( numColor != 7 ) - { - for( j = 0; j < 3; j++ ) // grab predefined color - curTextColor[j] = g_api->GetColor(numColor,j); - } - else - { - for( j = 0; j < 3; j++ ) // revert default color - curTextColor[j] = _drawTextColor[j]; - } - curTextColor[3] = _drawTextColor[3]; // copy alpha - - if( textLen == 1 ) - { - if( *text == '^' ) - { - hasColor = true; - return; // skip '^' - } - else if( hasColor && isdigit( *text )) - { - numColor = ColorIndex( *text ); - hasColor = false; // handled - return; // skip colornum - } - else hasColor = false; - } - for( int i = 0; i < textLen; i++ ) - { - int curCh = g_api->ProcessUtfChar( (unsigned char)text[i] ); - if( !curCh ) - { - continue; - } - - int abcA, abcB, abcC; - - staticFont->getCharABCwide( curCh, abcA, abcB, abcC ); - - float s0 = staticFontInfo->texCoord[curCh][0]; - float t0 = staticFontInfo->texCoord[curCh][1]; - float s1 = staticFontInfo->texCoord[curCh][2]; - float t1 = staticFontInfo->texCoord[curCh][3]; - int wide = abcB; - - iTotalWidth += abcA; - drawSetTexture( staticFontInfo->bindIndex[staticFontInfo->pageForChar[curCh]] ); - drawPrintChar( x + iTotalWidth, y, wide, tall, s0, t0, s1, t1, curTextColor ); - iTotalWidth += wide + abcC; - } - - _drawTextPos[0] += iTotalWidth; -} - -void CEngineSurface :: drawSetTextureRGBA( int id, const char* rgba, int wide, int tall ) -{ - g_api->UploadTexture( id, rgba, wide, tall ); -} - -void CEngineSurface :: drawSetTexture( int id ) -{ - g_api->BindTexture( id ); -} - -void CEngineSurface :: drawTexturedRect( int x0, int y0, int x1, int y1 ) -{ - vpoint_t rect[2]; - vpoint_t clippedRect[2]; - - InitVertex( rect[0], x0, y0, 0, 0 ); - InitVertex( rect[1], x1, y1, 1, 1 ); - - // fully clipped? - if( !ClipRect( rect[0], rect[1], &clippedRect[0], &clippedRect[1] )) - return; - - g_api->SetupDrawingImage( _drawColor ); - g_api->DrawQuad( &clippedRect[0], &clippedRect[1] ); -} - -void CEngineSurface :: pushMakeCurrent( Panel* panel, bool useInsets ) -{ - int insets[4] = { 0, 0, 0, 0 }; - int absExtents[4]; - int clipRect[4]; - - if( useInsets ) - panel->getInset( insets[0], insets[1], insets[2], insets[3] ); - panel->getAbsExtents( absExtents[0], absExtents[1], absExtents[2], absExtents[3] ); - panel->getClipRect( clipRect[0], clipRect[1], clipRect[2], clipRect[3] ); - - PaintStack *paintState = &paintStack[staticPaintStackPos]; - - assert( staticPaintStackPos < MAX_PAINT_STACK ); - - paintState->m_pPanel = panel; - - // determine corrected top left origin - paintState->iTranslateX = insets[0] + absExtents[0]; - paintState->iTranslateY = insets[1] + absExtents[1]; - // setup clipping rectangle for scissoring - paintState->iScissorLeft = clipRect[0]; - paintState->iScissorTop = clipRect[1]; - paintState->iScissorRight = clipRect[2]; - paintState->iScissorBottom = clipRect[3]; - - SetupPaintState( paintState ); - staticPaintStackPos++; -} - -void CEngineSurface :: popMakeCurrent( Panel *panel ) -{ - int top = staticPaintStackPos - 1; - - // more pops that pushes? - assert( top >= 0 ); - - // didn't pop in reverse order of push? - assert( paintStack[top].m_pPanel == panel ); - - staticPaintStackPos--; - - if( top > 0 ) SetupPaintState( &paintStack[top-1] ); -} - -bool CEngineSurface :: setFullscreenMode( int wide, int tall, int bpp ) -{ - return false; -} - -void CEngineSurface :: setWindowedMode( void ) -{ -} diff --git a/vgui_support/wscript b/vgui_support/wscript deleted file mode 100644 index b3613ec2..00000000 --- a/vgui_support/wscript +++ /dev/null @@ -1,125 +0,0 @@ -#! /usr/bin/env python -# encoding: utf-8 -# mittorn, 2018 - -from waflib import Logs -import os - -top = '.' - -VGUI_SUPPORTED_OS = ['win32', 'darwin', 'linux'] - -def options(opt): - grp = opt.add_option_group('VGUI options') - grp.add_option('--vgui', action = 'store', dest = 'VGUI_DEV', default='vgui-dev', - help = 'path to vgui-dev repo [default: %default]') - - grp.add_option('--disable-vgui', action = 'store_true', dest = 'NO_VGUI', default = False, - help = 'disable vgui_support [default: %default]') - - grp.add_option('--skip-vgui-sanity-check', action = 'store_false', dest = 'VGUI_SANITY_CHECK', default=False, - help = 'skip checking VGUI sanity [default: %default]' ) - return - -def configure(conf): - conf.env.NO_VGUI = conf.options.NO_VGUI - if conf.options.NO_VGUI: - return - - conf.start_msg('Does this architecture support VGUI?') - - if conf.env.DEST_CPU != 'x86' and not (conf.env.DEST_CPU == 'x86_64' and not conf.options.ALLOW64): - conf.end_msg('no') - Logs.warn('vgui is not supported on this CPU: ' + str(conf.env.DEST_CPU)) - conf.env.NO_VGUI = True - return - else: - conf.end_msg('yes') - - conf.start_msg('Does this OS support VGUI?') - if conf.env.DEST_OS not in VGUI_SUPPORTED_OS: - conf.end_msg('no') - Logs.warn('vgui is not supported on this OS: ' + str(conf.env.DEST_OS)) - conf.env.NO_VGUI = True - return - else: - conf.end_msg('yes') - - conf.start_msg('Does this toolchain able to link VGUI?') - if conf.env.DEST_OS == 'win32' and conf.env.COMPILER_CXX == 'g++': - conf.end_msg('no') - # we have ABI incompatibility ONLY on MinGW - Logs.warn('vgui_support can\'t be built with MinGW') - conf.env.NO_VGUI = True - return - else: - conf.end_msg('yes') - - if conf.env.NO_VGUI: - return - - if conf.options.VGUI_DEV: - conf.start_msg('Configuring VGUI by provided path') - conf.env.VGUI_DEV = conf.options.VGUI_DEV - else: - conf.start_msg('Configuring VGUI by default path') - conf.env.VGUI_DEV = 'vgui-dev' - - if conf.env.DEST_OS == 'win32': - conf.env.LIB_VGUI = ['vgui'] - conf.env.LIBPATH_VGUI = [os.path.abspath(os.path.join(conf.env.VGUI_DEV, 'lib/win32_vc6/'))] - else: - libpath = os.path.abspath(os.path.join(conf.env.VGUI_DEV, 'lib')) - if conf.env.DEST_OS == 'linux': - conf.env.LIB_VGUI = [':vgui.so'] - conf.env.LIBPATH_VGUI = [libpath] - elif conf.env.DEST_OS == 'darwin': - conf.env.LDFLAGS_VGUI = [os.path.join(libpath, 'vgui.dylib')] - else: - conf.fatal('vgui is not supported on this OS: ' + conf.env.DEST_OS) - conf.env.INCLUDES_VGUI = [os.path.abspath(os.path.join(conf.env.VGUI_DEV, 'include'))] - - conf.env.HAVE_VGUI = 1 - conf.end_msg('yes: {0}, {1}, {2}'.format(conf.env.LIB_VGUI, conf.env.LIBPATH_VGUI, conf.env.INCLUDES_VGUI)) - - if conf.env.HAVE_VGUI and conf.options.VGUI_SANITY_CHECK: - try: - conf.check_cxx( - fragment=''' - #include - int main( int argc, char **argv ) - { - return 0; - }''', - msg = 'Checking for library VGUI sanity', - use = 'VGUI', - execute = False) - except conf.errors.ConfigurationError: - conf.fatal("Can't compile simple program. Check your path to vgui-dev repository.") - -def build(bld): - if bld.env.NO_VGUI: - return - - libs = [] - - # basic build: dedicated only, no dependencies - if bld.env.DEST_OS != 'win32': - libs += ['DL','M'] - - libs.append('VGUI') - - source = bld.path.ant_glob(['*.cpp']) - - includes = [ '.', '../common', '../engine', '../public' ] - - bld.shlib( - source = source, - target = 'vgui_support', - features = 'cxx', - includes = includes, - use = libs, - rpath = '.', - install_path = bld.env.LIBDIR, - subsystem = bld.env.MSVC_SUBSYSTEM - ) diff --git a/waf.bat b/waf.bat index 92d4ed37..db5e1d46 100644 --- a/waf.bat +++ b/waf.bat @@ -1,7 +1,7 @@ @echo off rem try fix py2 build -chcp 1252 +chcp 65001 set PYTHONIOENCODING=UTF-8 rem from issue #964 diff --git a/wscript b/wscript index 2254efa4..268afffe 100644 --- a/wscript +++ b/wscript @@ -2,8 +2,7 @@ # encoding: utf-8 # a1batross, mittorn, 2018 -from __future__ import print_function -from waflib import Logs, Context, Configure +from waflib import Build, Context, Logs import sys import os @@ -14,62 +13,49 @@ top = '.' Context.Context.line_just = 55 # should fit for everything on 80x26 class Subproject: - name = '' - dedicated = True # if true will be ignored when building dedicated server - singlebin = False # if true will be ignored when singlebinary is set - ignore = False # if true will be ignored, set by user request - mandatory = False - - def __init__(self, name, dedicated=True, singlebin=False, mandatory = False, utility = False, fuzzer = False): + def __init__(self, name, fnFilter = None): self.name = name - self.dedicated = dedicated - self.singlebin = singlebin - self.mandatory = mandatory - self.utility = utility - self.fuzzer = fuzzer + self.fnFilter = fnFilter + + def is_exists(self, ctx): + return ctx.path.find_node(self.name + '/wscript') def is_enabled(self, ctx): - if not self.mandatory: - if self.name in ctx.env.IGNORE_PROJECTS: - self.ignore = True - - if self.ignore: + if not self.is_exists(ctx): return False - if ctx.env.SINGLE_BINARY and self.singlebin: - return False - - if ctx.env.DEST_OS == 'android' and self.singlebin: - return False - - if ctx.env.DEDICATED and self.dedicated: - return False - - if self.utility and not ctx.env.ENABLE_UTILS: - return False - - if self.fuzzer and not ctx.env.ENABLE_FUZZER: - return False + if self.fnFilter: + return self.fnFilter(ctx) return True SUBDIRS = [ - Subproject('public', dedicated=False, mandatory = True), - Subproject('game_launch', singlebin=True), - Subproject('ref_gl',), - Subproject('ref_soft'), - Subproject('mainui'), - Subproject('vgui_support'), - Subproject('stub/server', dedicated=False), - Subproject('stub/client'), + # always configured and built + Subproject('public'), + Subproject('filesystem'), + Subproject('engine'), + Subproject('stub/server'), Subproject('dllemu'), - Subproject('engine', dedicated=False), - Subproject('utils/mdldec', utility=True), - Subproject('utils/run-fuzzer', fuzzer=True) -] -def subdirs(): - return map(lambda x: x.name, SUBDIRS) + # disable only by engine feature, makes no sense to even parse subprojects in dedicated mode + Subproject('3rdparty/extras', lambda x: not x.env.DEDICATED and x.env.DEST_OS != 'android'), + Subproject('3rdparty/nanogl', lambda x: not x.env.DEDICATED and x.env.NANOGL), + Subproject('3rdparty/gl-wes-v2', lambda x: not x.env.DEDICATED and x.env.GLWES), + Subproject('3rdparty/gl4es', lambda x: not x.env.DEDICATED and x.env.GL4ES), + Subproject('ref/gl', lambda x: not x.env.DEDICATED and (x.env.GL or x.env.NANOGL or x.env.GLWES or x.env.GL4ES)), + Subproject('ref/soft', lambda x: not x.env.DEDICATED and x.env.SOFT), + Subproject('3rdparty/mainui', lambda x: not x.env.DEDICATED), + Subproject('3rdparty/vgui_support', lambda x: not x.env.DEDICATED), + Subproject('stub/client', lambda x: not x.env.DEDICATED), + Subproject('game_launch', lambda x: not x.env.SINGLE_BINARY and x.env.DEST_OS != 'android'), + + # disable only by external dependency presense + Subproject('3rdparty/opus', lambda x: not x.env.HAVE_SYSTEM_OPUS and not x.env.DEDICATED), + + # enabled optionally + Subproject('utils/mdldec', lambda x: x.env.ENABLE_UTILS), + Subproject('utils/run-fuzzer', lambda x: x.env.ENABLE_FUZZER), +] def options(opt): grp = opt.add_option_group('Common options') @@ -77,6 +63,9 @@ def options(opt): grp.add_option('-d', '--dedicated', action = 'store_true', dest = 'DEDICATED', default = False, help = 'build Xash Dedicated Server [default: %default]') + grp.add_option('--gamedir', action = 'store', dest = 'GAMEDIR', default = 'valve', + help = 'engine default game directory [default: %default]') + grp.add_option('--single-binary', action = 'store_true', dest = 'SINGLE_BINARY', default = False, help = 'build single "xash" binary (always enabled for dedicated) [default: %default]') @@ -86,18 +75,38 @@ def options(opt): grp.add_option('-P', '--enable-packaging', action = 'store_true', dest = 'PACKAGING', default = False, help = 'respect prefix option, useful for packaging for various operating systems [default: %default]') + grp.add_option('--enable-bundled-deps', action = 'store_true', dest = 'BUILD_BUNDLED_DEPS', default = False, + help = 'prefer to build bundled dependencies (like opus) instead of relying on system provided') + grp.add_option('--enable-bsp2', action = 'store_true', dest = 'SUPPORT_BSP2_FORMAT', default = False, help = 'build engine and renderers with BSP2 map support(recommended for Quake, breaks compatibility!) [default: %default]') grp.add_option('--low-memory-mode', action = 'store', dest = 'LOW_MEMORY', default = 0, type = 'int', help = 'enable low memory mode (only for devices have <128 ram)') - grp.add_option('--ignore-projects', action = 'store', dest = 'IGNORE_PROJECTS', default = None, - help = 'disable selected projects from build [default: %default]') - grp.add_option('--disable-werror', action = 'store_true', dest = 'DISABLE_WERROR', default = False, help = 'disable compilation abort on warning') + grp = opt.add_option_group('Renderers options') + + grp.add_option('--enable-all-renderers', action='store_true', dest='ALL_RENDERERS', default=False, + help = 'enable all renderers supported by Xash3D FWGS [default: %default]') + + grp.add_option('--enable-gles1', action='store_true', dest='NANOGL', default=False, + help = 'enable gles1 renderer [default: %default]') + + grp.add_option('--enable-gles2', action='store_true', dest='GLWES', default=False, + help = 'enable gles2 renderer [default: %default]') + + grp.add_option('--enable-gl4es', action='store_true', dest='GL4ES', default=False, + help = 'enable gles2 renderer [default: %default]') + + grp.add_option('--disable-gl', action='store_false', dest='GL', default=True, + help = 'disable opengl renderer [default: %default]') + + grp.add_option('--disable-soft', action='store_false', dest='SOFT', default=True, + help = 'disable soft renderer [default: %default]') + grp = opt.add_option_group('Utilities options') grp.add_option('--enable-utils', action = 'store_true', dest = 'ENABLE_UTILS', default = False, @@ -109,22 +118,15 @@ def options(opt): opt.load('compiler_optimizations subproject') for i in SUBDIRS: - if not i.mandatory and not opt.path.find_node(i.name+'/wscript'): - i.ignore = True + if not i.is_exists(opt): continue opt.add_subproject(i.name) - opt.load('xshlib xcompile compiler_cxx compiler_c sdl2 clang_compilation_database strip_on_install waf_unit_test msdev msvs') - if sys.platform == 'win32': - opt.load('msvc') - opt.load('reconfigure') + opt.load('xshlib xcompile compiler_cxx compiler_c sdl2 clang_compilation_database strip_on_install waf_unit_test msdev msvs msvc reconfigure') def configure(conf): conf.load('fwgslib reconfigure compiler_optimizations') - if conf.options.IGNORE_PROJECTS: - conf.env.IGNORE_PROJECTS = conf.options.IGNORE_PROJECTS.split(',') - conf.env.MSVC_TARGETS = ['x86' if not conf.options.ALLOW64 else 'x64'] # Load compilers early @@ -154,6 +156,7 @@ def configure(conf): conf.options.NO_VGUI= True # skip vgui conf.options.NANOGL = True conf.options.GLWES = True + conf.options.GL4ES = True conf.options.GL = False elif conf.env.MAGX: conf.options.USE_SELECT = True @@ -207,6 +210,7 @@ def configure(conf): # '-Werror=format=2', # '-Wdouble-promotion', # disable warning flood '-Wstrict-aliasing', + '-Wmisleading-indentation', ] c_compiler_optional_flags = [ @@ -257,11 +261,23 @@ def configure(conf): conf.env.DEDICATED = conf.options.DEDICATED conf.env.SINGLE_BINARY = conf.options.SINGLE_BINARY or conf.env.DEDICATED + conf.env.NANOGL = conf.options.NANOGL or conf.options.ALL_RENDERERS + conf.env.GLWES = conf.options.GLWES or conf.options.ALL_RENDERERS + conf.env.GL4ES = conf.options.GL4ES or conf.options.ALL_RENDERERS + conf.env.GL = conf.options.GL or conf.options.ALL_RENDERERS + conf.env.SOFT = conf.options.SOFT or conf.options.ALL_RENDERERS + + conf.env.GAMEDIR = conf.options.GAMEDIR + conf.define('XASH_GAMEDIR', conf.options.GAMEDIR) + if conf.env.DEST_OS != 'win32': conf.check_cc(lib='dl', mandatory=False) if not conf.env.LIB_M: # HACK: already added in xcompile! conf.check_cc(lib='m') + + if conf.env.DEST_OS == 'android': + conf.check_cc(lib='log') else: # Common Win32 libraries # Don't check them more than once, to save time @@ -300,17 +316,32 @@ def configure(conf): # set _FILE_OFFSET_BITS=64 for filesystems with 64-bit inodes if conf.env.DEST_OS != 'win32' and conf.env.DEST_SIZEOF_VOID_P == 4: - file_offset_bits_usable = conf.check_cc(fragment='''#define _FILE_OFFSET_BITS 64 - #include - #ifndef __USE_FILE_OFFSET64 - #error - #endif - int main(void){ return 0; }''', + # check was borrowed from libarchive source code + file_offset_bits_usable = conf.check_cc(fragment=''' +#define _FILE_OFFSET_BITS 64 +#include +#define KB ((off_t)1024) +#define MB ((off_t)1024 * KB) +#define GB ((off_t)1024 * MB) +#define TB ((off_t)1024 * GB) +int t2[(((64 * GB -1) % 671088649) == 268434537) + && (((TB - (64 * GB -1) + 255) % 1792151290) == 305159546)? 1: -1]; +int main(void) { return 0; }''', msg='Checking if _FILE_OFFSET_BITS can be defined to 64', mandatory=False) if file_offset_bits_usable: conf.define('_FILE_OFFSET_BITS', 64) else: conf.undefine('_FILE_OFFSET_BITS') + if conf.env.DEST_OS != 'win32': + strcasestr_frag = '''#include +int main(int argc, char **argv) { strcasestr(argv[1], argv[2]); return 0; }''' + + if conf.check_cc(msg='Checking for strcasestr', mandatory=False, fragment=strcasestr_frag): + conf.define('HAVE_STRCASESTR', 1) + elif conf.check_cc(msg='... with _GNU_SOURCE?', mandatory=False, fragment=strcasestr_frag, defines='_GNU_SOURCE=1'): + conf.define('_GNU_SOURCE', 1) + conf.define('HAVE_STRCASESTR', 1) + # check if we can use alloca.h or malloc.h if conf.check_cc(header_name='alloca.h', mandatory=False): conf.define('ALLOCA_H', 'alloca.h') @@ -319,7 +350,7 @@ def configure(conf): # indicate if we are packaging for Linux/BSD if conf.options.PACKAGING: - conf.env.LIBDIR = conf.env.BINDIR = '${PREFIX}/lib/xash3d' + conf.env.LIBDIR = conf.env.BINDIR = conf.env.LIBDIR + '/xash3d' conf.env.SHAREDIR = '${PREFIX}/share/xash3d' else: if sys.platform != 'win32' and not conf.env.DEST_OS == 'android': @@ -327,6 +358,17 @@ def configure(conf): conf.env.SHAREDIR = conf.env.LIBDIR = conf.env.BINDIR = conf.env.PREFIX + if not conf.options.BUILD_BUNDLED_DEPS: + # check if we can use system opus + conf.define('CUSTOM_MODES', 1) + + # try to link with export that only exists with CUSTOM_MODES defined + if conf.check_pkg('opus', 'opus', '''#include +int main(void){ return !opus_custom_encoder_init(0, 0, 0); }''', fatal = False): + conf.env.HAVE_SYSTEM_OPUS = True + else: + conf.undefine('CUSTOM_MODES') + conf.define('XASH_BUILD_COMMIT', conf.env.GIT_VERSION if conf.env.GIT_VERSION else 'notset') conf.define('XASH_LOW_MEMORY', conf.options.LOW_MEMORY) @@ -337,7 +379,13 @@ def configure(conf): conf.add_subproject(i.name) def build(bld): + # don't clean QtCreator files and reconfigure saved options + bld.clean_files = bld.bldnode.ant_glob('**', + excl='*.user configuration.py .lock* *conf_check_*/** config.log %s/*' % Build.CACHE_DIR, + quiet=True, generator=True) + bld.load('xshlib') + for i in SUBDIRS: if not i.is_enabled(bld): continue