diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a8f23722..7586564b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,7 +24,7 @@ jobs: - Release steps: - name: Check out files - uses: actions/checkout@v3 + uses: actions/checkout@v3.3.0 with: submodules: true fetch-depth: 0 @@ -32,7 +32,7 @@ jobs: lfs: false - name: Add msbuild to PATH - uses: microsoft/setup-msbuild@v1.1.3 + uses: microsoft/setup-msbuild@v1.3.1 - name: Generate project files run: tools/premake5 vs2022 @@ -44,7 +44,7 @@ jobs: run: msbuild /m /v:minimal /p:Configuration=${{matrix.configuration}} /p:Platform=Win32 build/iw4x.sln - name: Upload ${{matrix.configuration}} binaries - uses: actions/upload-artifact@v3.1.0 + uses: actions/upload-artifact@v3.1.2 with: name: ${{matrix.configuration}} binaries path: | @@ -66,13 +66,13 @@ jobs: run: echo "XLABS_MASTER_PATH=${{ secrets.XLABS_MASTER_SSH_PATH_DEV }}" >> $GITHUB_ENV - name: Download Release binaries - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v3.0.2 with: name: Release binaries # Set up committer info and GPG key - name: Install SSH key - uses: shimataro/ssh-key-action@v2 + uses: shimataro/ssh-key-action@v2.5.0 with: key: ${{ secrets.XLABS_MASTER_SSH_PRIVATE_KEY }} known_hosts: 'just-a-placeholder-so-we-dont-get-errors' diff --git a/.gitmodules b/.gitmodules index 4387e293..95e898f6 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,37 +1,46 @@ -[submodule "deps/zlib"] - path = deps/zlib - url = https://github.com/madler/zlib.git - branch = develop -[submodule "deps/libtommath"] - path = deps/libtommath - url = https://github.com/libtom/libtommath.git - branch = develop -[submodule "deps/libtomcrypt"] - path = deps/libtomcrypt - url = https://github.com/libtom/libtomcrypt.git - branch = develop -[submodule "deps/pdcurses"] - path = deps/pdcurses - url = https://github.com/wmcbrine/PDCurses.git - branch = master -[submodule "deps/mongoose"] - path = deps/mongoose - url = https://github.com/cesanta/mongoose.git - branch = master -[submodule "deps/protobuf"] - path = deps/protobuf - url = https://github.com/google/protobuf.git - branch = 3.20.x -[submodule "deps/udis86"] - path = deps/udis86 - url = https://github.com/vmt/udis86.git -[submodule "deps/dxsdk"] - path = deps/dxsdk - url = https://github.com/devKlausS/dxsdk.git -[submodule "deps/GSL"] - path = deps/GSL - url = https://github.com/microsoft/GSL.git -[submodule "deps/nlohmannjson"] - path = deps/json - url = https://github.com/nlohmann/json.git - branch = v3.11.2 +[submodule "deps/zlib"] + path = deps/zlib + url = https://github.com/madler/zlib.git + branch = develop +[submodule "deps/libtommath"] + path = deps/libtommath + url = https://github.com/libtom/libtommath.git + branch = develop +[submodule "deps/libtomcrypt"] + path = deps/libtomcrypt + url = https://github.com/libtom/libtomcrypt.git + branch = develop +[submodule "deps/pdcurses"] + path = deps/pdcurses + url = https://github.com/wmcbrine/PDCurses.git + branch = master +[submodule "deps/mongoose"] + path = deps/mongoose + url = https://github.com/cesanta/mongoose.git + branch = 7.9 +[submodule "deps/protobuf"] + path = deps/protobuf + url = https://github.com/google/protobuf.git + branch = 3.20.x +[submodule "deps/udis86"] + path = deps/udis86 + url = https://github.com/vmt/udis86.git +[submodule "deps/dxsdk"] + path = deps/dxsdk + url = https://github.com/devKlausS/dxsdk.git +[submodule "deps/GSL"] + path = deps/GSL + url = https://github.com/microsoft/GSL.git +[submodule "deps/nlohmannjson"] + path = deps/json + url = https://github.com/nlohmann/json.git + branch = v3.11.2 +[submodule "deps/discord-rpc"] + path = deps/discord-rpc + url = https://github.com/discord/discord-rpc.git +[submodule "deps/rapidjson"] + path = deps/rapidjson + url = https://github.com/Tencent/rapidjson.git +[submodule "deps/iw4-open-formats"] + path = deps/iw4-open-formats + url = https://github.com/XLabsProject/iw4-open-formats.git diff --git a/CHANGELOG.md b/CHANGELOG.md index 358e25a1..52eaa35c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,49 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog v0.3.0](http://keepachangelog.com/en/0.3.0/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [0.7.8] - 2023-03-17 + +### Added +- Clients can unprotect "saved" Dvars using the command line argument `-unprotect-dvars` (#694) +- Clients can protect all Dvars from being modified by the server using the command line argument `-protect-dvars` (#823) +- Add helpful information to script-related errors (#721) +- Add command line `-disable-mongoose` to disable IW4x's built-in webserver (#728) +- Add command line `-disable-rate-limit-check` to disable the rate check on RCon requests (#769) +- Muted clients GUID is saved to the `muted-users.json` file in the `userraw` folder (#732) +- Add `sv_nextMap` Dvar (#736) +- Add `elifdef` and `elifndef` directives to the menu preprocessor (#747) +- Add `r_drawRunners` Dvar (#758) +- Add `r_drawLights` Dvar (#814) +- Add Discord Rich Presence (#761) +- Add `rcon_timeout` Dvar (#769) +- Add `sv_clanName` Dvar (#771) +- Add `InitialWeaponRaise` GSC method (#804) +- Add `RconWhitelistAdd` SV Command (#804) +- Add more options to `bg_bouncesAllAngles` Dvar (#806) +- Add `GetChar` GSC function (#813) +- Add `bg_bunnyHopAuto` Dvar (#818) +- Add `bg_disableLandingSlowdown` Dvar (#818) +- Add new map porting utility tool that makes the map porting process between CoD8 to CoD6 easy + +### Changed +- Servers can no longer modify client Dvars that are saved in the client's config (#694) +- `banClient` and `muteClient` server commands do not apply to bots anymore (#730) +- Remove `zb_prefer_disk_assets` Dvar (#772) +- The max value of `perk_extendedMeleeRange`Dvar was increased (#782) + +### Fixed +- Fix bug where`reloadmenus` command would not free resources used by custom menus (#740) +- Fix bug where demo playback would stop when opening a laptop based killstreak (#699) +- Fix bug where mod download speed was inexplicably slow (#707) +- Fix bug where `g_hardcore` could not be set by servers (#708) +- Fix bots "user cmd angles" (#707) +- Fix bug where `FileRead` GSC function could return buffers that are too big (#767) +- Fix bug where the `OnPlayerSay` GSC function would cause issues (#776) + +### Known issues + +- Sound issue fix is experimental as the bug is not fully understood. + ## [0.7.7] - 2022-12-31 ### Added diff --git a/README.md b/README.md index fa6f3612..d7a32313 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ | `-version` | Print IW4x build info on startup. | | `-zonebuilder` | Start the interactive zonebuilder tool console instead of starting the game. | | `-nosteam` | Disable friends feature and do not update Steam about the game's current status just like an invisible mode. | -| `-protect-saved-dvars` | Block the server from modifying saved/archive dvars. | +| `-unprotect-dvars` | Allow the server to modify saved/archive dvars. | ## Disclaimer diff --git a/deps/GSL b/deps/GSL index f94c1f6f..43d60c5e 160000 --- a/deps/GSL +++ b/deps/GSL @@ -1 +1 @@ -Subproject commit f94c1f6f2b5e141d5f6eb3d284cd4a8cf9a81aac +Subproject commit 43d60c5e3891dab6491a76d0bac554a4a89d57f6 diff --git a/deps/discord-rpc b/deps/discord-rpc new file mode 160000 index 00000000..963aa9f3 --- /dev/null +++ b/deps/discord-rpc @@ -0,0 +1 @@ +Subproject commit 963aa9f3e5ce81a4682c6ca3d136cddda614db33 diff --git a/deps/iw4-open-formats b/deps/iw4-open-formats new file mode 160000 index 00000000..cf45b460 --- /dev/null +++ b/deps/iw4-open-formats @@ -0,0 +1 @@ +Subproject commit cf45b460fe32a8c858b30445df779e29821cfdee diff --git a/deps/libtomcrypt b/deps/libtomcrypt index 29986d04..2a1b2846 160000 --- a/deps/libtomcrypt +++ b/deps/libtomcrypt @@ -1 +1 @@ -Subproject commit 29986d04f2dca985ee64fbca1c7431ea3e3422f4 +Subproject commit 2a1b284677a51f587ab7cd9d97395e0c0c93a447 diff --git a/deps/mongoose b/deps/mongoose index 73813a83..4236405b 160000 --- a/deps/mongoose +++ b/deps/mongoose @@ -1 +1 @@ -Subproject commit 73813a838386f6ebca447eb54c626803163ee257 +Subproject commit 4236405b90e051310aadda818e21c811e404b4d8 diff --git a/deps/premake/discord-rpc.lua b/deps/premake/discord-rpc.lua new file mode 100644 index 00000000..34f3ee4d --- /dev/null +++ b/deps/premake/discord-rpc.lua @@ -0,0 +1,40 @@ +discordrpc = { + source = path.join(dependencies.basePath, "discord-rpc"), +} + +function discordrpc.import() + links "discord-rpc" + discordrpc.includes() +end + +function discordrpc.includes() + includedirs { + path.join(discordrpc.source, "include"), + } +end + +function discordrpc.project() + project "discord-rpc" + language "C++" + cppdialect "C++17" + + discordrpc.includes() + rapidjson.import(); + + files { + path.join(discordrpc.source, "src/*.h"), + path.join(discordrpc.source, "src/*.cpp"), + } + + removefiles { + path.join(discordrpc.source, "src/dllmain.cpp"), + path.join(discordrpc.source, "src/*_linux.cpp"), + path.join(discordrpc.source, "src/*_unix.cpp"), + path.join(discordrpc.source, "src/*_osx.cpp"), + } + + warnings "Off" + kind "StaticLib" +end + +table.insert(dependencies, discordrpc) diff --git a/deps/premake/fonts.lua b/deps/premake/fonts.lua index 6385cc8e..9d6cccfc 100644 --- a/deps/premake/fonts.lua +++ b/deps/premake/fonts.lua @@ -1,4 +1,6 @@ -fonts = {} +fonts = { + source = path.join(dependencies.basePath, "extra/font"), +} function fonts.import() fonts.includes() @@ -6,7 +8,7 @@ end function fonts.includes() includedirs { - path.join(dependencies.basePath, "extra/font"), + fonts.source, } end @@ -17,7 +19,7 @@ function fonts.project() fonts.includes() files { - path.join(dependencies.basePath, "extra/font/*.hpp"), + path.join(fonts.source, "Terminus_4.49.1.ttf.hpp"), } warnings "Off" diff --git a/deps/premake/gsl.lua b/deps/premake/gsl.lua index 7a2daf64..79309bbd 100644 --- a/deps/premake/gsl.lua +++ b/deps/premake/gsl.lua @@ -8,7 +8,7 @@ end function gsl.includes() includedirs { - path.join(gsl.source, "include") + path.join(gsl.source, "include"), } end diff --git a/deps/premake/iw4-open-formats.lua b/deps/premake/iw4-open-formats.lua new file mode 100644 index 00000000..9b6751da --- /dev/null +++ b/deps/premake/iw4-open-formats.lua @@ -0,0 +1,44 @@ +iw4_open_formats = { + source = path.join(dependencies.basePath, "iw4-open-formats"), +} + +function iw4_open_formats.import() + links "iw4-open-formats" + + iw4_open_formats.includes() +end + +function iw4_open_formats.includes() + includedirs { + path.join(iw4_open_formats.source, "include") + } +end + +function iw4_open_formats.project() + project "iw4-open-formats" + language "C++" + + iw4_open_formats.includes() + + pchheader "std_include.hpp" + pchsource (path.join(iw4_open_formats.source, "src/iw4-of/std_include.cpp")) + + files { + path.join(iw4_open_formats.source, "src/iw4-of/**.hpp"), + path.join(iw4_open_formats.source, "src/iw4-of/**.cpp"), + } + + includedirs { + path.join(iw4_open_formats.source, "src/iw4-of"), + path.join(iw4_open_formats.source, "include"), + } + + libtomcrypt.includes() + libtommath.includes() + rapidjson.includes() + zlib.includes() + + kind "StaticLib" +end + +table.insert(dependencies, iw4_open_formats) diff --git a/deps/premake/mongoose.lua b/deps/premake/mongoose.lua index edea2692..aa29a89e 100644 --- a/deps/premake/mongoose.lua +++ b/deps/premake/mongoose.lua @@ -3,13 +3,15 @@ mongoose = { } function mongoose.import() - links {"mongoose"} + links "mongoose" mongoose.includes() end function mongoose.includes() - includedirs {mongoose.source} + includedirs { + mongoose.source, + } end function mongoose.project() diff --git a/deps/premake/protobuf.lua b/deps/premake/protobuf.lua index a293576f..e0aaf0a7 100644 --- a/deps/premake/protobuf.lua +++ b/deps/premake/protobuf.lua @@ -38,10 +38,6 @@ function protobuf.project() } rules {"ProtobufCompiler"} - - defines {"_SCL_SECURE_NO_WARNINGS"} - - linkoptions {"-IGNORE:4221"} warnings "Off" kind "StaticLib" diff --git a/deps/premake/rapidjson.lua b/deps/premake/rapidjson.lua new file mode 100644 index 00000000..d1085120 --- /dev/null +++ b/deps/premake/rapidjson.lua @@ -0,0 +1,19 @@ +rapidjson = { + source = path.join(dependencies.basePath, "rapidjson"), +} + +function rapidjson.import() + rapidjson.includes() +end + +function rapidjson.includes() + includedirs { + path.join(rapidjson.source, "include"), + } +end + +function rapidjson.project() + +end + +table.insert(dependencies, rapidjson) diff --git a/deps/protobuf b/deps/protobuf index 8d5fdedd..53a1c5c1 160000 --- a/deps/protobuf +++ b/deps/protobuf @@ -1 +1 @@ -Subproject commit 8d5fdedd42ef361dcfc1531fba4f33470273f375 +Subproject commit 53a1c5c1d8b61984899b6877e491e5117ad486ba diff --git a/deps/rapidjson b/deps/rapidjson new file mode 160000 index 00000000..012be852 --- /dev/null +++ b/deps/rapidjson @@ -0,0 +1 @@ +Subproject commit 012be8528783cdbf4b7a9e64f78bd8f056b97e24 diff --git a/deps/zlib b/deps/zlib index 02a6049e..eb0e038b 160000 --- a/deps/zlib +++ b/deps/zlib @@ -1 +1 @@ -Subproject commit 02a6049eb3884c430268bb0fe3296d597a03174c +Subproject commit eb0e038b297f2c9877ed8b3515c6718a4b65d485 diff --git a/lib/include/stb_truetype.h b/lib/include/stb_truetype.hpp similarity index 99% rename from lib/include/stb_truetype.h rename to lib/include/stb_truetype.hpp index f6ab5b01..62595a15 100644 --- a/lib/include/stb_truetype.h +++ b/lib/include/stb_truetype.hpp @@ -5008,4 +5008,4 @@ AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------ -*/ \ No newline at end of file +*/ diff --git a/src/Components/Loader.cpp b/src/Components/Loader.cpp index 5c244e66..42189495 100644 --- a/src/Components/Loader.cpp +++ b/src/Components/Loader.cpp @@ -1,4 +1,5 @@ #include +#include #include "Modules/Bans.hpp" #include "Modules/Bots.hpp" @@ -14,15 +15,18 @@ #include "Modules/Console.hpp" #include "Modules/D3D9Ex.hpp" #include "Modules/Debug.hpp" +#include "Modules/Discord.hpp" #include "Modules/Discovery.hpp" #include "Modules/Download.hpp" #include "Modules/Elevators.hpp" +#include "Modules/FastFiles.hpp" #include "Modules/Gamepad.hpp" #include "Modules/Lean.hpp" #include "Modules/MapDump.hpp" #include "Modules/MapRotation.hpp" #include "Modules/NetworkDebug.hpp" #include "Modules/News.hpp" +#include "Modules/Party.hpp" #include "Modules/PlayerMovement.hpp" #include "Modules/PlayerName.hpp" #include "Modules/Playlist.hpp" @@ -44,12 +48,13 @@ #include "Modules/Theatre.hpp" #include "Modules/Threading.hpp" #include "Modules/UIFeeder.hpp" -#include "Modules/UserInfo.hpp" #include "Modules/VisionFile.hpp" #include "Modules/Voice.hpp" #include "Modules/Vote.hpp" #include "Modules/Weapon.hpp" +#include "Modules/BotLib/lPrecomp.hpp" + namespace Components { bool Loader::Pregame = true; @@ -79,14 +84,16 @@ namespace Components Uninitializing = false; Utils::Memory::GetAllocator()->clear(); + // High priority + Register(new Singleton()); + Register(new Auth()); Register(new Command()); Register(new Dvar()); - Register(new Exception()); // Install our exception handler as early as posssible to get better debug dumps from startup crashes - Register(new Flags()); + Register(new Exception()); // Install our exception handler as early as possible to get better debug dumps from startup crashes + Register(new IPCPipe()); Register(new Network()); Register(new Logger()); - Register(new Singleton()); Register(new UIScript()); Register(new ZoneBuilder()); @@ -107,6 +114,7 @@ namespace Components Register(new D3D9Ex()); Register(new Debug()); Register(new Dedicated()); + Register(new Discord()); Register(new Discovery()); Register(new Download()); Register(new Elevators()); @@ -115,7 +123,6 @@ namespace Components Register(new FileSystem()); Register(new Friends()); Register(new Gamepad()); - Register(new IPCPipe()); Register(new Lean()); Register(new Localization()); Register(new MapDump()); @@ -154,7 +161,6 @@ namespace Components Register(new Threading()); Register(new Toast()); Register(new UIFeeder()); - Register(new UserInfo()); Register(new VisionFile()); Register(new Voice()); Register(new Vote()); @@ -162,7 +168,9 @@ namespace Components Register(new Window()); Register(new Zones()); - Register(new GSC()); + Register(new GSC::GSC()); + + Register(new BotLib::lPrecomp()); Pregame = false; diff --git a/src/Components/Loader.hpp b/src/Components/Loader.hpp index 4957c9df..7fc7d59c 100644 --- a/src/Components/Loader.hpp +++ b/src/Components/Loader.hpp @@ -77,7 +77,6 @@ namespace Components #include "Modules/AssetHandler.hpp" #include "Modules/Dedicated.hpp" #include "Modules/Events.hpp" -#include "Modules/FastFiles.hpp" #include "Modules/FileSystem.hpp" #include "Modules/Friends.hpp" #include "Modules/IPCPipe.hpp" @@ -88,7 +87,6 @@ namespace Components #include "Modules/ModList.hpp" #include "Modules/ModelSurfs.hpp" #include "Modules/Node.hpp" -#include "Modules/Party.hpp" #include "Modules/Renderer.hpp" #include "Modules/Scheduler.hpp" #include "Modules/TextRenderer.hpp" diff --git a/src/Components/Modules/ArenaLength.cpp b/src/Components/Modules/ArenaLength.cpp index d7a94f09..3c0c2a93 100644 --- a/src/Components/Modules/ArenaLength.cpp +++ b/src/Components/Modules/ArenaLength.cpp @@ -2,7 +2,10 @@ namespace Components { - Game::newMapArena_t ArenaLength::NewArenas[128]; + constexpr auto NEW_ARENA_COUNT = 128; + + Game::newMapArena_t ArenaLength::NewArenas[NEW_ARENA_COUNT]; + char* ArenaLength::NewArenaInfos[NEW_ARENA_COUNT]; __declspec(naked) void ArenaLength::ArenaMapOffsetHook1() { @@ -57,16 +60,28 @@ namespace Components ArenaLength::ArenaLength() { - // Reallocate array - Utils::Hook::Set(0x417807, &ArenaLength::NewArenas[0]); - Utils::Hook::Set(0x420717, &ArenaLength::NewArenas[0]); - Utils::Hook::Set(0x49BD22, &ArenaLength::NewArenas[0]); - Utils::Hook::Set(0x4A9649, &ArenaLength::NewArenas[0]); - Utils::Hook::Set(0x4A97C2, &ArenaLength::NewArenas[0]); - Utils::Hook::Set(0x4D077E, &ArenaLength::NewArenas[0]); - Utils::Hook::Set(0x630B00, &ArenaLength::NewArenas[0]); - Utils::Hook::Set(0x630B2E, &ArenaLength::NewArenas[0]); - Utils::Hook::Set(0x632782, &ArenaLength::NewArenas[0]); + // Reallocate ui_arenaInfos + Utils::Hook::Set(0x4A95F0 + 3, ArenaLength::NewArenaInfos); + Utils::Hook::Set(0x4A9620 + 3, ArenaLength::NewArenaInfos); + Utils::Hook::Set(0x4A9653 + 3, ArenaLength::NewArenaInfos); + Utils::Hook::Set(0x4A9684 + 3, ArenaLength::NewArenaInfos); + Utils::Hook::Set(0x4A96B7 + 3, ArenaLength::NewArenaInfos); + Utils::Hook::Set(0x4A97B3 + 3, ArenaLength::NewArenaInfos); + Utils::Hook::Set(0x630A9A + 3, ArenaLength::NewArenaInfos); + + // Increase size - patch max arena count + Utils::Hook::Set(0x630AA2 + 1, NEW_ARENA_COUNT); + + // Reallocate sharedUiInfo.mapList + Utils::Hook::Set(0x417807, ArenaLength::NewArenas); + Utils::Hook::Set(0x420717, ArenaLength::NewArenas); + Utils::Hook::Set(0x49BD22, ArenaLength::NewArenas); + Utils::Hook::Set(0x4A9649, ArenaLength::NewArenas); + Utils::Hook::Set(0x4A97C2, ArenaLength::NewArenas); + Utils::Hook::Set(0x4D077E, ArenaLength::NewArenas); + Utils::Hook::Set(0x630B00, ArenaLength::NewArenas); + Utils::Hook::Set(0x630B2E, ArenaLength::NewArenas); + Utils::Hook::Set(0x632782, ArenaLength::NewArenas); Utils::Hook::Set(0x4A967A, ArenaLength::NewArenas[0].description); Utils::Hook::Set(0x4A96AD, ArenaLength::NewArenas[0].mapimage); @@ -109,12 +124,7 @@ namespace Components Utils::Hook(0x632799, ArenaLength::ArenaMapOffsetHook3, HOOK_JUMP).install()->quick(); Utils::Hook(0x4064B2, ArenaLength::ArenaMapOffsetHook4, HOOK_JUMP).install()->quick(); - Utils::Hook::Set(0x4A95F8, 32); - + Utils::Hook::Set(0x4A95F8, sizeof(Game::newMapArena_t::mapName)); Utils::Hook::Set(0x42F22B, offsetof(Game::newMapArena_t, mapName) - offsetof(Game::newMapArena_t, other)); - - // patch max arena count - constexpr auto arenaCount = sizeof(ArenaLength::NewArenas) / sizeof(Game::newMapArena_t); - Utils::Hook::Set(0x630AA3, arenaCount); } } diff --git a/src/Components/Modules/ArenaLength.hpp b/src/Components/Modules/ArenaLength.hpp index f495b5c7..7b1c7eee 100644 --- a/src/Components/Modules/ArenaLength.hpp +++ b/src/Components/Modules/ArenaLength.hpp @@ -7,7 +7,8 @@ namespace Components public: ArenaLength(); - static Game::newMapArena_t NewArenas[128]; + static Game::newMapArena_t NewArenas[]; + static char* NewArenaInfos[]; private: static void ArenaMapOffsetHook1(); diff --git a/src/Components/Modules/AssetHandler.cpp b/src/Components/Modules/AssetHandler.cpp index baf089bf..825fb0f0 100644 --- a/src/Components/Modules/AssetHandler.cpp +++ b/src/Components/Modules/AssetHandler.cpp @@ -1,4 +1,5 @@ #include +#include "FastFiles.hpp" #include "Weapon.hpp" #include "AssetInterfaces/IFont_s.hpp" @@ -570,210 +571,6 @@ namespace Components AssetHandler::OnLoad([](Game::XAssetType type, Game::XAssetHeader asset, std::string name, bool*) { -#ifdef DEBUG -// #define DUMP_TECHSETS -#ifdef DUMP_TECHSETS - if (type == Game::XAssetType::ASSET_TYPE_VERTEXDECL && !name.empty() && name[0] != ',') - { - std::filesystem::create_directories("techsets/vertexdecl"); - - auto vertexdecl = asset.vertexDecl; - - std::vector routingData; - for (int i = 0; i < vertexdecl->streamCount; i++) - { - routingData.push_back(json11::Json::object - { - { "source", (int)vertexdecl->routing.data[i].source }, - { "dest", (int)vertexdecl->routing.data[i].dest }, - }); - } - - std::vector declData; - for (int i = 0; i < 16; i++) - { - if (vertexdecl->routing.decl[i]) - { - routingData.push_back(int(vertexdecl->routing.decl[i])); - } - else - { - routingData.push_back(nullptr); - } - } - - nlohmann::json vertexData = json11::Json::object - { - { "name", vertexdecl->name }, - { "streamCount", vertexdecl->streamCount }, - { "hasOptionalSource", vertexdecl->hasOptionalSource }, - { "routing", routingData }, - { "decl", declData }, - }; - - auto stringData = vertexData.dump(); - - auto fp = fopen(Utils::String::VA("techsets/vertexdecl/%s.%s.json", vertexdecl->name, Zones::Version() > 276 ? "codo" : "iw4"), "wb"); - fwrite(&stringData[0], stringData.size(), 1, fp); - fclose(fp); - } - - if (type == Game::ASSET_TYPE_TECHNIQUE_SET && !name.empty() && name[0] != ',') - { - std::filesystem::create_directory("techsets"); - std::filesystem::create_directories("techsets/techniques"); - - auto techset = asset.techniqueSet; - - std::vector techniques; - for (int technique = 0; technique < 48; technique++) - { - auto curTech = techset->techniques[technique]; - if (curTech) - { - std::vector passDataArray; - for (int pass = 0; pass < curTech->passCount; pass++) - { - auto curPass = &curTech->passArray[pass]; - - std::vector argDataArray; - for (int arg = 0; arg < curPass->perObjArgCount + curPass->perPrimArgCount + curPass->stableArgCount; arg++) - { - auto curArg = &curPass->args[arg]; - - if (curArg->type == 1 || curArg->type == 7) - { - std::vector literalConsts; - if (curArg->u.literalConst != 0) - { - literalConsts.push_back(curArg->u.literalConst[0]); - literalConsts.push_back(curArg->u.literalConst[1]); - literalConsts.push_back(curArg->u.literalConst[2]); - literalConsts.push_back(curArg->u.literalConst[3]); - } - - nlohmann::json argData = json11::Json::object - { - { "type", curArg->type }, - { "value", literalConsts }, - }; - argDataArray.push_back(argData); - } - else if (curArg->type == 3 || curArg->type == 5) - { - nlohmann::json argData = json11::Json::object - { - { "type", curArg->type }, - { "firstRow", curArg->u.codeConst.firstRow }, - { "rowCount", curArg->u.codeConst.rowCount }, - { "index", curArg->u.codeConst.index }, - }; - argDataArray.push_back(argData); - } - else - { - nlohmann::json argData = json11::Json::object - { - { "type", curArg->type }, - { "value", static_cast(curArg->u.codeSampler) }, - }; - argDataArray.push_back(argData); - } - } - - nlohmann::json passData = json11::Json::object - { - { "perObjArgCount", curPass->perObjArgCount }, - { "perPrimArgCount", curPass->perPrimArgCount }, - { "stableArgCount", curPass->stableArgCount }, - { "args", argDataArray }, - { "pixelShader", curPass->pixelShader ? curPass->pixelShader->name : "" }, - { "vertexShader", curPass->vertexShader ? curPass->vertexShader->name : "" }, - { "vertexDecl", curPass->vertexDecl ? curPass->vertexDecl->name : "" }, - }; - passDataArray.push_back(passData); - } - - nlohmann::json techData = json11::Json::object - { - { "name", curTech->name }, - { "index", technique }, - { "flags", curTech->flags }, - { "numPasses", curTech->passCount }, - { "pass", passDataArray }, - }; - - auto stringData = techData.dump(); - - auto fp = fopen(Utils::String::VA("techsets/techniques/%s.%s.json", curTech->name, Zones::Version() > 276 ? "codo" : "iw4"), "wb"); - fwrite(&stringData[0], stringData.size(), 1, fp); - fclose(fp); - - nlohmann::json techsetTechnique = json11::Json::object - { - { "name", curTech->name }, - { "index", technique }, - }; - techniques.push_back(techsetTechnique); - } - else - { - techniques.push_back(nullptr); - } - } - - nlohmann::json techsetData = json11::Json::object - { - { "name", techset->name }, - { "techniques", techniques }, - }; - - auto stringData = techsetData.dump(); - - auto fp = fopen(Utils::String::VA("techsets/%s.%s.json", techset->name, Zones::Version() > 276 ? "codo" : "iw4"), "wb"); - fwrite(&stringData[0], stringData.size(), 1, fp); - fclose(fp); - } -#endif - - if (type == Game::XAssetType::ASSET_TYPE_TECHNIQUE_SET && Zones::Version() >= 460) - { - auto techset = asset.techniqueSet; - if (techset) - { - for (int t = 0; t < 48; t++) - { - if (techset->techniques[t]) - { - for (int p = 0; p < techset->techniques[t]->passCount; p++) - { - for (int a = 0; a < techset->techniques[t]->passArray[p].perObjArgCount + - techset->techniques[t]->passArray[p].perPrimArgCount + - techset->techniques[t]->passArray[p].stableArgCount; a++) - { - auto arg = &techset->techniques[t]->passArray[p].args[a]; - if (arg->type == 3 || arg->type == 5) - { - if (arg->u.codeConst.index > 140) - { - OutputDebugStringA(Utils::String::VA("codeConst %i is out of range for %s::%s[%i]\n", arg->u.codeConst.index, - techset->name, techset->techniques[t]->name, p)); - - if (!ZoneBuilder::IsEnabled()) - { - __debugbreak(); - } - } - } - } - } - } - } - } - } - -#endif - if (Dvar::Var("r_noVoid").get() && type == Game::XAssetType::ASSET_TYPE_XMODEL && name == "void") { asset.model->numLods = 0; diff --git a/src/Components/Modules/AssetInterfaces/IComWorld.cpp b/src/Components/Modules/AssetInterfaces/IComWorld.cpp index ae7aff94..c6584170 100644 --- a/src/Components/Modules/AssetInterfaces/IComWorld.cpp +++ b/src/Components/Modules/AssetInterfaces/IComWorld.cpp @@ -7,52 +7,7 @@ namespace Assets { void IComWorld::load(Game::XAssetHeader* header, const std::string& _name, Components::ZoneBuilder::Zone* builder) { - std::string name = _name; - Utils::String::Replace(name, "maps/mp/", ""); - Utils::String::Replace(name, ".d3dbsp", ""); - - Components::FileSystem::File mapFile(std::format("comworld/{}.iw4xComWorld", name)); - - if (mapFile.exists()) - { - Utils::Stream::Reader reader(builder->getAllocator(), mapFile.getBuffer()); - - __int64 magic = reader.read<__int64>(); - if (std::memcmp(&magic, "IW4xComW", 8)) - { - Components::Logger::Error(Game::ERR_FATAL, "Reading comworld '{}' failed, header is invalid!", name); - } - - int version = reader.read(); - if (version != IW4X_COMMAP_VERSION) - { - Components::Logger::Error(Game::ERR_FATAL, "Reading comworld '{}' failed, expected version is {}, but it was {}!", name, IW4X_COMMAP_VERSION, version); - } - - Game::ComWorld* asset = reader.readObject(); - header->comWorld = asset; - - if (asset->name) - { - asset->name = reader.readCString(); - } - - if (asset->primaryLights) - { - asset->primaryLights = reader.readArray(asset->primaryLightCount); - - for (unsigned int i = 0; i < asset->primaryLightCount; ++i) - { - Game::ComPrimaryLight* light = &asset->primaryLights[i]; - - if (light->defName) - { - light->defName = reader.readCString(); - Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_LIGHT_DEF, light->defName, builder); - } - } - } - } + header->comWorld = builder->getIW4OfApi()->read(Game::XAssetType::ASSET_TYPE_COMWORLD, _name); } void IComWorld::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) diff --git a/src/Components/Modules/AssetInterfaces/IFont_s.cpp b/src/Components/Modules/AssetInterfaces/IFont_s.cpp index b2988164..c68d2a19 100644 --- a/src/Components/Modules/AssetInterfaces/IFont_s.cpp +++ b/src/Components/Modules/AssetInterfaces/IFont_s.cpp @@ -2,7 +2,7 @@ #include "IFont_s.hpp" #define STB_TRUETYPE_IMPLEMENTATION -#include +#include namespace Assets { @@ -262,9 +262,9 @@ namespace Assets AssertSize(Game::Font_s, 24); AssertSize(Game::Glyph, 24); - Utils::Stream* buffer = builder->getBuffer(); - Game::Font_s* asset = header.font; - Game::Font_s* dest = buffer->dest(); + auto* buffer = builder->getBuffer(); + auto* asset = header.font; + auto* dest = buffer->dest(); buffer->save(asset); diff --git a/src/Components/Modules/AssetInterfaces/IFxEffectDef.cpp b/src/Components/Modules/AssetInterfaces/IFxEffectDef.cpp index 994a6349..0d366ce2 100644 --- a/src/Components/Modules/AssetInterfaces/IFxEffectDef.cpp +++ b/src/Components/Modules/AssetInterfaces/IFxEffectDef.cpp @@ -1,15 +1,15 @@ #include #include "IFxEffectDef.hpp" -#define IW4X_FX_VERSION 1 - namespace Assets { void IFxEffectDef::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) { if (!header->data) this->loadEfx(header, name, builder); // Check if we have an editor fx - if (!header->data) this->loadBinary(header, name, builder); // Check if we need to import a new one into the game + if (!header->data) this->loadFromIW4OF(header, name, builder); // Check if we need to import a new one into the game if (!header->data /*&& !builder->isPrimaryAsset()*/) this->loadNative(header, name, builder); // Check if there is a native one + + assert(header->data); } void IFxEffectDef::loadFxElemVisuals(Game::FxElemVisuals* visuals, char elemType, Components::ZoneBuilder::Zone* builder, Utils::Stream::Reader* reader) @@ -21,7 +21,7 @@ namespace Assets { if (visuals->model) { - visuals->model = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_XMODEL, reader->readString().data(), builder).model; + visuals->model = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_XMODEL, reader->readString(), builder).model; } break; @@ -44,7 +44,7 @@ namespace Assets { if (visuals->effectDef.handle) { - visuals->effectDef.handle = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_FX, reader->readString().data(), builder).fx; + visuals->effectDef.handle = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_FX, reader->readString(), builder).fx; } break; @@ -54,7 +54,7 @@ namespace Assets { if (visuals->material) { - visuals->material = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_MATERIAL, reader->readString().data(), builder).material; + visuals->material = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_MATERIAL, reader->readString(), builder).material; } break; @@ -62,139 +62,9 @@ namespace Assets } } - void IFxEffectDef::loadBinary(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) + void IFxEffectDef::loadFromIW4OF(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) { - Components::FileSystem::File fxFile(std::format("fx/{}.iw4xFx", name)); - - if (fxFile.exists()) - { - Utils::Stream::Reader buffer(builder->getAllocator(), fxFile.getBuffer()); - - __int64 magic = buffer.read<__int64>(); - if (std::memcmp(&magic, "IW4xFx ", 8)) - { - Components::Logger::Error(Game::ERR_FATAL, "Reading fx '{}' failed, header is invalid!", name); - } - - int version = buffer.read(); - if (version != IW4X_FX_VERSION) - { - Components::Logger::Error(Game::ERR_FATAL, "Reading fx '{}' failed, expected version is {}, but it was {}!", name, IW4X_FX_VERSION, version); - } - - Game::FxEffectDef* asset = buffer.readObject(); - header->fx = asset; - - if (asset->name) - { - asset->name = buffer.readCString(); - } - - if (asset->elemDefs) - { - asset->elemDefs = buffer.readArray(asset->elemDefCountEmission + asset->elemDefCountLooping + asset->elemDefCountOneShot); - - for (int i = 0; i < (asset->elemDefCountEmission + asset->elemDefCountLooping + asset->elemDefCountOneShot); ++i) - { - Game::FxElemDef* elemDef = &asset->elemDefs[i]; - - if (elemDef->velSamples) - { - elemDef->velSamples = buffer.readArray(elemDef->velIntervalCount + 1); - } - - if (elemDef->visSamples) - { - elemDef->visSamples = buffer.readArray(elemDef->visStateIntervalCount + 1); - } - - // Save_FxElemDefVisuals - { - if (elemDef->elemType == Game::FX_ELEM_TYPE_DECAL) - { - if (elemDef->visuals.markArray) - { - elemDef->visuals.markArray = buffer.readArray(elemDef->visualCount); - - for (char j = 0; j < elemDef->visualCount; ++j) - { - if (elemDef->visuals.markArray[j].materials[0]) - { - elemDef->visuals.markArray[j].materials[0] = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_MATERIAL, buffer.readString().data(), builder).material; - } - - if (elemDef->visuals.markArray[j].materials[1]) - { - elemDef->visuals.markArray[j].materials[1] = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_MATERIAL, buffer.readString().data(), builder).material; - } - } - } - } - else if (elemDef->visualCount > 1) - { - if (elemDef->visuals.array) - { - elemDef->visuals.array = buffer.readArray(elemDef->visualCount); - - for (char j = 0; j < elemDef->visualCount; ++j) - { - this->loadFxElemVisuals(&elemDef->visuals.array[j], elemDef->elemType, builder, &buffer); - } - } - } - else - { - this->loadFxElemVisuals(&elemDef->visuals.instance, elemDef->elemType, builder, &buffer); - } - } - - if (elemDef->effectOnImpact.handle) - { - elemDef->effectOnImpact.handle = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_FX, buffer.readString().data(), builder).fx; - } - - if (elemDef->effectOnDeath.handle) - { - elemDef->effectOnDeath.handle = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_FX, buffer.readString().data(), builder).fx; - } - - if (elemDef->effectEmitted.handle) - { - elemDef->effectEmitted.handle = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_FX, buffer.readString().data(), builder).fx; - } - - // Save_FxElemExtendedDefPtr - { - - if (elemDef->elemType == Game::FX_ELEM_TYPE_TRAIL) - { - // Save_FxTrailDef - { - if (elemDef->extended.trailDef) - { - Game::FxTrailDef* trailDef = buffer.readObject(); - elemDef->extended.trailDef = trailDef; - - if (trailDef->verts) - { - trailDef->verts = buffer.readArray(trailDef->vertCount); - } - - if (trailDef->inds) - { - trailDef->inds = buffer.readArray(trailDef->indCount); - } - } - } - } - else if (elemDef->extended.trailDef) - { - Components::Logger::Error(Game::ERR_FATAL, "Fx element of type {} has traildef, that's impossible?\n", elemDef->elemType); - } - } - } - } - } + header->fx = builder->getIW4OfApi()->read(Game::XAssetType::ASSET_TYPE_FX, name); } void IFxEffectDef::loadEfx(Game::XAssetHeader* /*header*/, const std::string& name, Components::ZoneBuilder::Zone* /*builder*/) @@ -270,7 +140,7 @@ namespace Assets // TODO: Convert editor fx to real fx } #else - (name); + (void)name; #endif } diff --git a/src/Components/Modules/AssetInterfaces/IFxEffectDef.hpp b/src/Components/Modules/AssetInterfaces/IFxEffectDef.hpp index 9a877050..0ad259ed 100644 --- a/src/Components/Modules/AssetInterfaces/IFxEffectDef.hpp +++ b/src/Components/Modules/AssetInterfaces/IFxEffectDef.hpp @@ -17,7 +17,7 @@ namespace Assets void loadEfx(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder); void loadNative(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder); - void loadBinary(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder); + void loadFromIW4OF(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder); void loadFxElemVisuals(Game::FxElemVisuals* visuals, char elemType, Components::ZoneBuilder::Zone* builder, Utils::Stream::Reader* reader); }; diff --git a/src/Components/Modules/AssetInterfaces/IFxWorld.cpp b/src/Components/Modules/AssetInterfaces/IFxWorld.cpp index 6988543f..4b35cc40 100644 --- a/src/Components/Modules/AssetInterfaces/IFxWorld.cpp +++ b/src/Components/Modules/AssetInterfaces/IFxWorld.cpp @@ -187,6 +187,19 @@ namespace Assets } void IFxWorld::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) + { + if (!header->fxWorld) loadFromDisk(header, name, builder); + if (!header->fxWorld) generate(header, name, builder); + + assert(header->fxWorld); + } + + void IFxWorld::loadFromDisk(Game::XAssetHeader* header, const std::string& _name, Components::ZoneBuilder::Zone* builder) + { + header->fxWorld = builder->getIW4OfApi()->read(Game::XAssetType::ASSET_TYPE_FXWORLD, _name); + } + + void IFxWorld::generate(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) { Game::FxWorld* map = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_FXWORLD, name.data()).fxWorld; if (map) return; @@ -194,7 +207,7 @@ namespace Assets // Generate map = builder->getAllocator()->allocate(); map->name = builder->getAllocator()->duplicateString(name); - + // No glass for you! ZeroMemory(&map->glassSys, sizeof(map->glassSys)); diff --git a/src/Components/Modules/AssetInterfaces/IFxWorld.hpp b/src/Components/Modules/AssetInterfaces/IFxWorld.hpp index a19ddf56..4f1590c1 100644 --- a/src/Components/Modules/AssetInterfaces/IFxWorld.hpp +++ b/src/Components/Modules/AssetInterfaces/IFxWorld.hpp @@ -10,5 +10,7 @@ namespace Assets void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override; void mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override; void load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) override; + void loadFromDisk(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder); + void generate(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder); }; } diff --git a/src/Components/Modules/AssetInterfaces/IGameWorldMp.cpp b/src/Components/Modules/AssetInterfaces/IGameWorldMp.cpp index 1656d480..47444470 100644 --- a/src/Components/Modules/AssetInterfaces/IGameWorldMp.cpp +++ b/src/Components/Modules/AssetInterfaces/IGameWorldMp.cpp @@ -80,115 +80,6 @@ namespace Assets void IGameWorldMp::load(Game::XAssetHeader* header, const std::string& _name, Components::ZoneBuilder::Zone* builder) { - std::string name = _name; - Utils::String::Replace(name, "maps/mp/", ""); - Utils::String::Replace(name, ".d3dbsp", ""); - - Components::FileSystem::File gameWorld(std::format("gameworld/{}.iw4x.json", name)); - - if (gameWorld.exists()) - { - nlohmann::json gameWorldJson; - - try - { - - gameWorldJson = nlohmann::json::parse(gameWorld.getBuffer()); - } - catch (const std::exception& e) - { - Components::Logger::PrintError(Game::CON_CHANNEL_ERROR, "Invalid JSON for gameworld {}! {}", name, e.what()); - return; - } - - auto* asset = builder->getAllocator()->allocate(); - - if (!gameWorldJson.is_object()) - { - Components::Logger::PrintError(Game::CON_CHANNEL_ERROR, "Invalid GameWorldMp json for {}\n", name); - return; - } - - auto version = gameWorldJson["version"].is_number() ? gameWorldJson["version"].get() : 0; - if (version != IW4X_GAMEWORLD_VERSION) - { - Components::Logger::PrintError(Game::CON_CHANNEL_ERROR, "Invalid GameWorld json version for {}, expected {} and got {}\n", name, IW4X_GAMEWORLD_VERSION, version); - return; - } - - if (!gameWorldJson["name"].is_string()) - { - Components::Logger::PrintError(Game::CON_CHANNEL_ERROR, "Missing gameworld name! on {}\n", name); - return; - } - - asset->name = builder->getAllocator()->duplicateString(gameWorldJson["name"].get()); - auto glassData = builder->getAllocator()->allocate(); - - if (gameWorldJson["glassData"].is_object()) - { - auto jsonGlassData = gameWorldJson["glassData"]; - - try - { - glassData->damageToDestroy = jsonGlassData["damageToDestroy"].get(); - glassData->damageToWeaken = jsonGlassData["damageToWeaken"].get(); - - if (jsonGlassData["glassNames"].is_array()) - { - nlohmann::json::array_t glassNames = jsonGlassData["glassNames"]; - glassData->glassNameCount = glassNames.size(); - glassData->glassNames = builder->getAllocator()->allocateArray(glassData->glassNameCount); - - for (size_t i = 0; i < glassData->glassNameCount; i++) - { - auto jsonGlassName = glassNames[i]; - glassData->glassNames[i].nameStr = builder->getAllocator()->duplicateString(jsonGlassName["nameStr"]); - - glassData->glassNames[i].name = jsonGlassName["name"].get(); - - if (jsonGlassName["piecesIndices"].is_array()) - { - nlohmann::json::array_t jsonPiecesIndices = jsonGlassName["piecesIndices"]; - glassData->glassNames[i].pieceCount = static_cast(jsonPiecesIndices.size()); - - for (size_t j = 0; j < glassData->glassNames[i].pieceCount; j++) - { - glassData->glassNames[i].pieceIndices[j] = jsonPiecesIndices[j].get(); - } - } - } - } - - if (gameWorldJson["glassPieces"].is_array()) - { - nlohmann::json::array_t glassPieces = gameWorldJson["glassPieces"]; - glassData->pieceCount = glassPieces.size(); - glassData->glassPieces = builder->getAllocator()->allocateArray(glassData->pieceCount); - - for (size_t i = 0; i < glassData->pieceCount; i++) - { - glassData->glassPieces[i].collapseTime = glassPieces[i]["collapseTime"].get(); - glassData->glassPieces[i].damageTaken = glassPieces[i]["damageTaken"].get(); - glassData->glassPieces[i].lastStateChangeTime = glassPieces[i]["lastStateChangeTime"].get(); - glassData->glassPieces[i].impactDir = glassPieces[i]["impactDir"].get(); - - nlohmann::json::array_t jsonPos = glassPieces[i]["impactPos"]; - glassData->glassPieces[i].impactPos[0] = jsonPos[0].get(); - glassData->glassPieces[i].impactPos[1] = jsonPos[1].get(); - } - } - } - catch (const nlohmann::json::exception& e) - { - Components::Logger::PrintError(Game::CON_CHANNEL_ERROR, "Malformed GameWorldMp json for {} ({})\n", name, e.what()); - return; - } - } - - asset->g_glassData = glassData; - - header->gameWorldMp = asset; - } + header->gameWorldMp = builder->getIW4OfApi()->read(Game::XAssetType::ASSET_TYPE_GAMEWORLD_MP, _name); } } diff --git a/src/Components/Modules/AssetInterfaces/IGfxImage.cpp b/src/Components/Modules/AssetInterfaces/IGfxImage.cpp index 3c7bfee4..4744e3ee 100644 --- a/src/Components/Modules/AssetInterfaces/IGfxImage.cpp +++ b/src/Components/Modules/AssetInterfaces/IGfxImage.cpp @@ -1,150 +1,11 @@ #include #include "IGfxImage.hpp" -#define IW4X_IMG_VERSION "0" - namespace Assets { void IGfxImage::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) { - Game::GfxImage* image = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_IMAGE, name.data()).image; - if (image && name[0] != '*') return; - - image = builder->getAllocator()->allocate(); - if (!image) - { - Components::Logger::Error(Game::ERR_FATAL, "Failed to allocate GfxImage structure!"); - return; - } - - image->name = builder->getAllocator()->duplicateString(name); - image->semantic = Game::TextureSemantic::TS_COLOR_MAP; - - const char* tempName = image->name; - if (tempName[0] == '*') tempName++; - - Components::FileSystem::File imageFile(std::format("images/{}.iw4xImage", tempName)); - if (imageFile.exists()) - { - Utils::Stream::Reader reader(builder->getAllocator(), imageFile.getBuffer()); - - __int64 magic = reader.read<__int64>(); - if (std::memcmp(&magic, "IW4xImg" IW4X_IMG_VERSION, 8)) - { - Components::Logger::Error(Game::ERR_FATAL, "Reading image '{}' failed, header is invalid!", name); - } - - image->mapType = reader.read(); - image->semantic = reader.read(); - image->category = reader.read(); - - int dataLength = reader.read(); - image->cardMemory.platform[0] = dataLength; - image->cardMemory.platform[1] = dataLength; - - Game::GfxImageLoadDefIW3 loadDef; - image->texture.loadDef = reinterpret_cast(reader.readArray(dataLength + 16)); - std::memcpy(&loadDef, image->texture.loadDef, sizeof(loadDef)); - - image->texture.loadDef->levelCount = loadDef.levelCount; - image->texture.loadDef->flags = loadDef.flags; - image->texture.loadDef->format = loadDef.format; - image->texture.loadDef->resourceSize = loadDef.resourceSize; - ZeroMemory(image->texture.loadDef->pad, 3); - - if (image->texture.loadDef->resourceSize != dataLength) - { - Components::Logger::Error(Game::ERR_FATAL, "Resource size doesn't match the data length ({})!\n", name); - } - - image->width = loadDef.dimensions[0]; - image->height = loadDef.dimensions[1]; - image->depth = loadDef.dimensions[2]; - - image->delayLoadPixels = true; - - header->image = image; - } - else if (name[0] != '*') - { - char nameBuffer[MAX_PATH] = { 0 }; - Components::Materials::FormatImagePath(nameBuffer, sizeof(nameBuffer), 0, 0, name.data()); - Components::FileSystem::File iwi(nameBuffer); - - if (!iwi.exists()) - { - Components::Logger::Error(Game::ERR_FATAL, "Loading image '{}' failed!", iwi.getName()); - return; - } - - auto iwiBuffer = iwi.getBuffer(); - - const Game::GfxImageFileHeader* iwiHeader = reinterpret_cast(iwiBuffer.data()); - - if (std::memcmp(iwiHeader->tag, "IWi", 3) && iwiHeader->version == 8) - { - Components::Logger::Error(Game::ERR_FATAL, "Image is not a valid IWi!"); - return; - } - - image->mapType = Game::MAPTYPE_2D; - image->cardMemory.platform[0] = iwiHeader->fileSizeForPicmip[0] - 32; - image->cardMemory.platform[1] = iwiHeader->fileSizeForPicmip[0] - 32; - - image->texture.loadDef = builder->getAllocator()->allocate(); - if (!image->texture.loadDef) - { - Components::Logger::Error(Game::ERR_FATAL, "Failed to allocate GfxImageLoadDef structure!"); - return; - } - - image->texture.loadDef->flags = iwiHeader->flags; - image->texture.loadDef->levelCount = 0; - - image->width = iwiHeader->dimensions[0]; - image->height = iwiHeader->dimensions[1]; - image->depth = iwiHeader->dimensions[2]; - - switch (iwiHeader->format) - { - case Game::IMG_FORMAT_BITMAP_RGBA: - { - image->texture.loadDef->format = 21; - break; - } - - case Game::IMG_FORMAT_BITMAP_RGB: - { - image->texture.loadDef->format = 20; - break; - } - - case Game::IMG_FORMAT_DXT1: - { - image->texture.loadDef->format = 0x31545844; - break; - } - - case Game::IMG_FORMAT_DXT3: - { - image->texture.loadDef->format = 0x33545844; - break; - } - - case Game::IMG_FORMAT_DXT5: - { - image->texture.loadDef->format = 0x35545844; - break; - } - - default: - { - break; - } - } - - header->image = image; - } + header->image = builder->getIW4OfApi()->read(Game::XAssetType::ASSET_TYPE_IMAGE, name); } void IGfxImage::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) diff --git a/src/Components/Modules/AssetInterfaces/IGfxLightDef.cpp b/src/Components/Modules/AssetInterfaces/IGfxLightDef.cpp index c2e58c70..6849e701 100644 --- a/src/Components/Modules/AssetInterfaces/IGfxLightDef.cpp +++ b/src/Components/Modules/AssetInterfaces/IGfxLightDef.cpp @@ -1,44 +1,11 @@ #include #include "IGfxLightDef.hpp" -#define IW4X_LIGHT_VERSION "0" - namespace Assets { void IGfxLightDef::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) { - Components::FileSystem::File mapFile(std::format("lights/{}.iw4xLight", name)); - - if (mapFile.exists()) - { - Utils::Stream::Reader reader(builder->getAllocator(), mapFile.getBuffer()); - - char* magic = reader.readArray(7); - if (std::memcmp(magic, "IW4xLit", 7)) - { - Components::Logger::Error(Game::ERR_FATAL, "Reading light '{}' failed, header is invalid!", name); - } - - std::string version; - version.push_back(reader.read()); - if (version != IW4X_LIGHT_VERSION) - { - Components::Logger::Error(Game::ERR_FATAL, "Reading light '{}' failed, expected version is {}, but it was {}!", name, IW4X_LIGHT_VERSION, version); - } - - Game::GfxLightDef* asset = reader.readObject(); - header->lightDef = asset; - - if (asset->name) - { - asset->name = reader.readCString(); - } - - if (asset->attenuation.image) - { - asset->attenuation.image = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_IMAGE, reader.readString().data(), builder).image; - } - } + header->lightDef = builder->getIW4OfApi()->read(Game::XAssetType::ASSET_TYPE_LIGHT_DEF, name); } void IGfxLightDef::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) diff --git a/src/Components/Modules/AssetInterfaces/IGfxWorld.cpp b/src/Components/Modules/AssetInterfaces/IGfxWorld.cpp index 8548c309..6334fad7 100644 --- a/src/Components/Modules/AssetInterfaces/IGfxWorld.cpp +++ b/src/Components/Modules/AssetInterfaces/IGfxWorld.cpp @@ -1,450 +1,16 @@ #include #include "IGfxWorld.hpp" -#define IW4X_GFXMAP_VERSION 1 - -// The xmodel vehicle_small_hatch_green_destructible_mp causes EXTREME lag -// when placed in the world, for reasons unknown. -// -// Something happens with the SModelSurfIterator which makes it load garbage -// as an XSurface in the middle of otherwise valid surfaces. This bug is very -// easy to reproduce with an empty map and just this car in the middle -// -// As of know we do not know why the iterator corruption occurs or what causes -// it. It doesn't seem linked to the SModel, nor to the materials or techsets, -// nor to the sortkeys, nor to the tilemode, boneinfo, and so on. So for now -// and to make it work for majority of users, we just swap the car. (no, using -// the identical car from iw4's favela_escape doesn't work either!) -// -// Two other models have this problem: ch_apartment_9story_noentry_02 and -// ch_apartment_5story_noentry_01 -// But these exist in mp_vacant in slightly different versions, and can be -// swapped safely by deleting the two .iw4XModel files and requiring mp_vacant -// or a minimal zone containing just these two models. -// -#define SWAP_GREEN_VEHICLE_XMODEL 1 - namespace Assets { - void IGfxWorld::loadGfxWorldDpvsStatic(Game::GfxWorld* world, Game::GfxWorldDpvsStatic* asset, Components::ZoneBuilder::Zone* builder, Utils::Stream::Reader* reader) - { - if (asset->sortedSurfIndex) - { - asset->sortedSurfIndex = reader->readArray(asset->staticSurfaceCount + asset->staticSurfaceCountNoDecal); - } - - if (asset->smodelInsts) - { - asset->smodelInsts = reader->readArray(asset->smodelCount); - } - - if (asset->surfaces) - { - asset->surfaces = reader->readArray(world->surfaceCount); - - for (unsigned int i = 0; i < world->surfaceCount; ++i) - { - Game::GfxSurface* surface = &asset->surfaces[i]; - - if (surface->material) - { - world->dpvs.surfaces[i].material = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_MATERIAL, reader->readString().data(), builder).material; - } - } - } - - if (asset->surfacesBounds) - { - asset->surfacesBounds = reader->readArray(world->surfaceCount); - } - - if (asset->smodelDrawInsts) - { - asset->smodelDrawInsts = reader->readArray(asset->smodelCount); - - for (unsigned int i = 0; i < asset->smodelCount; ++i) - { - Game::GfxStaticModelDrawInst* model = &asset->smodelDrawInsts[i]; - - if (model->model) - { - auto name = reader->readString(); - - model->model = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_XMODEL, name.data(), builder).model; - - assert(model->model); - } - } - } - } - - void IGfxWorld::loadGfxWorldDraw(Game::GfxWorldDraw* asset, Components::ZoneBuilder::Zone* builder, Utils::Stream::Reader* reader) - { - if (asset->reflectionProbes) - { - asset->reflectionProbes = reader->readArray(asset->reflectionProbeCount); - - for (unsigned int i = 0; i < asset->reflectionProbeCount; ++i) - { - asset->reflectionProbes[i] = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_IMAGE, reader->readString().data(), builder).image; - } - } - - if (asset->reflectionProbeOrigins) - { - asset->reflectionProbeOrigins = reader->readArray(asset->reflectionProbeCount); - } - - if (asset->lightmaps) - { - asset->lightmaps = reader->readArray(asset->lightmapCount); - - for (int i = 0; i < asset->lightmapCount; ++i) - { - Game::GfxLightmapArray* lightmapArray = &asset->lightmaps[i]; - - if (lightmapArray->primary) - { - lightmapArray->primary = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_IMAGE, reader->readString().data(), builder).image; - } - - if (lightmapArray->secondary) - { - lightmapArray->secondary = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_IMAGE, reader->readString().data(), builder).image; - } - } - } - - if (asset->lightmapOverridePrimary) - { - asset->lightmapOverridePrimary = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_IMAGE, reader->readString().data(), builder).image; - } - - if (asset->lightmapOverrideSecondary) - { - asset->lightmapOverrideSecondary = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_IMAGE, reader->readString().data(), builder).image; - } - - // saveGfxWorldVertexData - { - if (asset->vd.vertices) - { - asset->vd.vertices = reader->readArray(asset->vertexCount); - } - } - - // saveGfxWorldVertexLayerData - { - if (asset->vld.data) - { - // no align for char - asset->vld.data = reader->readArray(asset->vertexLayerDataSize); - } - } - - if (asset->indices) - { - asset->indices = reader->readArray(asset->indexCount); - } - } - void IGfxWorld::load(Game::XAssetHeader* header, const std::string& _name, Components::ZoneBuilder::Zone* builder) { - std::string name = _name; - Utils::String::Replace(name, "maps/mp/", ""); - Utils::String::Replace(name, ".d3dbsp", ""); - - Components::FileSystem::File mapFile(std::format("gfxworld/{}.iw4xGfxWorld", name)); - - if (mapFile.exists()) - { - Utils::Stream::Reader reader(builder->getAllocator(), mapFile.getBuffer()); - - __int64 magic = reader.read<__int64>(); - if (std::memcmp(&magic, "IW4xGfxW", 8)) - { - Components::Logger::Error(Game::ERR_FATAL, "Reading gfxworld '{}' failed, header is invalid!", name); - } - - int version = reader.read(); - if (version != IW4X_GFXMAP_VERSION) - { - Components::Logger::Error(Game::ERR_FATAL, "Reading gfxworld '{}' failed, expected version is {}, but it was {}!", name, IW4X_GFXMAP_VERSION, version); - } - - Game::GfxWorld* asset = reader.readObject(); - header->gfxWorld = asset; - - if (asset->name) - { - asset->name = reader.readCString(); - } - - if (asset->baseName) - { - asset->baseName = reader.readCString(); - } - - if (asset->skies) - { - asset->skies = reader.readArray(asset->skyCount); - - for (int i = 0; i < asset->skyCount; ++i) - { - Game::GfxSky* sky = &asset->skies[i]; - - if (sky->skyStartSurfs) - { - sky->skyStartSurfs = reader.readArray(sky->skySurfCount); - } - - if (sky->skyImage) - { - sky->skyImage = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_IMAGE, reader.readString().data(), builder).image; - } - } - } - - // GfxWorldDpvsPlanes - { - if (asset->dpvsPlanes.planes) - { - void* oldPtr = asset->dpvsPlanes.planes; - asset->dpvsPlanes.planes = reader.readArray(asset->planeCount); - - if (builder->getAllocator()->isPointerMapped(oldPtr)) - { - asset->dpvsPlanes.planes = builder->getAllocator()->getPointer(oldPtr); - } - else - { - builder->getAllocator()->mapPointer(oldPtr, asset->dpvsPlanes.planes); - Components::Logger::Print("GfxWorld dpvs planes not mapped. This shouldn't happen. Make sure to load the ClipMap first!\n"); - } - } - - if (asset->dpvsPlanes.nodes) - { - asset->dpvsPlanes.nodes = reader.readArray(asset->nodeCount); - } - } - - - int cellCount = asset->dpvsPlanes.cellCount; - - if (asset->aabbTreeCounts) - { - asset->aabbTreeCounts = reader.readArray(cellCount); - } - - if (asset->aabbTrees) - { - asset->aabbTrees = reader.readArray(cellCount); - - for (int i = 0; i < cellCount; ++i) - { - Game::GfxCellTree* cellTree = &asset->aabbTrees[i]; - - if (cellTree->aabbTree) - { - cellTree->aabbTree = reader.readArray(asset->aabbTreeCounts[i].aabbTreeCount); - - for (int j = 0; j < asset->aabbTreeCounts[i].aabbTreeCount; ++j) - { - Game::GfxAabbTree* aabbTree = &cellTree->aabbTree[j]; - - if (aabbTree->smodelIndexes) - { - unsigned short* oldPointer = aabbTree->smodelIndexes; - if(builder->getAllocator()->isPointerMapped(oldPointer)) - { - // We still have to read it - reader.readArray(aabbTree->smodelIndexCount); - - aabbTree->smodelIndexes = builder->getAllocator()->getPointer(oldPointer); - } - else - { - aabbTree->smodelIndexes = reader.readArray(aabbTree->smodelIndexCount); - - for (unsigned short k = 0; k < aabbTree->smodelIndexCount; ++k) - { - builder->getAllocator()->mapPointer(&oldPointer[k], &aabbTree->smodelIndexes[k]); - } - } - } - } - } - } - } - - if (asset->cells) - { - asset->cells = reader.readArray(cellCount); - - for (int i = 0; i < cellCount; ++i) - { - Game::GfxCell* cell = &asset->cells[i]; - - if (cell->portals) - { - cell->portals = reader.readArray(cell->portalCount); - - for (int j = 0; j < cell->portalCount; ++j) - { - Game::GfxPortal* portal = &cell->portals[j]; - - if (portal->vertices) - { - portal->vertices = reader.readArray(portal->vertexCount); - } - } - } - - if (cell->reflectionProbes) - { - cell->reflectionProbes = reader.readArray(cell->reflectionProbeCount); - } - } - } - - this->loadGfxWorldDraw(&asset->draw, builder, &reader); - - // GfxLightGrid - { - if (asset->lightGrid.rowDataStart) - { - asset->lightGrid.rowDataStart = reader.readArray((asset->lightGrid.maxs[asset->lightGrid.rowAxis] - asset->lightGrid.mins[asset->lightGrid.rowAxis]) + 1); - } - - if (asset->lightGrid.rawRowData) - { - asset->lightGrid.rawRowData = reader.readArray(asset->lightGrid.rawRowDataSize); - } - - if (asset->lightGrid.entries) - { - asset->lightGrid.entries = reader.readArray(asset->lightGrid.entryCount); - } - - if (asset->lightGrid.colors) - { - asset->lightGrid.colors = reader.readArray(asset->lightGrid.colorCount); - } - } - - if (asset->models) - { - asset->models = reader.readArray(asset->modelCount); - } - - if (asset->materialMemory) - { - asset->materialMemory = reader.readArray(asset->materialMemoryCount); - - for (int i = 0; i < asset->materialMemoryCount; ++i) - { - Game::MaterialMemory* materialMemory = &asset->materialMemory[i]; - - if (materialMemory->material) - { - materialMemory->material = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_MATERIAL, reader.readString().data(), builder).material; - } - } - } - - if (asset->sun.spriteMaterial) - { - asset->sun.spriteMaterial = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_MATERIAL, reader.readString().data(), builder).material; - } - - if (asset->sun.flareMaterial) - { - asset->sun.flareMaterial = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_MATERIAL, reader.readString().data(), builder).material; - } - - if (asset->outdoorImage) - { - asset->outdoorImage = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_IMAGE, reader.readString().data(), builder).image; - } - - if (asset->primaryLightCount > 0) - { - Utils::Stream::ClearPointer(&asset->primaryLightEntityShadowVis); - } - - if (asset->dpvsDyn.dynEntClientCount[0] > 0) - { - Utils::Stream::ClearPointer(&asset->sceneDynModel); - Utils::Stream::ClearPointer(&asset->primaryLightDynEntShadowVis[0]); - Utils::Stream::ClearPointer(&asset->nonSunPrimaryLightForModelDynEnt); - } - - if (asset->dpvsDyn.dynEntClientCount[1] > 0) - { - Utils::Stream::ClearPointer(&asset->sceneDynBrush); - Utils::Stream::ClearPointer(&asset->primaryLightDynEntShadowVis[1]); - } - - if (asset->shadowGeom) - { - asset->shadowGeom = reader.readArray(asset->primaryLightCount); - - for (unsigned int i = 0; i < asset->primaryLightCount; ++i) - { - Game::GfxShadowGeometry* shadowGeometry = &asset->shadowGeom[i]; - - if (shadowGeometry->sortedSurfIndex) - { - shadowGeometry->sortedSurfIndex = reader.readArray(shadowGeometry->surfaceCount); - } - - if (shadowGeometry->smodelIndex) - { - shadowGeometry->smodelIndex = reader.readArray(shadowGeometry->smodelCount); - } - } - } - - if (asset->lightRegion) - { - asset->lightRegion = reader.readArray(asset->primaryLightCount); - - for (unsigned int i = 0; i < asset->primaryLightCount; ++i) - { - Game::GfxLightRegion* lightRegion = &asset->lightRegion[i]; - - if (lightRegion->hulls) - { - lightRegion->hulls = reader.readArray(lightRegion->hullCount); - - for (unsigned int j = 0; j < lightRegion->hullCount; ++j) - { - Game::GfxLightRegionHull* lightRegionHull = &lightRegion->hulls[j]; - - if (lightRegionHull->axis) - { - lightRegionHull->axis = reader.readArray(lightRegionHull->axisCount); - } - } - } - } - } - - this->loadGfxWorldDpvsStatic(asset, &asset->dpvs, builder, &reader); - - // Obsolete, IW3 has no support for that - if (asset->heroOnlyLights) - { - asset->heroOnlyLights = reader.readArray(asset->heroOnlyLightCount); - } - } + header->gfxWorld = builder->getIW4OfApi()->read(Game::XAssetType::ASSET_TYPE_GFXWORLD, _name); } void IGfxWorld::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) { - Game::GfxWorld* asset = header.gfxWorld; - + auto* asset = header.gfxWorld; if (asset->draw.reflectionProbes) { for (unsigned int i = 0; i < asset->draw.reflectionProbeCount; ++i) @@ -455,7 +21,7 @@ namespace Assets if (asset->draw.lightmaps) { - for (int i = 0; i < asset->draw.lightmapCount; ++i) + for (auto i = 0; i < asset->draw.lightmapCount; ++i) { if (asset->draw.lightmaps[i].primary) { @@ -599,7 +165,7 @@ namespace Assets { buffer->align(Utils::Stream::ALIGN_4); - Game::GfxImage** imageDest = buffer->dest(); + auto** imageDest = buffer->dest(); buffer->saveArray(asset->reflectionProbes, asset->reflectionProbeCount); for (unsigned int i = 0; i < asset->reflectionProbeCount; ++i) @@ -648,13 +214,13 @@ namespace Assets buffer->align(Utils::Stream::ALIGN_4); - Game::GfxLightmapArray* lightmapArrayDestTable = buffer->dest(); + auto* lightmapArrayDestTable = buffer->dest(); buffer->saveArray(asset->lightmaps, asset->lightmapCount); for (int i = 0; i < asset->lightmapCount; ++i) { - Game::GfxLightmapArray* lightmapArrayDest = &lightmapArrayDestTable[i]; - Game::GfxLightmapArray* lightmapArray = &asset->lightmaps[i]; + auto* lightmapArrayDest = &lightmapArrayDestTable[i]; + auto* lightmapArray = &asset->lightmaps[i]; if (lightmapArray->primary) { @@ -854,13 +420,13 @@ namespace Assets SaveLogEnter("GfxSurface"); buffer->align(Utils::Stream::ALIGN_4); - Game::GfxSurface* destSurfaceTable = buffer->dest(); + auto* destSurfaceTable = buffer->dest(); buffer->saveArray(asset->surfaces, world->surfaceCount); for (unsigned int i = 0; i < world->surfaceCount; ++i) { - Game::GfxSurface* surface = &asset->surfaces[i]; - Game::GfxSurface* destSurface = &destSurfaceTable[i]; + auto* surface = &asset->surfaces[i]; + auto* destSurface = &destSurfaceTable[i]; if (surface->material) { @@ -890,13 +456,13 @@ namespace Assets SaveLogEnter("GfxStaticModelDrawInst"); buffer->align(Utils::Stream::ALIGN_4); - Game::GfxStaticModelDrawInst* destModelTable = buffer->dest(); + auto* destModelTable = buffer->dest(); buffer->saveArray(asset->smodelDrawInsts, asset->smodelCount); for (unsigned int i = 0; i < asset->smodelCount; ++i) { - Game::GfxStaticModelDrawInst* model = &asset->smodelDrawInsts[i]; - Game::GfxStaticModelDrawInst* destModel = &destModelTable[i]; + auto* model = &asset->smodelDrawInsts[i]; + auto* destModel = &destModelTable[i]; if (model->model) { @@ -986,8 +552,8 @@ namespace Assets Utils::Stream* buffer = builder->getBuffer(); SaveLogEnter("GfxWorld"); - Game::GfxWorld* asset = header.gfxWorld; - Game::GfxWorld* dest = buffer->dest(); + auto* asset = header.gfxWorld; + auto* dest = buffer->dest(); buffer->save(asset); buffer->pushBlock(Game::XFILE_BLOCK_VIRTUAL); @@ -1012,13 +578,13 @@ namespace Assets SaveLogEnter("GfxSky"); buffer->align(Utils::Stream::ALIGN_4); - Game::GfxSky* destSkyTable = buffer->dest(); + auto* destSkyTable = buffer->dest(); buffer->saveArray(asset->skies, asset->skyCount); for (int i = 0; i < asset->skyCount; ++i) { - Game::GfxSky* destSky = &destSkyTable[i]; - Game::GfxSky* sky = &asset->skies[i]; + auto* destSky = &destSkyTable[i]; + auto* sky = &asset->skies[i]; if (sky->skyStartSurfs) { @@ -1059,13 +625,13 @@ namespace Assets SaveLogEnter("GfxCellTree"); buffer->align(Utils::Stream::ALIGN_128); - Game::GfxCellTree* destCellTreeTable = buffer->dest(); + auto* destCellTreeTable = buffer->dest(); buffer->saveArray(asset->aabbTrees, cellCount); for (int i = 0; i < cellCount; ++i) { - Game::GfxCellTree* destCellTree = &destCellTreeTable[i]; - Game::GfxCellTree* cellTree = &asset->aabbTrees[i]; + auto* destCellTree = &destCellTreeTable[i]; + auto* cellTree = &asset->aabbTrees[i]; if (cellTree->aabbTree) { @@ -1073,7 +639,7 @@ namespace Assets SaveLogEnter("GfxAabbTree"); buffer->align(Utils::Stream::ALIGN_4); - Game::GfxAabbTree* destAabbTreeTable = buffer->dest(); + auto* destAabbTreeTable = buffer->dest(); buffer->saveArray(cellTree->aabbTree, asset->aabbTreeCounts[i].aabbTreeCount); // ok this one is based on some assumptions because the actual count is this @@ -1083,8 +649,8 @@ namespace Assets for (int j = 0; j < asset->aabbTreeCounts[i].aabbTreeCount; ++j) { - Game::GfxAabbTree* destAabbTree = &destAabbTreeTable[j]; - Game::GfxAabbTree* aabbTree = &cellTree->aabbTree[j]; + auto* destAabbTree = &destAabbTreeTable[j]; + auto* aabbTree = &cellTree->aabbTree[j]; if (aabbTree->smodelIndexes) { @@ -1122,13 +688,13 @@ namespace Assets SaveLogEnter("GfxCell"); buffer->align(Utils::Stream::ALIGN_4); - Game::GfxCell* destCellTable = buffer->dest(); + auto* destCellTable = buffer->dest(); buffer->saveArray(asset->cells, cellCount); for (int i = 0; i < cellCount; ++i) { - Game::GfxCell* destCell = &destCellTable[i]; - Game::GfxCell* cell = &asset->cells[i]; + auto* destCell = &destCellTable[i]; + auto* cell = &asset->cells[i]; if (cell->portals) { @@ -1136,13 +702,13 @@ namespace Assets SaveLogEnter("GfxPortal"); buffer->align(Utils::Stream::ALIGN_4); - Game::GfxPortal* destPortalTable = buffer->dest(); + auto* destPortalTable = buffer->dest(); buffer->saveArray(cell->portals, cell->portalCount); for (int j = 0; j < cell->portalCount; ++j) { - Game::GfxPortal* destPortal = &destPortalTable[j]; - Game::GfxPortal* portal = &cell->portals[j]; + auto* destPortal = &destPortalTable[j]; + auto* portal = &cell->portals[j]; if (portal->vertices) { @@ -1189,13 +755,13 @@ namespace Assets SaveLogEnter("MaterialMemory"); buffer->align(Utils::Stream::ALIGN_4); - Game::MaterialMemory* destMaterialMemoryTable = buffer->dest(); + auto* destMaterialMemoryTable = buffer->dest(); buffer->saveArray(asset->materialMemory, asset->materialMemoryCount); for (int i = 0; i < asset->materialMemoryCount; ++i) { - Game::MaterialMemory* destMaterialMemory = &destMaterialMemoryTable[i]; - Game::MaterialMemory* materialMemory = &asset->materialMemory[i]; + auto* destMaterialMemory = &destMaterialMemoryTable[i]; + auto* materialMemory = &asset->materialMemory[i]; if (materialMemory->material) { @@ -1284,13 +850,13 @@ namespace Assets SaveLogEnter("GfxShadowGeometry"); buffer->align(Utils::Stream::ALIGN_4); - Game::GfxShadowGeometry* destShadowGeometryTable = buffer->dest(); + auto* destShadowGeometryTable = buffer->dest(); buffer->saveArray(asset->shadowGeom, asset->primaryLightCount); for (unsigned int i = 0; i < asset->primaryLightCount; ++i) { - Game::GfxShadowGeometry* destShadowGeometry = &destShadowGeometryTable[i]; - Game::GfxShadowGeometry* shadowGeometry = &asset->shadowGeom[i]; + auto* destShadowGeometry = &destShadowGeometryTable[i]; + auto* shadowGeometry = &asset->shadowGeom[i]; if (shadowGeometry->sortedSurfIndex) { @@ -1317,13 +883,13 @@ namespace Assets SaveLogEnter("GfxLightRegion"); buffer->align(Utils::Stream::ALIGN_4); - Game::GfxLightRegion* destLightRegionTable = buffer->dest(); + auto* destLightRegionTable = buffer->dest(); buffer->saveArray(asset->lightRegion, asset->primaryLightCount); for (unsigned int i = 0; i < asset->primaryLightCount; ++i) { - Game::GfxLightRegion* destLightRegion = &destLightRegionTable[i]; - Game::GfxLightRegion* lightRegion = &asset->lightRegion[i]; + auto* destLightRegion = &destLightRegionTable[i]; + auto* lightRegion = &asset->lightRegion[i]; if (lightRegion->hulls) { @@ -1331,13 +897,13 @@ namespace Assets SaveLogEnter("GfxLightRegionHull"); buffer->align(Utils::Stream::ALIGN_4); - Game::GfxLightRegionHull* destLightRegionHullTable = buffer->dest(); + auto* destLightRegionHullTable = buffer->dest(); buffer->saveArray(lightRegion->hulls, lightRegion->hullCount); for (unsigned int j = 0; j < lightRegion->hullCount; ++j) { - Game::GfxLightRegionHull* destLightRegionHull = &destLightRegionHullTable[j]; - Game::GfxLightRegionHull* lightRegionHull = &lightRegion->hulls[j]; + auto* destLightRegionHull = &destLightRegionHullTable[j]; + auto* lightRegionHull = &lightRegion->hulls[j]; if (lightRegionHull->axis) { @@ -1373,7 +939,6 @@ namespace Assets Utils::Stream::ClearPointer(&dest->heroOnlyLights); } - //buffer->setPointerAssertion(false); buffer->popBlock(); SaveLogExit(); } diff --git a/src/Components/Modules/AssetInterfaces/IGfxWorld.hpp b/src/Components/Modules/AssetInterfaces/IGfxWorld.hpp index 24e3a71d..74f1786e 100644 --- a/src/Components/Modules/AssetInterfaces/IGfxWorld.hpp +++ b/src/Components/Modules/AssetInterfaces/IGfxWorld.hpp @@ -18,8 +18,5 @@ namespace Assets void savesunflare_t(Game::sunflare_t* asset, Game::sunflare_t* dest, Components::ZoneBuilder::Zone* builder); void saveGfxWorldDpvsStatic(Game::GfxWorld* world, Game::GfxWorldDpvsStatic* asset, Game::GfxWorldDpvsStatic* dest, int planeCount, Components::ZoneBuilder::Zone* builder); void saveGfxWorldDpvsDynamic(Game::GfxWorldDpvsDynamic* asset, Game::GfxWorldDpvsDynamic* dest, int cellCount, Components::ZoneBuilder::Zone* builder); - - void loadGfxWorldDraw(Game::GfxWorldDraw* asset, Components::ZoneBuilder::Zone* builder, Utils::Stream::Reader* reader); - void loadGfxWorldDpvsStatic(Game::GfxWorld* world, Game::GfxWorldDpvsStatic* asset, Components::ZoneBuilder::Zone* builder, Utils::Stream::Reader* reader); }; } diff --git a/src/Components/Modules/AssetInterfaces/ILoadedSound.cpp b/src/Components/Modules/AssetInterfaces/ILoadedSound.cpp index d651e99a..803bc4e6 100644 --- a/src/Components/Modules/AssetInterfaces/ILoadedSound.cpp +++ b/src/Components/Modules/AssetInterfaces/ILoadedSound.cpp @@ -5,106 +5,16 @@ namespace Assets { void ILoadedSound::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) { - Components::FileSystem::File soundFile(std::format("loaded_sound/{}", name)); - if (!soundFile.exists()) - { - header->loadSnd = Components::AssetHandler::FindOriginalAsset(this->getType(), name.data()).loadSnd; - return; - } - - Game::LoadedSound* sound = builder->getAllocator()->allocate(); - if (!sound) - { - Components::Logger::Print("Error allocating memory for sound structure!\n"); - return; - } - - Game::LoadedSound* reference = nullptr; - if (!reference) reference = Game::DB_FindXAssetHeader(Game::ASSET_TYPE_LOADED_SOUND, "weapons/c4_detpack/c4_drop_dirt1.wav").loadSnd; - - std::memcpy(sound, reference, sizeof(Game::LoadedSound)); - sound->sound.data = nullptr; - - Utils::Stream::Reader reader(builder->getAllocator(), soundFile.getBuffer()); - - unsigned int chunkIDBuffer = reader.read(); - if (chunkIDBuffer != 0x46464952) // RIFF - { - Components::Logger::Error(Game::ERR_FATAL, "Reading sound '{}' failed, header is invalid!", name); - return; - } - - unsigned int chunkSize = reader.read(); - - unsigned int format = reader.read(); - if (format != 0x45564157) // WAVE - { - Components::Logger::Error(Game::ERR_FATAL, "Reading sound '{}' failed, header is invalid!", name); - return; - } - - while (!sound->sound.data && !reader.end()) - { - chunkIDBuffer = reader.read(); - chunkSize = reader.read(); - - switch (chunkIDBuffer) - { - case 0x20746D66: // fmt - if (chunkSize >= 16) - { - sound->sound.info.format = reader.read(); - if (sound->sound.info.format != 1 && sound->sound.info.format != 17) - { - Components::Logger::Error(Game::ERR_FATAL, "Reading sound '{}' failed, invalid format!", name); - return; - } - - sound->sound.info.channels = reader.read(); - sound->sound.info.rate = reader.read(); - sound->sound.info.samples = reader.read(); - sound->sound.info.block_size = reader.read(); - sound->sound.info.bits = reader.read(); - - // skip any extra parameters - if (chunkSize > 16) - { - reader.seekRelative(chunkSize - 16); - } - } - break; - - case 0x61746164: // data - sound->sound.info.data_len = chunkSize; - sound->sound.data = reader.readArray(chunkSize); - break; - - default: - if (chunkSize > 0) - { - reader.seekRelative(chunkSize); - } - break; - } - } - - if (!sound->sound.info.data_ptr) - { - Components::Logger::Error(Game::ERR_FATAL, "Reading sound '{}' failed, invalid format!", name); - return; - } - - sound->name = builder->getAllocator()->duplicateString(name.data()); - header->loadSnd = sound; + header->loadSnd = builder->getIW4OfApi()->read(Game::XAssetType::ASSET_TYPE_LOADED_SOUND, name); } void ILoadedSound::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) { AssertSize(Game::LoadedSound, 44); - Utils::Stream* buffer = builder->getBuffer(); - Game::LoadedSound* asset = header.loadSnd; - Game::LoadedSound* dest = buffer->dest(); + auto* buffer = builder->getBuffer(); + auto* asset = header.loadSnd; + auto* dest = buffer->dest(); buffer->save(asset); buffer->pushBlock(Game::XFILE_BLOCK_VIRTUAL); diff --git a/src/Components/Modules/AssetInterfaces/IMapEnts.cpp b/src/Components/Modules/AssetInterfaces/IMapEnts.cpp index 11f4ac0a..48ed5c93 100644 --- a/src/Components/Modules/AssetInterfaces/IMapEnts.cpp +++ b/src/Components/Modules/AssetInterfaces/IMapEnts.cpp @@ -5,55 +5,7 @@ namespace Assets { void IMapEnts::load(Game::XAssetHeader* header, const std::string& _name, Components::ZoneBuilder::Zone* builder) { - std::string name = _name; - Utils::String::Replace(name, "maps/", ""); - Utils::String::Replace(name, "mp/", ""); - Utils::String::Replace(name, ".d3dbsp", ""); - - Components::FileSystem::File ents(std::format("mapents/{}.ents", name)); - if (ents.exists()) - { - Game::MapEnts* entites = builder->getAllocator()->allocate(); - Game::MapEnts* orgEnts = Components::AssetHandler::FindOriginalAsset(this->getType(), name.data()).mapEnts; - - // TODO: Get rid of that - if (!orgEnts) - { - orgEnts = Components::AssetHandler::FindOriginalAsset(Game::XAssetType::ASSET_TYPE_MAP_ENTS, "maps/iw4_credits.d3dbsp").mapEnts; - - if (!orgEnts) - { - Game::DB_EnumXAssets(Game::XAssetType::ASSET_TYPE_MAP_ENTS, [](Game::XAssetHeader header, void* mapEnts) - { - if (!*reinterpret_cast(mapEnts)) - { - *reinterpret_cast(mapEnts) = header.mapEnts; - } - }, &orgEnts, false); - } - } - - if (orgEnts) - { - std::memcpy(entites, orgEnts, sizeof Game::MapEnts); - } - else - { - entites->stageCount = 1; - entites->stages = builder->getAllocator()->allocate(); - entites->stages[0].name = "stage 0"; - entites->stages[0].triggerIndex = 0x400; - entites->stages[0].sunPrimaryLightIndex = 0x1; - } - - std::string entityString = ents.getBuffer(); - - entites->name = builder->getAllocator()->duplicateString(std::format("maps/mp/{}.d3dbsp", name)); - entites->entityString = builder->getAllocator()->duplicateString(entityString); - entites->numEntityChars = entityString.size() + 1; - - header->mapEnts = entites; - } + header->mapEnts = builder->getIW4OfApi()->read(Game::XAssetType::ASSET_TYPE_MAP_ENTS, _name); } void IMapEnts::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) diff --git a/src/Components/Modules/AssetInterfaces/IMaterial.cpp b/src/Components/Modules/AssetInterfaces/IMaterial.cpp index 42b63a8e..a30bfcf7 100644 --- a/src/Components/Modules/AssetInterfaces/IMaterial.cpp +++ b/src/Components/Modules/AssetInterfaces/IMaterial.cpp @@ -1,728 +1,18 @@ #include #include "IMaterial.hpp" -#define IW4X_MAT_BIN_VERSION "1" -#define IW4X_MAT_JSON_VERSION 1 - namespace Assets { - const std::unordered_map techSetCorrespondance = - { - {"effect", "effect_blend"}, - {"effect", "effect_blend"}, - {"effect_nofog", "effect_blend_nofog"}, - {"effect_zfeather", "effect_zfeather_blend"}, - {"effect_zfeather_falloff", "effect_zfeather_falloff_add"}, - {"effect_zfeather_nofog", "effect_zfeather_add_nofog"}, - - {"wc_unlit_add", "wc_unlit_add_lin"}, - {"wc_unlit_distfalloff", "wc_unlit_distfalloff_replace"}, - {"wc_unlit_multiply", "wc_unlit_multiply_lin"}, - {"wc_unlit_falloff_add", "wc_unlit_falloff_add_lin"}, - {"wc_unlit", "wc_unlit_replace_lin"}, - {"wc_unlit_alphatest", "wc_unlit_blend_lin"}, - {"wc_unlit_blend", "wc_unlit_blend_lin"}, - {"wc_unlit_replace", "wc_unlit_replace_lin"}, - {"wc_unlit_nofog", "wc_unlit_replace_lin_nofog_nocast" }, - - {"mc_unlit_replace", "mc_unlit_replace_lin"}, - {"mc_unlit_nofog", "mc_unlit_blend_nofog_ua"}, - {"mc_unlit", "mc_unlit_replace_lin_nocast"}, - {"mc_unlit_alphatest", "mc_unlit_blend_lin"}, - {"mc_effect_nofog", "mc_effect_blend_nofog"}, - {"mc_effect_falloff_add_nofog", "mc_effect_falloff_add_nofog_eyeoffset"}, - }; - void IMaterial::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) { - if (!header->data) this->loadJson(header, name, builder); // Check if we want to load a material from disk - if (!header->data) this->loadBinary(header, name, builder); // Check if we want to load a material from disk (binary format) + if (!header->data) this->loadFromDisk(header, name, builder); // Check if we want to load a material from disk if (!header->data) this->loadNative(header, name, builder); // Check if there is a native one + assert(header->data); } - - void IMaterial::loadJson(Game::XAssetHeader* header, const std::string& name, [[maybe_unused]] Components::ZoneBuilder::Zone* builder) + void IMaterial::loadFromDisk(Game::XAssetHeader* header, const std::string& name, [[maybe_unused]] Components::ZoneBuilder::Zone* builder) { - Components::FileSystem::File materialInfo(std::format("materials/{}.iw4x.json", name)); - - if (!materialInfo.exists()) return; - - Game::Material* asset = builder->getAllocator()->allocate(); - - - nlohmann::json materialJson; - try - { - materialJson = nlohmann::json::parse(materialInfo.getBuffer()); - } - catch (const std::exception& e) - { - Components::Logger::Print("Invalid material json for {} (broken json {})\n", name, e.what()); - } - - if (!materialJson.is_object()) - { - Components::Logger::Print("Invalid material json for {} (Is it zonebuilder format?)\n", name); - return; - } - - if (materialJson["version"].get() != IW4X_MAT_JSON_VERSION) - { - Components::Logger::PrintError(Game::CON_CHANNEL_ERROR, "Invalid material json version for {}, expected {} and got {}\n", name, IW4X_MAT_JSON_VERSION, materialJson["version"].get()); - return; - } - - try - { - asset->info.name = builder->getAllocator()->duplicateString(materialJson["name"].get()); - asset->info.gameFlags = static_cast(Utils::Json::ReadFlags(materialJson["gameFlags"].get(), sizeof(char))); - - asset->info.sortKey = materialJson["sortKey"].get(); - // * We do techset later * // - asset->info.textureAtlasRowCount = materialJson["textureAtlasRowCount"].get(); - asset->info.textureAtlasColumnCount = materialJson["textureAtlasColumnCount"].get(); - asset->info.surfaceTypeBits = static_cast(Utils::Json::ReadFlags(materialJson["surfaceTypeBits"].get(), sizeof(int))); - asset->info.hashIndex = materialJson["hashIndex"].get(); - asset->cameraRegion = materialJson["cameraRegion"].get(); - } - catch (const nlohmann::json::exception& e) - { - Components::Logger::PrintError(Game::CON_CHANNEL_ERROR, "Invalid material json for {} (broken json {})\n", name, e.what()); - return; - } - - if (materialJson["gfxDrawSurface"].is_object()) - { - asset->info.drawSurf.fields.customIndex = materialJson["gfxDrawSurface"]["customIndex"].get(); - asset->info.drawSurf.fields.hasGfxEntIndex = materialJson["gfxDrawSurface"]["hasGfxEntIndex"].get(); - asset->info.drawSurf.fields.materialSortedIndex = materialJson["gfxDrawSurface"]["materialSortedIndex"].get(); - asset->info.drawSurf.fields.objectId = materialJson["gfxDrawSurface"]["objectId"].get(); - asset->info.drawSurf.fields.prepass = materialJson["gfxDrawSurface"]["prepass"].get(); - asset->info.drawSurf.fields.primarySortKey = materialJson["gfxDrawSurface"]["primarySortKey"].get(); - asset->info.drawSurf.fields.reflectionProbeIndex = materialJson["gfxDrawSurface"]["reflectionProbeIndex"].get(); - asset->info.drawSurf.fields.sceneLightIndex = materialJson["gfxDrawSurface"]["sceneLightIndex"].get(); - asset->info.drawSurf.fields.surfType = materialJson["gfxDrawSurface"]["surfType"].get(); - asset->info.drawSurf.fields.unused = materialJson["gfxDrawSurface"]["unused"].get(); - asset->info.drawSurf.fields.useHeroLighting = materialJson["gfxDrawSurface"]["useHeroLighting"].get(); - } - - asset->stateFlags = static_cast(Utils::Json::ReadFlags(materialJson["stateFlags"].get(), sizeof(char))); - - if (materialJson["textureTable"].is_array()) - { - nlohmann::json::array_t textureTable = materialJson["textureTable"]; - asset->textureCount = static_cast(textureTable.size()); - asset->textureTable = builder->getAllocator()->allocateArray(asset->textureCount); - - for (size_t i = 0; i < textureTable.size(); i++) - { - auto& textureJson = textureTable[i]; - if (textureJson.is_object()) - { - Game::MaterialTextureDef* textureDef = &asset->textureTable[i]; - textureDef->semantic = textureJson["semantic"].get(); - textureDef->samplerState = textureJson["samplerState"].get(); - textureDef->nameStart = textureJson["nameStart"].get(); - textureDef->nameEnd = textureJson["nameEnd"].get(); - textureDef->nameHash = textureJson["nameHash"].get(); - - if (textureDef->semantic == Game::TextureSemantic::TS_WATER_MAP) - { - Game::water_t* water = builder->getAllocator()->allocate(); - - if (textureJson["water"].is_object()) - { - auto& waterJson = textureJson["water"]; - - if (waterJson["image"].is_string()) - { - auto imageName = waterJson["image"].get(); - - water->image = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_IMAGE, imageName.data(), builder).image; - } - - water->amplitude = waterJson["amplitude"].get(); - water->M = waterJson["M"].get(); - water->N = waterJson["N"].get(); - water->Lx = waterJson["Lx"].get(); - water->Lz = waterJson["Lz"].get(); - water->gravity = waterJson["gravity"].get(); - water->windvel = waterJson["windvel"].get(); - - auto winddir = waterJson["winddir"].get>(); - if (winddir.size() == 2) - { - std::copy(winddir.begin(), winddir.end(), water->winddir); - } - - auto codeConstant = waterJson["codeConstant"].get>(); - - if (codeConstant.size() == 4) - { - std::copy(codeConstant.begin(), codeConstant.end(), water->codeConstant); - } - - /// H0 - [[maybe_unused]] auto idealSize = water->M * water->N * sizeof(Game::complex_s); - auto h064 = waterJson["H0"].get(); - auto predictedSize = static_cast(std::ceilf((h064.size() / 4.f) * 3.f)); - assert(predictedSize >= idealSize); - - auto h0 = reinterpret_cast(builder->getAllocator()->allocate(predictedSize)); - - [[maybe_unused]] auto h0Result = base64_decode( - h064.data(), - h064.size(), - reinterpret_cast(h0), - &predictedSize - ); - - assert(h0Result == CRYPT_OK); - water->H0 = h0; - - /// WTerm - auto wTerm64 = waterJson["wTerm"].get(); - auto predictedWTermSize = static_cast(std::ceilf((wTerm64.size() / 4.f) * 3.f)); - - auto wTerm = reinterpret_cast(builder->getAllocator()->allocate(predictedWTermSize)); - - [[maybe_unused]] auto wTermResult = base64_decode( - wTerm64.data(), - wTerm64.size(), - reinterpret_cast(wTerm), - &predictedWTermSize - ); - - assert(wTermResult == CRYPT_OK); - water->wTerm = wTerm; - } - - textureDef->u.water = water; - } - else - { - textureDef->u.image = nullptr; - if (textureJson["image"].is_string()) - { - textureDef->u.image = Components::AssetHandler::FindAssetForZone - ( - Game::XAssetType::ASSET_TYPE_IMAGE, - textureJson["image"].get(), - builder - ).image; - } - } - } - } - } - - // Statebits - if (materialJson["stateBitsEntry"].is_array()) - { - nlohmann::json::array_t stateBitsEntry = materialJson["stateBitsEntry"]; - - for (size_t i = 0; i < std::min(stateBitsEntry.size(), 48u); i++) - { - asset->stateBitsEntry[i] = stateBitsEntry[i].get(); - } - } - - if (materialJson["stateBitsTable"].is_array()) - { - nlohmann::json::array_t array = materialJson["stateBitsTable"]; - asset->stateBitsCount = static_cast(array.size()); - - asset->stateBitsTable = builder->getAllocator()->allocateArray(array.size()); - - size_t statebitTableIndex = 0; - for (auto& jsonStateBitEntry : array) - { - auto stateBit = &asset->stateBitsTable[statebitTableIndex++]; - - unsigned int loadbits0 = 0; - unsigned int loadbits1 = 0; - -#define READ_INT_LB_FROM_JSON(x) unsigned int x = jsonStateBitEntry[#x].get() -#define READ_BOOL_LB_FROM_JSON(x) bool x = jsonStateBitEntry[#x].get() - - READ_INT_LB_FROM_JSON(srcBlendRgb); - READ_INT_LB_FROM_JSON(dstBlendRgb); - READ_INT_LB_FROM_JSON(blendOpRgb); - READ_INT_LB_FROM_JSON(srcBlendAlpha); - READ_INT_LB_FROM_JSON(dstBlendAlpha); - READ_INT_LB_FROM_JSON(blendOpAlpha); - READ_INT_LB_FROM_JSON(depthTest); - READ_INT_LB_FROM_JSON(polygonOffset); - - const auto alphaTest = jsonStateBitEntry["alphaTest"].get(); - const auto cullFace = jsonStateBitEntry["cullFace"].get(); - - READ_BOOL_LB_FROM_JSON(colorWriteRgb); - READ_BOOL_LB_FROM_JSON(colorWriteAlpha); - READ_BOOL_LB_FROM_JSON(polymodeLine); - - READ_BOOL_LB_FROM_JSON(gammaWrite); - READ_BOOL_LB_FROM_JSON(depthWrite); - READ_BOOL_LB_FROM_JSON(stencilFrontEnabled); - READ_BOOL_LB_FROM_JSON(stencilBackEnabled); - - READ_INT_LB_FROM_JSON(stencilFrontPass); - READ_INT_LB_FROM_JSON(stencilFrontFail); - READ_INT_LB_FROM_JSON(stencilFrontZFail); - READ_INT_LB_FROM_JSON(stencilFrontFunc); - READ_INT_LB_FROM_JSON(stencilBackPass); - READ_INT_LB_FROM_JSON(stencilBackFail); - READ_INT_LB_FROM_JSON(stencilBackZFail); - READ_INT_LB_FROM_JSON(stencilBackFunc); - - loadbits0 |= srcBlendRgb << Game::GFXS0_SRCBLEND_RGB_SHIFT; - loadbits0 |= dstBlendRgb << Game::GFXS0_DSTBLEND_RGB_SHIFT; - loadbits0 |= blendOpRgb << Game::GFXS0_BLENDOP_RGB_SHIFT; - loadbits0 |= srcBlendAlpha << Game::GFXS0_SRCBLEND_ALPHA_SHIFT; - loadbits0 |= dstBlendAlpha << Game::GFXS0_DSTBLEND_ALPHA_SHIFT; - loadbits0 |= blendOpAlpha << Game::GFXS0_BLENDOP_ALPHA_SHIFT; - - if (depthTest == -1) - { - loadbits1 |= Game::GFXS1_DEPTHTEST_DISABLE; - } - else - { - loadbits1 |= depthTest << Game::GFXS1_DEPTHTEST_SHIFT; - } - - loadbits1 |= polygonOffset << Game::GFXS1_POLYGON_OFFSET_SHIFT; - - if (alphaTest == "disable") - { - loadbits0 |= Game::GFXS0_ATEST_DISABLE; - } - else if (alphaTest == ">0") - { - loadbits0 |= Game::GFXS0_ATEST_GT_0; - } - else if (alphaTest == "<128") - { - loadbits0 |= Game::GFXS0_ATEST_LT_128; - } - else if (alphaTest == ">=128") - { - loadbits0 |= Game::GFXS0_ATEST_GE_128; - } - else - { - Components::Logger::PrintError(Game::CON_CHANNEL_ERROR, "Invalid alphatest loadbit0 '{}' in material {}\n", alphaTest, name); - return; - } - - if (cullFace == "none") - { - loadbits0 |= Game::GFXS0_CULL_NONE; - } - else if (cullFace == "back") - { - loadbits0 |= Game::GFXS0_CULL_BACK; - } - else if (cullFace == "front") - { - loadbits0 |= Game::GFXS0_CULL_FRONT; - } - else - { - Components::Logger::PrintError(Game::CON_CHANNEL_ERROR, "Invalid cullFace loadbit0 '{}' in material {}\n", cullFace, name); - return; - } - - if (gammaWrite) - { - loadbits0 |= Game::GFXS0_GAMMAWRITE; - } - - if (colorWriteAlpha) - { - loadbits0 |= Game::GFXS0_COLORWRITE_ALPHA; - } - - if (colorWriteRgb) - { - loadbits0 |= Game::GFXS0_COLORWRITE_RGB; - } - - if (polymodeLine) - { - loadbits0 |= Game::GFXS0_POLYMODE_LINE; - } - - if (depthWrite) - { - loadbits1 |= Game::GFXS1_DEPTHWRITE; - } - if (stencilFrontEnabled) - { - loadbits1 |= Game::GFXS1_STENCIL_FRONT_ENABLE; - } - if (stencilBackEnabled) - { - loadbits1 |= Game::GFXS1_STENCIL_BACK_ENABLE; - } - - loadbits1 |= stencilFrontPass << Game::GFXS1_STENCIL_FRONT_PASS_SHIFT; - loadbits1 |= stencilFrontFail << Game::GFXS1_STENCIL_FRONT_FAIL_SHIFT; - loadbits1 |= stencilFrontZFail << Game::GFXS1_STENCIL_FRONT_ZFAIL_SHIFT; - loadbits1 |= stencilFrontFunc << Game::GFXS1_STENCIL_FRONT_FUNC_SHIFT; - loadbits1 |= stencilBackPass << Game::GFXS1_STENCIL_BACK_PASS_SHIFT; - loadbits1 |= stencilBackFail << Game::GFXS1_STENCIL_BACK_FAIL_SHIFT; - loadbits1 |= stencilBackZFail << Game::GFXS1_STENCIL_BACK_ZFAIL_SHIFT; - loadbits1 |= stencilBackFunc << Game::GFXS1_STENCIL_BACK_FUNC_SHIFT; - - stateBit->loadBits[0] = loadbits0; - stateBit->loadBits[1] = loadbits1; - } - - // Constant table - if (materialJson["constantTable"].is_array()) - { - - nlohmann::json::array_t constants = materialJson["constantTable"]; - asset->constantCount = static_cast(constants.size()); - auto table = builder->getAllocator()->allocateArray(asset->constantCount); - - for (size_t constantIndex = 0; constantIndex < asset->constantCount; constantIndex++) - { - auto& constant = constants[constantIndex]; - auto entry = &table[constantIndex]; - - auto litVec = constant["literal"].get>(); - std::copy(litVec.begin(), litVec.end(), entry->literal); - - auto constantName = constant["name"].get(); - std::copy(constantName.begin(), constantName.end(), entry->name); - - entry->nameHash = constant["nameHash"].get(); - } - - asset->constantTable = table; - } - - if (materialJson["techniqueSet"].is_string()) - { - const std::string techsetName = materialJson["techniqueSet"].get(); - asset->techniqueSet = findWorkingTechset(techsetName, asset, builder); - - if (asset->techniqueSet == nullptr) - { - assert(false); - } - } - - header->material = asset; - } - } - - Game::MaterialTechniqueSet* IMaterial::findWorkingTechset(std::string techsetName, [[maybe_unused]] Game::Material* material, Components::ZoneBuilder::Zone* builder) const - { - Game::MaterialTechniqueSet* techset; - - // Pass 1: Identical techset (1:1) - techset = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_TECHNIQUE_SET, techsetName.data(), builder).techniqueSet; - if (techset != nullptr) - { - return techset; - } - - // We do no more cause we use CoD4 techset and they should always be present - // If one day we want to go back to mw2 fallback we can add extra steps here! - - return nullptr; - } - - void IMaterial::loadBinary(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) - { - static const char* techsetSuffix[] = - { - "_lin", - "_add_lin", - "_replace", - "_eyeoffset", - - "_blend", - "_blend_nofog", - "_add", - "_nofog", - "_nocast", - - "_add_lin_nofog", - }; - - Components::FileSystem::File materialFile(std::format("materials/{}.iw4xMaterial", name)); - if (!materialFile.exists()) return; - - Utils::Stream::Reader reader(builder->getAllocator(), materialFile.getBuffer()); - - char* magic = reader.readArray(7); - if (std::memcmp(magic, "IW4xMat", 7) != 0) - { - Components::Logger::Error(Game::ERR_FATAL, "Reading material '{}' failed, header is invalid!", name); - } - - std::string version; - version.push_back(reader.read()); - if (version != IW4X_MAT_BIN_VERSION) - { - Components::Logger::Error(Game::ERR_FATAL, "Reading material '{}' failed, expected version is {}, but it was {}!", name, IW4X_MAT_BIN_VERSION, version); - } - - auto* asset = reader.readObject(); - - if (asset->info.name) - { - asset->info.name = reader.readCString(); - } - - if (asset->techniqueSet) - { - std::string techsetName = reader.readString(); - if (!techsetName.empty() && techsetName.front() == ',') techsetName.erase(techsetName.begin()); - asset->techniqueSet = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_TECHNIQUE_SET, techsetName.data(), builder).techniqueSet; - - if (!asset->techniqueSet) - { - // Workaround for effect techsets having _nofog suffix - std::string suffix; - if (Utils::String::StartsWith(techsetName, "effect_") && Utils::String::EndsWith(techsetName, "_nofog")) - { - suffix = "_nofog"; - Utils::String::Replace(techsetName, suffix, ""); - } - - for (int i = 0; i < ARRAYSIZE(techsetSuffix); ++i) - { - Game::MaterialTechniqueSet* techsetPtr = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_TECHNIQUE_SET, (techsetName + techsetSuffix[i] + suffix).data(), builder).techniqueSet; - - if (techsetPtr) - { - asset->techniqueSet = techsetPtr; - - if (asset->techniqueSet->name[0] == ',') continue; // Try to find a better one - Components::Logger::Print("Techset '{}' has been mapped to '{}'\n", techsetName, asset->techniqueSet->name); - break; - } - } - } - else - { - Components::Logger::Print("Techset {} exists with the same name in iw4, and was mapped 1:1 with {}\n", techsetName, asset->techniqueSet->name); - } - - if (!asset->techniqueSet) - { - Components::Logger::Error(Game::ERR_FATAL, "Missing techset: '{}' not found", techsetName); - } - } - - if (asset->textureTable) - { - asset->textureTable = reader.readArray(asset->textureCount); - - for (char i = 0; i < asset->textureCount; ++i) - { - Game::MaterialTextureDef* textureDef = &asset->textureTable[i]; - - if (textureDef->semantic == Game::TextureSemantic::TS_WATER_MAP) - { - if (textureDef->u.water) - { - Game::water_t* water = reader.readObject(); - textureDef->u.water = water; - - // Save_water_t - if (water->H0) - { - water->H0 = reader.readArray(water->M * water->N); - } - - if (water->wTerm) - { - water->wTerm = reader.readArray(water->M * water->N); - } - - if (water->image) - { - water->image = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_IMAGE, reader.readString().data(), builder).image; - } - } - } - else if (textureDef->u.image) - { - textureDef->u.image = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_IMAGE, reader.readString().data(), builder).image; - } - } - } - - if (asset->constantTable) - { - asset->constantTable = reader.readArray(asset->constantCount); - } - - if (asset->stateBitsTable) - { - asset->stateBitsTable = reader.readArray(asset->stateBitsCount); - } - - header->material = asset; - - static thread_local bool replacementFound; - replacementFound = false; - - // Find correct sortkey by comparing techsets - Game::DB_EnumXAssetEntries(Game::XAssetType::ASSET_TYPE_MATERIAL, [asset](Game::XAssetEntry* entry) - { - if (!replacementFound) - { - Game::XAssetHeader header = entry->asset.header; - - const char* name = asset->techniqueSet->name; - if (name[0] == ',') ++name; - - if (std::string(name) == header.material->techniqueSet->name) - { - asset->info.sortKey = header.material->info.sortKey; - - // This is temp, as nobody has time to fix materials - asset->stateBitsCount = header.material->stateBitsCount; - asset->stateBitsTable = header.material->stateBitsTable; - std::memcpy(asset->stateBitsEntry, header.material->stateBitsEntry, ARRAYSIZE(asset->stateBitsEntry)); - asset->constantCount = header.material->constantCount; - asset->constantTable = header.material->constantTable; - Components::Logger::Print("For {}, copied constants & statebits from {}\n", asset->info.name, header.material->info.name); - replacementFound = true; - } - } - }, false); - - if (!replacementFound) - { - auto techsetMatches = [](Game::Material* m1, Game::Material* m2) - { - Game::MaterialTechniqueSet* t1 = m1->techniqueSet; - Game::MaterialTechniqueSet* t2 = m2->techniqueSet; - if (!t1 || !t2) return false; - if (t1->remappedTechniqueSet && t2->remappedTechniqueSet && std::string(t1->remappedTechniqueSet->name) == t2->remappedTechniqueSet->name) return true; - - for (int i = 0; i < ARRAYSIZE(t1->techniques); ++i) - { - if (!t1->techniques[i] && !t2->techniques[i]) continue;; - if (!t1->techniques[i] || !t2->techniques[i]) return false; - - // Apparently, this is really not that important - //if (t1->techniques[i]->flags != t2->techniques[i]->flags) return false; - } - - return true; - }; - - - Game::DB_EnumXAssetEntries(Game::XAssetType::ASSET_TYPE_MATERIAL, [asset, techsetMatches](Game::XAssetEntry* entry) - { - if (!replacementFound) - { - Game::XAssetHeader header = entry->asset.header; - - if (techsetMatches(header.material, asset)) - { - Components::Logger::Print("Material {} with techset {} has been mapped to {}\n", asset->info.name, asset->techniqueSet->name, header.material->techniqueSet->name); - asset->info.sortKey = header.material->info.sortKey; - replacementFound = true; - } - } - }, false); - } - - if (!replacementFound && asset->techniqueSet) - { - Components::Logger::Print("No replacement found for material {} with techset {}\n", asset->info.name, asset->techniqueSet->name); - std::string techName = asset->techniqueSet->name; - if (const auto itr = techSetCorrespondance.find(techName); itr != techSetCorrespondance.end()) - { - auto& iw4TechSetName = itr->second; - auto* iw4TechSet = Game::DB_FindXAssetEntry(Game::ASSET_TYPE_TECHNIQUE_SET, iw4TechSetName.data()); - - if (iw4TechSet) - { - Game::DB_EnumXAssetEntries(Game::XAssetType::ASSET_TYPE_MATERIAL, [asset, iw4TechSet](Game::XAssetEntry* entry) - { - if (!replacementFound) - { - Game::XAssetHeader header = entry->asset.header; - Components::Logger::Print("Material {} with techset {} has been mapped to {} (last chance!), taking the sort key of material {}\n", - asset->info.name, asset->techniqueSet->name, header.material->techniqueSet->name, header.material->info.name); - - // Yeah this has a tendency to fuck up a LOT of transparent materials - if (header.material->techniqueSet == iw4TechSet->asset.header.techniqueSet && std::string(header.material->info.name).find("icon") != std::string::npos) - { - Components::Logger::Print("Material {} with techset {} has been mapped to {} (last chance!), taking the sort key of material {}\n", - asset->info.name, asset->techniqueSet->name, header.material->techniqueSet->name, header.material->info.name); - - asset->info.sortKey = header.material->info.sortKey; - asset->techniqueSet = iw4TechSet->asset.header.techniqueSet; - - // this is terrible! - asset->stateBitsCount = header.material->stateBitsCount; - asset->stateBitsTable = header.material->stateBitsTable; - std::memcpy(asset->stateBitsEntry, header.material->stateBitsEntry, 48); - asset->constantCount = header.material->constantCount; - asset->constantTable = header.material->constantTable; - - replacementFound = true; - } - } - }, false); - - if (!replacementFound) - { - Components::Logger::Print("Could not find any loaded material with techset {} (in replacement of {}), so I cannot set the sortkey for material {}\n", iw4TechSetName, asset->techniqueSet->name, asset->info.name); - } - } - else - { - Components::Logger::Print("Could not find any loaded techset with iw4 name {} for iw3 techset {}\n", iw4TechSetName, asset->techniqueSet->name); - } - } - else - { - Components::Logger::Print("Could not match iw3 techset {} with any of the techsets I know! This is a critical error, there's a good chance the map will not be playable.\n", techName); - } - } - - if (!reader.end()) - { - Components::Logger::Error(Game::ERR_FATAL, "Material data left!"); - } - - /*char baseIndex = 0; - for (char i = 0; i < asset->stateBitsCount; ++i) - { - auto stateBits = asset->stateBitsTable[i]; - if (stateBits.loadBits[0] == 0x18128812 && - stateBits.loadBits[1] == 0xD) // Seems to be like a default stateBit causing a 'generic' initialization - { - baseIndex = i; - break; - } - } - - for (int i = 0; i < 48; ++i) - { - if (!asset->techniqueSet->techniques[i] && asset->stateBitsEntry[i] != -1) - { - asset->stateBitsEntry[i] = -1; - } - - if (asset->techniqueSet->techniques[i] && asset->stateBitsEntry[i] == -1) - { - asset->stateBitsEntry[i] = baseIndex; - } - }*/ + header->material = builder->getIW4OfApi()->read(Game::XAssetType::ASSET_TYPE_MATERIAL, name); } void IMaterial::loadNative(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* /*builder*/) diff --git a/src/Components/Modules/AssetInterfaces/IMaterial.hpp b/src/Components/Modules/AssetInterfaces/IMaterial.hpp index 2aa645b7..adec2e19 100644 --- a/src/Components/Modules/AssetInterfaces/IMaterial.hpp +++ b/src/Components/Modules/AssetInterfaces/IMaterial.hpp @@ -10,11 +10,7 @@ namespace Assets void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override; void mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override; void load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) override; - void loadJson(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder); + void loadFromDisk(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder); void loadNative(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder); - void loadBinary(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder); - - private: - Game::MaterialTechniqueSet* findWorkingTechset(const std::string techsetName, Game::Material* material, Components::ZoneBuilder::Zone* builder) const; }; } diff --git a/src/Components/Modules/AssetInterfaces/IMaterialPixelShader.cpp b/src/Components/Modules/AssetInterfaces/IMaterialPixelShader.cpp index 7c3737e4..99d56b85 100644 --- a/src/Components/Modules/AssetInterfaces/IMaterialPixelShader.cpp +++ b/src/Components/Modules/AssetInterfaces/IMaterialPixelShader.cpp @@ -1,8 +1,6 @@ #include #include "IMaterialPixelShader.hpp" -#define GFX_RENDERER_SHADER_SM3 0 - namespace Assets { @@ -19,21 +17,7 @@ namespace Assets void IMaterialPixelShader::loadBinary(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) { - Components::FileSystem::File psFile(std::format("ps/{}.cso", name)); - if (!psFile.exists()) return; - - auto buff = psFile.getBuffer(); - auto programSize = buff.size() / 4; - Game::MaterialPixelShader* asset = builder->getAllocator()->allocate(); - - asset->name = builder->getAllocator()->duplicateString(name); - asset->prog.loadDef.loadForRenderer = GFX_RENDERER_SHADER_SM3; - asset->prog.loadDef.programSize = static_cast(programSize); - asset->prog.loadDef.program = builder->getAllocator()->allocateArray(programSize); - memcpy_s(asset->prog.loadDef.program, buff.size(), buff.data(), buff.size()); - - - header->pixelShader = asset; + header->pixelShader = builder->getIW4OfApi()->read(Game::XAssetType::ASSET_TYPE_PIXELSHADER, name); } void IMaterialPixelShader::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) diff --git a/src/Components/Modules/AssetInterfaces/IMaterialTechniqueSet.cpp b/src/Components/Modules/AssetInterfaces/IMaterialTechniqueSet.cpp index 839c3fb0..c0e81d06 100644 --- a/src/Components/Modules/AssetInterfaces/IMaterialTechniqueSet.cpp +++ b/src/Components/Modules/AssetInterfaces/IMaterialTechniqueSet.cpp @@ -1,6 +1,8 @@ #include #include "IMaterialTechniqueSet.hpp" +#include + #define IW4X_TECHSET_VERSION 1 namespace Assets @@ -9,6 +11,11 @@ namespace Assets { if (!header->data) this->loadFromDisk(header, name, builder); // Check if we need to import a new one into the game if (!header->data) this->loadNative(header, name, builder); // Check if there is a native one + + if (!header->data) + { + AssertUnreachable; + } } void IMaterialTechniqueSet::loadNative(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* /*builder*/) @@ -16,200 +23,36 @@ namespace Assets header->techniqueSet = Components::AssetHandler::FindOriginalAsset(this->getType(), name.data()).techniqueSet; } - void IMaterialTechniqueSet::loadTechniqueFromDisk(Game::MaterialTechnique** tech, const std::string& name, Components::ZoneBuilder::Zone* builder) + void IMaterialTechniqueSet::loadFromDisk(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) { - AssertSize(Game::MaterialPass, 20); + header->techniqueSet = builder->getIW4OfApi()->read(Game::ASSET_TYPE_TECHNIQUE_SET, name); - Components::FileSystem::File techFile(std::format("techniques/{}.iw4x.json", name)); - if (!techFile.exists()) + auto ptr = header->techniqueSet; + if (ptr) { - *tech = nullptr; - - Components::Logger::Warning(Game::CON_CHANNEL_DONT_FILTER, "Missing technique '{}'\n", name); - return; - } - - nlohmann::json technique; - - try - { - technique = nlohmann::json::parse(techFile.getBuffer()); - } - catch (std::exception& e) - { - Components::Logger::Error(Game::ERR_FATAL, "Reading techset '{}' failed, file is messed up! {}", name, e.what()); - } - - int version = technique["version"].get(); - - if (version != IW4X_TECHSET_VERSION) - { - Components::Logger::Error(Game::ERR_FATAL, - "Reading technique '{}' failed, expected version is {}, but it was {}!", name, IW4X_TECHSET_VERSION, version); - } - - unsigned short flags = static_cast(Utils::Json::ReadFlags(technique["flags"].get(), sizeof(short))); - - if (technique["passArray"].is_array()) - { - nlohmann::json::array_t passArray = technique["passArray"]; - - Game::MaterialTechnique* asset = (Game::MaterialTechnique*)builder->getAllocator()->allocateArray(sizeof(Game::MaterialTechnique) + (sizeof(Game::MaterialPass) * (passArray.size() - 1))); - - asset->name = builder->getAllocator()->duplicateString(name); - asset->flags = flags; - asset->passCount = static_cast(passArray.size()); - - Game::MaterialPass* passes = builder->getAllocator()->allocateArray(asset->passCount); - std::memcpy(asset->passArray, passes, sizeof(Game::MaterialPass) * asset->passCount); - - for (unsigned short i = 0; i < asset->passCount; i++) + while (ptr->remappedTechniqueSet && ptr->remappedTechniqueSet != ptr) { - Game::MaterialPass* pass = &asset->passArray[i]; - auto jsonPass = passArray[i]; + ptr = ptr->remappedTechniqueSet; + builder->loadAsset(Game::ASSET_TYPE_TECHNIQUE_SET, ptr, false); - if (jsonPass["vertexDeclaration"].is_string()) + for (size_t i = 0; i < Game::TECHNIQUE_COUNT; i++) { - auto declName = jsonPass["vertexDeclaration"].get(); - pass->vertexDecl = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_VERTEXDECL, declName, builder).vertexDecl; - } - - if (jsonPass["vertexShader"].is_string()) - { - auto vsName = jsonPass["vertexShader"].get(); - pass->vertexShader = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_VERTEXSHADER, vsName, builder).vertexShader; - } - - if (jsonPass["pixelShader"].is_string()) - { - auto psName = jsonPass["pixelShader"].get(); - pass->pixelShader = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_PIXELSHADER, psName, builder).pixelShader; - } - - pass->perPrimArgCount = jsonPass["perPrimArgCount"].get(); - pass->perObjArgCount = jsonPass["perObjArgCount"].get(); - pass->stableArgCount = jsonPass["stableArgCount"].get(); - pass->customSamplerFlags = jsonPass["customSamplerFlags"].get(); - - - if (jsonPass["arguments"].is_array()) - { - nlohmann::json::array_t jsonAguments = jsonPass["arguments"]; - - pass->args = builder->getAllocator()->allocateArray(jsonAguments.size()); - - for (size_t j = 0; j < jsonAguments.size(); j++) + const auto technique = ptr->techniques[i]; + if (technique) { - auto jsonArgument = jsonAguments[j]; - Game::MaterialShaderArgument* argument = &pass->args[j]; - - argument->type = jsonArgument["type"].get(); - argument->dest = jsonArgument["dest"].get(); - - if (argument->type == Game::MaterialShaderArgumentType::MTL_ARG_LITERAL_VERTEX_CONST || - argument->type == Game::MaterialShaderArgumentType::MTL_ARG_LITERAL_PIXEL_CONST) + for (size_t j = 0; j < technique->passCount; j++) { - argument->u.literalConst = builder->getAllocator()->allocateArray(4); - - auto literals = jsonArgument["literals"].get>(); - std::copy(literals.begin(), literals.end(), argument->u.literalConst); - } - else if (argument->type == Game::MaterialShaderArgumentType::MTL_ARG_CODE_VERTEX_CONST || - argument->type == Game::MaterialShaderArgumentType::MTL_ARG_CODE_PIXEL_CONST) - { - if (jsonArgument["codeConst"].is_object()) - { - auto codeConst = jsonArgument["codeConst"]; - - argument->u.codeConst.index = codeConst["index"].get(); - argument->u.codeConst.firstRow = codeConst["firstRow"].get(); - argument->u.codeConst.rowCount = codeConst["rowCount"].get(); - } - } - else if (argument->type == Game::MaterialShaderArgumentType::MTL_ARG_MATERIAL_PIXEL_SAMPLER || - argument->type == Game::MaterialShaderArgumentType::MTL_ARG_MATERIAL_VERTEX_CONST || - argument->type == Game::MaterialShaderArgumentType::MTL_ARG_MATERIAL_PIXEL_CONST) - { - argument->u.nameHash = jsonArgument["nameHash"].get(); - } - else if (argument->type == Game::MaterialShaderArgumentType::MTL_ARG_CODE_PIXEL_SAMPLER) - { - argument->u.codeSampler = jsonArgument["codeSampler"].get(); + const auto pass = &technique->passArray[j]; + builder->loadAsset(Game::ASSET_TYPE_VERTEXDECL, pass->vertexDecl, true); + builder->loadAsset(Game::ASSET_TYPE_PIXELSHADER, pass->pixelShader, true); + builder->loadAsset(Game::ASSET_TYPE_VERTEXSHADER, pass->vertexShader, true); } } } } - - *tech = asset; } } - void IMaterialTechniqueSet::loadFromDisk(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) - { - Components::FileSystem::File tsFile(std::format("techsets/{}.iw4x.json", name)); - if (!tsFile.exists()) return; - - nlohmann::json techset; - - try - { - techset = nlohmann::json::parse(tsFile.getBuffer()); - } - catch (std::exception& e) - { - Components::Logger::Error(Game::ERR_FATAL, "Reading techset '{}' failed, file is messed up! {}", name, e.what()); - } - - auto version = techset["version"].get(); - if (version != IW4X_TECHSET_VERSION) - { - Components::Logger::Error(Game::ERR_FATAL, "Reading techset '{}' failed, expected version is {}, but it was {}!", - name, IW4X_TECHSET_VERSION, version); - } - - Game::MaterialTechniqueSet* asset = builder->getAllocator()->allocate(); - - if (asset == nullptr) - { - Components::Logger::Error(Game::ERR_FATAL, "Reading techset '{}' failed, allocation failed!", name); - return; - } - - if (techset["name"].is_string()) - { - asset->name = builder->getAllocator()->duplicateString(techset["name"].get()); - } - - asset->hasBeenUploaded = techset["hasBeenUploaded"].get(); - asset->worldVertFormat = techset["worldVertFormat"].get(); - - - if (techset["remappedTechniqueSet"].is_string()) - { - auto remapped = techset["remappedTechniqueSet"].get(); - - if (remapped != asset->name) - { - builder->loadAssetByName(Game::XAssetType::ASSET_TYPE_TECHNIQUE_SET, remapped, false); - } - } - - if (techset["techniques"].is_object()) - { - for (int i = 0; i < Game::TECHNIQUE_COUNT; i++) - { - auto technique = techset["techniques"].at(std::to_string(i)); - - if (technique.is_string()) - { - this->loadTechniqueFromDisk(&asset->techniques[i], technique.get(), builder); - } - } - } - - header->techniqueSet = asset; - } - void IMaterialTechniqueSet::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) { Game::MaterialTechniqueSet* asset = header.techniqueSet; diff --git a/src/Components/Modules/AssetInterfaces/IMaterialVertexDeclaration.cpp b/src/Components/Modules/AssetInterfaces/IMaterialVertexDeclaration.cpp index 26b16d11..af4894de 100644 --- a/src/Components/Modules/AssetInterfaces/IMaterialVertexDeclaration.cpp +++ b/src/Components/Modules/AssetInterfaces/IMaterialVertexDeclaration.cpp @@ -18,32 +18,7 @@ namespace Assets void IMaterialVertexDeclaration::loadBinary(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) { - Components::FileSystem::File declFile(std::format("decl/{}.iw4xDECL", name)); - if (!declFile.exists()) return; - - Utils::Stream::Reader reader(builder->getAllocator(), declFile.getBuffer()); - - char* magic = reader.readArray(8); - if (std::memcmp(magic, "IW4xDECL", 8)) - { - Components::Logger::Error(Game::ERR_FATAL, "Reading vertex declaration '{}' failed, header is invalid!", name); - } - - auto version = reader.read(); - if (version != IW4X_TECHSET_VERSION) - { - Components::Logger::Error(Game::ERR_FATAL, "Reading vertex declaration '{}' failed, expected version is {}, but it was {:d}!", - name, IW4X_TECHSET_VERSION, version); - } - - Game::MaterialVertexDeclaration* asset = reader.readObject(); - - if (asset->name) - { - asset->name = reader.readCString(); - } - - header->vertexDecl = asset; + header->vertexDecl = builder->getIW4OfApi()->read(Game::XAssetType::ASSET_TYPE_VERTEXDECL, name); } void IMaterialVertexDeclaration::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) diff --git a/src/Components/Modules/AssetInterfaces/IMaterialVertexShader.cpp b/src/Components/Modules/AssetInterfaces/IMaterialVertexShader.cpp index e4d7c9b5..bfa0c9b5 100644 --- a/src/Components/Modules/AssetInterfaces/IMaterialVertexShader.cpp +++ b/src/Components/Modules/AssetInterfaces/IMaterialVertexShader.cpp @@ -18,20 +18,7 @@ namespace Assets void IMaterialVertexShader::loadBinary(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) { - Components::FileSystem::File vsFile(std::format("vs/{}.cso", name)); - if (!vsFile.exists()) return; - - auto buff = vsFile.getBuffer(); - auto programSize = buff.size() / 4; - Game::MaterialVertexShader* asset = builder->getAllocator()->allocate(); - - asset->name = builder->getAllocator()->duplicateString(name); - asset->prog.loadDef.loadForRenderer = GFX_RENDERER_SHADER_SM3; - asset->prog.loadDef.programSize = static_cast(programSize); - asset->prog.loadDef.program = builder->getAllocator()->allocateArray(programSize); - memcpy_s(asset->prog.loadDef.program, buff.size(), buff.data(), buff.size()); - - header->vertexShader = asset; + header->vertexShader = builder->getIW4OfApi()->read(Game::XAssetType::ASSET_TYPE_VERTEXSHADER, name); } void IMaterialVertexShader::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) diff --git a/src/Components/Modules/AssetInterfaces/IPhysPreset.cpp b/src/Components/Modules/AssetInterfaces/IPhysPreset.cpp index ad753a5c..d485e230 100644 --- a/src/Components/Modules/AssetInterfaces/IPhysPreset.cpp +++ b/src/Components/Modules/AssetInterfaces/IPhysPreset.cpp @@ -7,9 +7,9 @@ namespace Assets { AssertSize(Game::PhysPreset, 44); - Utils::Stream* buffer = builder->getBuffer(); - Game::PhysPreset* asset = header.physPreset; - Game::PhysPreset* dest = buffer->dest(); + auto* buffer = builder->getBuffer(); + auto* asset = header.physPreset; + auto* dest = buffer->dest(); buffer->save(asset); buffer->pushBlock(Game::XFILE_BLOCK_VIRTUAL); @@ -28,4 +28,14 @@ namespace Assets buffer->popBlock(); } + + void IPhysPreset::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) + { + loadFromDisk(header, name, builder); + } + + void IPhysPreset::loadFromDisk(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) + { + header->physPreset = builder->getIW4OfApi()->read(Game::XAssetType::ASSET_TYPE_PHYSPRESET, name); + } } diff --git a/src/Components/Modules/AssetInterfaces/IPhysPreset.hpp b/src/Components/Modules/AssetInterfaces/IPhysPreset.hpp index f3939a08..47fb85e8 100644 --- a/src/Components/Modules/AssetInterfaces/IPhysPreset.hpp +++ b/src/Components/Modules/AssetInterfaces/IPhysPreset.hpp @@ -8,5 +8,7 @@ namespace Assets Game::XAssetType getType() override { return Game::XAssetType::ASSET_TYPE_PHYSPRESET; } void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override; + void load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) override; + void loadFromDisk(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder); }; } diff --git a/src/Components/Modules/AssetInterfaces/IRawFile.cpp b/src/Components/Modules/AssetInterfaces/IRawFile.cpp index 5a254acd..15782c93 100644 --- a/src/Components/Modules/AssetInterfaces/IRawFile.cpp +++ b/src/Components/Modules/AssetInterfaces/IRawFile.cpp @@ -1,42 +1,13 @@ #include #include "IRawFile.hpp" +#include + namespace Assets { void IRawFile::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) { - Components::FileSystem::File rawFile(name); - - if (!rawFile.exists()) - { - return; - } - - auto* asset = builder->getAllocator()->allocate(); - if (!asset) - { - return; - } - - asset->name = builder->getAllocator()->duplicateString(name); - asset->len = static_cast(rawFile.getBuffer().size()); - - const auto compressedData = Utils::Compression::ZLib::Compress(rawFile.getBuffer()); - // Only save the compressed buffer if we gained space - if (compressedData.size() < rawFile.getBuffer().size()) - { - asset->buffer = builder->getAllocator()->allocateArray(compressedData.size()); - std::memcpy(const_cast(asset->buffer), compressedData.data(), compressedData.size()); - asset->compressedLen = static_cast(compressedData.size()); - } - else - { - asset->buffer = builder->getAllocator()->allocateArray(rawFile.getBuffer().size() + 1); - std::memcpy(const_cast(asset->buffer), rawFile.getBuffer().data(), rawFile.getBuffer().size()); - asset->compressedLen = 0; - } - - header->rawfile = asset; + header->rawfile = builder->getIW4OfApi()->read(Game::XAssetType::ASSET_TYPE_RAWFILE, name); } void IRawFile::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) diff --git a/src/Components/Modules/AssetInterfaces/ISndCurve.cpp b/src/Components/Modules/AssetInterfaces/ISndCurve.cpp index 1a7d0f01..a3a7a496 100644 --- a/src/Components/Modules/AssetInterfaces/ISndCurve.cpp +++ b/src/Components/Modules/AssetInterfaces/ISndCurve.cpp @@ -7,9 +7,9 @@ namespace Assets { AssertSize(Game::SndCurve, 136); - Utils::Stream* buffer = builder->getBuffer(); - Game::SndCurve* asset = header.sndCurve; - Game::SndCurve* dest = buffer->dest(); + auto* buffer = builder->getBuffer(); + auto* asset = header.sndCurve; + auto* dest = buffer->dest(); buffer->save(asset); buffer->pushBlock(Game::XFILE_BLOCK_VIRTUAL); @@ -22,4 +22,14 @@ namespace Assets buffer->popBlock(); } + + void ISndCurve::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) + { + header->sndCurve = builder->getIW4OfApi()->read(Game::XAssetType::ASSET_TYPE_SOUND_CURVE, name); + + if (!header->sndCurve) + { + header->sndCurve = Components::AssetHandler::FindOriginalAsset(Game::XAssetType::ASSET_TYPE_SOUND_CURVE, name.data()).sndCurve; + } + } } diff --git a/src/Components/Modules/AssetInterfaces/ISndCurve.hpp b/src/Components/Modules/AssetInterfaces/ISndCurve.hpp index 0b0d7564..0d2cc831 100644 --- a/src/Components/Modules/AssetInterfaces/ISndCurve.hpp +++ b/src/Components/Modules/AssetInterfaces/ISndCurve.hpp @@ -8,5 +8,6 @@ namespace Assets Game::XAssetType getType() override { return Game::XAssetType::ASSET_TYPE_SOUND_CURVE; } void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override; + void load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) override; }; } diff --git a/src/Components/Modules/AssetInterfaces/IXAnimParts.cpp b/src/Components/Modules/AssetInterfaces/IXAnimParts.cpp index 0bd5e2ce..e8b9860c 100644 --- a/src/Components/Modules/AssetInterfaces/IXAnimParts.cpp +++ b/src/Components/Modules/AssetInterfaces/IXAnimParts.cpp @@ -1,108 +1,11 @@ #include #include "IXAnimParts.hpp" -#define IW4X_ANIM_VERSION 1 - namespace Assets { void IXAnimParts::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) { - Components::FileSystem::File animFile(std::format("xanim/{}.iw4xAnim", name)); - - if (animFile.exists()) - { - Utils::Stream::Reader reader(builder->getAllocator(), animFile.getBuffer()); - - __int64 magic = reader.read<__int64>(); - if (std::memcmp(&magic, "IW4xAnim", 8)) - { - Components::Logger::Error(Game::ERR_FATAL, "Reading animation '{}' failed, header is invalid!", name); - } - - int version = reader.read(); - if (version != IW4X_ANIM_VERSION) - { - Components::Logger::Error(Game::ERR_FATAL, "Reading animation '{}' failed, expected version is {}, but it was {}!", name, IW4X_ANIM_VERSION, version); - } - - Game::XAnimParts* xanim = reader.readArray(); - - if (xanim) - { - if (xanim->name) - { - xanim->name = reader.readCString(); - } - - if (xanim->names) - { - xanim->names = builder->getAllocator()->allocateArray(xanim->boneCount[Game::PART_TYPE_ALL]); - for (int i = 0; i < xanim->boneCount[Game::PART_TYPE_ALL]; ++i) - { - xanim->names[i] = static_cast(Game::SL_GetString(reader.readCString(), 0)); - } - } - - if (xanim->notify) - { - xanim->notify = reader.readArray(xanim->notifyCount); - - for (int i = 0; i < xanim->notifyCount; ++i) - { - xanim->notify[i].name = static_cast(Game::SL_GetString(reader.readCString(), 0)); - } - } - - if (xanim->dataByte) - { - xanim->dataByte = reader.readArray(xanim->dataByteCount); - } - - if (xanim->dataShort) - { - xanim->dataShort = reader.readArray(xanim->dataShortCount); - } - - if (xanim->dataInt) - { - xanim->dataInt = reader.readArray(xanim->dataIntCount); - } - - if (xanim->randomDataByte) - { - xanim->randomDataByte = reader.readArray(xanim->randomDataByteCount); - } - - if (xanim->randomDataShort) - { - xanim->randomDataShort = reader.readArray(xanim->randomDataShortCount); - } - - if (xanim->randomDataInt) - { - xanim->randomDataInt = reader.readArray(xanim->randomDataIntCount); - } - - if (xanim->indices.data) - { - if (xanim->numframes < 256) - { - xanim->indices._1 = reader.readArray(xanim->indexCount); - } - else - { - xanim->indices._2 = reader.readArray(xanim->indexCount); - } - } - - if (!reader.end()) - { - Components::Logger::Error(Game::ERR_FATAL, "Reading animation '{}' failed, remaining raw data found!", name); - } - - header->parts = xanim; - } - } + header->parts = builder->getIW4OfApi()->read(Game::XAssetType::ASSET_TYPE_XANIMPARTS, name); } void IXAnimParts::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) diff --git a/src/Components/Modules/AssetInterfaces/IXModel.cpp b/src/Components/Modules/AssetInterfaces/IXModel.cpp index 305a624c..d4444712 100644 --- a/src/Components/Modules/AssetInterfaces/IXModel.cpp +++ b/src/Components/Modules/AssetInterfaces/IXModel.cpp @@ -1,298 +1,30 @@ #include #include "IXModel.hpp" -#define IW4X_MODEL_VERSION 8 - namespace Assets { - void IXModel::loadXSurfaceCollisionTree(Game::XSurfaceCollisionTree* entry, Utils::Stream::Reader* reader) - { - if (entry->nodes) - { - entry->nodes = reader->readArrayOnce(entry->nodeCount); - } - - if (entry->leafs) - { - entry->leafs = reader->readArrayOnce(entry->leafCount); - } - } - - void IXModel::loadXSurface(Game::XSurface* surf, Utils::Stream::Reader* reader, [[maybe_unused]] Components::ZoneBuilder::Zone* builder) - { - if (surf->vertInfo.vertsBlend) - { - surf->vertInfo.vertsBlend = reader->readArrayOnce(surf->vertInfo.vertCount[0] + (surf->vertInfo.vertCount[1] * 3) + (surf->vertInfo.vertCount[2] * 5) + (surf->vertInfo.vertCount[3] * 7)); - } - - // Access vertex block - if (surf->verts0) - { - surf->verts0 = reader->readArrayOnce(surf->vertCount); - } - - // Save_XRigidVertListArray - if (surf->vertList) - { - surf->vertList = reader->readArrayOnce(surf->vertListCount); - - for (unsigned int i = 0; i < surf->vertListCount; ++i) - { - Game::XRigidVertList* rigidVertList = &surf->vertList[i]; - - if (rigidVertList->collisionTree) - { - rigidVertList->collisionTree = reader->readObject(); - this->loadXSurfaceCollisionTree(rigidVertList->collisionTree, reader); - } - } - } - - // Access index block - if (surf->triIndices) - { - surf->triIndices = reader->readArrayOnce(surf->triCount * 3); - } - } - - void IXModel::loadXModelSurfs(Game::XModelSurfs* asset, Utils::Stream::Reader* reader, Components::ZoneBuilder::Zone* builder) - { - if (asset->name) - { - asset->name = reader->readCString(); - } - - if (asset->surfs) - { - asset->surfs = reader->readArrayOnce(asset->numsurfs); - - for (int i = 0; i < asset->numsurfs; ++i) - { - this->loadXSurface(&asset->surfs[i], reader, builder); - } - } - } - void IXModel::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) { - Components::FileSystem::File modelFile(std::format("xmodel/{}.iw4xModel", name)); + header->model = builder->getIW4OfApi()->read(Game::XAssetType::ASSET_TYPE_XMODEL, name); - if (!builder->isPrimaryAsset() && (!Components::ZoneBuilder::PreferDiskAssetsDvar.get() || !modelFile.exists())) + if (header->model) { - header->model = Components::AssetHandler::FindOriginalAsset(this->getType(), name.data()).model; - if (header->model) return; - } - - - if (modelFile.exists()) - { - Utils::Stream::Reader reader(builder->getAllocator(), modelFile.getBuffer()); - - __int64 magic = reader.read<__int64>(); - if (std::memcmp(&magic, "IW4xModl", 8)) + // ??? + if (header->model->physCollmap) { - Components::Logger::Error(Game::ERR_FATAL, "Reading model '{}' failed, header is invalid!", name); + Components::AssetHandler::StoreTemporaryAsset(Game::XAssetType::ASSET_TYPE_PHYSCOLLMAP, { header->model->physCollmap }); } - int version = reader.read(); - if (version != IW4X_MODEL_VERSION) + if (header->model->physPreset) { - Components::Logger::Error(Game::ERR_FATAL, "Reading model '{}' failed, expected version is {}, but it was {}!", name, IW4X_MODEL_VERSION, version); + Components::AssetHandler::StoreTemporaryAsset(Game::XAssetType::ASSET_TYPE_PHYSPRESET, { header->model->physPreset }); } - Game::XModel* asset = reader.readObject(); - - if (asset->name) + for (size_t i = 0; i < header->model->numLods; i++) { - asset->name = reader.readCString(); + const auto& info = header->model->lodInfo[i]; + Components::AssetHandler::StoreTemporaryAsset(Game::XAssetType::ASSET_TYPE_XMODEL_SURFS, { info.modelSurfs }); } - - if (asset->boneNames) - { - asset->boneNames = builder->getAllocator()->allocateArray(asset->numBones); - - for (char i = 0; i < asset->numBones; ++i) - { - asset->boneNames[i] = static_cast(Game::SL_GetString(reader.readCString(), 0)); - } - } - - if (asset->parentList) - { - asset->parentList = reader.readArrayOnce(asset->numBones - asset->numRootBones); - } - - if (asset->quats) - { - asset->quats = reader.readArrayOnce((asset->numBones - asset->numRootBones) * 4); - } - - if (asset->trans) - { - asset->trans = reader.readArrayOnce((asset->numBones - asset->numRootBones) * 3); - } - - if (asset->partClassification) - { - asset->partClassification = reader.readArrayOnce(asset->numBones); - } - - if (asset->baseMat) - { - asset->baseMat = reader.readArrayOnce(asset->numBones); - } - - if (asset->materialHandles) - { - asset->materialHandles = reader.readArray(asset->numsurfs); - - for (unsigned char i = 0; i < asset->numsurfs; ++i) - { - if (asset->materialHandles[i]) - { - asset->materialHandles[i] = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_MATERIAL, reader.readString(), builder).material; - } - } - } - - // Save_XModelLodInfoArray - { - for (unsigned int i = 0; i < 4; ++i) - { - if (asset->lodInfo[i].modelSurfs) - { - asset->lodInfo[i].modelSurfs = reader.readObject(); - this->loadXModelSurfs(asset->lodInfo[i].modelSurfs, &reader, builder); - Components::AssetHandler::StoreTemporaryAsset(Game::XAssetType::ASSET_TYPE_XMODEL_SURFS, { asset->lodInfo[i].modelSurfs }); - - asset->lodInfo[i].surfs = asset->lodInfo[i].modelSurfs->surfs; - - // Zero that for now, it breaks the models. - // TODO: Figure out how that can be converted - asset->lodInfo[i].smcBaseIndexPlusOne = 0; - asset->lodInfo[i].smcSubIndexMask = 0; - asset->lodInfo[i].smcBucket = 0; - } - } - } - - // Save_XModelCollSurfArray - if (asset->collSurfs) - { - asset->collSurfs = reader.readArray(asset->numCollSurfs); - - for (int i = 0; i < asset->numCollSurfs; ++i) - { - Game::XModelCollSurf_s* collSurf = &asset->collSurfs[i]; - - if (collSurf->collTris) - { - collSurf->collTris = reader.readArray(collSurf->numCollTris); - } - } - } - - if (asset->boneInfo) - { - asset->boneInfo = reader.readArray(asset->numBones); - } - - if (asset->physPreset) - { - asset->physPreset = reader.readObject(); - - if (asset->physPreset->name) - { - asset->physPreset->name = reader.readCString(); - } - - if (asset->physPreset->sndAliasPrefix) - { - asset->physPreset->sndAliasPrefix = reader.readCString(); - } - - // This is an experiment, ak74 fails though - if (asset->name == "weapon_ak74u"s) - { - asset->physPreset = nullptr; - } - else - { - Game::PhysPreset* preset = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_PHYSPRESET, asset->physPreset->name, builder).physPreset; - if (preset) - { - asset->physPreset = preset; - } - else - { - Components::AssetHandler::StoreTemporaryAsset(Game::XAssetType::ASSET_TYPE_PHYSPRESET, { asset->physPreset }); - } - } - } - - if (asset->physCollmap) - { - Game::PhysCollmap* collmap = reader.readObject(); - asset->physCollmap = collmap; - - if (collmap->name) - { - collmap->name = reader.readCString(); - } - - if (collmap->geoms) - { - collmap->geoms = reader.readArray(collmap->count); - - for (unsigned int i = 0; i < collmap->count; ++i) - { - Game::PhysGeomInfo* geom = &collmap->geoms[i]; - - if (geom->brushWrapper) - { - Game::BrushWrapper* brush = reader.readObject(); - geom->brushWrapper = brush; - { - if (brush->brush.sides) - { - brush->brush.sides = reader.readArray(brush->brush.numsides); - for (unsigned short j = 0; j < brush->brush.numsides; ++j) - { - Game::cbrushside_t* side = &brush->brush.sides[j]; - - // TODO: Add pointer support - if (side->plane) - { - side->plane = reader.readObject(); - } - } - } - - if (brush->brush.baseAdjacentSide) - { - brush->brush.baseAdjacentSide = reader.readArray(brush->totalEdgeCount); - } - } - - // TODO: Add pointer support - if (brush->planes) - { - brush->planes = reader.readArray(brush->brush.numsides); - } - } - } - - Components::AssetHandler::StoreTemporaryAsset(Game::XAssetType::ASSET_TYPE_PHYSCOLLMAP, { asset->physCollmap }); - // asset->physCollmap = nullptr; - } - } - - if (!reader.end()) - { - Components::Logger::Error(Game::ERR_FATAL, "Reading model '{}' failed, remaining raw data found!", name); - } - - header->model = asset; } } diff --git a/src/Components/Modules/AssetInterfaces/IXModel.hpp b/src/Components/Modules/AssetInterfaces/IXModel.hpp index 44388f3d..c43ffec2 100644 --- a/src/Components/Modules/AssetInterfaces/IXModel.hpp +++ b/src/Components/Modules/AssetInterfaces/IXModel.hpp @@ -10,11 +10,5 @@ namespace Assets void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override; void mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override; void load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) override; - - private: - std::map triIndicies; - void loadXModelSurfs(Game::XModelSurfs* asset, Utils::Stream::Reader* reader, Components::ZoneBuilder::Zone* builder); - void loadXSurface(Game::XSurface* surf, Utils::Stream::Reader* reader, Components::ZoneBuilder::Zone* builder); - void loadXSurfaceCollisionTree(Game::XSurfaceCollisionTree* entry, Utils::Stream::Reader* reader); }; } diff --git a/src/Components/Modules/AssetInterfaces/IclipMap_t.cpp b/src/Components/Modules/AssetInterfaces/IclipMap_t.cpp index f0524227..82a1c3fd 100644 --- a/src/Components/Modules/AssetInterfaces/IclipMap_t.cpp +++ b/src/Components/Modules/AssetInterfaces/IclipMap_t.cpp @@ -1,8 +1,6 @@ #include #include "IclipMap_t.hpp" -#define IW4X_CLIPMAP_VERSION 2 - namespace Assets { void IclipMap_t::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) @@ -37,7 +35,7 @@ namespace Assets buffer->align(Utils::Stream::ALIGN_4); // not sure if this is needed but both brushside and brushedge need it and it can't hurt - for (int i = 0; i < asset->planeCount; ++i) + for (size_t i = 0; i < asset->planeCount; ++i) { builder->storePointer(&asset->planes[i]); buffer->save(&asset->planes[i]); @@ -170,6 +168,11 @@ namespace Assets Utils::Stream::ClearPointer(&nodes[i].plane); } } + else + { + AssertUnreachable; + Components::Logger::Error(Game::ERR_FATAL, "node {} of clipmap {} has no plane!", i, asset->name); + } } Utils::Stream::ClearPointer(&dest->nodes); @@ -192,7 +195,13 @@ namespace Assets SaveLogEnter("cLeafBrush_t"); buffer->align(Utils::Stream::ALIGN_2); - buffer->saveArray(asset->leafbrushes, asset->numLeafBrushes); + + for (size_t i = 0; i < asset->numLeafBrushes; i++) + { + builder->storePointer(&asset->leafbrushes[i]); + buffer->saveObject(asset->leafbrushes[i]); + } + Utils::Stream::ClearPointer(&dest->leafbrushes); SaveLogExit(); @@ -278,7 +287,7 @@ namespace Assets buffer->align(Utils::Stream::ALIGN_4); - for (int i = 0; i < asset->borderCount; ++i) + for (size_t i = 0; i < asset->borderCount; ++i) { builder->storePointer(&asset->borders[i]); buffer->save(&asset->borders[i]); @@ -564,381 +573,13 @@ namespace Assets } } } - builder->loadAsset(Game::XAssetType::ASSET_TYPE_MAP_ENTS, asset); + + builder->loadAsset(Game::XAssetType::ASSET_TYPE_MAP_ENTS, asset->mapEnts); } void IclipMap_t::load(Game::XAssetHeader* header, const std::string& _name, Components::ZoneBuilder::Zone* builder) { - std::string name = _name; - Utils::String::Replace(name, "maps/mp/", ""); - Utils::String::Replace(name, ".d3dbsp", ""); - - Components::FileSystem::File clipFile(std::format("clipmap/{}.iw4xClipMap", name)); - if (!clipFile.exists()) - { - return; - } - - Game::clipMap_t* clipMap = builder->getAllocator()->allocate(); - if (!clipMap) - { - Components::Logger::Print("Error allocating memory for clipMap_t structure!\n"); - return; - } - - Game::clipMap_t* orgClipMap = nullptr; - Game::DB_EnumXAssets(Game::XAssetType::ASSET_TYPE_CLIPMAP_MP, [](Game::XAssetHeader header, void* clipMap) - { - if (!*reinterpret_cast(clipMap)) - { - *reinterpret_cast(clipMap) = header.clipMap; - } - }, &orgClipMap, false); - - if (orgClipMap) std::memcpy(clipMap, orgClipMap, sizeof Game::clipMap_t); - - Utils::Stream::Reader reader(builder->getAllocator(), clipFile.getBuffer()); - - __int64 magic = reader.read<__int64>(); - if (std::memcmp(&magic, "IW4xClip", 8)) - { - Components::Logger::Error(Game::ERR_FATAL, "Reading clipMap_t '{}' failed, header is invalid!", name); - } - - int version = reader.read(); - if (version > IW4X_CLIPMAP_VERSION) - { - Components::Logger::Error(Game::ERR_FATAL, "Reading clipmap '{}' failed, expected version is {}, but it was {}!", name, IW4X_CLIPMAP_VERSION, version); - } - - clipMap->name = reader.readCString(); - - clipMap->planeCount = reader.read(); - clipMap->numStaticModels = reader.read(); - clipMap->numMaterials = reader.read(); - clipMap->numBrushSides = reader.read(); - clipMap->numBrushEdges = reader.read(); - clipMap->numNodes = reader.read(); - clipMap->numLeafs = reader.read(); - clipMap->leafbrushNodesCount = reader.read(); - clipMap->numLeafBrushes = reader.read(); - clipMap->numLeafSurfaces = reader.read(); - clipMap->vertCount = reader.read(); - clipMap->triCount = reader.read(); - clipMap->borderCount = reader.read(); - clipMap->partitionCount = reader.read(); - clipMap->aabbTreeCount = reader.read(); - clipMap->numSubModels = reader.read(); - clipMap->numBrushes = reader.read(); - clipMap->dynEntCount[0] = reader.read(); - clipMap->dynEntCount[1] = reader.read(); - - if (clipMap->planeCount) - { - void* oldPtr = reader.read(); - clipMap->planes = reader.readArray(clipMap->planeCount); - - if (builder->getAllocator()->isPointerMapped(oldPtr)) - { - clipMap->planes = builder->getAllocator()->getPointer(oldPtr); - Components::Logger::Print("ClipMap dpvs planes already mapped. This shouldn't happen. Make sure to load the ClipMap before the GfxWorld!\n"); - } - else - { - builder->getAllocator()->mapPointer(oldPtr, clipMap->planes); - } - } - - if (clipMap->numStaticModels) - { - clipMap->staticModelList = builder->getAllocator()->allocateArray(clipMap->numStaticModels); - for (unsigned int i = 0; i < clipMap->numStaticModels; ++i) - { - std::string modelName = reader.readString(); - if (modelName != "NONE"s) - { - clipMap->staticModelList[i].xmodel = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_XMODEL, modelName, builder).model; - } - float* buf = reader.readArray(18); - memcpy(&clipMap->staticModelList[i].origin, buf, sizeof(float) * 18); - } - } - - if (clipMap->numMaterials) - { - clipMap->materials = builder->getAllocator()->allocateArray(clipMap->numMaterials); - for (unsigned int j = 0; j < clipMap->numMaterials; ++j) - { - clipMap->materials[j].name = reader.readArray(64); - clipMap->materials[j].surfaceFlags = reader.read(); - clipMap->materials[j].contents = reader.read(); - } - } - - if (clipMap->numBrushSides) - { - clipMap->brushsides = builder->getAllocator()->allocateArray(clipMap->numBrushSides); - for (unsigned int i = 0; i < clipMap->numBrushSides; ++i) - { - int planeIndex = reader.read(); - if (planeIndex < 0 || planeIndex >= clipMap->planeCount) - { - Components::Logger::Error(Game::ERR_FATAL, "invalid plane index"); - return; - } - clipMap->brushsides[i].plane = &clipMap->planes[planeIndex]; - clipMap->brushsides[i].materialNum = static_cast(reader.read()); // materialNum - clipMap->brushsides[i].firstAdjacentSideOffset = static_cast(reader.read()); // firstAdjacentSide - clipMap->brushsides[i].edgeCount = reader.read(); // edgeCount - } - } - - if (clipMap->numBrushEdges) - { - clipMap->brushEdges = reader.readArray(clipMap->numBrushEdges); - } - - if (clipMap->numNodes) - { - clipMap->nodes = builder->getAllocator()->allocateArray(clipMap->numNodes); - for (unsigned int i = 0; i < clipMap->numNodes; ++i) - { - int planeIndex = reader.read(); - if (planeIndex < 0 || planeIndex >= clipMap->planeCount) - { - Components::Logger::Error(Game::ERR_FATAL, "invalid plane index\n"); - return; - } - clipMap->nodes[i].plane = &clipMap->planes[planeIndex]; - clipMap->nodes[i].children[0] = reader.read(); - clipMap->nodes[i].children[1] = reader.read(); - } - } - - if (clipMap->numLeafs) - { - clipMap->leafs = reader.readArray(clipMap->numLeafs); - } - - if (clipMap->leafbrushNodesCount) - { - clipMap->leafbrushNodes = builder->getAllocator()->allocateArray(clipMap->leafbrushNodesCount); - for (unsigned int i = 0; i < clipMap->leafbrushNodesCount; ++i) - { - clipMap->leafbrushNodes[i] = reader.read(); - - if (clipMap->leafbrushNodes[i].leafBrushCount > 0) - { - clipMap->leafbrushNodes[i].data.leaf.brushes = reader.readArray(clipMap->leafbrushNodes[i].leafBrushCount); - } - } - } - - if (clipMap->numLeafBrushes) - { - clipMap->leafbrushes = reader.readArray(clipMap->numLeafBrushes); - } - - if (clipMap->numLeafSurfaces) - { - clipMap->leafsurfaces = reader.readArray(clipMap->numLeafSurfaces); - } - - if (clipMap->vertCount) - { - clipMap->verts = reader.readArray(clipMap->vertCount); - } - - if (clipMap->triCount) - { - clipMap->triIndices = reader.readArray(clipMap->triCount * 3); - clipMap->triEdgeIsWalkable = reader.readArray(4 * ((3 * clipMap->triCount + 31) >> 5)); - } - - if (clipMap->borderCount) - { - clipMap->borders = reader.readArray(clipMap->borderCount); - } - - if (clipMap->partitionCount) - { - clipMap->partitions = builder->getAllocator()->allocateArray(clipMap->partitionCount); - for (int i = 0; i < clipMap->partitionCount; ++i) - { - clipMap->partitions[i].triCount = reader.read(); - clipMap->partitions[i].borderCount = reader.read(); - clipMap->partitions[i].firstTri = reader.read(); - - if (clipMap->partitions[i].borderCount > 0) - { - int index = reader.read(); - if (index < 0 || index > clipMap->borderCount) - { - Components::Logger::Error(Game::ERR_FATAL, "invalid border index\n"); - return; - } - clipMap->partitions[i].borders = &clipMap->borders[index]; - } - } - } - - if (clipMap->aabbTreeCount) - { - clipMap->aabbTrees = reader.readArray(clipMap->aabbTreeCount); - } - - if (clipMap->numSubModels) - { - clipMap->cmodels = reader.readArray(clipMap->numSubModels); - } - - if (clipMap->numBrushes) - { - clipMap->brushes = builder->getAllocator()->allocateArray(clipMap->numBrushes); - memset(clipMap->brushes, 0, sizeof(Game::cbrush_t) * clipMap->numBrushes); - for (int i = 0; i < clipMap->numBrushes; ++i) - { - clipMap->brushes[i].numsides = reader.read() & 0xFFFF; // todo: check for overflow here - if (clipMap->brushes[i].numsides > 0) - { - auto index = reader.read(); - if (index < 0 || index > clipMap->numBrushSides) - { - Components::Logger::Error(Game::ERR_FATAL, "invalid side index\n"); - return; - } - clipMap->brushes[i].sides = &clipMap->brushsides[index]; - } - else - { - clipMap->brushes[i].sides = nullptr; - } - - auto index = reader.read(); - if (index > clipMap->numBrushEdges) - { - Components::Logger::Error(Game::ERR_FATAL, "invalid edge index\n"); - return; - } - clipMap->brushes[i].baseAdjacentSide = &clipMap->brushEdges[index]; - - char* tmp = reader.readArray(12); - memcpy(&clipMap->brushes[i].axialMaterialNum, tmp, 12); - - //todo check for overflow - for (int r = 0; r < 2; ++r) - { - for (int c = 0; c < 3; ++c) - { - clipMap->brushes[i].firstAdjacentSideOffsets[r][c] = reader.read() & 0xFF; - } - } - - tmp = reader.readArray(6); - memcpy(&clipMap->brushes[i].edgeCount, tmp, 6); - } - - clipMap->brushBounds = reader.readArray(clipMap->numBrushes); - clipMap->brushContents = reader.readArray(clipMap->numBrushes); - } - - for (int x = 0; x < 2; ++x) - { - if (clipMap->dynEntCount[x]) - { - clipMap->dynEntDefList[x] = builder->getAllocator()->allocateArray(clipMap->dynEntCount[x]); - for (int i = 0; i < clipMap->dynEntCount[x]; ++i) - { - clipMap->dynEntDefList[x][i].type = reader.read(); - clipMap->dynEntDefList[x][i].pose = reader.read(); - std::string tempName = reader.readString(); - if (tempName != "NONE"s) - { - clipMap->dynEntDefList[x][i].xModel = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_XMODEL, tempName, builder).model; - } - - clipMap->dynEntDefList[x][i].brushModel = reader.read(); - clipMap->dynEntDefList[x][i].physicsBrushModel = reader.read(); - - tempName = reader.readString(); - if (tempName != "NONE"s) - { - clipMap->dynEntDefList[x][i].destroyFx = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_FX, tempName, builder).fx; - } - - tempName = reader.readString(); - if (tempName != "NONE"s) - { - clipMap->dynEntDefList[x][i].physPreset = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_PHYSPRESET, tempName, builder).physPreset; - } - - clipMap->dynEntDefList[x][i].health = reader.read(); - clipMap->dynEntDefList[x][i].mass = reader.read(); - clipMap->dynEntDefList[x][i].contents = reader.read(); - } - } - } - - clipMap->smodelNodeCount = reader.read(); - clipMap->smodelNodes = reader.readArray(clipMap->smodelNodeCount); - - clipMap->mapEnts = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_MAP_ENTS, std::format("maps/mp/{}.d3dbsp", name), builder).mapEnts; - - // add triggers to mapEnts - if (version >= 2) { - if (clipMap->numSubModels > 0) { - clipMap->mapEnts->trigger.count = clipMap->numSubModels; - clipMap->mapEnts->trigger.hullCount = clipMap->numSubModels; - - Game::TriggerHull* hulls = builder->getAllocator()->allocateArray(clipMap->mapEnts->trigger.hullCount); - Game::TriggerModel* models = builder->getAllocator()->allocateArray(clipMap->mapEnts->trigger.count); - - for (unsigned int i = 0; i < clipMap->numSubModels; ++i) - { - models[i] = reader.read(); - hulls[i] = reader.read(); - } - - size_t slabCount = reader.read(); - clipMap->mapEnts->trigger.slabCount = slabCount; - Game::TriggerSlab* slabs = builder->getAllocator()->allocateArray(clipMap->mapEnts->trigger.slabCount); - for (unsigned int i = 0; i < clipMap->mapEnts->trigger.slabCount; i++) { - slabs[i] = reader.read(); - } - - - clipMap->mapEnts->trigger.models = &models[0]; - clipMap->mapEnts->trigger.hulls = &hulls[0]; - clipMap->mapEnts->trigger.slabs = &slabs[0]; - } - } - - clipMap->checksum = reader.read(); - - // This mustn't be null and has to have at least 1 'valid' entry. - if (!clipMap->smodelNodeCount || !clipMap->smodelNodes) - { - clipMap->smodelNodeCount = 1; - clipMap->smodelNodes = builder->getAllocator()->allocateArray(clipMap->smodelNodeCount); - - clipMap->smodelNodes[0].bounds.halfSize[0] = -131072.000f; - clipMap->smodelNodes[0].bounds.halfSize[1] = -131072.000f; - clipMap->smodelNodes[0].bounds.halfSize[2] = -131072.000f; - } - - // These mustn't be null, but they don't need to be valid. - for (int i = 0; i < 2 && clipMap->dynEntCount[i]; ++i) - { - Utils::Stream::ClearPointer(&clipMap->dynEntPoseList[i]); - Utils::Stream::ClearPointer(&clipMap->dynEntClientList[i]); - Utils::Stream::ClearPointer(&clipMap->dynEntCollList[i]); - } - - if (!reader.end()) - { - Components::Logger::Error(Game::ERR_FATAL, "Clipmap data left!"); - } - - header->clipMap = clipMap; + header->clipMap = builder->getIW4OfApi()->read(Game::XAssetType::ASSET_TYPE_CLIPMAP_MP, _name); + assert(header->data); } } diff --git a/src/Components/Modules/AssetInterfaces/IclipMap_t.hpp b/src/Components/Modules/AssetInterfaces/IclipMap_t.hpp index fbd5e307..2edb3089 100644 --- a/src/Components/Modules/AssetInterfaces/IclipMap_t.hpp +++ b/src/Components/Modules/AssetInterfaces/IclipMap_t.hpp @@ -10,6 +10,7 @@ namespace Assets void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override; void mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override; void load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) override; + static void dump(Game::XAssetHeader /*header*/); private: class SModelQuadtree diff --git a/src/Components/Modules/AssetInterfaces/Isnd_alias_list_t.cpp b/src/Components/Modules/AssetInterfaces/Isnd_alias_list_t.cpp index 21a88b27..76b21b2b 100644 --- a/src/Components/Modules/AssetInterfaces/Isnd_alias_list_t.cpp +++ b/src/Components/Modules/AssetInterfaces/Isnd_alias_list_t.cpp @@ -1,355 +1,18 @@ #include #include "Isnd_alias_list_t.hpp" +#include + namespace Assets { void Isnd_alias_list_t::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) { - Components::FileSystem::File aliasFile(std::format("sounds/{}.json", name)); - if (!aliasFile.exists()) + header->sound = builder->getIW4OfApi()->read(Game::XAssetType::ASSET_TYPE_SOUND, name); + + if (!header->sound) { - header->sound = Components::AssetHandler::FindOriginalAsset(this->getType(), name.data()).sound; - return; + header->sound = Components::AssetHandler::FindOriginalAsset(Game::XAssetType::ASSET_TYPE_SOUND_CURVE, name.data()).sound; } - - auto* aliasList = builder->getAllocator()->allocate(); - if (!aliasList) - { - Components::Logger::PrintError(Game::CON_CHANNEL_ERROR, "Failed to allocate memory for sound alias structure!\n"); - return; - } - - nlohmann::json infoData; - try - { - infoData = nlohmann::json::parse(aliasFile.getBuffer()); - } - catch (const nlohmann::json::parse_error& ex) - { - Components::Logger::PrintError(Game::CON_CHANNEL_ERROR, "Json Parse Error: {}\n", ex.what()); - return; - } - - - nlohmann::json aliasesContainer = infoData["head"]; - nlohmann::json::array_t aliases = aliasesContainer; - - aliasList->count = aliases.size(); - - // Allocate - aliasList->head = builder->getAllocator()->allocateArray(aliasList->count); - if (!aliasList->head) - { - Components::Logger::Print("Error allocating memory for sound alias structure!\n"); - return; - } - - aliasList->aliasName = builder->getAllocator()->duplicateString(name); - - for (size_t i = 0; i < aliasList->count; i++) - { - nlohmann::json head = aliasesContainer[i]; - - if (!infoData.is_object()) - { - Components::Logger::Error(Game::ERR_FATAL, "Failed to load sound {}!", name); - return; - } - - aliasList->head->soundFile = builder->getAllocator()->allocate(); - if (!aliasList->head->soundFile) - { - Components::Logger::Print("Error allocating memory for sound alias structure!\n"); - return; - } - - Game::snd_alias_t* alias = aliasList->head; - - // try and parse everything and if it fails then fail for the whole file - auto type = head["type"]; - auto subtitle = head["subtitle"]; - auto secondaryAliasName = head["secondaryAliasName"]; - auto chainAliasName = head["chainAliasName"]; - auto soundFile = head["soundFile"]; - auto sequence = head["sequence"]; - auto volMin = head["volMin"]; - auto volMax = head["volMax"]; - auto pitchMin = head["pitchMin"]; - auto pitchMax = head["pitchMax"]; - auto distMin = head["distMin"]; - auto distMax = head["distMax"]; - auto flags = head["flags"]; - auto slavePercentage = head["slavePercentage"]; - auto probability = head["probability"]; - auto lfePercentage = head["lfePercentage"]; - auto centerPercentage = head["centerPercentage"]; - auto startDelay = head["startDelay"]; - auto volumeFalloffCurve = head["volumeFalloffCurve"]; - auto envelopMin = head["envelopMin"]; - auto envelopMax = head["envelopMax"]; - auto envelopPercentage = head["envelopPercentage"]; - auto speakerMap = head["speakerMap"]; - auto aliasName = head["aliasName"]; - - // Fix casing - if (soundFile.is_null()) - { - soundFile = head["soundfile"]; - - Components::Logger::Print("Fixed casing on {}\n", name); - } - - if (type.is_null() || soundFile.is_null()) - { - Components::Logger::Print("Type is {}\n", type.dump()); - Components::Logger::Print("SoundFile is {}\n", soundFile.dump()); - Components::Logger::Error(Game::ERR_FATAL, "Failed to parse sound {}! Each alias must have at least a type and a soundFile\n", name); - return; - } - -#define CHECK(x, type) (x.is_##type##() || x.is_null()) - - // TODO: actually support all of those properties - if (!CHECK(type, number)) - { - Components::Logger::Print("{} is not number but {} ({})\n", "type", Utils::Json::TypeToString(type.type()), type.dump()); - } - - if (!CHECK(subtitle, string)) - { - Components::Logger::Print("{} is not string but {} ({})\n", "subtitle", Utils::Json::TypeToString(subtitle.type()), subtitle.dump()); - } - - if (!CHECK(aliasName, string)) - { - Components::Logger::Print("{} is not string but {} ({})\n", "aliasName", Utils::Json::TypeToString(aliasName.type()), aliasName.dump()); - } - - if (!CHECK(secondaryAliasName, string)) - { - Components::Logger::Print("{} is not string but {} ({})\n", "secondaryAliasName", Utils::Json::TypeToString(secondaryAliasName.type()), secondaryAliasName.dump()); - } - - if (!CHECK(chainAliasName, string)) - { - Components::Logger::Print("{} is not string but {} ({})\n", "chainAliasName", Utils::Json::TypeToString(chainAliasName.type()), chainAliasName.dump()); - } - - if (!CHECK(soundFile, string)) - { - Components::Logger::Print("{} is not string but {} ({})\n", "soundFile", Utils::Json::TypeToString(soundFile.type()), soundFile.dump()); - } - - if (!CHECK(sequence, number)) - { - Components::Logger::Print("{} is not number but {} ({})\n", "sequence", Utils::Json::TypeToString(sequence.type()), sequence.dump()); - } - - if (!CHECK(volMin, number)) - { - Components::Logger::Print("{} is not number but {} ({})\n", "volMin", Utils::Json::TypeToString(volMin.type()), volMin.dump()); - } - - if (!CHECK(volMax, number)) - { - Components::Logger::Print("{} is not number but {} ({})\n", "volMax", Utils::Json::TypeToString(volMax.type()), volMax.dump()); - } - - if (!CHECK(pitchMin, number)) - { - Components::Logger::Print("{} is not number but {} ({})\n", "pitchMin", Utils::Json::TypeToString(pitchMin.type()), pitchMin.dump()); - } - - if (!CHECK(pitchMax, number)) - { - Components::Logger::Print("{} is not number but {} ()\n", "pitchMax", Utils::Json::TypeToString(pitchMax.type()), pitchMax.dump()); - } - - if (!CHECK(probability, number)) - { - Components::Logger::Print("{} is not number but {} ({}))\n", "probability", Utils::Json::TypeToString(probability.type()), probability.dump()); - } - - if (!CHECK(lfePercentage, number)) - { - Components::Logger::Print("{} is not number but {} ({})\n", "lfePercentage", Utils::Json::TypeToString(lfePercentage.type()), lfePercentage.dump()); - } - - if (!CHECK(centerPercentage, number)) - { - Components::Logger::Print("{} is not number but {} ({})\n", "centerPercentage", Utils::Json::TypeToString(centerPercentage.type()), centerPercentage.dump()); - } - - if (!CHECK(startDelay, number)) - { - Components::Logger::Print("{} is not number but {} ({})\n", "startDelay", Utils::Json::TypeToString(startDelay.type()), startDelay.dump()); - } - - if (!CHECK(volumeFalloffCurve, string)) - { - Components::Logger::Print("{}s is not string but {} ({})\n", "volumeFalloffCurve", Utils::Json::TypeToString(volumeFalloffCurve.type()), volumeFalloffCurve.dump()); - } - - if (!CHECK(envelopMin, number)) - { - Components::Logger::Print("{} is not number but {} ({})\n", "envelopMin", Utils::Json::TypeToString(envelopMin.type()), envelopMin.dump()); - } - - if (!CHECK(envelopMax, number)) - { - Components::Logger::Print("{} is not number but {} ({})\n", "envelopMax", Utils::Json::TypeToString(envelopMax.type()), envelopMax.dump()); - } - - if (!CHECK(envelopPercentage, number)) - { - Components::Logger::Print("{} is not number but {} ({})\n", "envelopPercentage", Utils::Json::TypeToString(envelopPercentage.type()), envelopPercentage.dump()); - } - - if (!CHECK(speakerMap, object)) - { - Components::Logger::Print("{} is not object but {} ({})\n", "speakerMap", Utils::Json::TypeToString(speakerMap.type()), speakerMap.dump()); - } - - - if (CHECK(type, number) && CHECK(aliasName, string) && CHECK(subtitle, string) && CHECK(secondaryAliasName, string) && CHECK(chainAliasName, string) && - CHECK(soundFile, string) && CHECK(sequence, number) && CHECK(volMin, number) && CHECK(volMax, number) && CHECK(pitchMin, number) && - CHECK(pitchMax, number) && CHECK(distMin, number) && CHECK(distMax, number) && CHECK(flags, number) && CHECK(slavePercentage, number) && - CHECK(probability, number) && CHECK(lfePercentage, number) && CHECK(centerPercentage, number) && CHECK(startDelay, number) && - CHECK(volumeFalloffCurve, string) && CHECK(envelopMin, number) && CHECK(envelopMax, number) && CHECK(envelopPercentage, number) && - CHECK(speakerMap, object)) - { - - alias->soundFile->exists = true; - alias->aliasName = builder->getAllocator()->duplicateString(aliasName.get()); - - if (subtitle.is_string()) - { - alias->subtitle = builder->getAllocator()->duplicateString(subtitle.get()); - } - if (secondaryAliasName.is_string()) - { - alias->secondaryAliasName = builder->getAllocator()->duplicateString(secondaryAliasName.get()); - } - if (chainAliasName.is_string()) - { - alias->chainAliasName = builder->getAllocator()->duplicateString(chainAliasName.get()); - } - - alias->sequence = sequence.get(); - alias->volMin = volMin.get(); - alias->volMax = volMax.get(); - alias->pitchMin = pitchMin.get(); - alias->pitchMax = pitchMax.get(); - alias->distMin = distMin.get(); - alias->distMax = distMax.get(); - alias->flags = flags.get(); - alias->___u15.slavePercentage = slavePercentage.get(); - alias->probability = probability.get(); - alias->lfePercentage = lfePercentage.get(); - alias->centerPercentage = centerPercentage.get(); - alias->startDelay = startDelay.get(); - alias->envelopMin = envelopMin.get(); - alias->envelopMax = envelopMax.get(); - alias->envelopPercentage = envelopPercentage.get(); - - // Speaker map object - if (!speakerMap.is_null()) - { - alias->speakerMap = builder->getAllocator()->allocate(); - if (!alias->speakerMap) - { - Components::Logger::Print("Error allocating memory for speakermap in sound alias{}!\n", alias->aliasName); - return; - } - - alias->speakerMap->name = builder->getAllocator()->duplicateString(speakerMap["name"].get()); - alias->speakerMap->isDefault = speakerMap["isDefault"].get(); - - if (speakerMap["channelMaps"].is_array()) - { - nlohmann::json::array_t channelMaps = speakerMap["channelMaps"]; - - assert(channelMaps.size() <= 4); - - // channelMapIndex should never exceed 1 - for (size_t channelMapIndex = 0; channelMapIndex < 2; channelMapIndex++) - { - // subChannelIndex should never exceed 1 - for (size_t subChannelIndex = 0; subChannelIndex < 2; subChannelIndex++) - { - nlohmann::json channelMap = channelMaps[channelMapIndex * 2 + subChannelIndex]; // 0-3 - - nlohmann::json::array_t speakers = channelMap["speakers"]; - - alias->speakerMap->channelMaps[channelMapIndex][subChannelIndex].speakerCount = speakers.size(); - - for (size_t speakerIndex = 0; speakerIndex < alias->speakerMap->channelMaps[channelMapIndex][subChannelIndex].speakerCount; speakerIndex++) - { - auto speaker = speakers[speakerIndex]; - alias->speakerMap->channelMaps[channelMapIndex][subChannelIndex].speakers[speakerIndex].levels[0] = speaker["levels0"].get(); - alias->speakerMap->channelMaps[channelMapIndex][subChannelIndex].speakers[speakerIndex].levels[1] = speaker["levels1"].get(); - alias->speakerMap->channelMaps[channelMapIndex][subChannelIndex].speakers[speakerIndex].numLevels = speaker["numLevels"].get(); - alias->speakerMap->channelMaps[channelMapIndex][subChannelIndex].speakers[speakerIndex].speaker = speaker["speaker"].get(); - } - } - } - } - } - - if (volumeFalloffCurve.is_string()) - { - auto fallOffCurve = volumeFalloffCurve.get(); - - if (fallOffCurve.empty()) - { - fallOffCurve = "$default"; - } - - auto curve = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_SOUND_CURVE, fallOffCurve, builder).sndCurve; - alias->volumeFalloffCurve = curve; - } - - if (static_cast(type.get()) == Game::snd_alias_type_t::SAT_LOADED) // Loaded - { - alias->soundFile->type = Game::SAT_LOADED; - alias->soundFile->u.loadSnd = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_LOADED_SOUND, soundFile.get(), builder).loadSnd; - } - else if (static_cast(type.get()) == Game::snd_alias_type_t::SAT_STREAMED) // Streamed - { - alias->soundFile->type = Game::SAT_STREAMED; - - std::string streamedFile = soundFile.get(); - std::string directory = ""s; - int split = streamedFile.find_last_of('/'); - - if (split >= 0) - { - directory = streamedFile.substr(0, split); - streamedFile = streamedFile.substr(split+1); - } - - alias->soundFile->u.streamSnd.filename.info.raw.dir = builder->getAllocator()->duplicateString(directory.c_str()); - alias->soundFile->u.streamSnd.filename.info.raw.name = builder->getAllocator()->duplicateString(streamedFile.c_str()); - } - else - { - Components::Logger::Error(Game::ERR_FATAL, "Failed to parse sound {}! Invalid sound type {}\n", name, type.get()); - return; - } - - aliasList->head[i] = *alias; - } - else - { - Components::Logger::Error(Game::ERR_FATAL, "Failed to parse sound {}!\n", name); - return; - } - } - - header->sound = aliasList; - -#undef CHECK - } void Isnd_alias_list_t::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) @@ -390,8 +53,16 @@ namespace Assets if (asset->aliasName) { - buffer->saveString(builder->getAssetName(this->getType(), asset->aliasName)); - Utils::Stream::ClearPointer(&dest->aliasName); + if (builder->hasPointer(asset->aliasName)) + { + dest->aliasName = builder->getPointer(asset->aliasName); + } + else + { + builder->storePointer(asset->aliasName); + buffer->saveString(asset->aliasName); + Utils::Stream::ClearPointer(&dest->aliasName); + } } if (asset->head) @@ -417,8 +88,16 @@ namespace Assets if (alias->aliasName) { - buffer->saveString(alias->aliasName); - Utils::Stream::ClearPointer(&destAlias->aliasName); + if (builder->hasPointer(alias->aliasName)) + { + destAlias->aliasName = builder->getPointer(alias->aliasName); + } + else + { + builder->storePointer(alias->aliasName); + buffer->saveString(alias->aliasName); + Utils::Stream::ClearPointer(&destAlias->aliasName); + } } if (alias->subtitle) @@ -465,7 +144,7 @@ namespace Assets { if (alias->soundFile->type == Game::snd_alias_type_t::SAT_LOADED) { - destSoundFile->u.loadSnd = builder->saveSubAsset(Game::XAssetType::ASSET_TYPE_LOADED_SOUND, alias->soundFile->u.loadSnd).loadSnd; + destSoundFile->u.loadSnd = builder->saveSubAsset(Game::ASSET_TYPE_LOADED_SOUND, alias->soundFile->u.loadSnd).loadSnd; } else { @@ -492,7 +171,7 @@ namespace Assets if (alias->volumeFalloffCurve) { - destAlias->volumeFalloffCurve = builder->saveSubAsset(Game::XAssetType::ASSET_TYPE_SOUND_CURVE, alias->volumeFalloffCurve).sndCurve; + destAlias->volumeFalloffCurve = builder->saveSubAsset(Game::ASSET_TYPE_SOUND_CURVE, alias->volumeFalloffCurve).sndCurve; } if (alias->speakerMap) diff --git a/src/Components/Modules/Auth.cpp b/src/Components/Modules/Auth.cpp index 9bf90a68..c0181db1 100644 --- a/src/Components/Modules/Auth.cpp +++ b/src/Components/Modules/Auth.cpp @@ -1,4 +1,8 @@ #include +#include + +#include + #include "Bans.hpp" namespace Components @@ -467,7 +471,7 @@ namespace Components } else { - const auto level = static_cast(atoi(params->get(1))); + const auto level = std::strtoul(params->get(1), nullptr, 10); Auth::IncreaseSecurityLevel(level); } }); diff --git a/src/Components/Modules/Bans.cpp b/src/Components/Modules/Bans.cpp index 955fbe81..507223bd 100644 --- a/src/Components/Modules/Bans.cpp +++ b/src/Components/Modules/Bans.cpp @@ -3,6 +3,8 @@ namespace Components { + const char* Bans::BanListFile = "userraw/bans.json"; + // Have only one instance of IW4x read/write the file std::unique_lock Bans::Lock() { @@ -87,7 +89,7 @@ namespace Components void Bans::SaveBans(const BanList* list) { - assert(list != nullptr); + assert(list); const auto _ = Lock(); @@ -105,7 +107,8 @@ namespace Components ipEntry.bytes[0] & 0xFF, ipEntry.bytes[1] & 0xFF, ipEntry.bytes[2] & 0xFF, - ipEntry.bytes[3] & 0xFF)); + ipEntry.bytes[3] & 0xFF) + ); } const nlohmann::json bans = nlohmann::json @@ -114,18 +117,17 @@ namespace Components { "id", idVector }, }; - FileSystem::FileWriter ("bans.json").write(bans.dump()); + Utils::IO::WriteFile(BanListFile, bans.dump()); } void Bans::LoadBans(BanList* list) { - assert(list != nullptr); + assert(list); const auto _ = Lock(); - FileSystem::File bans("bans.json"); - - if (!bans.exists()) + const auto bans = Utils::IO::ReadFile(BanListFile); + if (bans.empty()) { Logger::Debug("bans.json does not exist"); return; @@ -134,14 +136,20 @@ namespace Components nlohmann::json banData; try { - banData = nlohmann::json::parse(bans.getBuffer()); + banData = nlohmann::json::parse(bans); } - catch (const nlohmann::json::parse_error& ex) + catch (const std::exception& ex) { Logger::PrintError(Game::CON_CHANNEL_ERROR, "Json Parse Error: {}\n", ex.what()); return; } + if (!banData.contains("id") || !banData.contains("ip")) + { + Logger::PrintError(Game::CON_CHANNEL_ERROR, "bans.json contains invalid data\n"); + return; + } + const auto& idList = banData["id"]; const auto& ipList = banData["ip"]; @@ -252,7 +260,6 @@ namespace Components } const auto num = std::atoi(input); - if (num < 0 || num >= *Game::svs_clientCount) { Logger::Print("Bad client slot: {}\n", num); @@ -266,8 +273,13 @@ namespace Components return; } + if (cl->bIsTestClient) + { + return; + } + const std::string reason = params->size() < 3 ? "EXE_ERR_BANNED_PERM" : params->join(2); - Bans::BanClient(&Game::svs_clients[num], reason); + BanClient(&Game::svs_clients[num], reason); }); Command::Add("unbanClient", [](Command::Params* params) diff --git a/src/Components/Modules/Bans.hpp b/src/Components/Modules/Bans.hpp index 0ba36d12..322a4a12 100644 --- a/src/Components/Modules/Bans.hpp +++ b/src/Components/Modules/Bans.hpp @@ -9,8 +9,6 @@ namespace Components Bans(); - static std::unique_lock Lock(); - static void BanClient(Game::client_t* cl, const std::string& reason); static void UnbanClient(SteamID id); static void UnbanClient(Game::netIP_t ip); @@ -25,6 +23,10 @@ namespace Components std::vector ipList; }; + static const char* BanListFile; + + static std::unique_lock Lock(); + static void LoadBans(BanList* list); static void SaveBans(const BanList* list); }; diff --git a/src/Components/Modules/BotLib/lPrecomp.cpp b/src/Components/Modules/BotLib/lPrecomp.cpp new file mode 100644 index 00000000..c3291aa4 --- /dev/null +++ b/src/Components/Modules/BotLib/lPrecomp.cpp @@ -0,0 +1,155 @@ +#include +#include "lPrecomp.hpp" + +namespace Components::BotLib +{ + // Two new directives! Refer to (https://en.cppreference.com/w/cpp/preprocessor/conditional) + Game::directive_s lPrecomp::directives[] = + { + {"if", Game::PC_Directive_if}, + {"ifdef", Game::PC_Directive_ifdef}, + {"ifndef", Game::PC_Directive_ifndef}, + {"elifdef", lPrecomp::PC_Directive_elifdef}, + {"elifndef", lPrecomp::PC_Directive_elifndef}, + {"elif", Game::PC_Directive_elif}, + {"else", Game::PC_Directive_else}, + {"endif", Game::PC_Directive_endif}, + {"include", Game::PC_Directive_include}, + {"define", Game::PC_Directive_define}, + {"undef", Game::PC_Directive_undef}, + {"line", Game::PC_Directive_line}, + {"error", Game::PC_Directive_error}, + {"pragma", Game::PC_Directive_pragma}, + {"eval", Game::PC_Directive_eval}, + {"evalfloat", Game::PC_Directive_evalfloat}, + {nullptr, nullptr} + }; + + int lPrecomp::PC_Directive_elif_def(Game::source_s* source, int* value, int type) + { + Game::token_s token; + int skip; + + if (!Game::PC_ReadLine(source, &token, false)) + { + Game::SourceError(source, "#elifdef without name"); + return false; + } + + if (token.type != TT_NAME) + { + Game::PC_UnreadSourceToken(source, &token); + Game::SourceError(source, "expected name after #elifdef, found %s", token.string); + return false; + } + + auto* d = Game::PC_FindHashedDefine(source->definehash, token.string); + *value = skip = (type == INDENT_IFDEF) == (d == nullptr); + return true; + } + + // #elifdef identifier is essentially equivalent to #elif defined identifier + int lPrecomp::PC_Directive_elifdef(Game::source_s* source) + { + int type; + int skip; + + Game::PC_PopIndent(source, &type, &skip); + if (!type || type == INDENT_ELSE) + { + Game::SourceError(source, "misplaced #elifdef"); + return false; + } + + int value; + if (PC_Directive_elif_def(source, &value, INDENT_IFDEF)) + { + if (skip == Game::SKIP_YES) + { + skip = value; + } + else + { + skip = Game::SKIP_ALL_ELIFS; + } + + Game::PC_PushIndent(source, INDENT_ELIF, skip); + return true; + } + + return false; + } + + // #elifndef identifier is essentially equivalent to #elif !defined identifier + int lPrecomp::PC_Directive_elifndef(Game::source_s* source) + { + int type; + int skip; + + Game::PC_PopIndent(source, &type, &skip); + if (!type || type == INDENT_ELSE) + { + Game::SourceError(source, "misplaced #elifndef"); + return false; + } + + int value; + if (PC_Directive_elif_def(source, &value, INDENT_IFNDEF)) + { + if (skip == Game::SKIP_YES) + { + skip = value; + } + else + { + skip = Game::SKIP_ALL_ELIFS; + } + + Game::PC_PushIndent(source, INDENT_ELIF, skip); + return true; + } + + return false; + } + + int lPrecomp::PC_ReadDirective(Game::source_s* source) + { + Game::token_s token; + + // Read the directive name + if (!Game::PC_ReadSourceToken(source, &token)) + { + Game::SourceError(source, "found # without name"); + return false; + } + + // Directive name must be on the same line + if (token.linescrossed > 0) + { + Game::PC_UnreadSourceToken(source, &token); + Game::SourceError(source, "found # at end of line"); + return false; + } + + // If it is a name + if (token.type == TT_NAME) + { + // Find the precompiler directive + for (auto i = 0; directives[i].name; ++i) + { + if (!std::strcmp(directives[i].name, token.string)) + { + return directives[i].func(source); + } + } + } + + Game::SourceError(source, "unknown precompiler directive %s", token.string); + return false; + } + + lPrecomp::lPrecomp() + { + Utils::Hook(0x4ACD19, PC_ReadDirective, HOOK_CALL).install()->quick(); + } +} diff --git a/src/Components/Modules/BotLib/lPrecomp.hpp b/src/Components/Modules/BotLib/lPrecomp.hpp new file mode 100644 index 00000000..4ecdfda7 --- /dev/null +++ b/src/Components/Modules/BotLib/lPrecomp.hpp @@ -0,0 +1,46 @@ +#pragma once + +namespace Components::BotLib +{ + class lPrecomp : public Component + { + public: + lPrecomp(); + + private: + enum + { + INDENT_IF = 1, + INDENT_ELSE = 2, + INDENT_ELIF = 4, + INDENT_IFDEF = 8, + INDENT_IFNDEF = 10, + }; + + enum + { + TT_STRING = 1, + TT_LITERAL = 2, + TT_NUMBER = 3, + TT_NAME = 4, + TT_PUNCTUATION = 5, + TT_DECIMAL = 8, + TT_HEX = 0x100, + TT_OCTAL = 0x200, + TT_BINARY = 0x400, + TT_FLOAT = 0x800, + TT_INTEGER = 0x1000, + TT_LONG = 0x2000, + TT_UNSIGNED = 0x4000, + }; + + static Game::directive_s directives[]; + + static int PC_Directive_elif_def(Game::source_s* source, int* value, int type); + + static int PC_Directive_elifdef(Game::source_s* source); + static int PC_Directive_elifndef(Game::source_s* source); + + static int PC_ReadDirective(Game::source_s* source); + }; +} diff --git a/src/Components/Modules/Bots.cpp b/src/Components/Modules/Bots.cpp index 3eb92273..369e98f6 100644 --- a/src/Components/Modules/Bots.cpp +++ b/src/Components/Modules/Bots.cpp @@ -1,13 +1,20 @@ #include +#include + #include "Bots.hpp" +#include "ServerList.hpp" #include "GSC/Script.hpp" +// From Quake-III +#define ANGLE2SHORT(x) ((int)((x) * (USHRT_MAX + 1) / 360.0f) & USHRT_MAX) +#define SHORT2ANGLE(x) ((x)* (360.0f / (USHRT_MAX + 1))) + namespace Components { std::vector Bots::BotNames; - Dvar::Var Bots::SVRandomBotNames; + Dvar::Var Bots::SVClanName; struct BotMovementInfo { @@ -45,72 +52,112 @@ namespace Components { "activate", Game::CMD_BUTTON_ACTIVATE }, }; + void Bots::RandomizeBotNames() + { + std::random_device rd; + std::mt19937 gen(rd()); + std::ranges::shuffle(BotNames, gen); + } + + void Bots::UpdateBotNames() + { + const auto masterPort = (*Game::com_masterPort)->current.integer; + const auto* masterServerName = (*Game::com_masterServerName)->current.string; + + Game::netadr_t master; + if (ServerList::GetMasterServer(masterServerName, masterPort, master)) + { + Logger::Print("Getting bots...\n"); + Network::Send(master, "getbots"); + } + } + + void Bots::LoadBotNames() + { + FileSystem::File bots("bots.txt"); + + if (!bots.exists()) + { + return; + } + + auto data = Utils::String::Split(bots.getBuffer(), '\n'); + + auto i = 0; + for (auto& entry : data) + { + if (i >= 18) + { + // Only parse 18 names from the file + break; + } + + // Take into account for CR line endings + Utils::String::Replace(entry, "\r", ""); + // Remove whitespace + Utils::String::Trim(entry); + + if (entry.empty()) + { + continue; + } + + std::string clanAbbrev; + + // Check if there is a clan tag + if (const auto pos = entry.find(','); pos != std::string::npos) + { + // Only start copying over from non-null characters (otherwise it can be "<=") + if ((pos + 1) < entry.size()) + { + clanAbbrev = entry.substr(pos + 1); + } + + entry = entry.substr(0, pos); + } + + BotNames.emplace_back(std::make_pair(entry, clanAbbrev)); + ++i; + } + + if (i) + { + RandomizeBotNames(); + } + } + int Bots::BuildConnectString(char* buffer, const char* connectString, int num, int, int protocol, int checksum, int statVer, int statStuff, int port) { static size_t botId = 0; // Loop over the BotNames vector static bool loadedNames = false; // Load file only once - const char* botName; - const char* clanName; + std::string botName; + std::string clanName; - if (BotNames.empty() && !loadedNames) + if (!loadedNames) { - FileSystem::File bots("bots.txt"); loadedNames = true; - - if (bots.exists()) - { - auto data = Utils::String::Split(bots.getBuffer(), '\n'); - - if (SVRandomBotNames.get()) - { - std::random_device rd; - std::mt19937 gen(rd()); - std::ranges::shuffle(data, gen); - } - - for (auto& entry : data) - { - // Take into account for CR line endings - Utils::String::Replace(entry, "\r", ""); - // Remove whitespace - Utils::String::Trim(entry); - - if (!entry.empty()) - { - std::string clanAbbrev; - - // Check if there is a clan tag - if (const auto pos = entry.find(','); pos != std::string::npos) - { - // Only start copying over from non-null characters (otherwise it can be "<=") - if ((pos + 1) < entry.size()) - { - clanAbbrev = entry.substr(pos + 1); - } - - entry = entry.substr(0, pos); - } - - BotNames.emplace_back(std::make_pair(entry, clanAbbrev)); - } - } - } + LoadBotNames(); } if (!BotNames.empty()) { botId %= BotNames.size(); const auto index = botId++; - botName = BotNames[index].first.data(); - clanName = BotNames[index].second.data(); + botName = BotNames[index].first; + clanName = BotNames[index].second; } else { - botName = Utils::String::VA("bot%d", ++botId); - clanName = "BOT"; + botName = std::format("bot{}", ++botId); + clanName = "BOT"s; } - return _snprintf_s(buffer, 0x400, _TRUNCATE, connectString, num, botName, clanName, protocol, checksum, statVer, statStuff, port); + if (const auto svClanName = SVClanName.get(); !svClanName.empty()) + { + clanName = svClanName; + } + + return _snprintf_s(buffer, 0x400, _TRUNCATE, connectString, num, botName.data(), clanName.data(), protocol, checksum, statVer, statStuff, port); } void Bots::Spawn(unsigned int count) @@ -120,8 +167,10 @@ namespace Components Scheduler::Once([] { auto* ent = Game::SV_AddTestClient(); - if (ent == nullptr) + if (!ent) + { return; + } Scheduler::Once([ent] { @@ -156,15 +205,15 @@ namespace Components void Bots::AddMethods() { - Script::AddMethMultiple(GScr_isTestClient, false, {"IsTestClient", "IsBot"}); // Usage: self IsTestClient(); + GSC::Script::AddMethMultiple(GScr_isTestClient, false, {"IsTestClient", "IsBot"}); // Usage: self IsTestClient(); - Script::AddMethod("BotStop", [](Game::scr_entref_t entref) // Usage: BotStop(); + GSC::Script::AddMethod("BotStop", [](Game::scr_entref_t entref) // Usage: BotStop(); { - const auto* ent = Game::GetPlayerEntity(entref); + const auto* ent = GSC::Script::Scr_GetPlayerEntity(entref); if (Game::SV_IsTestClient(ent->s.number) == 0) { - Game::Scr_Error("^1BotStop: Can only call on a bot!\n"); + Game::Scr_Error("BotStop: Can only call on a bot!"); return; } @@ -173,19 +222,19 @@ namespace Components g_botai[entref.entnum].active = true; }); - Script::AddMethod("BotWeapon", [](Game::scr_entref_t entref) // Usage: BotWeapon(); + GSC::Script::AddMethod("BotWeapon", [](Game::scr_entref_t entref) // Usage: BotWeapon(); { - const auto* ent = Game::GetPlayerEntity(entref); + const auto* ent = GSC::Script::Scr_GetPlayerEntity(entref); if (Game::SV_IsTestClient(ent->s.number) == 0) { - Game::Scr_Error("^1BotWeapon: Can only call on a bot!\n"); + Game::Scr_Error("BotWeapon: Can only call on a bot!"); return; } const auto* weapon = Game::Scr_GetString(0); - if (weapon == nullptr || weapon[0] == '\0') + if (!weapon || !*weapon) { g_botai[entref.entnum].weapon = 1; return; @@ -196,27 +245,27 @@ namespace Components g_botai[entref.entnum].active = true; }); - Script::AddMethod("BotAction", [](Game::scr_entref_t entref) // Usage: BotAction(); + GSC::Script::AddMethod("BotAction", [](Game::scr_entref_t entref) // Usage: BotAction(); { - const auto* ent = Game::GetPlayerEntity(entref); + const auto* ent = GSC::Script::Scr_GetPlayerEntity(entref); if (Game::SV_IsTestClient(ent->s.number) == 0) { - Game::Scr_Error("^1BotAction: Can only call on a bot!\n"); + Game::Scr_Error("BotAction: Can only call on a bot!"); return; } const auto* action = Game::Scr_GetString(0); - if (action == nullptr) + if (!action) { - Game::Scr_ParamError(0, "^1BotAction: Illegal parameter!\n"); + Game::Scr_ParamError(0, "BotAction: Illegal parameter!"); return; } if (action[0] != '+' && action[0] != '-') { - Game::Scr_ParamError(0, "^1BotAction: Sign for action must be '+' or '-'.\n"); + Game::Scr_ParamError(0, "BotAction: Sign for action must be '+' or '-'"); return; } @@ -234,16 +283,16 @@ namespace Components return; } - Game::Scr_ParamError(0, "^1BotAction: Unknown action.\n"); + Game::Scr_ParamError(0, "BotAction: Unknown action"); }); - Script::AddMethod("BotMovement", [](Game::scr_entref_t entref) // Usage: BotMovement(, ); + GSC::Script::AddMethod("BotMovement", [](Game::scr_entref_t entref) // Usage: BotMovement(, ); { - const auto* ent = Game::GetPlayerEntity(entref); + const auto* ent = GSC::Script::Scr_GetPlayerEntity(entref); if (Game::SV_IsTestClient(ent->s.number) == 0) { - Game::Scr_Error("^1BotMovement: Can only call on a bot!\n"); + Game::Scr_Error("BotMovement: Can only call on a bot!"); return; } @@ -258,8 +307,10 @@ namespace Components void Bots::BotAiAction(Game::client_t* cl) { - if (cl->gentity == nullptr) + if (!cl->gentity) + { return; + } const auto entnum = cl->gentity->s.number; @@ -280,10 +331,13 @@ namespace Components userCmd.rightmove = g_botai[entnum].right; userCmd.weapon = g_botai[entnum].weapon; + userCmd.angles[0] = ANGLE2SHORT((cl->gentity->client->ps.viewangles[0] - cl->gentity->client->ps.delta_angles[0])); + userCmd.angles[1] = ANGLE2SHORT((cl->gentity->client->ps.viewangles[1] - cl->gentity->client->ps.delta_angles[1])); + userCmd.angles[2] = ANGLE2SHORT((cl->gentity->client->ps.viewangles[2] - cl->gentity->client->ps.delta_angles[2])); + Game::SV_ClientThink(cl, &userCmd); } - constexpr auto SV_BotUserMove = 0x626E50; __declspec(naked) void Bots::SV_BotUserMove_Hk() { __asm @@ -345,7 +399,30 @@ namespace Components Utils::Hook(0x441B80, G_SelectWeaponIndex_Hk, HOOK_JUMP).install()->quick(); - SVRandomBotNames = Dvar::Register("sv_randomBotNames", false, Game::DVAR_NONE, "Randomize the bots' names"); + Events::OnDvarInit([] + { + SVClanName = Dvar::Register("sv_clanName", "", Game::DVAR_NONE, "The clan name for test clients"); + }); + + Scheduler::OnGameInitialized(UpdateBotNames, Scheduler::Pipeline::MAIN); + + Network::OnClientPacket("getbotsResponse", [](const Network::Address& address, const std::string& data) + { + const auto masterPort = (*Game::com_masterPort)->current.integer; + const auto* masterServerName = (*Game::com_masterServerName)->current.string; + + Network::Address master(Utils::String::VA("%s:%u", masterServerName, masterPort)); + if (master == address) + { + auto botNames = Utils::String::Split(data, '\n'); + for (const auto& entry : botNames) + { + BotNames.emplace_back(std::make_pair(entry, "BOT")); + } + + RandomizeBotNames(); + } + }); // Reset BotMovementInfo.active when client is dropped Events::OnClientDisconnect([](const int clientNum) @@ -362,13 +439,19 @@ namespace Components Command::Add("spawnBot", [](Command::Params* params) { - auto count = 1u; + if (!Dedicated::IsRunning()) + { + Logger::Print("Server is not running.\n"); + return; + } + + std::size_t count = 1; if (params->size() > 1) { if (params->get(1) == "all"s) { - count = *Game::svs_clientCount; + count = Game::MAX_CLIENTS; } else { @@ -385,18 +468,9 @@ namespace Components } } - count = std::min(static_cast(*Game::svs_clientCount), count); + count = std::min(Game::MAX_CLIENTS, count); - // Check if ingame and host - if (!Game::SV_Loaded()) - { - Toast::Show("cardicon_headshot", "^1Error", "You need to be host to spawn bots!", 3000); - Logger::Print("You need to be host to spawn bots!\n"); - return; - } - - Toast::Show("cardicon_headshot", "^2Success", Utils::String::VA("Spawning %d %s...", count, (count == 1 ? "bot" : "bots")), 3000); - Logger::Debug("Spawning {} {}", count, (count == 1 ? "bot" : "bots")); + Logger::Print("Spawning {} {}", count, (count == 1 ? "bot" : "bots")); Spawn(count); }); diff --git a/src/Components/Modules/Bots.hpp b/src/Components/Modules/Bots.hpp index f47f7a9a..efa4d3a7 100644 --- a/src/Components/Modules/Bots.hpp +++ b/src/Components/Modules/Bots.hpp @@ -11,8 +11,11 @@ namespace Components using botData = std::pair< std::string, std::string>; static std::vector BotNames; - static Dvar::Var SVRandomBotNames; + static Dvar::Var SVClanName; + static void RandomizeBotNames(); + static void UpdateBotNames(); + static void LoadBotNames(); static int BuildConnectString(char* buffer, const char* connectString, int num, int, int protocol, int checksum, int statVer, int statStuff, int port); static void Spawn(unsigned int count); diff --git a/src/Components/Modules/CardTitles.cpp b/src/Components/Modules/CardTitles.cpp index 37e9045c..b7c0d6d0 100644 --- a/src/Components/Modules/CardTitles.cpp +++ b/src/Components/Modules/CardTitles.cpp @@ -16,7 +16,7 @@ namespace Components { auto result = lookupResult; - const auto* username = Dvar::Var("name").get(); + const auto* username = Dvar::Name.get(); if (std::strcmp(data->name, username) == 0) { result += 0xFE000000; @@ -170,7 +170,7 @@ namespace Components playerTitle[0] = '\0'; } - list.append(std::format("\\{}\\{}", std::to_string(i), playerTitle)); + list.append(std::format("\\{}\\{}", i, playerTitle)); } const auto* command = Utils::String::Format("{:c} customTitles \"{}\"", 21, list); @@ -197,10 +197,10 @@ namespace Components CardTitles::CardTitles() { - Scheduler::Once([] + Events::OnDvarInit([] { CustomTitle = Dvar::Register("customTitle", "", Game::DVAR_USERINFO | Game::DVAR_ARCHIVE, "Custom card title"); - }, Scheduler::Pipeline::MAIN); + }); std::memset(&CustomTitles, 0, sizeof(char[Game::MAX_CLIENTS][18])); diff --git a/src/Components/Modules/Ceg.cpp b/src/Components/Modules/Ceg.cpp index cf765d2b..f6fb5a33 100644 --- a/src/Components/Modules/Ceg.cpp +++ b/src/Components/Modules/Ceg.cpp @@ -33,6 +33,13 @@ namespace Components Utils::Hook::Nop(0x43CA16, 9); Utils::Hook::Nop(0x505426, 9); + // Removed on IW5 MP (unprotected) but present on IW5 SP (protected) - CEG uninitialization / Steam Shutdown + Utils::Hook::Set(0x4F6370, 0xC3); + + // Remove 'Steam Start' checking for DRM IPC + Utils::Hook::Nop(0x451145, 5); + Utils::Hook::Set(0x45114C, 0xEB); + // Disable some checks on certain game events Utils::Hook::Nop(0x43EC96, 9); Utils::Hook::Nop(0x4675C6, 9); diff --git a/src/Components/Modules/Chat.cpp b/src/Components/Modules/Chat.cpp index ef7e8eb3..34e5ebac 100644 --- a/src/Components/Modules/Chat.cpp +++ b/src/Components/Modules/Chat.cpp @@ -4,6 +4,7 @@ #include "Voice.hpp" #include "GSC/Script.hpp" +#include "GSC/ScriptExtension.hpp" namespace Components { @@ -11,16 +12,22 @@ namespace Components Dvar::Var Chat::sv_disableChat; Dvar::Var Chat::sv_sayName; - Game::dvar_t** Chat::cg_chatHeight = reinterpret_cast(0x7ED398); - Game::dvar_t** Chat::cg_chatTime = reinterpret_cast(0x9F5DE8); - bool Chat::SendChat; Utils::Concurrency::Container Chat::MutedList; + const char* Chat::MutedListFile = "userraw/muted-users.json"; bool Chat::CanAddCallback = true; std::vector Chat::SayCallbacks; + // Have only one instance of IW4x read/write the file + std::unique_lock Chat::Lock() + { + static Utils::NamedMutex mutex{"iw4x-mute-list-lock"}; + std::unique_lock lock{mutex}; + return lock; + } + const char* Chat::EvaluateSay(char* text, Game::gentity_t* player, int mode) { SendChat = true; @@ -59,6 +66,19 @@ namespace Components Game::SV_GameSendServerCommand(player - Game::g_entities, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"You are muted\"", 0x65)); } + if (sv_disableChat.get()) + { + SendChat = false; + Game::SV_GameSendServerCommand(player - Game::g_entities, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"Chat is disabled\"", 0x65)); + } + + // Message might be empty after processing the '/' + if (text[msgIndex] == '\0') + { + SendChat = false; + return text; + } + for (const auto& callback : SayCallbacks) { if (!ChatCallback(player, callback.getPos(), (text + msgIndex), mode)) @@ -67,12 +87,6 @@ namespace Components } } - if (sv_disableChat.get()) - { - SendChat = false; - Game::SV_GameSendServerCommand(player - Game::g_entities, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"Chat is disabled\"", 0x65)); - } - TextRenderer::StripMaterialTextIcons(text, text, std::strlen(text) + 1); Game::Scr_AddEntity(player); @@ -167,9 +181,9 @@ namespace Components // Text can only be 150 characters maximum. This is bigger than the teamChatMsgs buffers with 160 characters // Therefore it is not needed to check for buffer lengths - const auto chatHeight = (*cg_chatHeight)->current.integer; + const auto chatHeight = (*Game::cg_chatHeight)->current.integer; const auto chatWidth = static_cast(cg_chatWidth.get()); - const auto chatTime = (*cg_chatTime)->current.integer; + const auto chatTime = (*Game::cg_chatTime)->current.integer; if (chatHeight <= 0 || static_cast(chatHeight) > std::extent_v || chatWidth <= 0 || chatTime <= 0) { Game::cgsArray[0].teamLastChatPos = 0; @@ -179,7 +193,7 @@ namespace Components TextRenderer::FontIconInfo fontIconInfo{}; auto len = 0.0f; - auto lastColor = static_cast(TEXT_COLOR_DEFAULT); + auto lastColor = static_cast>(TextColor::TEXT_COLOR_DEFAULT); char* lastSpace = nullptr; char* lastFontIcon = nullptr; char* p = Game::cgsArray[0].teamChatMsgs[Game::cgsArray[0].teamChatPos % chatHeight]; @@ -232,7 +246,9 @@ namespace Components Game::cgsArray[0].teamChatPos++; if (Game::cgsArray[0].teamChatPos - Game::cgsArray[0].teamLastChatPos > chatHeight) + { Game::cgsArray[0].teamLastChatPos = Game::cgsArray[0].teamChatPos + 1 - chatHeight; + } } __declspec(naked) void Chat::CG_AddToTeamChat_Stub() @@ -255,7 +271,20 @@ namespace Components const auto clientNum = ent - Game::g_entities; const auto xuid = Game::svs_clients[clientNum].steamID; - const auto result = MutedList.access([&](muteList& clients) + const auto result = MutedList.access([&](const muteList& clients) + { + return clients.contains(xuid); + }); + + return result; + } + + bool Chat::IsMuted(const Game::client_t* cl) + { + const auto clientNum = cl - Game::svs_clients; + const auto xuid = Game::svs_clients[clientNum].steamID; + + const auto result = MutedList.access([&](const muteList& clients) { return clients.contains(xuid); }); @@ -269,6 +298,7 @@ namespace Components MutedList.access([&](muteList& clients) { clients.insert(xuid); + SaveMutedList(clients); }); Logger::Print("{} was muted\n", client->name); @@ -291,6 +321,67 @@ namespace Components clients.clear(); else clients.erase(id); + + SaveMutedList(clients); + }); + } + + void Chat::SaveMutedList(const muteList& list) + { + const auto _ = Lock(); + + const nlohmann::json mutedUsers = nlohmann::json + { + { "SteamID", list }, + }; + + Utils::IO::WriteFile(MutedListFile, mutedUsers.dump()); + } + + void Chat::LoadMutedList() + { + const auto _ = Lock(); + + const auto mutedUsers = Utils::IO::ReadFile(MutedListFile); + if (mutedUsers.empty()) + { + Logger::Debug("muted-users.json does not exist"); + return; + } + + nlohmann::json mutedUsersData; + try + { + mutedUsersData = nlohmann::json::parse(mutedUsers); + } + catch (const std::exception& ex) + { + Logger::PrintError(Game::CON_CHANNEL_ERROR, "Json Parse Error: {}\n", ex.what()); + return; + } + + if (!mutedUsersData.contains("SteamID")) + { + Logger::PrintError(Game::CON_CHANNEL_ERROR, "muted-users.json contains invalid data\n"); + return; + } + + const auto& list = mutedUsersData["SteamID"]; + if (!list.is_array()) + { + return; + } + + MutedList.access([&](muteList& clients) + { + const nlohmann::json::array_t arr = list; + for (auto& entry : arr) + { + if (entry.is_number_unsigned()) + { + clients.insert(entry.get()); + } + } }); } @@ -312,7 +403,7 @@ namespace Components } const auto* client = Game::SV_GetPlayerByNum(); - if (client != nullptr) + if (client && !client->bIsTestClient) { Voice::SV_MuteClient(client - Game::svs_clients); MuteClient(client); @@ -335,8 +426,12 @@ namespace Components } const auto* client = Game::SV_GetPlayerByNum(); + if (client->bIsTestClient) + { + return; + } - if (client != nullptr) + if (client) { UnmuteClient(client); Voice::SV_UnmuteClient(client - Game::svs_clients); @@ -466,42 +561,46 @@ namespace Components int Chat::ChatCallback(Game::gentity_s* self, const char* codePos, const char* message, int mode) { - const auto entityId = Game::Scr_GetEntityId(self - Game::g_entities, 0); + constexpr auto paramcount = 2; Scripting::StackIsolation _; Game::Scr_AddInt(mode); Game::Scr_AddString(message); - Game::VariableValue value; - value.type = Game::VAR_OBJECT; - value.u.uintValue = entityId; + const auto objId = Game::Scr_GetEntityId(self - Game::g_entities, 0); + Game::AddRefToObject(objId); + const auto id = Game::VM_Execute_0(Game::AllocThread(objId), codePos, paramcount); - Game::AddRefToValue(value.type, value.u); - const auto localId = Game::AllocThread(entityId); + const auto result = GetCallbackReturn(); - const auto result = Game::VM_Execute_0(localId, codePos, 2); - Game::RemoveRefToObject(result); + Game::RemoveRefToValue(Game::scrVmPub->top->type, Game::scrVmPub->top->u); - return GetCallbackReturn(); + Game::scrVmPub->top->type = Game::VAR_UNDEFINED; + --Game::scrVmPub->top; + --Game::scrVmPub->inparamcount; + + Game::Scr_FreeThread(static_cast(id)); + + return result; } void Chat::AddScriptFunctions() { - Script::AddFunction("OnPlayerSay", [] // gsc: OnPlayerSay() + GSC::Script::AddFunction("OnPlayerSay", [] // gsc: OnPlayerSay() { if (Game::Scr_GetNumParam() != 1) { - Game::Scr_Error("^1OnPlayerSay: Needs one function pointer!\n"); + Game::Scr_Error("OnPlayerSay: Needs one function pointer!"); return; } if (!CanAddCallback) { - Game::Scr_Error("^1OnPlayerSay: Cannot add a callback in this context"); + Game::Scr_Error("OnPlayerSay: Cannot add a callback in this context"); return; } - const auto* func = Script::GetCodePosForParam(0); + const auto* func = GSC::ScriptExtension::GetCodePosForParam(0); SayCallbacks.emplace_back(func); }); } @@ -514,6 +613,8 @@ namespace Components sv_disableChat = Dvar::Register("sv_disableChat", false, Game::DVAR_NONE, "Disable chat messages from clients"); Events::OnSVInit(AddChatCommands); + LoadMutedList(); + // Intercept chat sending Utils::Hook(0x4D000B, PreSayStub, HOOK_CALL).install()->quick(); Utils::Hook(0x4D00D4, PostSayStub, HOOK_CALL).install()->quick(); diff --git a/src/Components/Modules/Chat.hpp b/src/Components/Modules/Chat.hpp index 55a37738..34cbffea 100644 --- a/src/Components/Modules/Chat.hpp +++ b/src/Components/Modules/Chat.hpp @@ -8,23 +8,25 @@ namespace Components public: Chat(); + static bool IsMuted(const Game::gentity_s* ent); + static bool IsMuted(const Game::client_t* cl); + private: static Dvar::Var cg_chatWidth; static Dvar::Var sv_disableChat; static Dvar::Var sv_sayName; - // Game dvars - static Game::dvar_t** cg_chatHeight; - static Game::dvar_t** cg_chatTime; - static bool SendChat; using muteList = std::unordered_set; static Utils::Concurrency::Container MutedList; + static const char* MutedListFile; static bool CanAddCallback; // ClientCommand & GSC thread are the same static std::vector SayCallbacks; + static std::unique_lock Lock(); + static const char* EvaluateSay(char* text, Game::gentity_t* player, int mode); static void PreSayStub(); @@ -34,10 +36,12 @@ namespace Components static void CG_AddToTeamChat(const char* text); static void CG_AddToTeamChat_Stub(); - static bool IsMuted(const Game::gentity_s* ent); static void MuteClient(const Game::client_t* client); static void UnmuteClient(const Game::client_t* client); static void UnmuteInternal(std::uint64_t id, bool everyone = false); + static void SaveMutedList(const muteList& list); + static void LoadMutedList(); + static void AddChatCommands(); static int GetCallbackReturn(); diff --git a/src/Components/Modules/ClanTags.cpp b/src/Components/Modules/ClanTags.cpp index dfa3a664..0db83001 100644 --- a/src/Components/Modules/ClanTags.cpp +++ b/src/Components/Modules/ClanTags.cpp @@ -5,7 +5,7 @@ namespace Components { - Game::dvar_t* ClanTags::ClanName; + const Game::dvar_t* ClanTags::ClanName; // bgs_t and clientState_s do not have this char ClanTags::ClientState[Game::MAX_CLIENTS][5]; @@ -28,7 +28,7 @@ namespace Components for (std::size_t i = 0; i < Game::MAX_CLIENTS; ++i) { - list.append(std::format("\\{}\\{}", std::to_string(i), ClientState[i])); + list.append(std::format("\\{}\\{}", i, ClientState[i])); } Game::SV_GameSendServerCommand(-1, Game::SV_CMD_CAN_IGNORE, Utils::String::Format("{:c} clanNames \"{}\"", 22, list)); @@ -118,7 +118,7 @@ namespace Components { AssertIn(clientNum, Game::MAX_CLIENTS); - auto* clanAbbrev = Game::Info_ValueForKey(s, "clanAbbrev"); + const auto* clanAbbrev = Game::Info_ValueForKey(s, "clanAbbrev"); if (clanAbbrev[0] == '\0') { @@ -193,11 +193,11 @@ namespace Components void __declspec(naked) ClanTags::PlayerCards_SetCachedPlayerData_Stub() { - static DWORD func = 0x4D6F80; // I_strncpyz + using namespace Game; __asm { - call func + call I_strncpyz add esp, 0xC mov byte ptr [esi + 0x3C], 0x0 @@ -235,8 +235,7 @@ namespace Components { Events::OnClientInit([] { - ClanName = Game::Dvar_RegisterString("clanName", "", Game::DVAR_ARCHIVE, - "Your clan abbreviation"); + ClanName = Game::Dvar_RegisterString("clanName", "", Game::DVAR_ARCHIVE, "Your clan abbreviation"); }); std::memset(&ClientState, 0, sizeof(char[Game::MAX_CLIENTS][5])); diff --git a/src/Components/Modules/ClanTags.hpp b/src/Components/Modules/ClanTags.hpp index 7f3aec77..6618504b 100644 --- a/src/Components/Modules/ClanTags.hpp +++ b/src/Components/Modules/ClanTags.hpp @@ -14,7 +14,7 @@ namespace Components static void CL_SanitizeClanName(); private: - static Game::dvar_t* ClanName; + static const Game::dvar_t* ClanName; static const char* dvarNameList[]; diff --git a/src/Components/Modules/ClientCommand.cpp b/src/Components/Modules/ClientCommand.cpp index 5de247e1..5256b1de 100644 --- a/src/Components/Modules/ClientCommand.cpp +++ b/src/Components/Modules/ClientCommand.cpp @@ -66,12 +66,12 @@ namespace Components if (!CheatsOk(ent)) return; - ent->client->flags ^= Game::PLAYER_FLAG_NOCLIP; + ent->client->flags ^= Game::PF_NOCLIP; const auto entNum = ent->s.number; Logger::Debug("Noclip toggled for entity {}", entNum); - Game::SV_GameSendServerCommand(entNum, Game::SV_CMD_CAN_IGNORE, VA("%c \"%s\"", 0x65, (ent->client->flags & Game::PLAYER_FLAG_NOCLIP) ? "GAME_NOCLIPON" : "GAME_NOCLIPOFF")); + Game::SV_GameSendServerCommand(entNum, Game::SV_CMD_CAN_IGNORE, VA("%c \"%s\"", 0x65, (ent->client->flags & Game::PF_NOCLIP) ? "GAME_NOCLIPON" : "GAME_NOCLIPOFF")); }); Add("ufo", [](Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params) @@ -79,12 +79,12 @@ namespace Components if (!CheatsOk(ent)) return; - ent->client->flags ^= Game::PLAYER_FLAG_UFO; + ent->client->flags ^= Game::PF_UFO; const auto entNum = ent->s.number; Logger::Debug("UFO toggled for entity {}", entNum); - Game::SV_GameSendServerCommand(entNum, Game::SV_CMD_CAN_IGNORE, VA("%c \"%s\"", 0x65, (ent->client->flags & Game::PLAYER_FLAG_UFO) ? "GAME_UFOON" : "GAME_UFOOFF")); + Game::SV_GameSendServerCommand(entNum, Game::SV_CMD_CAN_IGNORE, VA("%c \"%s\"", 0x65, (ent->client->flags & Game::PF_UFO) ? "GAME_UFOON" : "GAME_UFOOFF")); }); Add("god", [](Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params) @@ -363,7 +363,7 @@ namespace Components void ClientCommand::AddScriptFunctions() { - Script::AddFunction("DropAllBots", [] // gsc: DropAllBots(); + GSC::Script::AddFunction("DropAllBots", [] // gsc: DropAllBots(); { Game::SV_DropAllBots(); }); diff --git a/src/Components/Modules/Command.cpp b/src/Components/Modules/Command.cpp index 9afca701..0aa4430c 100644 --- a/src/Components/Modules/Command.cpp +++ b/src/Components/Modules/Command.cpp @@ -24,12 +24,12 @@ namespace Components assert(Game::cmd_args->nesting < Game::CMD_MAX_NESTING); } - int Command::ClientParams::size() const + int Command::ClientParams::size() const noexcept { return Game::cmd_args->argc[this->nesting_]; } - const char* Command::ClientParams::get(const int index) const + const char* Command::ClientParams::get(const int index) const noexcept { if (index >= this->size()) { @@ -45,12 +45,12 @@ namespace Components assert(Game::sv_cmd_args->nesting < Game::CMD_MAX_NESTING); } - int Command::ServerParams::size() const + int Command::ServerParams::size() const noexcept { return Game::sv_cmd_args->argc[this->nesting_]; } - const char* Command::ServerParams::get(const int index) const + const char* Command::ServerParams::get(const int index) const noexcept { if (index >= this->size()) { @@ -62,42 +62,40 @@ namespace Components void Command::Add(const char* name, const std::function& callback) { - Add(name, [callback]([[maybe_unused]] const Command::Params* params) + Add(name, [callback]([[maybe_unused]] const Params* params) { callback(); }); } - void Command::Add(const char* name, const std::function& callback) + void Command::Add(const char* name, const std::function& callback) { const auto command = Utils::String::ToLower(name); - if (!Command::FunctionMap.contains(command)) + if (!FunctionMap.contains(command)) { - Command::AddRaw(name, Command::MainCallback); + AddRaw(name, MainCallback); } - Command::FunctionMap.insert_or_assign(command, std::move(callback)); + FunctionMap.insert_or_assign(command, callback); } - void Command::AddSV(const char* name, const std::function& callback) + void Command::AddSV(const char* name, const std::function& callback) { if (Loader::IsPregame()) { MessageBoxA(nullptr, "Registering server commands in pregame state is illegal!", nullptr, MB_ICONERROR); - -#ifdef DEBUG +#ifdef _DEBUG __debugbreak(); #endif - return; } const auto command = Utils::String::ToLower(name); - if (!Command::FunctionMapSV.contains(command)) + if (!FunctionMapSV.contains(command)) { - Command::AddRawSV(name, Command::MainCallbackSV); + AddRawSV(name, MainCallbackSV); } FunctionMapSV.insert_or_assign(command, callback); @@ -105,21 +103,23 @@ namespace Components void Command::AddRaw(const char* name, void(*callback)(), bool key) { - Game::Cmd_AddCommand(name, callback, Command::Allocate(), key); + Game::Cmd_AddCommand(name, callback, Allocate(), key); } void Command::AddRawSV(const char* name, void(*callback)()) { - Game::Cmd_AddServerCommand(name, callback, Command::Allocate()); + Game::Cmd_AddServerCommand(name, callback, Allocate()); // If the main command is registered as Cbuf_AddServerText, the command will be redirected to the SV handler - Command::AddRaw(name, Game::Cbuf_AddServerText_f, false); + AddRaw(name, Game::Cbuf_AddServerText_f, false); } void Command::Execute(std::string command, bool sync) { command.append("\n"); // Make sure it's terminated + assert(command.size() < Game::MAX_CMD_LINE); + if (sync) { Game::Cmd_ExecuteSingleCommand(0, 0, command.data()); @@ -130,13 +130,13 @@ namespace Components } } - Game::cmd_function_t* Command::Find(const std::string& command) + Game::cmd_function_s* Command::Find(const std::string& command) { auto* cmdFunction = *Game::cmd_functions; - while (cmdFunction != nullptr) + while (cmdFunction) { - if (cmdFunction->name != nullptr && cmdFunction->name == command) + if (cmdFunction->name && Utils::String::Compare(cmdFunction->name, command)) { return cmdFunction; } @@ -147,9 +147,9 @@ namespace Components return nullptr; } - Game::cmd_function_t* Command::Allocate() + Game::cmd_function_s* Command::Allocate() { - return Utils::Memory::GetAllocator()->allocate(); + return Utils::Memory::GetAllocator()->allocate(); } void Command::MainCallback() @@ -173,4 +173,52 @@ namespace Components itr->second(¶ms); } } + + const std::vector& Command::GetExceptions() + { + static const auto exceptions = []() -> std::vector + { + std::vector values = + { + "cmd", + "exec", + "map", + }; + + if (Flags::HasFlag("disable-notifies")) + { + values.emplace_back("vstr"); + values.emplace_back("wait"); + } + + return values; + }(); + + return exceptions; + } + + bool Command::CL_ShouldSendNotify_Hk(const char* cmd) + { + if (!cmd) + { + return false; + } + + const auto& exceptions = GetExceptions(); + for (const auto& entry : exceptions) + { + if (Utils::String::Compare(cmd, entry)) + { + return false; + } + } + + return true; + } + + Command::Command() + { + // Protect players from invasive servers + Utils::Hook(0x434BD4, CL_ShouldSendNotify_Hk, HOOK_CALL).install()->quick(); // CL_CheckNotify + } } diff --git a/src/Components/Modules/Command.hpp b/src/Components/Modules/Command.hpp index 9ed9889c..57cc4f67 100644 --- a/src/Components/Modules/Command.hpp +++ b/src/Components/Modules/Command.hpp @@ -5,7 +5,7 @@ namespace Components class Command : public Component { public: - static_assert(sizeof(Game::cmd_function_t) == 0x18); + static_assert(sizeof(Game::cmd_function_s) == 0x18); class Params { @@ -13,8 +13,13 @@ namespace Components Params() = default; virtual ~Params() = default; - [[nodiscard]] virtual int size() const = 0; - [[nodiscard]] virtual const char* get(int index) const = 0; + Params(Params&&) = delete; + Params(const Params&) = delete; + Params& operator=(Params&&) = delete; + Params& operator=(const Params&) = delete; + + [[nodiscard]] virtual int size() const noexcept = 0; + [[nodiscard]] virtual const char* get(int index) const noexcept = 0; [[nodiscard]] virtual std::string join(int index) const; virtual const char* operator[](const int index) @@ -28,8 +33,8 @@ namespace Components public: ClientParams(); - [[nodiscard]] int size() const override; - [[nodiscard]] const char* get(int index) const override; + [[nodiscard]] int size() const noexcept override; + [[nodiscard]] const char* get(int index) const noexcept override; private: int nesting_; @@ -40,16 +45,16 @@ namespace Components public: ServerParams(); - [[nodiscard]] int size() const override; - [[nodiscard]] const char* get(int index) const override; + [[nodiscard]] int size() const noexcept override; + [[nodiscard]] const char* get(int index) const noexcept override; private: int nesting_; }; - Command() = default; + Command(); - static Game::cmd_function_t* Allocate(); + static Game::cmd_function_s* Allocate(); static void Add(const char* name, const std::function& callback); static void Add(const char* name, const std::function& callback); @@ -57,7 +62,7 @@ namespace Components static void AddSV(const char* name, const std::function& callback); static void Execute(std::string command, bool sync = true); - static Game::cmd_function_t* Find(const std::string& command); + static Game::cmd_function_s* Find(const std::string& command); private: static std::unordered_map> FunctionMap; @@ -67,5 +72,8 @@ namespace Components static void MainCallback(); static void MainCallbackSV(); + + static const std::vector& GetExceptions(); + static bool CL_ShouldSendNotify_Hk(const char* cmd); }; } diff --git a/src/Components/Modules/ConnectProtocol.cpp b/src/Components/Modules/ConnectProtocol.cpp index 1542b28e..4a44ad34 100644 --- a/src/Components/Modules/ConnectProtocol.cpp +++ b/src/Components/Modules/ConnectProtocol.cpp @@ -200,7 +200,8 @@ namespace Components { if (Used()) { - Command::Execute(std::format("connect {}", ConnectString), false); + const auto* cmd = Utils::String::Format("connect {}", ConnectString); + Command::Execute(cmd, false); } } @@ -211,7 +212,8 @@ namespace Components // IPC handler IPCPipe::On("connect", [](const std::string& data) { - Command::Execute(std::format("connect {}", data), false); + const auto* cmd = Utils::String::Format("connect {}", data); + Command::Execute(cmd, false); }); // Invocation handler @@ -227,7 +229,7 @@ namespace Components if (!Singleton::IsFirstInstance()) { IPCPipe::Write("connect", ConnectString); - ExitProcess(0); + ExitProcess(EXIT_SUCCESS); } else { @@ -237,7 +239,7 @@ namespace Components Scheduler::Once([] { Command::Execute("openmenu popup_reconnectingtoparty", false); - }, Scheduler::Pipeline::CLIENT, 8s); + }, Scheduler::Pipeline::MAIN, 8s); } } } diff --git a/src/Components/Modules/Console.cpp b/src/Components/Modules/Console.cpp index 04c5b418..ed61489c 100644 --- a/src/Components/Modules/Console.cpp +++ b/src/Components/Modules/Console.cpp @@ -1,6 +1,8 @@ #include #include "Console.hpp" +#include "Terminus_4.49.1.ttf.hpp" + #include #ifdef MOUSE_MOVED @@ -32,14 +34,14 @@ namespace Components bool Console::SkipShutdown = false; COLORREF Console::TextColor = -#if DEBUG +#if _DEBUG RGB(255, 200, 117); #else RGB(120, 237, 122); #endif COLORREF Console::BackgroundColor = -#if DEBUG +#if _DEBUG RGB(35, 21, 0); #else RGB(25, 32, 25); @@ -64,16 +66,16 @@ namespace Components const std::string mapname = (*Game::sv_mapname)->current.string; const auto hostname = TextRenderer::StripColors((*Game::sv_hostname)->current.string); - if (Console::HasConsole) + if (HasConsole) { SetConsoleTitleA(hostname.data()); auto clientCount = 0; - auto maxclientCount = *Game::svs_clientCount; + auto maxClientCount = *Game::svs_clientCount; - if (maxclientCount) + if (maxClientCount) { - for (int i = 0; i < maxclientCount; ++i) + for (auto i = 0; i < maxClientCount; ++i) { if (Game::svs_clients[i].header.state >= Game::CS_CONNECTED) { @@ -83,18 +85,17 @@ namespace Components } else { - maxclientCount = Dvar::Var("party_maxplayers").get(); - //maxclientCount = Game::Party_GetMaxPlayers(*Game::partyIngame); - clientCount = Game::PartyHost_CountMembers(reinterpret_cast(0x1081C00)); + maxClientCount = *Game::party_maxplayers ? (*Game::party_maxplayers)->current.integer : 18; + clientCount = Game::PartyHost_CountMembers(Game::g_lobbyData); } wclear(InfoWindow); - wprintw(InfoWindow, "%s : %d/%d players : map %s", hostname.data(), clientCount, maxclientCount, (!mapname.empty()) ? mapname.data() : "none"); + wprintw(InfoWindow, "%s : %d/%d players : map %s", hostname.data(), clientCount, maxClientCount, (!mapname.empty()) ? mapname.data() : "none"); wnoutrefresh(InfoWindow); } - else if (IsWindow(Console::GetWindow()) != FALSE) + else if (IsWindow(GetWindow()) != FALSE) { - SetWindowTextA(Console::GetWindow(), Utils::String::VA("IW4x(" VERSION ") : %s", hostname.data())); + SetWindowTextA(GetWindow(), Utils::String::VA("IW4x(" VERSION ") : %s", hostname.data())); } } @@ -106,35 +107,35 @@ namespace Components void Console::RefreshOutput() { - prefresh(OutputWindow, ((Console::OutputTop > 0) ? (Console::OutputTop - 1) : 0), 0, 1, 0, Console::Height - 2, Console::Width - 1); + prefresh(OutputWindow, ((OutputTop > 0) ? (OutputTop - 1) : 0), 0, 1, 0, Height - 2, Width - 1); } void Console::ScrollOutput(int amount) { - Console::OutputTop += amount; + OutputTop += amount; - if (Console::OutputTop > OUTPUT_MAX_TOP) + if (OutputTop > OUTPUT_MAX_TOP) { - Console::OutputTop = OUTPUT_MAX_TOP; + OutputTop = OUTPUT_MAX_TOP; } - else if (Console::OutputTop < 0) + else if (OutputTop < 0) { - Console::OutputTop = 0; + OutputTop = 0; } // make it only scroll the top if there's more than HEIGHT lines - if (Console::OutBuffer >= 0) + if (OutBuffer >= 0) { - Console::OutBuffer += amount; + OutBuffer += amount; - if (Console::OutBuffer >= Console::Height) + if (OutBuffer >= Height) { - Console::OutBuffer = -1; + OutBuffer = -1; } - if (Console::OutputTop < Console::Height) + if (OutputTop < Height) { - Console::OutputTop = 0; + OutputTop = 0; } } } @@ -149,17 +150,13 @@ namespace Components if (getDpiForWindow) { - dpi = getDpiForWindow(hWnd); + dpi = static_cast(getDpiForWindow(hWnd)); } else if (getDpiForMonitor) { HMONITOR hMonitor = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST); UINT xdpi, ydpi; - LRESULT success = getDpiForMonitor(hMonitor, 0, &xdpi, &ydpi); - if (success == S_OK) - { - dpi = static_cast(ydpi); - } + getDpiForMonitor(hMonitor, 0, &xdpi, &ydpi); dpi = 96; } @@ -167,33 +164,33 @@ namespace Components { HDC hDC = GetDC(hWnd); INT ydpi = GetDeviceCaps(hDC, LOGPIXELSY); - ReleaseDC(NULL, hDC); + ReleaseDC(nullptr, hDC); dpi = ydpi; } constexpr auto unawareDpi = 96.0f; - return dpi / unawareDpi; + return static_cast(dpi) / unawareDpi; } const char* Console::Input() { - if (!Console::HasConsole) + if (!HasConsole) { - Console::ShowPrompt(); + ShowPrompt(); wrefresh(InputWindow); - Console::HasConsole = true; + HasConsole = true; } - int currentTime = static_cast(GetTickCount64()); // Make our compiler happy - if ((currentTime - Console::LastRefresh) > 250) + auto currentTime = static_cast(GetTickCount64()); // Make our compiler happy + if ((currentTime - LastRefresh) > 250) { - Console::RefreshOutput(); - Console::LastRefresh = currentTime; + RefreshOutput(); + LastRefresh = currentTime; } - int c = wgetch(InputWindow); + auto c = wgetch(InputWindow); if (c == ERR) { @@ -208,28 +205,28 @@ namespace Components wattron(OutputWindow, COLOR_PAIR(10) | A_BOLD); wprintw(OutputWindow, "%s", "]"); - if (Console::LineBufferIndex) + if (LineBufferIndex) { - wprintw(OutputWindow, "%s", Console::LineBuffer); + wprintw(OutputWindow, "%s", LineBuffer); } wprintw(OutputWindow, "%s", "\n"); wattroff(OutputWindow, A_BOLD); wclear(InputWindow); - Console::ShowPrompt(); + ShowPrompt(); wrefresh(InputWindow); - Console::ScrollOutput(1); - Console::RefreshOutput(); + ScrollOutput(1); + RefreshOutput(); - if (Console::LineBufferIndex) + if (LineBufferIndex) { - strcpy_s(Console::LineBuffer2, Console::LineBuffer); - strcat_s(Console::LineBuffer, "\n"); - Console::LineBufferIndex = 0; - return Console::LineBuffer; + strcpy_s(LineBuffer2, LineBuffer); + strcat_s(LineBuffer, "\n"); + LineBufferIndex = 0; + return LineBuffer; } break; @@ -237,22 +234,22 @@ namespace Components case 'c' - 'a' + 1: // ctrl-c case 27: { - Console::LineBuffer[0] = '\0'; - Console::LineBufferIndex = 0; + LineBuffer[0] = '\0'; + LineBufferIndex = 0; wclear(InputWindow); - Console::ShowPrompt(); + ShowPrompt(); wrefresh(InputWindow); break; } case 8: // backspace { - if (Console::LineBufferIndex > 0) + if (LineBufferIndex > 0) { - Console::LineBufferIndex--; - Console::LineBuffer[Console::LineBufferIndex] = '\0'; + LineBufferIndex--; + LineBuffer[LineBufferIndex] = '\0'; wprintw(InputWindow, "%c %c", static_cast(c), static_cast(c)); wrefresh(InputWindow); @@ -261,35 +258,35 @@ namespace Components } case KEY_PPAGE: { - Console::ScrollOutput(-1); - Console::RefreshOutput(); + ScrollOutput(-1); + RefreshOutput(); break; } case KEY_NPAGE: { - Console::ScrollOutput(1); - Console::RefreshOutput(); + ScrollOutput(1); + RefreshOutput(); break; } case KEY_UP: { wclear(InputWindow); - Console::ShowPrompt(); - wprintw(InputWindow, "%s", Console::LineBuffer2); + ShowPrompt(); + wprintw(InputWindow, "%s", LineBuffer2); wrefresh(InputWindow); - strcpy_s(Console::LineBuffer, Console::LineBuffer2); - Console::LineBufferIndex = strlen(Console::LineBuffer); + strcpy_s(LineBuffer, LineBuffer2); + LineBufferIndex = static_cast(std::strlen(LineBuffer)); break; } default: - if (c <= 127 && Console::LineBufferIndex < 1022) + if (c <= 127 && LineBufferIndex < 1022) { - // temporary workaround , find out what overwrites our index later on + // temporary workaround, find out what overwrites our index later on //consoleLineBufferIndex = strlen(consoleLineBuffer); - Console::LineBuffer[Console::LineBufferIndex++] = static_cast(c); - Console::LineBuffer[Console::LineBufferIndex] = '\0'; + LineBuffer[LineBufferIndex++] = static_cast(c); + LineBuffer[LineBufferIndex] = '\0'; wprintw(InputWindow, "%c", static_cast(c)); wrefresh(InputWindow); } @@ -318,31 +315,31 @@ namespace Components void Console::Create() { - Console::OutputTop = 0; - Console::OutBuffer = 0; - Console::LastRefresh = 0; - Console::LineBufferIndex = 0; - Console::HasConsole = false; + OutputTop = 0; + OutBuffer = 0; + LastRefresh = 0; + LineBufferIndex = 0; + HasConsole = false; CONSOLE_SCREEN_BUFFER_INFO info; if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info)) { - Console::Width = info.dwSize.X; - Console::Height = info.srWindow.Bottom - info.srWindow.Top + 1; + Width = info.dwSize.X; + Height = info.srWindow.Bottom - info.srWindow.Top + 1; } else { - Console::Height = 25; - Console::Width = 80; + Height = 25; + Width = 80; } initscr(); raw(); noecho(); - OutputWindow = newpad(Console::Height - 1, Console::Width); - InputWindow = newwin(1, Console::Width, Console::Height - 1, 0); - InfoWindow = newwin(1, Console::Width, 0, 0); + OutputWindow = newpad(Height - 1, Width); + InputWindow = newwin(1, Width, Height - 1, 0); + InfoWindow = newwin(1, Width, 0, 0); scrollok(OutputWindow, true); idlok(OutputWindow, true); @@ -370,7 +367,7 @@ namespace Components wrefresh(InfoWindow); wrefresh(InputWindow); - Console::RefreshOutput(); + RefreshOutput(); } void Console::Error(const char* fmt, ...) @@ -384,7 +381,7 @@ namespace Components Logger::PrintError(Game::CON_CHANNEL_ERROR, "{}\n", buf); - Console::RefreshOutput(); + RefreshOutput(); if (IsDebuggerPresent()) { @@ -394,7 +391,7 @@ namespace Components } } - TerminateProcess(GetCurrentProcess(), 0xDEADDEAD); + TerminateProcess(GetCurrentProcess(), EXIT_FAILURE); } void Console::Print(const char* message) @@ -406,11 +403,9 @@ namespace Components { if (*p == '^') { - char color; ++p; - color = (*p - '0'); - + const char color = (*p - '0'); if (color < 9 && color > 0) { wattron(OutputWindow, COLOR_PAIR(color + 2)); @@ -426,26 +421,26 @@ namespace Components wattron(OutputWindow, COLOR_PAIR(9)); - Console::RefreshOutput(); + RefreshOutput(); } - HFONT __stdcall Console::ReplaceFont( - [[maybe_unused]] int cHeight, - int cWidth, - int cEscapement, - int cOrientation, - [[maybe_unused]] int cWeight, - DWORD bItalic, - DWORD bUnderline, - DWORD bStrikeOut, - DWORD iCharSet, - [[maybe_unused]] DWORD iOutPrecision, - DWORD iClipPrecision, - [[maybe_unused]] DWORD iQuality, - [[maybe_unused]] DWORD iPitchAndFamily, + HFONT CALLBACK Console::ReplaceFont( + [[maybe_unused]] int cHeight, + int cWidth, + int cEscapement, + int cOrientation, + [[maybe_unused]] int cWeight, + DWORD bItalic, + DWORD bUnderline, + DWORD bStrikeOut, + DWORD iCharSet, + [[maybe_unused]] DWORD iOutPrecision, + DWORD iClipPrecision, + [[maybe_unused]] DWORD iQuality, + [[maybe_unused]] DWORD iPitchAndFamily, [[maybe_unused]] LPCSTR pszFaceName) { - auto font = CreateFontA( + HFONT font = CreateFontA( 12, cWidth, cEscapement, @@ -459,7 +454,8 @@ namespace Components iClipPrecision, NONANTIALIASED_QUALITY, 0x31, - "Terminus (TTF)"); // Terminus (TTF) + "Terminus (TTF)" + ); // Terminus (TTF) return font; } @@ -467,7 +463,7 @@ namespace Components void Console::GetWindowPos(HWND hWnd, int* x, int* y) { HWND hWndParent = GetParent(hWnd); - POINT p = { 0 }; + POINT p{}; MapWindowPoints(hWnd, hWndParent, &p, 1); @@ -478,8 +474,8 @@ namespace Components BOOL CALLBACK Console::ResizeChildWindow(HWND hwndChild, LPARAM lParam) { auto id = GetWindowLong(hwndChild, GWL_ID); - bool isInputBox = id == INPUT_BOX; - bool isOutputBox = id == OUTPUT_BOX; + auto isInputBox = id == INPUT_BOX; + auto isOutputBox = id == OUTPUT_BOX; if (isInputBox || isOutputBox) { @@ -496,31 +492,31 @@ namespace Components HWND parent = Utils::Hook::Get(0x64A3288); - float scale = GetDpiScale(parent); + auto scale = GetDpiScale(parent); if (isInputBox) { - int newX = childX; // No change! - int newY = static_cast((newParentRect.bottom - newParentRect.top) - 65 * scale); - int newWidth = static_cast((newParentRect.right - newParentRect.left) - 29 * scale); - int newHeight = static_cast((childRect.bottom - childRect.top) * scale); // No change! + auto newX = childX; // No change! + auto newY = static_cast((newParentRect.bottom - newParentRect.top) - 65 * scale); + auto newWidth = static_cast((newParentRect.right - newParentRect.left) - 29 * scale); + auto newHeight = static_cast((childRect.bottom - childRect.top) * scale); // No change! MoveWindow(hwndChild, newX, newY, newWidth, newHeight, TRUE); } if (isOutputBox) { - int newX = childX; // No change! - int newY = childY; // No change! - int newWidth = static_cast((newParentRect.right - newParentRect.left) - 29); - - int margin = 70; + auto newX = childX; // No change! + auto newY = childY; // No change! + auto newWidth = static_cast((newParentRect.right - newParentRect.left) - 29); #ifdef REMOVE_HEADERBAR - margin = 10; + constexpr auto margin = 10; +#else + constexpr auto margin = 70; #endif - int newHeight = static_cast((newParentRect.bottom - newParentRect.top) - 74 * scale - margin); + auto newHeight = static_cast((newParentRect.bottom - newParentRect.top) - 74 * scale - margin); MoveWindow(hwndChild, newX, newY, newWidth, newHeight, TRUE); } @@ -536,23 +532,21 @@ namespace Components // around whenever clearing occurs. void Console::MakeRoomForText([[maybe_unused]] int addedCharacters) { - constexpr unsigned int maxChars = 0x4000; - constexpr unsigned int maxAffectedChars = 0x100; + constexpr auto maxChars = 0x4000; + constexpr auto maxAffectedChars = 0x100; HWND outputBox = Utils::Hook::Get(0x64A328C); - unsigned int totalChars; - unsigned int totalClearLength = 0; + auto totalClearLength = 0; char str[maxAffectedChars]; - unsigned int fetchedCharacters = static_cast(GetWindowText(outputBox, str, maxAffectedChars)); - - totalChars = GetWindowTextLengthA(outputBox); + const auto fetchedCharacters = GetWindowTextA(outputBox, str, maxAffectedChars); + auto totalChars = GetWindowTextLengthA(outputBox); while (totalChars - totalClearLength > maxChars) { - unsigned int clearLength = maxAffectedChars; // Default to full clear + auto clearLength = maxAffectedChars; // Default to full clear - for (size_t i = 0; i < fetchedCharacters; i++) + for (auto i = 0; i < fetchedCharacters; i++) { if (str[i] == '\n') { @@ -567,10 +561,10 @@ namespace Components if (totalClearLength > 0) { - SendMessage(outputBox, WM_SETREDRAW, FALSE, 0); - SendMessage(outputBox, EM_SETSEL, 0, totalClearLength); - SendMessage(outputBox, EM_REPLACESEL, FALSE, 0); - SendMessage(outputBox, WM_SETREDRAW, TRUE, 0); + SendMessageA(outputBox, WM_SETREDRAW, FALSE, 0); + SendMessageA(outputBox, EM_SETSEL, 0, totalClearLength); + SendMessageA(outputBox, EM_REPLACESEL, FALSE, 0); + SendMessageA(outputBox, WM_SETREDRAW, TRUE, 0); } Utils::Hook::Set(0x64A38B8, totalChars - totalClearLength); @@ -596,18 +590,11 @@ namespace Components { switch (Msg) { - - case WM_CREATE: { - BOOL darkMode = true; - -#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 - if (SUCCEEDED(DwmSetWindowAttribute(hWnd, DWMWA_USE_IMMERSIVE_DARK_MODE, reinterpret_cast(&darkMode), sizeof(darkMode)))) - { - // cool ! - } - + BOOL darkMode = TRUE; + constexpr auto DWMWA_USE_IMMERSIVE_DARK_MODE = 20; + DwmSetWindowAttribute(hWnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &darkMode, sizeof(darkMode)); break; } @@ -630,7 +617,6 @@ namespace Components return 0; } - // Fall through to basegame return Utils::Hook::Call(0x64DC50)(hWnd, Msg, wParam, lParam); } @@ -645,14 +631,14 @@ namespace Components void Console::ApplyConsoleStyle() { - Utils::Hook::Set(0x428A8E, 0); // Adjust logo Y pos - Utils::Hook::Set(0x428A90, 0); // Adjust logo X pos - Utils::Hook::Set(0x428AF2, 67); // Adjust output Y pos - Utils::Hook::Set(0x428AC5, 397); // Adjust input Y pos - Utils::Hook::Set(0x428951, 609); // Reduce window width - Utils::Hook::Set(0x42895D, 423); // Reduce window height - Utils::Hook::Set(0x428AC0, 597); // Reduce input width - Utils::Hook::Set(0x428AED, 596); // Reduce output width + Utils::Hook::Set(0x428A8E, 0); // Adjust logo Y pos + Utils::Hook::Set(0x428A90, 0); // Adjust logo X pos + Utils::Hook::Set(0x428AF2, 67); // Adjust output Y pos + Utils::Hook::Set(0x428AC5, 397); // Adjust input Y pos + Utils::Hook::Set(0x428951, 609); // Reduce window width + Utils::Hook::Set(0x42895D, 423); // Reduce window height + Utils::Hook::Set(0x428AC0, 597); // Reduce input width + Utils::Hook::Set(0x428AED, 596); // Reduce output width DWORD fontsInstalled; CustomConsoleFont = AddFontMemResourceEx(const_cast(reinterpret_cast(Font::Terminus::DATA)), Font::Terminus::LENGTH, 0, &fontsInstalled); @@ -680,35 +666,37 @@ namespace Components // Never reset text Utils::Hook::Nop(0x4F57DF, 0x4F57F6 - 0x4F57DF); - Utils::Hook(0x4F57DF, Console::Sys_PrintStub, HOOK_JUMP).install()->quick(); + Utils::Hook(0x4F57DF, Sys_PrintStub, HOOK_JUMP).install()->quick(); } void Console::ConsoleRunner() { - Console::SkipShutdown = false; + SkipShutdown = false; Game::Sys_ShowConsole(); MSG message; - while (IsWindow(Console::GetWindow()) != FALSE && GetMessageA(&message, nullptr, 0, 0)) + while (IsWindow(GetWindow()) != FALSE && GetMessageA(&message, nullptr, 0, 0)) { TranslateMessage(&message); DispatchMessageA(&message); } - if (Console::SkipShutdown) return; + if (SkipShutdown) return; - if (Game::Sys_Milliseconds() - Console::LastRefresh > 100 && + if (Game::Sys_Milliseconds() -LastRefresh > 100 && MessageBoxA(nullptr, "The application is not responding anymore, do you want to force its termination?", "Application is not responding", MB_ICONEXCLAMATION | MB_YESNO) == IDYES) { // Force process termination // if the main thread is not responding - OutputDebugStringA("Process termination forced, as the main thread is not responding!"); +#ifdef _DEBUG + OutputDebugStringA("Process termination was forced as the main thread is not responding!"); +#endif // We can not force the termination in this thread // The destructor would be called in this thread // and would try to join this thread, which is impossible - TerminateProcess(GetCurrentProcess(), 0xFFFFFFFF); + TerminateProcess(GetCurrentProcess(), EXIT_FAILURE); } else { @@ -729,7 +717,7 @@ namespace Components va_list ap; va_start(ap, fmt); - _vsnprintf_s(buffer, _TRUNCATE, fmt, ap); + vsnprintf_s(buffer, _TRUNCATE, fmt, ap); va_end(ap); perror(buffer); @@ -759,7 +747,7 @@ namespace Components void Console::StoreSafeArea() { // Backup the original safe area - Console::OriginalSafeArea = *Game::safeArea; + OriginalSafeArea = *Game::safeArea; // Apply new safe area and border float border = 6.0f; @@ -775,12 +763,12 @@ namespace Components void Console::RestoreSafeArea() { // Restore the initial safe area - *Game::safeArea = Console::OriginalSafeArea; + *Game::safeArea = OriginalSafeArea; } void Console::SetSkipShutdown() { - Console::SkipShutdown = true; + SkipShutdown = true; } void Console::FreeNativeConsole() @@ -798,11 +786,10 @@ namespace Components void Console::ShowAsyncConsole() { - Console::ConsoleThread = std::thread(Console::ConsoleRunner); + ConsoleThread = std::thread(ConsoleRunner); } - Game::dvar_t* Console::RegisterConColor(const char* dvarName, float r, float g, float b, float a, float min, - float max, unsigned __int16 flags, const char* description) + Game::dvar_t* Console::RegisterConColor(const char* dvarName, float r, float g, float b, float a, float min, float max, unsigned __int16 flags, const char* description) { static struct { @@ -857,7 +844,7 @@ namespace Components { Command::Add("con_echo", [] { - Console::Con_ToggleConsole(); + Con_ToggleConsole(); Game::I_strncpyz(Game::g_consoleField->buffer, "\\echo ", sizeof(Game::field_t::buffer)); Game::g_consoleField->cursor = static_cast(std::strlen(Game::g_consoleField->buffer)); Game::Field_AdjustScroll(Game::ScrPlace_GetFullPlacement(), Game::g_consoleField); @@ -878,24 +865,27 @@ namespace Components Utils::Hook::Set(0x5A4400, consoleColor); // Remove the need to type '\' or '/' to send a console command - Utils::Hook::Set(0x431565, 0xEB); + Utils::Hook::Set(0x431565, 0xEB); // Internal console - Utils::Hook(0x4F690C, Console::Con_ToggleConsole, HOOK_CALL).install()->quick(); - Utils::Hook(0x4F65A5, Console::Con_ToggleConsole, HOOK_JUMP).install()->quick(); + Utils::Hook(0x4F690C, Con_ToggleConsole, HOOK_CALL).install()->quick(); + Utils::Hook(0x4F65A5, Con_ToggleConsole, HOOK_JUMP).install()->quick(); + + // Allow the client console to always be opened (sv_allowClientConsole) + Utils::Hook::Nop(0x4F68EC, 2); // Patch safearea for ingame-console - Utils::Hook(0x5A50EF, Console::DrawSolidConsoleStub, HOOK_CALL).install()->quick(); + Utils::Hook(0x5A50EF, DrawSolidConsoleStub, HOOK_CALL).install()->quick(); // Check for bad food ;) - Utils::Hook(0x4CB9F4, Console::GetAutoCompleteFileList, HOOK_CALL).install()->quick(); + Utils::Hook(0x4CB9F4, GetAutoCompleteFileList, HOOK_CALL).install()->quick(); // Patch console dvars - Utils::Hook(0x4829AB, Console::RegisterConColor, HOOK_CALL).install()->quick(); - Utils::Hook(0x4829EE, Console::RegisterConColor, HOOK_CALL).install()->quick(); - Utils::Hook(0x482A31, Console::RegisterConColor, HOOK_CALL).install()->quick(); - Utils::Hook(0x482A7A, Console::RegisterConColor, HOOK_CALL).install()->quick(); - Utils::Hook(0x482AC3, Console::RegisterConColor, HOOK_CALL).install()->quick(); + Utils::Hook(0x4829AB, RegisterConColor, HOOK_CALL).install()->quick(); + Utils::Hook(0x4829EE, RegisterConColor, HOOK_CALL).install()->quick(); + Utils::Hook(0x482A31, RegisterConColor, HOOK_CALL).install()->quick(); + Utils::Hook(0x482A7A, RegisterConColor, HOOK_CALL).install()->quick(); + Utils::Hook(0x482AC3, RegisterConColor, HOOK_CALL).install()->quick(); // Modify console style ApplyConsoleStyle(); @@ -909,7 +899,7 @@ namespace Components if (Dedicated::IsEnabled() && !ZoneBuilder::IsEnabled()) { - Scheduler::Loop(Console::RefreshStatus, Scheduler::Pipeline::MAIN); + Scheduler::Loop(RefreshStatus, Scheduler::Pipeline::MAIN); } // Code below is not necessary when performing unit tests! @@ -918,8 +908,8 @@ namespace Components // External console if (Flags::HasFlag("stdout")) { - Utils::Hook(0x4B2080, Console::StdOutPrint, HOOK_JUMP).install()->quick(); - Utils::Hook(0x43D570, Console::StdOutError, HOOK_JUMP).install()->quick(); + Utils::Hook(0x4B2080, StdOutPrint, HOOK_JUMP).install()->quick(); + Utils::Hook(0x43D570, StdOutError, HOOK_JUMP).install()->quick(); } else if (Flags::HasFlag("console") || ZoneBuilder::IsEnabled()) // ZoneBuilder uses the game's console, until the native one is adapted. { @@ -930,43 +920,43 @@ namespace Components Utils::Hook(0x60BB68, [] { - Console::ShowAsyncConsole(); + ShowAsyncConsole(); }, HOOK_CALL).install()->quick(); - Utils::Hook(0x4D69A2, []() + Utils::Hook(0x4D69A2, [] { - Console::SetSkipShutdown(); + SetSkipShutdown(); // Sys_DestroyConsole Utils::Hook::Call(0x4528A0)(); - if (Console::ConsoleThread.joinable()) + if (ConsoleThread.joinable()) { - Console::ConsoleThread.join(); + ConsoleThread.join(); } }, HOOK_CALL).install()->quick(); Scheduler::Loop([] { - Console::LastRefresh = Game::Sys_Milliseconds(); + LastRefresh = Game::Sys_Milliseconds(); }, Scheduler::Pipeline::MAIN); } - else if (Dedicated::IsEnabled()/* || ZoneBuilder::IsEnabled()*/) + else if (Dedicated::IsEnabled()) { DWORD type = GetFileType(GetStdHandle(STD_INPUT_HANDLE)); if (type != FILE_TYPE_CHAR) { MessageBoxA(nullptr, "Console not supported, please use '-stdout' or '-console' flag!", "ERRROR", MB_ICONERROR); - TerminateProcess(GetCurrentProcess(), 1); + TerminateProcess(GetCurrentProcess(), EXIT_FAILURE); } Utils::Hook::Nop(0x60BB58, 11); - Utils::Hook(0x4305E0, Console::Create, HOOK_JUMP).install()->quick(); - Utils::Hook(0x4528A0, Console::Destroy, HOOK_JUMP).install()->quick(); - Utils::Hook(0x4B2080, Console::Print, HOOK_JUMP).install()->quick(); - Utils::Hook(0x43D570, Console::Error, HOOK_JUMP).install()->quick(); - Utils::Hook(0x4859A5, Console::Input, HOOK_CALL).install()->quick(); + Utils::Hook(0x4305E0, Create, HOOK_JUMP).install()->quick(); + Utils::Hook(0x4528A0, Destroy, HOOK_JUMP).install()->quick(); + Utils::Hook(0x4B2080, Print, HOOK_JUMP).install()->quick(); + Utils::Hook(0x43D570, Error, HOOK_JUMP).install()->quick(); + Utils::Hook(0x4859A5, Input, HOOK_CALL).install()->quick(); } else if(!Loader::IsPerformingUnitTests()) { @@ -976,10 +966,10 @@ namespace Components Console::~Console() { - Console::SetSkipShutdown(); - if (Console::ConsoleThread.joinable()) + SetSkipShutdown(); + if (ConsoleThread.joinable()) { - Console::ConsoleThread.join(); + ConsoleThread.join(); } } } diff --git a/src/Components/Modules/Console.hpp b/src/Components/Modules/Console.hpp index cd138a47..0ccad217 100644 --- a/src/Components/Modules/Console.hpp +++ b/src/Components/Modules/Console.hpp @@ -1,7 +1,5 @@ #pragma once -#include "Terminus_4.49.1.ttf.hpp" - #define OUTPUT_HEIGHT 250 #define OUTPUT_MAX_TOP (OUTPUT_HEIGHT - (Console::Height - 2)) @@ -81,21 +79,7 @@ namespace Components static LRESULT CALLBACK ConWndProc(HWND hWnd, UINT Msg, WPARAM wParam, unsigned int lParam); static ATOM CALLBACK RegisterClassHook(WNDCLASSA* lpWndClass); static BOOL CALLBACK ResizeChildWindow(HWND hwndChild, LPARAM lParam); - static HFONT CALLBACK ReplaceFont( - int cHeight, - int cWidth, - int cEscapement, - int cOrientation, - int cWeight, - DWORD bItalic, - DWORD bUnderline, - DWORD bStrikeOut, - DWORD iCharSet, - DWORD iOutPrecision, - DWORD iClipPrecision, - DWORD iQuality, - DWORD iPitchAndFamily, - LPCSTR pszFaceName); + static HFONT CALLBACK ReplaceFont(int cHeight, int cWidth, int cEscapement, int cOrientation, int cWeight, DWORD bItalic, DWORD bUnderline, DWORD bStrikeOut, DWORD iCharSet, DWORD iOutPrecision, DWORD iClipPrecision, DWORD iQuality, DWORD iPitchAndFamily, LPCSTR pszFaceName); static void ApplyConsoleStyle(); static void GetWindowPos(HWND hWnd, int* x, int* y); static void Sys_PrintStub(); diff --git a/src/Components/Modules/D3D9Ex.cpp b/src/Components/Modules/D3D9Ex.cpp index ca5f296f..31787f16 100644 --- a/src/Components/Modules/D3D9Ex.cpp +++ b/src/Components/Modules/D3D9Ex.cpp @@ -3,6 +3,8 @@ namespace Components { + Dvar::Var D3D9Ex::RUseD3D9Ex; + #pragma region D3D9Device HRESULT D3D9Ex::D3D9Device::QueryInterface(REFIID riid, void** ppvObj) @@ -630,7 +632,7 @@ namespace Components #pragma region D3D9 - HRESULT __stdcall D3D9Ex::D3D9::QueryInterface(REFIID riid, void** ppvObj) + HRESULT WINAPI D3D9Ex::D3D9::QueryInterface(REFIID riid, void** ppvObj) { *ppvObj = nullptr; @@ -644,84 +646,84 @@ namespace Components return hRes; } - ULONG __stdcall D3D9Ex::D3D9::AddRef() + ULONG WINAPI D3D9Ex::D3D9::AddRef() { return m_pIDirect3D9->AddRef(); } - ULONG __stdcall D3D9Ex::D3D9::Release() + ULONG WINAPI D3D9Ex::D3D9::Release() { ULONG count = m_pIDirect3D9->Release(); if (!count) delete this; return count; } - HRESULT __stdcall D3D9Ex::D3D9::RegisterSoftwareDevice(void* pInitializeFunction) + HRESULT WINAPI D3D9Ex::D3D9::RegisterSoftwareDevice(void* pInitializeFunction) { return m_pIDirect3D9->RegisterSoftwareDevice(pInitializeFunction); } - UINT __stdcall D3D9Ex::D3D9::GetAdapterCount() + UINT WINAPI D3D9Ex::D3D9::GetAdapterCount() { return m_pIDirect3D9->GetAdapterCount(); } - HRESULT __stdcall D3D9Ex::D3D9::GetAdapterIdentifier(UINT Adapter, DWORD Flags, D3DADAPTER_IDENTIFIER9* pIdentifier) + HRESULT WINAPI D3D9Ex::D3D9::GetAdapterIdentifier(UINT Adapter, DWORD Flags, D3DADAPTER_IDENTIFIER9* pIdentifier) { return m_pIDirect3D9->GetAdapterIdentifier(Adapter, Flags, pIdentifier); } - UINT __stdcall D3D9Ex::D3D9::GetAdapterModeCount(UINT Adapter, D3DFORMAT Format) + UINT WINAPI D3D9Ex::D3D9::GetAdapterModeCount(UINT Adapter, D3DFORMAT Format) { return m_pIDirect3D9->GetAdapterModeCount(Adapter, Format); } - HRESULT __stdcall D3D9Ex::D3D9::EnumAdapterModes(UINT Adapter, D3DFORMAT Format, UINT Mode, D3DDISPLAYMODE* pMode) + HRESULT WINAPI D3D9Ex::D3D9::EnumAdapterModes(UINT Adapter, D3DFORMAT Format, UINT Mode, D3DDISPLAYMODE* pMode) { return m_pIDirect3D9->EnumAdapterModes(Adapter, Format, Mode, pMode); } - HRESULT __stdcall D3D9Ex::D3D9::GetAdapterDisplayMode(UINT Adapter, D3DDISPLAYMODE* pMode) + HRESULT WINAPI D3D9Ex::D3D9::GetAdapterDisplayMode(UINT Adapter, D3DDISPLAYMODE* pMode) { return m_pIDirect3D9->GetAdapterDisplayMode(Adapter, pMode); } - HRESULT __stdcall D3D9Ex::D3D9::CheckDeviceType(UINT iAdapter, D3DDEVTYPE DevType, D3DFORMAT DisplayFormat, D3DFORMAT BackBufferFormat, BOOL bWindowed) + HRESULT WINAPI D3D9Ex::D3D9::CheckDeviceType(UINT iAdapter, D3DDEVTYPE DevType, D3DFORMAT DisplayFormat, D3DFORMAT BackBufferFormat, BOOL bWindowed) { return m_pIDirect3D9->CheckDeviceType(iAdapter, DevType, DisplayFormat, BackBufferFormat, bWindowed); } - HRESULT __stdcall D3D9Ex::D3D9::CheckDeviceFormat(UINT Adapter, D3DDEVTYPE DeviceType, D3DFORMAT AdapterFormat, DWORD Usage, D3DRESOURCETYPE RType, D3DFORMAT CheckFormat) + HRESULT WINAPI D3D9Ex::D3D9::CheckDeviceFormat(UINT Adapter, D3DDEVTYPE DeviceType, D3DFORMAT AdapterFormat, DWORD Usage, D3DRESOURCETYPE RType, D3DFORMAT CheckFormat) { return m_pIDirect3D9->CheckDeviceFormat(Adapter, DeviceType, AdapterFormat, Usage, RType, CheckFormat); } - HRESULT __stdcall D3D9Ex::D3D9::CheckDeviceMultiSampleType(UINT Adapter, D3DDEVTYPE DeviceType, D3DFORMAT SurfaceFormat, BOOL Windowed, D3DMULTISAMPLE_TYPE MultiSampleType, DWORD* pQualityLevels) + HRESULT WINAPI D3D9Ex::D3D9::CheckDeviceMultiSampleType(UINT Adapter, D3DDEVTYPE DeviceType, D3DFORMAT SurfaceFormat, BOOL Windowed, D3DMULTISAMPLE_TYPE MultiSampleType, DWORD* pQualityLevels) { return m_pIDirect3D9->CheckDeviceMultiSampleType(Adapter, DeviceType, SurfaceFormat, Windowed, MultiSampleType, pQualityLevels); } - HRESULT __stdcall D3D9Ex::D3D9::CheckDepthStencilMatch(UINT Adapter, D3DDEVTYPE DeviceType, D3DFORMAT AdapterFormat, D3DFORMAT RenderTargetFormat, D3DFORMAT DepthStencilFormat) + HRESULT WINAPI D3D9Ex::D3D9::CheckDepthStencilMatch(UINT Adapter, D3DDEVTYPE DeviceType, D3DFORMAT AdapterFormat, D3DFORMAT RenderTargetFormat, D3DFORMAT DepthStencilFormat) { return m_pIDirect3D9->CheckDepthStencilMatch(Adapter, DeviceType, AdapterFormat, RenderTargetFormat, DepthStencilFormat); } - HRESULT __stdcall D3D9Ex::D3D9::CheckDeviceFormatConversion(UINT Adapter, D3DDEVTYPE DeviceType, D3DFORMAT SourceFormat, D3DFORMAT TargetFormat) + HRESULT WINAPI D3D9Ex::D3D9::CheckDeviceFormatConversion(UINT Adapter, D3DDEVTYPE DeviceType, D3DFORMAT SourceFormat, D3DFORMAT TargetFormat) { return m_pIDirect3D9->CheckDeviceFormatConversion(Adapter, DeviceType, SourceFormat, TargetFormat); } - HRESULT __stdcall D3D9Ex::D3D9::GetDeviceCaps(UINT Adapter, D3DDEVTYPE DeviceType, D3DCAPS9* pCaps) + HRESULT WINAPI D3D9Ex::D3D9::GetDeviceCaps(UINT Adapter, D3DDEVTYPE DeviceType, D3DCAPS9* pCaps) { return m_pIDirect3D9->GetDeviceCaps(Adapter, DeviceType, pCaps); } - HMONITOR __stdcall D3D9Ex::D3D9::GetAdapterMonitor(UINT Adapter) + HMONITOR WINAPI D3D9Ex::D3D9::GetAdapterMonitor(UINT Adapter) { return m_pIDirect3D9->GetAdapterMonitor(Adapter); } - HRESULT __stdcall D3D9Ex::D3D9::CreateDevice(UINT Adapter, D3DDEVTYPE DeviceType, HWND hFocusWindow, DWORD BehaviorFlags, D3DPRESENT_PARAMETERS* pPresentationParameters, IDirect3DDevice9** ppReturnedDeviceInterface) + HRESULT WINAPI D3D9Ex::D3D9::CreateDevice(UINT Adapter, D3DDEVTYPE DeviceType, HWND hFocusWindow, DWORD BehaviorFlags, D3DPRESENT_PARAMETERS* pPresentationParameters, IDirect3DDevice9** ppReturnedDeviceInterface) { HRESULT hres = m_pIDirect3D9->CreateDevice(Adapter, DeviceType, hFocusWindow, BehaviorFlags, pPresentationParameters, ppReturnedDeviceInterface); *ppReturnedDeviceInterface = new D3D9Ex::D3D9Device(*ppReturnedDeviceInterface); @@ -730,28 +732,26 @@ namespace Components #pragma endregion - IDirect3D9* __stdcall D3D9Ex::Direct3DCreate9Stub(UINT sdk) + IDirect3D9* CALLBACK D3D9Ex::Direct3DCreate9Stub(UINT sdk) { - if (Dvar::Var("r_useD3D9Ex").get()) + if (RUseD3D9Ex.get()) { IDirect3D9Ex* test = nullptr; if (FAILED(Direct3DCreate9Ex(sdk, &test))) return nullptr; - return (new D3D9Ex::D3D9(test)); - } - else - { - return Direct3DCreate9(sdk); + return (new D3D9(test)); } + + return Direct3DCreate9(sdk); } D3D9Ex::D3D9Ex() { if (Dedicated::IsEnabled()) return; - Dvar::Register("r_useD3D9Ex", false, Game::DVAR_ARCHIVE, "Use extended d3d9 interface!"); + RUseD3D9Ex = Dvar::Register("r_useD3D9Ex", false, Game::DVAR_ARCHIVE, "Use extended d3d9 interface!"); // Hook Interface creation - Utils::Hook::Set(0x6D74D0, D3D9Ex::Direct3DCreate9Stub); + Utils::Hook::Set(0x6D74D0, Direct3DCreate9Stub); } } diff --git a/src/Components/Modules/D3D9Ex.hpp b/src/Components/Modules/D3D9Ex.hpp index f3a232f6..1395631c 100644 --- a/src/Components/Modules/D3D9Ex.hpp +++ b/src/Components/Modules/D3D9Ex.hpp @@ -8,132 +8,131 @@ namespace Components D3D9Ex(); private: - class D3D9Device : public IDirect3DDevice9 { public: - D3D9Device(IDirect3DDevice9* pOriginal) : m_pIDirect3DDevice9(pOriginal) {}; - virtual ~D3D9Device() {}; + D3D9Device(IDirect3DDevice9* pOriginal) : m_pIDirect3DDevice9(pOriginal) {} + virtual ~D3D9Device() = default; - HRESULT __stdcall QueryInterface(REFIID riid, void** ppvObj) override; - ULONG __stdcall AddRef() override; - ULONG __stdcall Release() override; - HRESULT __stdcall TestCooperativeLevel() override; - UINT __stdcall GetAvailableTextureMem() override; - HRESULT __stdcall EvictManagedResources() override; - HRESULT __stdcall GetDirect3D(IDirect3D9** ppD3D9) override; - HRESULT __stdcall GetDeviceCaps(D3DCAPS9* pCaps) override; - HRESULT __stdcall GetDisplayMode(UINT iSwapChain, D3DDISPLAYMODE* pMode) override; - HRESULT __stdcall GetCreationParameters(D3DDEVICE_CREATION_PARAMETERS *pParameters) override; - HRESULT __stdcall SetCursorProperties(UINT XHotSpot, UINT YHotSpot, IDirect3DSurface9* pCursorBitmap) override; - void __stdcall SetCursorPosition(int X, int Y, DWORD Flags) override; - BOOL __stdcall ShowCursor(BOOL bShow) override; - HRESULT __stdcall CreateAdditionalSwapChain(D3DPRESENT_PARAMETERS* pPresentationParameters, IDirect3DSwapChain9** pSwapChain) override; - HRESULT __stdcall GetSwapChain(UINT iSwapChain, IDirect3DSwapChain9** pSwapChain) override; - UINT __stdcall GetNumberOfSwapChains() override; - HRESULT __stdcall Reset(D3DPRESENT_PARAMETERS* pPresentationParameters) override; - HRESULT __stdcall Present(CONST RECT* pSourceRect, CONST RECT* pDestRect, HWND hDestWindowOverride, CONST RGNDATA* pDirtyRegion) override; - HRESULT __stdcall GetBackBuffer(UINT iSwapChain, UINT iBackBuffer, D3DBACKBUFFER_TYPE Type, IDirect3DSurface9** ppBackBuffer) override; - HRESULT __stdcall GetRasterStatus(UINT iSwapChain, D3DRASTER_STATUS* pRasterStatus) override; - HRESULT __stdcall SetDialogBoxMode(BOOL bEnableDialogs) override; - void __stdcall SetGammaRamp(UINT iSwapChain, DWORD Flags, CONST D3DGAMMARAMP* pRamp) override; - void __stdcall GetGammaRamp(UINT iSwapChain, D3DGAMMARAMP* pRamp) override; - HRESULT __stdcall CreateTexture(UINT Width, UINT Height, UINT Levels, DWORD Usage, D3DFORMAT Format, D3DPOOL Pool, IDirect3DTexture9** ppTexture, HANDLE* pSharedHandle) override; - HRESULT __stdcall CreateVolumeTexture(UINT Width, UINT Height, UINT Depth, UINT Levels, DWORD Usage, D3DFORMAT Format, D3DPOOL Pool, IDirect3DVolumeTexture9** ppVolumeTexture, HANDLE* pSharedHandle) override; - HRESULT __stdcall CreateCubeTexture(UINT EdgeLength, UINT Levels, DWORD Usage, D3DFORMAT Format, D3DPOOL Pool, IDirect3DCubeTexture9** ppCubeTexture, HANDLE* pSharedHandle) override; - HRESULT __stdcall CreateVertexBuffer(UINT Length, DWORD Usage, DWORD FVF, D3DPOOL Pool, IDirect3DVertexBuffer9** ppVertexBuffer, HANDLE* pSharedHandle) override; - HRESULT __stdcall CreateIndexBuffer(UINT Length, DWORD Usage, D3DFORMAT Format, D3DPOOL Pool, IDirect3DIndexBuffer9** ppIndexBuffer, HANDLE* pSharedHandle) override; - HRESULT __stdcall CreateRenderTarget(UINT Width, UINT Height, D3DFORMAT Format, D3DMULTISAMPLE_TYPE MultiSample, DWORD MultisampleQuality, BOOL Lockable, IDirect3DSurface9** ppSurface, HANDLE* pSharedHandle) override; - HRESULT __stdcall CreateDepthStencilSurface(UINT Width, UINT Height, D3DFORMAT Format, D3DMULTISAMPLE_TYPE MultiSample, DWORD MultisampleQuality, BOOL Discard, IDirect3DSurface9** ppSurface, HANDLE* pSharedHandle) override; - HRESULT __stdcall UpdateSurface(IDirect3DSurface9* pSourceSurface, CONST RECT* pSourceRect, IDirect3DSurface9* pDestinationSurface, CONST POINT* pDestPoint) override; - HRESULT __stdcall UpdateTexture(IDirect3DBaseTexture9* pSourceTexture, IDirect3DBaseTexture9* pDestinationTexture) override; - HRESULT __stdcall GetRenderTargetData(IDirect3DSurface9* pRenderTarget, IDirect3DSurface9* pDestSurface) override; - HRESULT __stdcall GetFrontBufferData(UINT iSwapChain, IDirect3DSurface9* pDestSurface) override; - HRESULT __stdcall StretchRect(IDirect3DSurface9* pSourceSurface, CONST RECT* pSourceRect, IDirect3DSurface9* pDestSurface, CONST RECT* pDestRect, D3DTEXTUREFILTERTYPE Filter) override; - HRESULT __stdcall ColorFill(IDirect3DSurface9* pSurface, CONST RECT* pRect, D3DCOLOR color) override; - HRESULT __stdcall CreateOffscreenPlainSurface(UINT Width, UINT Height, D3DFORMAT Format, D3DPOOL Pool, IDirect3DSurface9** ppSurface, HANDLE* pSharedHandle) override; - HRESULT __stdcall SetRenderTarget(DWORD RenderTargetIndex, IDirect3DSurface9* pRenderTarget) override; - HRESULT __stdcall GetRenderTarget(DWORD RenderTargetIndex, IDirect3DSurface9** ppRenderTarget) override; - HRESULT __stdcall SetDepthStencilSurface(IDirect3DSurface9* pNewZStencil) override; - HRESULT __stdcall GetDepthStencilSurface(IDirect3DSurface9** ppZStencilSurface) override; - HRESULT __stdcall BeginScene() override; - HRESULT __stdcall EndScene() override; - HRESULT __stdcall Clear(DWORD Count, CONST D3DRECT* pRects, DWORD Flags, D3DCOLOR Color, float Z, DWORD Stencil) override; - HRESULT __stdcall SetTransform(D3DTRANSFORMSTATETYPE State, CONST D3DMATRIX* pMatrix) override; - HRESULT __stdcall GetTransform(D3DTRANSFORMSTATETYPE State, D3DMATRIX* pMatrix) override; - HRESULT __stdcall MultiplyTransform(D3DTRANSFORMSTATETYPE State, CONST D3DMATRIX* pMatrix) override; - HRESULT __stdcall SetViewport(CONST D3DVIEWPORT9* pViewport) override; - HRESULT __stdcall GetViewport(D3DVIEWPORT9* pViewport) override; - HRESULT __stdcall SetMaterial(CONST D3DMATERIAL9* pMaterial) override; - HRESULT __stdcall GetMaterial(D3DMATERIAL9* pMaterial) override; - HRESULT __stdcall SetLight(DWORD Index, CONST D3DLIGHT9* pLight) override; - HRESULT __stdcall GetLight(DWORD Index, D3DLIGHT9* pLight) override; - HRESULT __stdcall LightEnable(DWORD Index, BOOL Enable) override; - HRESULT __stdcall GetLightEnable(DWORD Index, BOOL* pEnable) override; - HRESULT __stdcall SetClipPlane(DWORD Index, CONST float* pPlane) override; - HRESULT __stdcall GetClipPlane(DWORD Index, float* pPlane) override; - HRESULT __stdcall SetRenderState(D3DRENDERSTATETYPE State, DWORD Value) override; - HRESULT __stdcall GetRenderState(D3DRENDERSTATETYPE State, DWORD* pValue) override; - HRESULT __stdcall CreateStateBlock(D3DSTATEBLOCKTYPE Type, IDirect3DStateBlock9** ppSB) override; - HRESULT __stdcall BeginStateBlock() override; - HRESULT __stdcall EndStateBlock(IDirect3DStateBlock9** ppSB) override; - HRESULT __stdcall SetClipStatus(CONST D3DCLIPSTATUS9* pClipStatus) override; - HRESULT __stdcall GetClipStatus(D3DCLIPSTATUS9* pClipStatus) override; - HRESULT __stdcall GetTexture(DWORD Stage, IDirect3DBaseTexture9** ppTexture) override; - HRESULT __stdcall SetTexture(DWORD Stage, IDirect3DBaseTexture9* pTexture) override; - HRESULT __stdcall GetTextureStageState(DWORD Stage, D3DTEXTURESTAGESTATETYPE Type, DWORD* pValue) override; - HRESULT __stdcall SetTextureStageState(DWORD Stage, D3DTEXTURESTAGESTATETYPE Type, DWORD Value) override; - HRESULT __stdcall GetSamplerState(DWORD Sampler, D3DSAMPLERSTATETYPE Type, DWORD* pValue) override; - HRESULT __stdcall SetSamplerState(DWORD Sampler, D3DSAMPLERSTATETYPE Type, DWORD Value) override; - HRESULT __stdcall ValidateDevice(DWORD* pNumPasses) override; - HRESULT __stdcall SetPaletteEntries(UINT PaletteNumber, CONST PALETTEENTRY* pEntries) override; - HRESULT __stdcall GetPaletteEntries(UINT PaletteNumber, PALETTEENTRY* pEntries) override; - HRESULT __stdcall SetCurrentTexturePalette(UINT PaletteNumber) override; - HRESULT __stdcall GetCurrentTexturePalette(UINT *PaletteNumber) override; - HRESULT __stdcall SetScissorRect(CONST RECT* pRect) override; - HRESULT __stdcall GetScissorRect(RECT* pRect) override; - HRESULT __stdcall SetSoftwareVertexProcessing(BOOL bSoftware) override; - BOOL __stdcall GetSoftwareVertexProcessing() override; - HRESULT __stdcall SetNPatchMode(float nSegments) override; - float __stdcall GetNPatchMode() override; - HRESULT __stdcall DrawPrimitive(D3DPRIMITIVETYPE PrimitiveType, UINT StartVertex, UINT PrimitiveCount) override; - HRESULT __stdcall DrawIndexedPrimitive(D3DPRIMITIVETYPE PrimitiveType, INT BaseVertexIndex, UINT MinVertexIndex, UINT NumVertices, UINT startIndex, UINT primCount) override; - HRESULT __stdcall DrawPrimitiveUP(D3DPRIMITIVETYPE PrimitiveType, UINT PrimitiveCount, CONST void* pVertexStreamZeroData, UINT VertexStreamZeroStride) override; - HRESULT __stdcall DrawIndexedPrimitiveUP(D3DPRIMITIVETYPE PrimitiveType, UINT MinVertexIndex, UINT NumVertices, UINT PrimitiveCount, CONST void* pIndexData, D3DFORMAT IndexDataFormat, CONST void* pVertexStreamZeroData, UINT VertexStreamZeroStride) override; - HRESULT __stdcall ProcessVertices(UINT SrcStartIndex, UINT DestIndex, UINT VertexCount, IDirect3DVertexBuffer9* pDestBuffer, IDirect3DVertexDeclaration9* pVertexDecl, DWORD Flags) override; - HRESULT __stdcall CreateVertexDeclaration(CONST D3DVERTEXELEMENT9* pVertexElements, IDirect3DVertexDeclaration9** ppDecl) override; - HRESULT __stdcall SetVertexDeclaration(IDirect3DVertexDeclaration9* pDecl) override; - HRESULT __stdcall GetVertexDeclaration(IDirect3DVertexDeclaration9** ppDecl) override; - HRESULT __stdcall SetFVF(DWORD FVF) override; - HRESULT __stdcall GetFVF(DWORD* pFVF) override; - HRESULT __stdcall CreateVertexShader(CONST DWORD* pFunction, IDirect3DVertexShader9** ppShader) override; - HRESULT __stdcall SetVertexShader(IDirect3DVertexShader9* pShader) override; - HRESULT __stdcall GetVertexShader(IDirect3DVertexShader9** ppShader) override; - HRESULT __stdcall SetVertexShaderConstantF(UINT StartRegister, CONST float* pConstantData, UINT Vector4fCount) override; - HRESULT __stdcall GetVertexShaderConstantF(UINT StartRegister, float* pConstantData, UINT Vector4fCount) override; - HRESULT __stdcall SetVertexShaderConstantI(UINT StartRegister, CONST int* pConstantData, UINT Vector4iCount) override; - HRESULT __stdcall GetVertexShaderConstantI(UINT StartRegister, int* pConstantData, UINT Vector4iCount) override; - HRESULT __stdcall SetVertexShaderConstantB(UINT StartRegister, CONST BOOL* pConstantData, UINT BoolCount) override; - HRESULT __stdcall GetVertexShaderConstantB(UINT StartRegister, BOOL* pConstantData, UINT BoolCount) override; - HRESULT __stdcall SetStreamSource(UINT StreamNumber, IDirect3DVertexBuffer9* pStreamData, UINT OffsetInBytes, UINT Stride) override; - HRESULT __stdcall GetStreamSource(UINT StreamNumber, IDirect3DVertexBuffer9** ppStreamData, UINT* OffsetInBytes, UINT* pStride) override; - HRESULT __stdcall SetStreamSourceFreq(UINT StreamNumber, UINT Divider) override; - HRESULT __stdcall GetStreamSourceFreq(UINT StreamNumber, UINT* Divider) override; - HRESULT __stdcall SetIndices(IDirect3DIndexBuffer9* pIndexData) override; - HRESULT __stdcall GetIndices(IDirect3DIndexBuffer9** ppIndexData) override; - HRESULT __stdcall CreatePixelShader(CONST DWORD* pFunction, IDirect3DPixelShader9** ppShader) override; - HRESULT __stdcall SetPixelShader(IDirect3DPixelShader9* pShader) override; - HRESULT __stdcall GetPixelShader(IDirect3DPixelShader9** ppShader) override; - HRESULT __stdcall SetPixelShaderConstantF(UINT StartRegister, CONST float* pConstantData, UINT Vector4fCount) override; - HRESULT __stdcall GetPixelShaderConstantF(UINT StartRegister, float* pConstantData, UINT Vector4fCount) override; - HRESULT __stdcall SetPixelShaderConstantI(UINT StartRegister, CONST int* pConstantData, UINT Vector4iCount) override; - HRESULT __stdcall GetPixelShaderConstantI(UINT StartRegister, int* pConstantData, UINT Vector4iCount) override; - HRESULT __stdcall SetPixelShaderConstantB(UINT StartRegister, CONST BOOL* pConstantData, UINT BoolCount) override; - HRESULT __stdcall GetPixelShaderConstantB(UINT StartRegister, BOOL* pConstantData, UINT BoolCount) override; - HRESULT __stdcall DrawRectPatch(UINT Handle, CONST float* pNumSegs, CONST D3DRECTPATCH_INFO* pRectPatchInfo) override; - HRESULT __stdcall DrawTriPatch(UINT Handle, CONST float* pNumSegs, CONST D3DTRIPATCH_INFO* pTriPatchInfo) override; - HRESULT __stdcall DeletePatch(UINT Handle) override; - HRESULT __stdcall CreateQuery(D3DQUERYTYPE Type, IDirect3DQuery9** ppQuery) override; + HRESULT WINAPI QueryInterface(REFIID riid, void** ppvObj) override; + ULONG WINAPI AddRef() override; + ULONG WINAPI Release() override; + HRESULT WINAPI TestCooperativeLevel() override; + UINT WINAPI GetAvailableTextureMem() override; + HRESULT WINAPI EvictManagedResources() override; + HRESULT WINAPI GetDirect3D(IDirect3D9** ppD3D9) override; + HRESULT WINAPI GetDeviceCaps(D3DCAPS9* pCaps) override; + HRESULT WINAPI GetDisplayMode(UINT iSwapChain, D3DDISPLAYMODE* pMode) override; + HRESULT WINAPI GetCreationParameters(D3DDEVICE_CREATION_PARAMETERS *pParameters) override; + HRESULT WINAPI SetCursorProperties(UINT XHotSpot, UINT YHotSpot, IDirect3DSurface9* pCursorBitmap) override; + void WINAPI SetCursorPosition(int X, int Y, DWORD Flags) override; + BOOL WINAPI ShowCursor(BOOL bShow) override; + HRESULT WINAPI CreateAdditionalSwapChain(D3DPRESENT_PARAMETERS* pPresentationParameters, IDirect3DSwapChain9** pSwapChain) override; + HRESULT WINAPI GetSwapChain(UINT iSwapChain, IDirect3DSwapChain9** pSwapChain) override; + UINT WINAPI GetNumberOfSwapChains() override; + HRESULT WINAPI Reset(D3DPRESENT_PARAMETERS* pPresentationParameters) override; + HRESULT WINAPI Present(CONST RECT* pSourceRect, CONST RECT* pDestRect, HWND hDestWindowOverride, CONST RGNDATA* pDirtyRegion) override; + HRESULT WINAPI GetBackBuffer(UINT iSwapChain, UINT iBackBuffer, D3DBACKBUFFER_TYPE Type, IDirect3DSurface9** ppBackBuffer) override; + HRESULT WINAPI GetRasterStatus(UINT iSwapChain, D3DRASTER_STATUS* pRasterStatus) override; + HRESULT WINAPI SetDialogBoxMode(BOOL bEnableDialogs) override; + void WINAPI SetGammaRamp(UINT iSwapChain, DWORD Flags, CONST D3DGAMMARAMP* pRamp) override; + void WINAPI GetGammaRamp(UINT iSwapChain, D3DGAMMARAMP* pRamp) override; + HRESULT WINAPI CreateTexture(UINT Width, UINT Height, UINT Levels, DWORD Usage, D3DFORMAT Format, D3DPOOL Pool, IDirect3DTexture9** ppTexture, HANDLE* pSharedHandle) override; + HRESULT WINAPI CreateVolumeTexture(UINT Width, UINT Height, UINT Depth, UINT Levels, DWORD Usage, D3DFORMAT Format, D3DPOOL Pool, IDirect3DVolumeTexture9** ppVolumeTexture, HANDLE* pSharedHandle) override; + HRESULT WINAPI CreateCubeTexture(UINT EdgeLength, UINT Levels, DWORD Usage, D3DFORMAT Format, D3DPOOL Pool, IDirect3DCubeTexture9** ppCubeTexture, HANDLE* pSharedHandle) override; + HRESULT WINAPI CreateVertexBuffer(UINT Length, DWORD Usage, DWORD FVF, D3DPOOL Pool, IDirect3DVertexBuffer9** ppVertexBuffer, HANDLE* pSharedHandle) override; + HRESULT WINAPI CreateIndexBuffer(UINT Length, DWORD Usage, D3DFORMAT Format, D3DPOOL Pool, IDirect3DIndexBuffer9** ppIndexBuffer, HANDLE* pSharedHandle) override; + HRESULT WINAPI CreateRenderTarget(UINT Width, UINT Height, D3DFORMAT Format, D3DMULTISAMPLE_TYPE MultiSample, DWORD MultisampleQuality, BOOL Lockable, IDirect3DSurface9** ppSurface, HANDLE* pSharedHandle) override; + HRESULT WINAPI CreateDepthStencilSurface(UINT Width, UINT Height, D3DFORMAT Format, D3DMULTISAMPLE_TYPE MultiSample, DWORD MultisampleQuality, BOOL Discard, IDirect3DSurface9** ppSurface, HANDLE* pSharedHandle) override; + HRESULT WINAPI UpdateSurface(IDirect3DSurface9* pSourceSurface, CONST RECT* pSourceRect, IDirect3DSurface9* pDestinationSurface, CONST POINT* pDestPoint) override; + HRESULT WINAPI UpdateTexture(IDirect3DBaseTexture9* pSourceTexture, IDirect3DBaseTexture9* pDestinationTexture) override; + HRESULT WINAPI GetRenderTargetData(IDirect3DSurface9* pRenderTarget, IDirect3DSurface9* pDestSurface) override; + HRESULT WINAPI GetFrontBufferData(UINT iSwapChain, IDirect3DSurface9* pDestSurface) override; + HRESULT WINAPI StretchRect(IDirect3DSurface9* pSourceSurface, CONST RECT* pSourceRect, IDirect3DSurface9* pDestSurface, CONST RECT* pDestRect, D3DTEXTUREFILTERTYPE Filter) override; + HRESULT WINAPI ColorFill(IDirect3DSurface9* pSurface, CONST RECT* pRect, D3DCOLOR color) override; + HRESULT WINAPI CreateOffscreenPlainSurface(UINT Width, UINT Height, D3DFORMAT Format, D3DPOOL Pool, IDirect3DSurface9** ppSurface, HANDLE* pSharedHandle) override; + HRESULT WINAPI SetRenderTarget(DWORD RenderTargetIndex, IDirect3DSurface9* pRenderTarget) override; + HRESULT WINAPI GetRenderTarget(DWORD RenderTargetIndex, IDirect3DSurface9** ppRenderTarget) override; + HRESULT WINAPI SetDepthStencilSurface(IDirect3DSurface9* pNewZStencil) override; + HRESULT WINAPI GetDepthStencilSurface(IDirect3DSurface9** ppZStencilSurface) override; + HRESULT WINAPI BeginScene() override; + HRESULT WINAPI EndScene() override; + HRESULT WINAPI Clear(DWORD Count, CONST D3DRECT* pRects, DWORD Flags, D3DCOLOR Color, float Z, DWORD Stencil) override; + HRESULT WINAPI SetTransform(D3DTRANSFORMSTATETYPE State, CONST D3DMATRIX* pMatrix) override; + HRESULT WINAPI GetTransform(D3DTRANSFORMSTATETYPE State, D3DMATRIX* pMatrix) override; + HRESULT WINAPI MultiplyTransform(D3DTRANSFORMSTATETYPE State, CONST D3DMATRIX* pMatrix) override; + HRESULT WINAPI SetViewport(CONST D3DVIEWPORT9* pViewport) override; + HRESULT WINAPI GetViewport(D3DVIEWPORT9* pViewport) override; + HRESULT WINAPI SetMaterial(CONST D3DMATERIAL9* pMaterial) override; + HRESULT WINAPI GetMaterial(D3DMATERIAL9* pMaterial) override; + HRESULT WINAPI SetLight(DWORD Index, CONST D3DLIGHT9* pLight) override; + HRESULT WINAPI GetLight(DWORD Index, D3DLIGHT9* pLight) override; + HRESULT WINAPI LightEnable(DWORD Index, BOOL Enable) override; + HRESULT WINAPI GetLightEnable(DWORD Index, BOOL* pEnable) override; + HRESULT WINAPI SetClipPlane(DWORD Index, CONST float* pPlane) override; + HRESULT WINAPI GetClipPlane(DWORD Index, float* pPlane) override; + HRESULT WINAPI SetRenderState(D3DRENDERSTATETYPE State, DWORD Value) override; + HRESULT WINAPI GetRenderState(D3DRENDERSTATETYPE State, DWORD* pValue) override; + HRESULT WINAPI CreateStateBlock(D3DSTATEBLOCKTYPE Type, IDirect3DStateBlock9** ppSB) override; + HRESULT WINAPI BeginStateBlock() override; + HRESULT WINAPI EndStateBlock(IDirect3DStateBlock9** ppSB) override; + HRESULT WINAPI SetClipStatus(CONST D3DCLIPSTATUS9* pClipStatus) override; + HRESULT WINAPI GetClipStatus(D3DCLIPSTATUS9* pClipStatus) override; + HRESULT WINAPI GetTexture(DWORD Stage, IDirect3DBaseTexture9** ppTexture) override; + HRESULT WINAPI SetTexture(DWORD Stage, IDirect3DBaseTexture9* pTexture) override; + HRESULT WINAPI GetTextureStageState(DWORD Stage, D3DTEXTURESTAGESTATETYPE Type, DWORD* pValue) override; + HRESULT WINAPI SetTextureStageState(DWORD Stage, D3DTEXTURESTAGESTATETYPE Type, DWORD Value) override; + HRESULT WINAPI GetSamplerState(DWORD Sampler, D3DSAMPLERSTATETYPE Type, DWORD* pValue) override; + HRESULT WINAPI SetSamplerState(DWORD Sampler, D3DSAMPLERSTATETYPE Type, DWORD Value) override; + HRESULT WINAPI ValidateDevice(DWORD* pNumPasses) override; + HRESULT WINAPI SetPaletteEntries(UINT PaletteNumber, CONST PALETTEENTRY* pEntries) override; + HRESULT WINAPI GetPaletteEntries(UINT PaletteNumber, PALETTEENTRY* pEntries) override; + HRESULT WINAPI SetCurrentTexturePalette(UINT PaletteNumber) override; + HRESULT WINAPI GetCurrentTexturePalette(UINT *PaletteNumber) override; + HRESULT WINAPI SetScissorRect(CONST RECT* pRect) override; + HRESULT WINAPI GetScissorRect(RECT* pRect) override; + HRESULT WINAPI SetSoftwareVertexProcessing(BOOL bSoftware) override; + BOOL WINAPI GetSoftwareVertexProcessing() override; + HRESULT WINAPI SetNPatchMode(float nSegments) override; + float WINAPI GetNPatchMode() override; + HRESULT WINAPI DrawPrimitive(D3DPRIMITIVETYPE PrimitiveType, UINT StartVertex, UINT PrimitiveCount) override; + HRESULT WINAPI DrawIndexedPrimitive(D3DPRIMITIVETYPE PrimitiveType, INT BaseVertexIndex, UINT MinVertexIndex, UINT NumVertices, UINT startIndex, UINT primCount) override; + HRESULT WINAPI DrawPrimitiveUP(D3DPRIMITIVETYPE PrimitiveType, UINT PrimitiveCount, CONST void* pVertexStreamZeroData, UINT VertexStreamZeroStride) override; + HRESULT WINAPI DrawIndexedPrimitiveUP(D3DPRIMITIVETYPE PrimitiveType, UINT MinVertexIndex, UINT NumVertices, UINT PrimitiveCount, CONST void* pIndexData, D3DFORMAT IndexDataFormat, CONST void* pVertexStreamZeroData, UINT VertexStreamZeroStride) override; + HRESULT WINAPI ProcessVertices(UINT SrcStartIndex, UINT DestIndex, UINT VertexCount, IDirect3DVertexBuffer9* pDestBuffer, IDirect3DVertexDeclaration9* pVertexDecl, DWORD Flags) override; + HRESULT WINAPI CreateVertexDeclaration(CONST D3DVERTEXELEMENT9* pVertexElements, IDirect3DVertexDeclaration9** ppDecl) override; + HRESULT WINAPI SetVertexDeclaration(IDirect3DVertexDeclaration9* pDecl) override; + HRESULT WINAPI GetVertexDeclaration(IDirect3DVertexDeclaration9** ppDecl) override; + HRESULT WINAPI SetFVF(DWORD FVF) override; + HRESULT WINAPI GetFVF(DWORD* pFVF) override; + HRESULT WINAPI CreateVertexShader(CONST DWORD* pFunction, IDirect3DVertexShader9** ppShader) override; + HRESULT WINAPI SetVertexShader(IDirect3DVertexShader9* pShader) override; + HRESULT WINAPI GetVertexShader(IDirect3DVertexShader9** ppShader) override; + HRESULT WINAPI SetVertexShaderConstantF(UINT StartRegister, CONST float* pConstantData, UINT Vector4fCount) override; + HRESULT WINAPI GetVertexShaderConstantF(UINT StartRegister, float* pConstantData, UINT Vector4fCount) override; + HRESULT WINAPI SetVertexShaderConstantI(UINT StartRegister, CONST int* pConstantData, UINT Vector4iCount) override; + HRESULT WINAPI GetVertexShaderConstantI(UINT StartRegister, int* pConstantData, UINT Vector4iCount) override; + HRESULT WINAPI SetVertexShaderConstantB(UINT StartRegister, CONST BOOL* pConstantData, UINT BoolCount) override; + HRESULT WINAPI GetVertexShaderConstantB(UINT StartRegister, BOOL* pConstantData, UINT BoolCount) override; + HRESULT WINAPI SetStreamSource(UINT StreamNumber, IDirect3DVertexBuffer9* pStreamData, UINT OffsetInBytes, UINT Stride) override; + HRESULT WINAPI GetStreamSource(UINT StreamNumber, IDirect3DVertexBuffer9** ppStreamData, UINT* OffsetInBytes, UINT* pStride) override; + HRESULT WINAPI SetStreamSourceFreq(UINT StreamNumber, UINT Divider) override; + HRESULT WINAPI GetStreamSourceFreq(UINT StreamNumber, UINT* Divider) override; + HRESULT WINAPI SetIndices(IDirect3DIndexBuffer9* pIndexData) override; + HRESULT WINAPI GetIndices(IDirect3DIndexBuffer9** ppIndexData) override; + HRESULT WINAPI CreatePixelShader(CONST DWORD* pFunction, IDirect3DPixelShader9** ppShader) override; + HRESULT WINAPI SetPixelShader(IDirect3DPixelShader9* pShader) override; + HRESULT WINAPI GetPixelShader(IDirect3DPixelShader9** ppShader) override; + HRESULT WINAPI SetPixelShaderConstantF(UINT StartRegister, CONST float* pConstantData, UINT Vector4fCount) override; + HRESULT WINAPI GetPixelShaderConstantF(UINT StartRegister, float* pConstantData, UINT Vector4fCount) override; + HRESULT WINAPI SetPixelShaderConstantI(UINT StartRegister, CONST int* pConstantData, UINT Vector4iCount) override; + HRESULT WINAPI GetPixelShaderConstantI(UINT StartRegister, int* pConstantData, UINT Vector4iCount) override; + HRESULT WINAPI SetPixelShaderConstantB(UINT StartRegister, CONST BOOL* pConstantData, UINT BoolCount) override; + HRESULT WINAPI GetPixelShaderConstantB(UINT StartRegister, BOOL* pConstantData, UINT BoolCount) override; + HRESULT WINAPI DrawRectPatch(UINT Handle, CONST float* pNumSegs, CONST D3DRECTPATCH_INFO* pRectPatchInfo) override; + HRESULT WINAPI DrawTriPatch(UINT Handle, CONST float* pNumSegs, CONST D3DTRIPATCH_INFO* pTriPatchInfo) override; + HRESULT WINAPI DeletePatch(UINT Handle) override; + HRESULT WINAPI CreateQuery(D3DQUERYTYPE Type, IDirect3DQuery9** ppQuery) override; private: IDirect3DDevice9 *m_pIDirect3DDevice9; @@ -145,28 +144,30 @@ namespace Components D3D9(IDirect3D9Ex *pOriginal) : m_pIDirect3D9(pOriginal) {}; virtual ~D3D9() {}; - HRESULT __stdcall QueryInterface(REFIID riid, void** ppvObj) override; - ULONG __stdcall AddRef() override; - ULONG __stdcall Release() override; - HRESULT __stdcall RegisterSoftwareDevice(void* pInitializeFunction) override; - UINT __stdcall GetAdapterCount() override; - HRESULT __stdcall GetAdapterIdentifier(UINT Adapter, DWORD Flags, D3DADAPTER_IDENTIFIER9* pIdentifier) override; - UINT __stdcall GetAdapterModeCount(UINT Adapter, D3DFORMAT Format) override; - HRESULT __stdcall EnumAdapterModes(UINT Adapter, D3DFORMAT Format, UINT Mode, D3DDISPLAYMODE* pMode) override; - HRESULT __stdcall GetAdapterDisplayMode(UINT Adapter, D3DDISPLAYMODE* pMode) override; - HRESULT __stdcall CheckDeviceType(UINT iAdapter, D3DDEVTYPE DevType, D3DFORMAT DisplayFormat, D3DFORMAT BackBufferFormat, BOOL bWindowed) override; - HRESULT __stdcall CheckDeviceFormat(UINT Adapter, D3DDEVTYPE DeviceType, D3DFORMAT AdapterFormat, DWORD Usage, D3DRESOURCETYPE RType, D3DFORMAT CheckFormat) override; - HRESULT __stdcall CheckDeviceMultiSampleType(UINT Adapter, D3DDEVTYPE DeviceType, D3DFORMAT SurfaceFormat, BOOL Windowed, D3DMULTISAMPLE_TYPE MultiSampleType, DWORD* pQualityLevels) override; - HRESULT __stdcall CheckDepthStencilMatch(UINT Adapter, D3DDEVTYPE DeviceType, D3DFORMAT AdapterFormat, D3DFORMAT RenderTargetFormat, D3DFORMAT DepthStencilFormat) override; - HRESULT __stdcall CheckDeviceFormatConversion(UINT Adapter, D3DDEVTYPE DeviceType, D3DFORMAT SourceFormat, D3DFORMAT TargetFormat) override; - HRESULT __stdcall GetDeviceCaps(UINT Adapter, D3DDEVTYPE DeviceType, D3DCAPS9* pCaps) override; - HMONITOR __stdcall GetAdapterMonitor(UINT Adapter) override; - HRESULT __stdcall CreateDevice(UINT Adapter, D3DDEVTYPE DeviceType, HWND hFocusWindow, DWORD BehaviorFlags, D3DPRESENT_PARAMETERS* pPresentationParameters, IDirect3DDevice9** ppReturnedDeviceInterface) override; + HRESULT WINAPI QueryInterface(REFIID riid, void** ppvObj) override; + ULONG WINAPI AddRef() override; + ULONG WINAPI Release() override; + HRESULT WINAPI RegisterSoftwareDevice(void* pInitializeFunction) override; + UINT WINAPI GetAdapterCount() override; + HRESULT WINAPI GetAdapterIdentifier(UINT Adapter, DWORD Flags, D3DADAPTER_IDENTIFIER9* pIdentifier) override; + UINT WINAPI GetAdapterModeCount(UINT Adapter, D3DFORMAT Format) override; + HRESULT WINAPI EnumAdapterModes(UINT Adapter, D3DFORMAT Format, UINT Mode, D3DDISPLAYMODE* pMode) override; + HRESULT WINAPI GetAdapterDisplayMode(UINT Adapter, D3DDISPLAYMODE* pMode) override; + HRESULT WINAPI CheckDeviceType(UINT iAdapter, D3DDEVTYPE DevType, D3DFORMAT DisplayFormat, D3DFORMAT BackBufferFormat, BOOL bWindowed) override; + HRESULT WINAPI CheckDeviceFormat(UINT Adapter, D3DDEVTYPE DeviceType, D3DFORMAT AdapterFormat, DWORD Usage, D3DRESOURCETYPE RType, D3DFORMAT CheckFormat) override; + HRESULT WINAPI CheckDeviceMultiSampleType(UINT Adapter, D3DDEVTYPE DeviceType, D3DFORMAT SurfaceFormat, BOOL Windowed, D3DMULTISAMPLE_TYPE MultiSampleType, DWORD* pQualityLevels) override; + HRESULT WINAPI CheckDepthStencilMatch(UINT Adapter, D3DDEVTYPE DeviceType, D3DFORMAT AdapterFormat, D3DFORMAT RenderTargetFormat, D3DFORMAT DepthStencilFormat) override; + HRESULT WINAPI CheckDeviceFormatConversion(UINT Adapter, D3DDEVTYPE DeviceType, D3DFORMAT SourceFormat, D3DFORMAT TargetFormat) override; + HRESULT WINAPI GetDeviceCaps(UINT Adapter, D3DDEVTYPE DeviceType, D3DCAPS9* pCaps) override; + HMONITOR WINAPI GetAdapterMonitor(UINT Adapter) override; + HRESULT WINAPI CreateDevice(UINT Adapter, D3DDEVTYPE DeviceType, HWND hFocusWindow, DWORD BehaviorFlags, D3DPRESENT_PARAMETERS* pPresentationParameters, IDirect3DDevice9** ppReturnedDeviceInterface) override; private: IDirect3D9 *m_pIDirect3D9; }; - static IDirect3D9* __stdcall Direct3DCreate9Stub(UINT sdk); + static Dvar::Var RUseD3D9Ex; + + static IDirect3D9* CALLBACK Direct3DCreate9Stub(UINT sdk); }; } diff --git a/src/Components/Modules/Debug.cpp b/src/Components/Modules/Debug.cpp index 9e742f22..5b8bcfcd 100644 --- a/src/Components/Modules/Debug.cpp +++ b/src/Components/Modules/Debug.cpp @@ -102,8 +102,6 @@ namespace Components const char Debug::StrTemplate[] = "%s: %s All those moments will be lost in time, like tears in rain."; - const float Debug::ColorWhite[] = {1.0f, 1.0f, 1.0f, 1.0f}; - std::string Debug::BuildPMFlagsString(const Game::playerState_s* ps) { std::string result; @@ -163,19 +161,19 @@ namespace Components auto* const font2 = Game::UI_GetFontHandle(scrPlace, 6, MY_SCALE2); Game::UI_DrawText(scrPlace, "Client View of Flags", maxChars, font2, -60.0f, 0, 1, 1, - MY_SCALE2, ColorWhite, 1); + MY_SCALE2, TextRenderer::WHITE_COLOR, 1); const auto pmf = BuildPMFlagsString(&cgameGlob->predictedPlayerState); - Game::UI_DrawText(scrPlace, pmf.data(), maxChars, font1, 30.0f, MY_Y, 1, 1, MY_SCALE_2, ColorWhite, 3); + Game::UI_DrawText(scrPlace, pmf.data(), maxChars, font1, 30.0f, MY_Y, 1, 1, MY_SCALE_2, TextRenderer::WHITE_COLOR, 3); const auto pof = BuildPOFlagsString(&cgameGlob->predictedPlayerState); - Game::UI_DrawText(scrPlace, pof.data(), maxChars, font1, 350.0f, MY_Y, 1, 1, MY_SCALE_2, ColorWhite, 3); + Game::UI_DrawText(scrPlace, pof.data(), maxChars, font1, 350.0f, MY_Y, 1, 1, MY_SCALE_2, TextRenderer::WHITE_COLOR, 3); const auto plf = BuildPLFlagsString(&cgameGlob->predictedPlayerState); - Game::UI_DrawText(scrPlace, plf.data(), maxChars, font1, 350.0f, 250.0f, 1, 1, MY_SCALE_2, ColorWhite, 3); + Game::UI_DrawText(scrPlace, plf.data(), maxChars, font1, 350.0f, 250.0f, 1, 1, MY_SCALE_2, TextRenderer::WHITE_COLOR, 3); const auto pef = BuildPEFlagsString(&cgameGlob->predictedPlayerState); - Game::UI_DrawText(scrPlace, pef.data(), maxChars, font1, 525.0f, MY_Y, 1, 1, MY_SCALE_2, ColorWhite, 3); + Game::UI_DrawText(scrPlace, pef.data(), maxChars, font1, 525.0f, MY_Y, 1, 1, MY_SCALE_2, TextRenderer::WHITE_COLOR, 3); } void Debug::CG_DrawDebugPlayerHealth(const int localClientNum) @@ -223,27 +221,33 @@ namespace Components sprintf_s(strFinal, StrTemplate, font1->fontName, StrButtons); Game::UI_FilterStringForButtonAnimation(strFinal, sizeof(strFinal)); - Game::UI_DrawText(scrPlace, strFinal, std::numeric_limits::max(), font1, MY_X, 10.0f, 1, 1, 0.4f, ColorWhite, 3); + Game::UI_DrawText(scrPlace, strFinal, std::numeric_limits::max(), font1, MY_X, 10.0f, 1, 1, 0.4f, TextRenderer::WHITE_COLOR, 3); sprintf_s(strFinal, StrTemplate, font2->fontName, StrButtons); Game::UI_FilterStringForButtonAnimation(strFinal, sizeof(strFinal)); - Game::UI_DrawText(scrPlace, strFinal, std::numeric_limits::max(), font2, MY_X, 35.0f, 1, 1, 0.4f, ColorWhite, 3); + Game::UI_DrawText(scrPlace, strFinal, std::numeric_limits::max(), font2, MY_X, 35.0f, 1, 1, 0.4f, TextRenderer::WHITE_COLOR, 3); sprintf_s(strFinal, StrTemplate, font3->fontName, StrButtons); Game::UI_FilterStringForButtonAnimation(strFinal, sizeof(strFinal)); - Game::UI_DrawText(scrPlace, strFinal, std::numeric_limits::max(), font3, MY_X, 60.0f, 1, 1, 0.4f, ColorWhite, 3); + Game::UI_DrawText(scrPlace, strFinal, std::numeric_limits::max(), font3, MY_X, 60.0f, 1, 1, 0.4f, TextRenderer::WHITE_COLOR, 3); sprintf_s(strFinal, StrTemplate, font5->fontName, StrButtons); Game::UI_FilterStringForButtonAnimation(strFinal, sizeof(strFinal)); - Game::UI_DrawText(scrPlace, strFinal, std::numeric_limits::max(), font5, MY_X, 85.0f, 1, 1, 0.4f, ColorWhite, 3); + Game::UI_DrawText(scrPlace, strFinal, std::numeric_limits::max(), font5, MY_X, 85.0f, 1, 1, 0.4f, TextRenderer::WHITE_COLOR, 3); sprintf_s(strFinal, StrTemplate, font6->fontName, StrButtons); Game::UI_FilterStringForButtonAnimation(strFinal, sizeof(strFinal)); - Game::UI_DrawText(scrPlace, strFinal, std::numeric_limits::max(), font6, MY_X, 110.0f, 1, 1, 0.4f, ColorWhite, 3); + Game::UI_DrawText(scrPlace, strFinal, std::numeric_limits::max(), font6, MY_X, 110.0f, 1, 1, 0.4f, TextRenderer::WHITE_COLOR, 3); } void Debug::CG_DrawDebugOverlays_Hk(const int localClientNum) { + assert(DebugOverlay); + if (!DebugOverlay) + { + return; + } + switch (DebugOverlay->current.integer) { case 2: @@ -269,9 +273,9 @@ namespace Components void Debug::Com_Bug_f(Command::Params* params) { - char newFileName[0x105]{}; - char to_ospath[MAX_PATH]{}; - char from_ospath[MAX_PATH]{}; + char newFileName[MAX_PATH]{}; + char to_ospath[MAX_OSPATH]{}; + char from_ospath[MAX_OSPATH]{}; const char* bug; if (!*Game::logfile) @@ -344,10 +348,8 @@ namespace Components nullptr, }; - DebugOverlay = Game::Dvar_RegisterEnum("debugOverlay", debugOverlayNames_0, 0, - Game::DVAR_NONE, "Toggles the display of various debug info."); - BugName = Game::Dvar_RegisterString("bug_name", "bug0", - Game::DVAR_CHEAT | Game::DVAR_CODINFO, "Name appended to the copied console log"); + DebugOverlay = Game::Dvar_RegisterEnum("debugOverlay", debugOverlayNames_0, 0, Game::DVAR_NONE, "Toggles the display of various debug info."); + BugName = Game::Dvar_RegisterString("bug_name", "bug0", Game::DVAR_NONE, "Name appended to the copied console log"); } const Game::dvar_t* Debug::Dvar_Register_PlayerDebugHealth(const char* name, bool value, [[maybe_unused]] std::uint16_t flags, const char* description) @@ -358,7 +360,7 @@ namespace Components Debug::Debug() { - Scheduler::Once(CL_InitDebugDvars, Scheduler::Pipeline::MAIN); + Events::OnDvarInit(CL_InitDebugDvars); // Hook end of CG_DrawDebugOverlays (This is to ensure some checks are done before our hook is executed). Utils::Hook(0x49CB0A, CG_DrawDebugOverlays_Hk, HOOK_JUMP).install()->quick(); diff --git a/src/Components/Modules/Debug.hpp b/src/Components/Modules/Debug.hpp index ac18b2c5..57691886 100644 --- a/src/Components/Modules/Debug.hpp +++ b/src/Components/Modules/Debug.hpp @@ -28,8 +28,6 @@ namespace Components static constexpr auto MY_X = -25.0f; static constexpr auto MY_Y = 20.0f; - static const float ColorWhite[]; - static std::string BuildPMFlagsString(const Game::playerState_s* ps); static std::string BuildPOFlagsString(const Game::playerState_s* ps); static std::string BuildPLFlagsString(const Game::playerState_s* ps); diff --git a/src/Components/Modules/Dedicated.cpp b/src/Components/Modules/Dedicated.cpp index 589aa997..51291f79 100644 --- a/src/Components/Modules/Dedicated.cpp +++ b/src/Components/Modules/Dedicated.cpp @@ -1,6 +1,9 @@ #include +#include + #include "CardTitles.hpp" #include "ClanTags.hpp" +#include "Party.hpp" #include "ServerCommands.hpp" namespace Components @@ -8,6 +11,7 @@ namespace Components SteamID Dedicated::PlayerGuids[18][2]; Dvar::Var Dedicated::SVLanOnly; + Dvar::Var Dedicated::SVMOTD; Dvar::Var Dedicated::COMLogFilter; bool Dedicated::IsEnabled() @@ -76,7 +80,7 @@ namespace Components __asm { pushad - call Dedicated::PostInitialization + call PostInitialization popad // Start Com_EvenLoop @@ -85,6 +89,36 @@ namespace Components } } + void Dedicated::Com_ClampMsec(const int msec) + { + if (msec > 500 && msec < 500000) + { + Game::Com_PrintWarning(Game::CON_CHANNEL_SYSTEM, "Hitch warning: %i msec frame time\n", msec); + } + } + + __declspec(naked) void Dedicated::Com_ClampMsec_Stub() + { + using namespace Game; + + __asm + { + pushad + + push ecx + call Com_ClampMsec + add esp, 0x4 + + popad + + // Game's code + mov edx, dword ptr com_sv_running + + push 0x47DDB8 + ret + } + } + void Dedicated::TransmitGuids() { std::string list = Utils::String::VA("%c", 20); @@ -96,7 +130,7 @@ namespace Components list.append(Utils::String::VA(" %llX", Game::svs_clients[i].steamID)); Utils::InfoString info(Game::svs_clients[i].userinfo); - list.append(Utils::String::VA(" %llX", strtoull(info.get("realsteamId").data(), nullptr, 16))); + list.append(Utils::String::VA(" %llX", std::strtoull(info.get("realsteamId").data(), nullptr, 16))); } else { @@ -111,10 +145,9 @@ namespace Components { Scheduler::Once([] { - const auto partyEnable = Dvar::Var("party_enable").get(); std::string mapname = (*Game::sv_mapname)->current.string; - if (!partyEnable) // Time wrapping should not occur in party servers, but yeah... + if (!Party::IsEnabled()) // Time wrapping should not occur in party servers, but yeah... { if (mapname.empty()) mapname = "mp_rust"; Command::Execute(std::format("map {}", mapname), true); @@ -127,13 +160,13 @@ namespace Components void Dedicated::Heartbeat() { // Do not send a heartbeat if sv_lanOnly is set to true - if (Dedicated::SVLanOnly.get()) + if (SVLanOnly.get()) { return; } - auto masterPort = Dvar::Var("masterPort").get(); - const auto* masterServerName = Dvar::Var("masterServerName").get(); + const auto masterPort = (*Game::com_masterPort)->current.integer; + const auto* masterServerName = (*Game::com_masterServerName)->current.string; Network::Address master(Utils::String::VA("%s:%u", masterServerName, masterPort)); @@ -141,25 +174,19 @@ namespace Components Network::SendCommand(master, "heartbeat", "IW4"); } - Game::dvar_t* Dedicated::Dvar_RegisterSVNetworkFps(const char* dvarName, int, int min, int, int, const char* description) - { - return Game::Dvar_RegisterInt(dvarName, 1000, min, 1000, Game::DVAR_NONE, description); - } - Dedicated::Dedicated() { - Dedicated::COMLogFilter = Dvar::Register("com_logFilter", true, + COMLogFilter = Dvar::Register("com_logFilter", true, Game::DVAR_LATCH, "Removes ~95% of unneeded lines from the log"); - if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled()) + if (IsEnabled() || ZoneBuilder::IsEnabled()) { // Make sure all callbacks are handled Scheduler::Loop(Steam::SteamAPI_RunCallbacks, Scheduler::Pipeline::SERVER); - Dedicated::SVLanOnly = Dvar::Register("sv_lanOnly", false, - Game::DVAR_NONE, "Don't act as node"); + SVLanOnly = Dvar::Register("sv_lanOnly", false, Game::DVAR_NONE, "Don't act as node"); - Utils::Hook(0x60BE98, Dedicated::InitDedicatedServer, HOOK_CALL).install()->quick(); + Utils::Hook(0x60BE98, InitDedicatedServer, HOOK_CALL).install()->quick(); Utils::Hook::Set(0x683370, 0xC3); // steam sometimes doesn't like the server @@ -198,9 +225,6 @@ namespace Components // isHost script call return 0 Utils::Hook::Set(0x5DEC04, 0); - // Manually register sv_network_fps - Utils::Hook(0x4D3C7B, Dedicated::Dvar_RegisterSVNetworkFps, HOOK_CALL).install()->quick(); - // r_loadForRenderer default to 0 Utils::Hook::Set(0x519DDF, 0); @@ -217,18 +241,21 @@ namespace Components Utils::Hook::Set(0x4B4D19, 0xEB); // Intercept time wrapping - Utils::Hook(0x62737D, Dedicated::TimeWrapStub, HOOK_CALL).install()->quick(); + Utils::Hook(0x62737D, TimeWrapStub, HOOK_CALL).install()->quick(); //Utils::Hook::Set(0x62735C, 50'000); // Time wrap after 50 seconds (for testing - i don't want to wait 3 weeks) if (!ZoneBuilder::IsEnabled()) { - Scheduler::Once([] + Events::OnDvarInit([] { - Dvar::Register("sv_motd", "", Game::DVAR_NONE, "A custom message of the day for servers"); - }, Scheduler::Pipeline::MAIN); + SVMOTD = Dvar::Register("sv_motd", "", Game::DVAR_NONE, "A custom message of the day for servers"); + }); // Post initialization point - Utils::Hook(0x60BFBF, Dedicated::PostInitializationStub, HOOK_JUMP).install()->quick(); + Utils::Hook(0x60BFBF, PostInitializationStub, HOOK_JUMP).install()->quick(); + + Utils::Hook(0x47DDB2, Com_ClampMsec_Stub, HOOK_JUMP).install()->quick(); // Com_Frame_Try_Block_Function + Utils::Hook::Nop(0x47DDB2 + 5, 1); // Transmit custom data Scheduler::Loop([] @@ -238,16 +265,16 @@ namespace Components }, Scheduler::Pipeline::SERVER, 10s); // Heartbeats - Scheduler::Once(Dedicated::Heartbeat, Scheduler::Pipeline::SERVER); - Scheduler::Loop(Dedicated::Heartbeat, Scheduler::Pipeline::SERVER, 2min); + Scheduler::Once(Heartbeat, Scheduler::Pipeline::SERVER); + Scheduler::Loop(Heartbeat, Scheduler::Pipeline::SERVER, 2min); } } else { - for (int i = 0; i < ARRAYSIZE(Dedicated::PlayerGuids); ++i) + for (int i = 0; i < ARRAYSIZE(PlayerGuids); ++i) { - Dedicated::PlayerGuids[i][0].bits = 0; - Dedicated::PlayerGuids[i][1].bits = 0; + PlayerGuids[i][0].bits = 0; + PlayerGuids[i][1].bits = 0; } // Intercept server commands @@ -255,12 +282,12 @@ namespace Components { for (int client = 0; client < 18; client++) { - Dedicated::PlayerGuids[client][0].bits = strtoull(params->get(2 * client + 1), nullptr, 16); - Dedicated::PlayerGuids[client][1].bits = strtoull(params->get(2 * client + 2), nullptr, 16); + PlayerGuids[client][0].bits = std::strtoull(params->get(2 * client + 1), nullptr, 16); + PlayerGuids[client][1].bits = std::strtoull(params->get(2 * client + 2), nullptr, 16); - if (Steam::Proxy::SteamFriends && Dedicated::PlayerGuids[client][1].bits != 0) + if (Steam::Proxy::SteamFriends && PlayerGuids[client][1].bits != 0) { - Steam::Proxy::SteamFriends->SetPlayedWith(Dedicated::PlayerGuids[client][1]); + Steam::Proxy::SteamFriends->SetPlayedWith(PlayerGuids[client][1]); } } @@ -270,9 +297,9 @@ namespace Components Scheduler::Loop([] { - if (Dedicated::IsRunning()) + if (IsRunning()) { - Dedicated::TransmitGuids(); + TransmitGuids(); } }, Scheduler::Pipeline::SERVER, 15s); } diff --git a/src/Components/Modules/Dedicated.hpp b/src/Components/Modules/Dedicated.hpp index 236ce7f2..1a8ff668 100644 --- a/src/Components/Modules/Dedicated.hpp +++ b/src/Components/Modules/Dedicated.hpp @@ -9,6 +9,7 @@ namespace Components static SteamID PlayerGuids[18][2]; static Dvar::Var SVLanOnly; + static Dvar::Var SVMOTD; static Dvar::Var COMLogFilter; static bool IsEnabled(); @@ -22,10 +23,11 @@ namespace Components static void PostInitialization(); static void PostInitializationStub(); + static void Com_ClampMsec(int msec); + static void Com_ClampMsec_Stub(); + static void TransmitGuids(); static void TimeWrapStub(Game::errorParm_t code, const char* message); - - static Game::dvar_t* Dvar_RegisterSVNetworkFps(const char* dvarName, int value, int min, int max, int flags, const char* description); }; } diff --git a/src/Components/Modules/Discord.cpp b/src/Components/Modules/Discord.cpp new file mode 100644 index 00000000..12889f5e --- /dev/null +++ b/src/Components/Modules/Discord.cpp @@ -0,0 +1,148 @@ +#include +#include "Discord.hpp" +#include "Party.hpp" + +#include + +namespace Components +{ + static DiscordRichPresence DiscordPresence; + + bool Discord::Initialized_; + + static unsigned int GetDiscordNonce() + { + static auto nonce = Utils::Cryptography::Rand::GenerateInt(); + return nonce; + } + + static void Ready([[maybe_unused]] const DiscordUser* request) + { + ZeroMemory(&DiscordPresence, sizeof(DiscordPresence)); + + DiscordPresence.instance = 1; + + Logger::Print("Discord: Ready\n"); + + Discord_UpdatePresence(&DiscordPresence); + } + + static void JoinGame(const char* joinSecret) + { + Game::Cbuf_AddText(0, Utils::String::VA("connect %s\n", joinSecret)); + } + + static void JoinRequest(const DiscordUser* request) + { + Logger::Debug("Discord: Join request from {} ({})\n", request->username, request->userId); + Discord_Respond(request->userId, DISCORD_REPLY_IGNORE); + } + + static void Errored(const int errorCode, const char* message) + { + Logger::Print(Game::CON_CHANNEL_ERROR, "Discord: Error ({}): {}\n", errorCode, message); + } + + void Discord::UpdateDiscord() + { + Discord_RunCallbacks(); + + if (!Game::CL_IsCgameInitialized()) + { + DiscordPresence.details = "Multiplayer"; + DiscordPresence.state = "Main Menu"; + DiscordPresence.largeImageKey = "menu_multiplayer"; + + DiscordPresence.partySize = 0; + DiscordPresence.partyMax = 0; + DiscordPresence.startTimestamp = 0; + + Discord_UpdatePresence(&DiscordPresence); + + return; + } + + const auto* map = Game::UI_GetMapDisplayName((*Game::ui_mapname)->current.string); + + const Game::StringTable* table; + Game::StringTable_GetAsset_FastFile("mp/gameTypesTable.csv", &table); + const auto row = Game::StringTable_LookupRowNumForValue(table, 0, (*Game::ui_gametype)->current.string); + if (row != -1) + { + const auto* value = Game::StringTable_GetColumnValueForRow(table, row, 1); + const auto* localize = Game::DB_FindXAssetHeader(Game::ASSET_TYPE_LOCALIZE_ENTRY, value).localize; + DiscordPresence.details = Utils::String::Format("{} on {}", localize ? localize->value : "Team Deathmatch", map); + } + else + { + DiscordPresence.details = Utils::String::Format("Team Deathmatch on {}", map); + } + + const auto* hostName = Game::cls->servername; + if (std::strcmp(hostName, "localhost") == 0) + { + DiscordPresence.state = "Private Match"; + DiscordPresence.partyPrivacy = DISCORD_PARTY_PRIVATE; + } + else + { + char hostNameBuffer[256]{}; + TextRenderer::StripColors(Party::GetHostName().data(), hostNameBuffer, sizeof(hostNameBuffer)); + TextRenderer::StripAllTextIcons(hostNameBuffer, hostNameBuffer, sizeof(hostNameBuffer)); + + DiscordPresence.state = hostNameBuffer; + DiscordPresence.partyPrivacy = DISCORD_PARTY_PUBLIC; + } + + std::hash hashFn; + const auto address = Party::Target(); + + DiscordPresence.partyId = Utils::String::VA("%zu", hashFn(address) ^ GetDiscordNonce()); + DiscordPresence.joinSecret = address.getCString(); + DiscordPresence.partySize = Game::cgArray[0].snap ? Game::cgArray[0].snap->numClients : 1; + DiscordPresence.partyMax = Party::GetMaxClients(); + + if (!DiscordPresence.startTimestamp) + { + DiscordPresence.startTimestamp = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + } + + DiscordPresence.largeImageKey = "menu_multiplayer"; + + Discord_UpdatePresence(&DiscordPresence); + } + + Discord::Discord() + { + if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled()) + { + return; + } + + DiscordEventHandlers handlers; + ZeroMemory(&handlers, sizeof(handlers)); + handlers.ready = Ready; + handlers.errored = Errored; + handlers.disconnected = Errored; + handlers.joinGame = JoinGame; + handlers.spectateGame = nullptr; + handlers.joinRequest = JoinRequest; + + Discord_Initialize("1072930169385394288", &handlers, 1, nullptr); + + Scheduler::Once(UpdateDiscord, Scheduler::Pipeline::MAIN); + Scheduler::Loop(UpdateDiscord, Scheduler::Pipeline::MAIN, 15s); + + Initialized_ = true; + } + + void Discord::preDestroy() + { + if (!Initialized_) + { + return; + } + + Discord_Shutdown(); + } +} diff --git a/src/Components/Modules/Discord.hpp b/src/Components/Modules/Discord.hpp new file mode 100644 index 00000000..0d98e524 --- /dev/null +++ b/src/Components/Modules/Discord.hpp @@ -0,0 +1,17 @@ +#pragma once + +namespace Components +{ + class Discord : public Component + { + public: + Discord(); + + void preDestroy() override; + + private: + static bool Initialized_; + + static void UpdateDiscord(); + }; +} diff --git a/src/Components/Modules/Discovery.cpp b/src/Components/Modules/Discovery.cpp index 86bee2ee..15bd600d 100644 --- a/src/Components/Modules/Discovery.cpp +++ b/src/Components/Modules/Discovery.cpp @@ -1,4 +1,6 @@ #include +#include + #include "Discovery.hpp" #include "ServerList.hpp" @@ -28,6 +30,8 @@ namespace Components IsTerminating = false; Thread = std::thread([] { + Com_InitThreadData(); + while (!IsTerminating) { if (IsPerforming) @@ -47,7 +51,7 @@ namespace Components IsPerforming = false; } - std::this_thread::sleep_for(50ms); + Game::Sys_Sleep(50); } }); diff --git a/src/Components/Modules/Download.cpp b/src/Components/Modules/Download.cpp index a1fc0c8e..a5b1ce7d 100644 --- a/src/Components/Modules/Download.cpp +++ b/src/Components/Modules/Download.cpp @@ -1,5 +1,10 @@ #include +#include +#include + #include "Download.hpp" +#include "MapRotation.hpp" +#include "Party.hpp" #include "ServerInfo.hpp" #include @@ -8,6 +13,13 @@ namespace Components { static mg_mgr Mgr; + Dvar::Var Download::SV_wwwDownload; + Dvar::Var Download::SV_wwwBaseUrl; + + Dvar::Var Download::UIDlTimeLeft; + Dvar::Var Download::UIDlProgress; + Dvar::Var Download::UIDlTransRate; + Download::ClientDownload Download::CLDownload; std::thread Download::ServerThread; @@ -27,9 +39,9 @@ namespace Components Scheduler::Once([] { - Dvar::Var("ui_dl_timeLeft").set(Utils::String::FormatTimeSpan(0)); - Dvar::Var("ui_dl_progress").set("(0/0) %"); - Dvar::Var("ui_dl_transRate").set("0.0 MB/s"); + UIDlTimeLeft.set(Utils::String::FormatTimeSpan(0)); + UIDlProgress.set("(0/0) %"); + UIDlTransRate.set("0.0 MB/s"); }, Scheduler::Pipeline::MAIN); Command::Execute("openmenu mod_download_popmenu", false); @@ -138,7 +150,7 @@ namespace Components } auto host = "http://" + download->target.getString(); - auto fastHost = Dvar::Var("sv_wwwBaseUrl").get(); + auto fastHost = SV_wwwBaseUrl.get(); if (Utils::String::StartsWith(fastHost, "https://")) { download->thread.detach(); @@ -173,7 +185,7 @@ namespace Components // -mod.ff // /-mod2 // ... - if (Dvar::Var("sv_wwwDownload").get()) + if (SV_wwwDownload.get()) { if (!Utils::String::EndsWith(fastHost, "/")) fastHost.append("/"); url = fastHost + path; @@ -211,7 +223,7 @@ namespace Components DownloadProgress(&fDownload, bytes - fDownload.receivedBytes); }); - bool result = false; + auto result = false; fDownload.buffer = webIO.get(url, &result); if (!result) fDownload.buffer.clear(); @@ -321,6 +333,7 @@ namespace Components Scheduler::Once([] { Game::Dvar_SetString(*Game::fs_gameDirVar, mod.data()); + const_cast((*Game::fs_gameDirVar))->modified = true; mod.clear(); @@ -336,10 +349,6 @@ namespace Components } } -#pragma endregion - -#pragma region Server - void Download::DownloadProgress(FileDownload* fDownload, std::size_t bytes) { fDownload->receivedBytes += bytes; @@ -365,22 +374,22 @@ namespace Components Scheduler::Once([] { framePushed = false; - Dvar::Var("ui_dl_progress").set(Utils::String::VA("(%d/%d) %d%%", dlIndex, dlSize, dlProgress)); - }, Scheduler::Pipeline::CLIENT); + UIDlProgress.set(std::format("({}/{}) {}%", dlIndex, dlSize, dlProgress)); + }, Scheduler::Pipeline::MAIN); } auto delta = Game::Sys_Milliseconds() - fDownload->download->lastTimeStamp; if (delta > 300) { - bool doFormat = fDownload->download->lastTimeStamp != 0; + const auto doFormat = fDownload->download->lastTimeStamp != 0; fDownload->download->lastTimeStamp = Game::Sys_Milliseconds(); - auto dataLeft = fDownload->download->totalBytes - fDownload->download->downBytes; + const auto dataLeft = fDownload->download->totalBytes - fDownload->download->downBytes; int timeLeft = 0; if (fDownload->download->timeStampBytes) { - double timeLeftD = ((1.0 * dataLeft) / fDownload->download->timeStampBytes) * delta; + const double timeLeftD = ((1.0 * dataLeft) / fDownload->download->timeStampBytes) * delta; timeLeft = static_cast(timeLeftD); } @@ -394,8 +403,8 @@ namespace Components Scheduler::Once([] { - Dvar::Var("ui_dl_timeLeft").set(Utils::String::FormatTimeSpan(dlTimeLeft)); - Dvar::Var("ui_dl_transRate").set(Utils::String::FormatBandwidth(dlTsBytes, dlDelta)); + UIDlTimeLeft.set(Utils::String::FormatTimeSpan(dlTimeLeft)); + UIDlTransRate.set(Utils::String::FormatBandwidth(dlTsBytes, dlDelta)); }, Scheduler::Pipeline::MAIN); } @@ -403,6 +412,10 @@ namespace Components } } +#pragma endregion + +#pragma region Server + static std::string InfoHandler() { const auto status = ServerInfo::GetInfo(); @@ -411,6 +424,7 @@ namespace Components std::unordered_map info; info["status"] = status.to_json(); info["host"] = host.to_json(); + info["map_rotation"] = MapRotation::to_json(); std::vector players; @@ -449,35 +463,36 @@ namespace Components static std::string ListHandler() { static nlohmann::json jsonList; - static auto handled = false; + static std::filesystem::path fsGamePre; - const std::filesystem::path fs_gameDirVar((*Game::fs_gameDirVar)->current.string); + const std::filesystem::path fsGame = (*Game::fs_gameDirVar)->current.string; - if (!fs_gameDirVar.empty() && !handled) + if (!fsGame.empty() && (fsGamePre != fsGame)) { - handled = true; + fsGamePre = fsGame; std::vector fileList; - const auto path = Dvar::Var("fs_basepath").get() / fs_gameDirVar; + const auto path = (*Game::fs_basepath)->current.string / fsGame; auto list = FileSystem::GetSysFileList(path.generic_string(), "iwd", false); list.emplace_back("mod.ff"); for (const auto& file : list) { auto filename = path / file; - if (file.find("_svr_") != std::string::npos) + + if (file.find("_svr_") != std::string::npos) // Files that are 'server only' are skipped { continue; } - std::unordered_map jsonFileList; auto fileBuffer = Utils::IO::ReadFile(filename.generic_string()); if (fileBuffer.empty()) { continue; } + std::unordered_map jsonFileList; jsonFileList["name"] = file; jsonFileList["size"] = fileBuffer.size(); jsonFileList["hash"] = Utils::Cryptography::SHA256::Compute(fileBuffer, true); @@ -496,7 +511,7 @@ namespace Components static std::string mapNamePre; static nlohmann::json jsonList; - const auto mapName = (Party::IsInUserMapLobby() ? Dvar::Var("ui_mapname").get() : Maps::GetUserMap()->getName()); + const std::string mapName = Party::IsInUserMapLobby() ? (*Game::ui_mapname)->current.string : Maps::GetUserMap()->getName(); if (!Maps::GetUserMap()->isValid() && !Party::IsInUserMapLobby()) { mapNamePre.clear(); @@ -508,7 +523,7 @@ namespace Components mapNamePre = mapName; - const std::filesystem::path basePath(Dvar::Var("fs_basepath").get()); + const std::filesystem::path basePath = (*Game::fs_basepath)->current.string; const auto path = basePath / "usermaps" / mapName; for (std::size_t i = 0; i < ARRAYSIZE(Maps::UserMapFiles); ++i) @@ -541,17 +556,16 @@ namespace Components Utils::String::Replace(url, "\\", "/"); - // Strip /file - url = url.substr(6); + url = url.substr(6); // Strip /file Utils::String::Replace(url, "%20", " "); auto isMap = false; if (url.starts_with("map/")) { isMap = true; - url = url.substr(4); + url = url.substr(4); // Strip map/ - auto mapName = (Party::IsInUserMapLobby() ? Dvar::Var("ui_mapname").get() : Maps::GetUserMap()->getName()); + std::string mapName = (Party::IsInUserMapLobby() ? (*Game::ui_mapname)->current.string : Maps::GetUserMap()->getName()); auto isValidFile = false; for (std::size_t i = 0; i < ARRAYSIZE(Maps::UserMapFiles); ++i) { @@ -579,10 +593,10 @@ namespace Components } } - std::string file; - const auto fsGame = Dvar::Var("fs_game").get(); - const auto path = Dvar::Var("fs_basepath").get() + "\\" + (isMap ? "" : fsGame + "\\") + url; + const std::string fsGame = (*Game::fs_gameDirVar)->current.string; + const auto path = std::format("{}\\{}{}", (*Game::fs_basepath)->current.string, isMap ? ""s : (fsGame + "\\"s), url); + std::string file; if ((!isMap && fsGame.empty()) || !Utils::IO::ReadFile(path, &file)) { mg_http_reply(c, 404, "Content-Type: text/html\r\n", "404 - Not Found %s", path.data()); @@ -598,44 +612,7 @@ namespace Components } } - static void HTMLHandler(mg_connection* c, mg_http_message* hm) - { - auto url = "html" + std::string(hm->uri.ptr, hm->uri.len); - FileSystem::File file; - - if (url.ends_with("/")) - { - url.append("index.html"); - file = FileSystem::File(url); - } - else - { - file = FileSystem::File(url); - if (!file.exists()) - { - url.append("/index.html"); - file = FileSystem::File(url); - } - } - - const auto mimeType = Utils::GetMimeType(url); - - if (file.exists()) - { - mg_printf(c, "%s", "HTTP/1.1 200 OK\r\n"); - mg_printf(c, "Content-Type: %s\r\n", mimeType.data()); - mg_printf(c, "Content-Length: %d\r\n", static_cast(file.getBuffer().size())); - mg_printf(c, "%s", "Connection: close\r\n"); - mg_printf(c, "%s", "\r\n"); - mg_send(c, file.getBuffer().data(), file.getBuffer().size()); - } - else - { - mg_http_reply(c, 404, "Content-Type: text/html\r\n", "404 - Not Found"); - } - } - - static void EventHandler(mg_connection* c, int ev, void* ev_data, [[maybe_unused]] void* fn_data) + static void EventHandler(mg_connection* c, const int ev, void* ev_data, [[maybe_unused]] void* fn_data) { if (ev != MG_EV_HTTP_MSG) { @@ -643,7 +620,7 @@ namespace Components } auto* hm = static_cast(ev_data); - std::string url(hm->uri.ptr, hm->uri.len); + const std::string url(hm->uri.ptr, hm->uri.len); if (url.starts_with("/info")) { @@ -666,8 +643,12 @@ namespace Components } else { - HTMLHandler(c, hm); + mg_http_serve_opts opts = { .root_dir = "iw4x/html" }; // Serve local dir + mg_http_serve_dir(c, hm, &opts); } + + c->is_resp = FALSE; // This is important, the lack of this line of code will make the server die (in-game) + c->is_draining = TRUE; } #pragma endregion @@ -680,37 +661,41 @@ namespace Components if (Dedicated::IsEnabled()) { - mg_mgr_init(&Mgr); - - Network::OnStart([] + if (!Flags::HasFlag("disable-mongoose")) { - const auto* nc = mg_http_listen(&Mgr, Utils::String::VA(":%hu", Network::GetPort()), &EventHandler, &Mgr); - if (!nc) - { - Logger::PrintError(Game::CON_CHANNEL_ERROR, "Failed to bind TCP socket, mod download won't work!\n"); - } - }); + mg_mgr_init(&Mgr); - ServerRunning = true; - Terminate = false; - ServerThread = Utils::Thread::CreateNamedThread("Mongoose", [] - { - Com_InitThreadData(); - - while (!Terminate) + Network::OnStart([] { - mg_mgr_poll(&Mgr, 100); - } - }); + const auto* nc = mg_http_listen(&Mgr, Utils::String::VA(":%hu", Network::GetPort()), &EventHandler, &Mgr); + if (!nc) + { + Logger::PrintError(Game::CON_CHANNEL_ERROR, "Failed to bind TCP socket, mod download won't work!\n"); + Terminate = true; + } + }); + + ServerRunning = true; + Terminate = false; + ServerThread = Utils::Thread::CreateNamedThread("Mongoose", [] + { + Com_InitThreadData(); + + while (!Terminate) + { + mg_mgr_poll(&Mgr, 1000); + } + }); + } } else { - Scheduler::Once([] + Events::OnDvarInit([] { - Dvar::Register("ui_dl_timeLeft", "", Game::DVAR_NONE, ""); - Dvar::Register("ui_dl_progress", "", Game::DVAR_NONE, ""); - Dvar::Register("ui_dl_transRate", "", Game::DVAR_NONE, ""); - }, Scheduler::Pipeline::MAIN); + UIDlTimeLeft = Dvar::Register("ui_dl_timeLeft", "", Game::DVAR_NONE, ""); + UIDlProgress = Dvar::Register("ui_dl_progress", "", Game::DVAR_NONE, ""); + UIDlTransRate = Dvar::Register("ui_dl_transRate", "", Game::DVAR_NONE, ""); + }); UIScript::Add("mod_download_cancel", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) { @@ -718,11 +703,11 @@ namespace Components }); } - Scheduler::Once([] + Events::OnDvarInit([] { - Dvar::Register("sv_wwwDownload", false, Game::DVAR_NONE, "Set to true to enable downloading maps/mods from an external server."); - Dvar::Register("sv_wwwBaseUrl", "", Game::DVAR_NONE, "Set to the base url for the external map download."); - }, Scheduler::Pipeline::MAIN); + SV_wwwDownload = Dvar::Register("sv_wwwDownload", false, Game::DVAR_NONE, "Set to true to enable downloading maps/mods from an external server."); + SV_wwwBaseUrl = Dvar::Register("sv_wwwBaseUrl", "", Game::DVAR_NONE, "Set to the base url for the external map download."); + }); } Download::~Download() diff --git a/src/Components/Modules/Download.hpp b/src/Components/Modules/Download.hpp index 84ccfd93..fa3dd654 100644 --- a/src/Components/Modules/Download.hpp +++ b/src/Components/Modules/Download.hpp @@ -13,6 +13,13 @@ namespace Components static void InitiateClientDownload(const std::string& mod, bool needPassword, bool map = false); static void InitiateMapDownload(const std::string& map, bool needPassword); + static Dvar::Var SV_wwwDownload; + static Dvar::Var SV_wwwBaseUrl; + + static Dvar::Var UIDlTimeLeft; + static Dvar::Var UIDlProgress; + static Dvar::Var UIDlTransRate; + private: class ClientDownload { diff --git a/src/Components/Modules/Dvar.cpp b/src/Components/Modules/Dvar.cpp index b15e1721..8ded131e 100644 --- a/src/Components/Modules/Dvar.cpp +++ b/src/Components/Modules/Dvar.cpp @@ -2,16 +2,13 @@ namespace Components { - const char* Dvar::ArchiveDvarPath = "userraw/archivedvars.cfg"; - Dvar::Var Dvar::Name; Dvar::Var::Var(const std::string& dvarName) + : dvar_(Game::Dvar_FindVar(dvarName.data())) { - this->dvar_ = Game::Dvar_FindVar(dvarName.data()); - // If the dvar can't be found it will be registered as an empty string dvar - if (this->dvar_ == nullptr) + if (!this->dvar_) { this->dvar_ = const_cast(Game::Dvar_SetFromStringByNameFromSource(dvarName.data(), "", Game::DVAR_SOURCE_INTERNAL)); } @@ -24,13 +21,17 @@ namespace Components template <> const char* Dvar::Var::get() { - if (this->dvar_ == nullptr) + if (!this->dvar_) + { return ""; + } if (this->dvar_->type == Game::DVAR_TYPE_STRING || this->dvar_->type == Game::DVAR_TYPE_ENUM) { - if (this->dvar_->current.string != nullptr) + if (this->dvar_->current.string) + { return this->dvar_->current.string; + } } return ""; @@ -38,8 +39,10 @@ namespace Components template <> int Dvar::Var::get() { - if (this->dvar_ == nullptr) + if (!this->dvar_) + { return 0; + } if (this->dvar_->type == Game::DVAR_TYPE_INT || this->dvar_->type == Game::DVAR_TYPE_ENUM) { @@ -51,8 +54,10 @@ namespace Components template <> unsigned int Dvar::Var::get() { - if (this->dvar_ == nullptr) + if (!this->dvar_) + { return 0; + } if (this->dvar_->type == Game::DVAR_TYPE_INT) { @@ -64,8 +69,10 @@ namespace Components template <> float Dvar::Var::get() { - if (this->dvar_ == nullptr) + if (!this->dvar_) + { return 0.f; + } if (this->dvar_->type == Game::DVAR_TYPE_FLOAT) { @@ -75,26 +82,12 @@ namespace Components return 0.f; } - template <> float* Dvar::Var::get() - { - static Game::vec4_t vector{0.f, 0.f, 0.f, 0.f}; - - if (this->dvar_ == nullptr) - return vector; - - if (this->dvar_->type == Game::DVAR_TYPE_FLOAT_2 || this->dvar_->type == Game::DVAR_TYPE_FLOAT_3 || - this->dvar_->type == Game::DVAR_TYPE_FLOAT_4) - { - return this->dvar_->current.vector; - } - - return vector; - } - template <> bool Dvar::Var::get() { - if (this->dvar_ == nullptr) + if (!this->dvar_) + { return false; + } if (this->dvar_->type == Game::DVAR_TYPE_BOOL) { @@ -111,7 +104,9 @@ namespace Components void Dvar::Var::set(const char* string) { + assert(string); assert(this->dvar_->type == Game::DVAR_TYPE_STRING); + if (this->dvar_) { Game::Dvar_SetString(this->dvar_, string); @@ -206,17 +201,7 @@ namespace Components return Game::Dvar_RegisterFloat(dvarName, value, min, max, flag, description); } - void Dvar::ResetDvarsValue() - { - if (!Utils::IO::FileExists(ArchiveDvarPath)) - return; - - Command::Execute("exec archivedvars.cfg", true); - // Clean up - Utils::IO::RemoveFile(ArchiveDvarPath); - } - - Game::dvar_t* Dvar::Dvar_RegisterName(const char* name, const char* /*default*/, std::uint16_t flags, const char* description) + const Game::dvar_t* Dvar::Dvar_RegisterName(const char* dvarName, const char* /*value*/, std::uint16_t flags, const char* description) { // Name watcher if (!Dedicated::IsEnabled() && !ZoneBuilder::IsEnabled()) @@ -250,19 +235,29 @@ namespace Components { const char* steamName = Steam::Proxy::SteamFriends->GetPersonaName(); - if (steamName && *steamName != '\0') + if (steamName && *steamName) { username = steamName; } } - Name = Register(name, username.data(), flags | Game::DVAR_ARCHIVE, description); + Name = Register(dvarName, username.data(), flags | Game::DVAR_ARCHIVE, description); return Name.get(); } + const Game::dvar_t* Dvar::Dvar_RegisterSVNetworkFps(const char* dvarName, int /*value*/, int min, int /*max*/, std::uint16_t /*flags*/, const char* description) + { + return Game::Dvar_RegisterInt(dvarName, 1000, min, 1000, Game::DVAR_NONE, description); + } + + const Game::dvar_t* Dvar::Dvar_RegisterPerkExtendedMeleeRange(const char* dvarName, float value, float min, float /*max*/, std::uint16_t flags, const char* description) + { + return Game::Dvar_RegisterFloat(dvarName, value, min, 10000.0f, flags, description); + } + void Dvar::SetFromStringByNameSafeExternal(const char* dvarName, const char* string) { - static std::array exceptions = + static std::array exceptions = { "ui_showEndOfGame", "systemlink", @@ -276,7 +271,7 @@ namespace Components for (const auto& entry : exceptions) { - if (Utils::String::Compare(dvarName, entry)) + if (!_stricmp(dvarName, entry)) { Game::Dvar_SetFromStringByNameFromSource(dvarName, string, Game::DVAR_SOURCE_INTERNAL); return; @@ -291,47 +286,52 @@ namespace Components Game::Dvar_SetFromStringByNameFromSource(dvarName, string, Game::DVAR_SOURCE_EXTERNAL); } - bool Dvar::AreArchiveDvarsProtected() + bool Dvar::AreArchiveDvarsUnprotected() { static std::optional flag; if (!flag.has_value()) { - flag.emplace(Flags::HasFlag("protect-saved-dvars")); + flag.emplace(Flags::HasFlag("unprotect-dvars")); } return flag.value(); } - void Dvar::SaveArchiveDvar(const Game::dvar_t* var) + bool Dvar::IsSettingDvarsDisabled() { - if (!Utils::IO::FileExists(ArchiveDvarPath)) + static std::optional flag; + + if (!flag.has_value()) { - Utils::IO::WriteFile(ArchiveDvarPath, "// generated by IW4x, do not modify\n", false); + flag.emplace(Flags::HasFlag("protect-dvars")); } - Utils::IO::WriteFile(ArchiveDvarPath, std::format("set {} \"{}\"\n", var->name, Game::Dvar_DisplayableValue(var)), true); + return flag.value(); } void Dvar::DvarSetFromStringByName_Stub(const char* dvarName, const char* value) { + if (IsSettingDvarsDisabled()) + { + Logger::Debug("Not allowing server to set '{}'", dvarName); + return; + } + // Save the dvar original value if it has the archive flag const auto* dvar = Game::Dvar_FindVar(dvarName); - if (dvar != nullptr && dvar->flags & Game::DVAR_ARCHIVE) + if (dvar && dvar->flags & Game::DVAR_ARCHIVE) { - if (AreArchiveDvarsProtected()) + if (!AreArchiveDvarsUnprotected()) { Logger::Print(Game::CON_CHANNEL_CONSOLEONLY, "Not allowing server to override saved dvar '{}'\n", dvar->name); return; } -#ifdef DEBUG_DVARS Logger::Print(Game::CON_CHANNEL_CONSOLEONLY, "Server is overriding saved dvar '{}'\n", dvarName); -#endif - SaveArchiveDvar(dvar); } - if (dvar != nullptr && std::strcmp(dvar->name, "com_errorResolveCommand") == 0) + if (dvar && std::strcmp(dvar->name, "com_errorResolveCommand") == 0) { Logger::Print(Game::CON_CHANNEL_CONSOLEONLY, "Not allowing server to set '{}'\n", dvar->name); return; @@ -417,11 +417,6 @@ namespace Components // remove write protection from fs_game Utils::Hook::Xor(0x6431EA, Game::DVAR_INIT); - // cheat protect g_hardcore - Utils::Hook::Xor(0x5E374F, Game::DVAR_CHEAT); - Utils::Hook::Xor(0x4D3689, Game::DVAR_CHEAT); - Utils::Hook::Xor(0x4197C3, Game::DVAR_CHEAT); - // set cg_fov max to 160.0 // because that's the max on SP static float cg_Fov = 160.0f; @@ -440,6 +435,12 @@ namespace Components // Hook dvar 'name' registration Utils::Hook(0x40531C, Dvar_RegisterName, HOOK_CALL).install()->quick(); + // Hook dvar 'sv_network_fps' registration + Utils::Hook(0x4D3C7B, Dvar_RegisterSVNetworkFps, HOOK_CALL).install()->quick(); + + // Hook dvar 'perk_extendedMeleeRange' and set a higher max, better than having people force this with external programs + Utils::Hook(0x492D2F, Dvar_RegisterPerkExtendedMeleeRange, HOOK_CALL).install()->quick(); + // un-cheat safeArea_* and add archive flags Utils::Hook::Xor(0x42E3F5, Game::DVAR_ROM | Game::DVAR_ARCHIVE); //safeArea_adjusted_horizontal Utils::Hook::Xor(0x42E423, Game::DVAR_ROM | Game::DVAR_ARCHIVE); //safeArea_adjusted_vertical @@ -467,12 +468,6 @@ namespace Components // Hook Dvar_SetFromStringByName inside CG_SetClientDvarFromServer so we can reset dvars when the player leaves the server Utils::Hook(0x59386A, DvarSetFromStringByName_Stub, HOOK_CALL).install()->quick(); - // If the game closed abruptly, the dvars would not have been restored - Scheduler::Once(ResetDvarsValue, Scheduler::Pipeline::MAIN); - - // Reset archive dvars when client leaves a server - Events::OnSteamDisconnect(ResetDvarsValue); - // For debugging Utils::Hook(0x6483FA, Dvar_RegisterVariant_Stub, HOOK_JUMP).install()->quick(); Utils::Hook(0x648438, Dvar_RegisterVariant_Stub, HOOK_JUMP).install()->quick(); @@ -480,9 +475,4 @@ namespace Components // Fix crash Utils::Hook(0x4B7120, Dvar_EnumToString_Stub, HOOK_JUMP).install()->quick(); } - - Dvar::~Dvar() - { - Utils::IO::RemoveFile(ArchiveDvarPath); - } } diff --git a/src/Components/Modules/Dvar.hpp b/src/Components/Modules/Dvar.hpp index a34154c6..489248c8 100644 --- a/src/Components/Modules/Dvar.hpp +++ b/src/Components/Modules/Dvar.hpp @@ -32,25 +32,24 @@ namespace Components }; Dvar(); - ~Dvar(); // Only strings and bools use this type of declaration template static Var Register(const char* dvarName, T value, std::uint16_t flag, const char* description); template static Var Register(const char* dvarName, T value, T min, T max, std::uint16_t flag, const char* description); - static void ResetDvarsValue(); - - private: - static const char* ArchiveDvarPath; static Var Name; - static Game::dvar_t* Dvar_RegisterName(const char* name, const char* defaultVal, std::uint16_t flags, const char* description); + private: + + static const Game::dvar_t* Dvar_RegisterName(const char* dvarName, const char* value, std::uint16_t flags, const char* description); + static const Game::dvar_t* Dvar_RegisterSVNetworkFps(const char* dvarName, int value, int min, int max, std::uint16_t flags, const char* description); + static const Game::dvar_t* Dvar_RegisterPerkExtendedMeleeRange(const char* dvarName, float value, float min, float max, std::uint16_t flags, const char* description); static void SetFromStringByNameExternal(const char* dvarName, const char* string); static void SetFromStringByNameSafeExternal(const char* dvarName, const char* string); - static bool AreArchiveDvarsProtected(); - static void SaveArchiveDvar(const Game::dvar_t* var); + static bool AreArchiveDvarsUnprotected(); + static bool IsSettingDvarsDisabled(); static void DvarSetFromStringByName_Stub(const char* dvarName, const char* value); static void OnRegisterVariant(Game::dvar_t* dvar); diff --git a/src/Components/Modules/Elevators.cpp b/src/Components/Modules/Elevators.cpp index 5c4d80da..f6cc4464 100644 --- a/src/Components/Modules/Elevators.cpp +++ b/src/Components/Modules/Elevators.cpp @@ -7,14 +7,14 @@ namespace Components int Elevators::PM_CorrectAllSolid(Game::pmove_s* pm, Game::pml_t* pml, Game::trace_t* trace) { - assert(pm != nullptr); - assert(pm->ps != nullptr); + assert(pm); + assert(pm->ps); Game::vec3_t point; auto* ps = pm->ps; auto i = 0; - const auto elevatorSetting = Elevators::BG_Elevators.get(); + const auto elevatorSetting = BG_Elevators.get(); while (true) { point[0] = ps->origin[0] + (*Game::CorrectSolidDeltas)[i][0]; @@ -24,7 +24,7 @@ namespace Components Game::PM_playerTrace(pm, trace, point, point, &pm->bounds, ps->clientNum, pm->tracemask); // If the player wishes to glitch without effort they can do so - if (!trace->startsolid || elevatorSetting == Elevators::EASY) + if (!trace->startsolid || elevatorSetting == EASY) { ps->origin[0] = point[0]; ps->origin[1] = point[1]; @@ -35,7 +35,7 @@ namespace Components // If elevators are disabled we need to check that startsolid is false before proceeding // like later versions of the game do - if (!trace->startsolid || elevatorSetting >= Elevators::ENABLED) + if (!trace->startsolid || elevatorSetting >= ENABLED) { break; } @@ -69,7 +69,7 @@ namespace Components Game::PM_Trace(pm, results, start, end, bounds, passEntityNum, contentMask); // Allow the player to stand even when there is no headroom - if (Elevators::BG_Elevators.get() == Elevators::EASY) + if (BG_Elevators.get() == EASY) { results->allsolid = false; } @@ -85,7 +85,7 @@ namespace Components push [esp + 0x8 + 0x24] push [esp + 0x8 + 0x24] push eax - call Elevators::PM_CorrectAllSolid + call PM_CorrectAllSolid add esp, 0xC mov [esp + 0x20], eax @@ -98,7 +98,7 @@ namespace Components Elevators::Elevators() { - Scheduler::Once([] + Events::OnDvarInit([] { static const char* values[] = { @@ -108,16 +108,15 @@ namespace Components nullptr }; - Elevators::BG_Elevators = Game::Dvar_RegisterEnum("bg_elevators", values, - Elevators::ENABLED, Game::DVAR_CODINFO, "Elevators glitch settings"); - }, Scheduler::Pipeline::MAIN); + BG_Elevators = Game::Dvar_RegisterEnum("bg_elevators", values, ENABLED, Game::DVAR_CODINFO, "Elevators glitch settings"); + }); - Utils::Hook(0x57369E, Elevators::PM_CorrectAllSolidStub, HOOK_CALL).install()->quick(); // PM_GroundTrace + Utils::Hook(0x57369E, PM_CorrectAllSolidStub, HOOK_CALL).install()->quick(); // PM_GroundTrace // Place hooks in PM_CheckDuck. If the elevators dvar is set to easy the // flags for duck/prone will always be removed from the player state - Utils::Hook(0x570EC5, Elevators::PM_Trace_Hk, HOOK_CALL).install()->quick(); - Utils::Hook(0x570E0B, Elevators::PM_Trace_Hk, HOOK_CALL).install()->quick(); - Utils::Hook(0x570D70, Elevators::PM_Trace_Hk, HOOK_CALL).install()->quick(); + Utils::Hook(0x570EC5, PM_Trace_Hk, HOOK_CALL).install()->quick(); + Utils::Hook(0x570E0B, PM_Trace_Hk, HOOK_CALL).install()->quick(); + Utils::Hook(0x570D70, PM_Trace_Hk, HOOK_CALL).install()->quick(); } } diff --git a/src/Components/Modules/Events.cpp b/src/Components/Modules/Events.cpp index e03522ec..0796f4cf 100644 --- a/src/Components/Modules/Events.cpp +++ b/src/Components/Modules/Events.cpp @@ -3,16 +3,23 @@ namespace Components { Utils::Signal Events::ClientDisconnectSignal; + Utils::Signal Events::ClientConnectSignal; Utils::Signal Events::SteamDisconnectSignal; Utils::Signal Events::ShutdownSystemSignal; Utils::Signal Events::ClientInitSignal; Utils::Signal Events::ServerInitSignal; + Utils::Signal Events::DvarInitSignal; void Events::OnClientDisconnect(const Utils::Slot& callback) { ClientDisconnectSignal.connect(callback); } + void Events::OnClientConnect(const Utils::Slot& callback) + { + ClientConnectSignal.connect(callback); + } + void Events::OnSteamDisconnect(const Utils::Slot& callback) { SteamDisconnectSignal.connect(callback); @@ -33,6 +40,11 @@ namespace Components ServerInitSignal.connect(callback); } + void Events::OnDvarInit(const Utils::Slot& callback) + { + DvarInitSignal.connect(callback); + } + /* * Should be called when a client drops from the server * but not "between levels" (Quake-III-Arena) @@ -44,6 +56,13 @@ namespace Components Utils::Hook::Call(0x4AA430)(clientNum); // ClientDisconnect } + void Events::SV_UserinfoChanged_Hk(Game::client_t* cl) + { + ClientConnectSignal(cl); + + Utils::Hook::Call(0x401950)(cl); // SV_UserinfoChanged + } + void Events::SteamDisconnect_Hk() { SteamDisconnectSignal(); @@ -74,10 +93,20 @@ namespace Components Utils::Hook::Call(0x474320)(); // SV_InitGameMode } + void Events::Com_InitDvars_Hk() + { + DvarInitSignal(); + DvarInitSignal.clear(); + + Utils::Hook::Call(0x60AD10)(); // Com_InitDvars + } + Events::Events() { Utils::Hook(0x625235, ClientDisconnect_Hk, HOOK_CALL).install()->quick(); // SV_FreeClient + Utils::Hook(0x4612BD, SV_UserinfoChanged_Hk, HOOK_CALL).install()->quick(); // SV_DirectConnect + Utils::Hook(0x403582, SteamDisconnect_Hk, HOOK_CALL).install()->quick(); // CL_Disconnect Utils::Hook(0x47548B, Scr_ShutdownSystem_Hk, HOOK_CALL).install()->quick(); // G_LoadGame @@ -85,6 +114,8 @@ namespace Components Utils::Hook(0x60BE5B, CL_InitOnceForAllClients_HK, HOOK_CALL).install()->quick(); // Com_Init_Try_Block_Function + Utils::Hook(0x60BB3A, Com_InitDvars_Hk, HOOK_CALL).install()->quick(); // Com_Init_Try_Block_Function + Utils::Hook(0x4D3665, SV_Init_Hk, HOOK_CALL).install()->quick(); // SV_Init } } diff --git a/src/Components/Modules/Events.hpp b/src/Components/Modules/Events.hpp index a47f6794..8b672fc1 100644 --- a/src/Components/Modules/Events.hpp +++ b/src/Components/Modules/Events.hpp @@ -6,6 +6,7 @@ namespace Components { public: typedef void(ClientCallback)(int clientNum); + typedef void(ClientConnectCallback)(Game::client_t* cl); typedef void(Callback)(); Events(); @@ -13,6 +14,9 @@ namespace Components // Server side static void OnClientDisconnect(const Utils::Slot& callback); + // Server side + static void OnClientConnect(const Utils::Slot& callback); + // Client side static void OnSteamDisconnect(const Utils::Slot& callback); @@ -23,17 +27,24 @@ namespace Components // Client & Server (triggered once) static void OnSVInit(const Utils::Slot& callback); + // Client & Server (triggered once) + static void OnDvarInit(const Utils::Slot& callback); + private: static Utils::Signal ClientDisconnectSignal; + static Utils::Signal ClientConnectSignal; static Utils::Signal SteamDisconnectSignal; static Utils::Signal ShutdownSystemSignal; static Utils::Signal ClientInitSignal; static Utils::Signal ServerInitSignal; + static Utils::Signal DvarInitSignal; static void ClientDisconnect_Hk(int clientNum); + static void SV_UserinfoChanged_Hk(Game::client_t* cl); static void SteamDisconnect_Hk(); static void Scr_ShutdownSystem_Hk(unsigned char sys); static void CL_InitOnceForAllClients_HK(); static void SV_Init_Hk(); + static void Com_InitDvars_Hk(); }; } diff --git a/src/Components/Modules/Exception.cpp b/src/Components/Modules/Exception.cpp index f02c307b..7860a375 100644 --- a/src/Components/Modules/Exception.cpp +++ b/src/Components/Modules/Exception.cpp @@ -134,7 +134,9 @@ namespace Components if (!MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, static_cast(MiniDumpType), &ex, nullptr, nullptr)) { MessageBoxA(nullptr, Utils::String::Format("There was an error creating the minidump ({})! Hit OK to close the program.", Utils::GetLastWindowsError()), "ERROR", MB_OK | MB_ICONERROR); +#ifdef _DEBUG OutputDebugStringA("Failed to create new minidump!"); +#endif Utils::OutputDebugLastError(); TerminateProcess(GetCurrentProcess(), ExceptionInfo->ExceptionRecord->ExceptionCode); } diff --git a/src/Components/Modules/FastFiles.cpp b/src/Components/Modules/FastFiles.cpp index c3b3cb01..e56732f1 100644 --- a/src/Components/Modules/FastFiles.cpp +++ b/src/Components/Modules/FastFiles.cpp @@ -1,11 +1,17 @@ #include +#include + +#include "FastFiles.hpp" + namespace Components { FastFiles::Key FastFiles::CurrentKey; symmetric_CTR FastFiles::CurrentCTR; std::vector FastFiles::ZonePaths; + Dvar::Var FastFiles::g_loadingInitialZones; + bool FastFiles::IsIW4xZone = false; bool FastFiles::StreamRead = false; @@ -129,8 +135,10 @@ namespace Components // This has to be called only once, when the game starts - void FastFiles::LoadInitialZones(Game::XZoneInfo *zoneInfo, unsigned int zoneCount, int sync) + void FastFiles::LoadInitialZones(Game::XZoneInfo* zoneInfo, unsigned int zoneCount, int sync) { + g_loadingInitialZones.set(true); + std::vector data; Utils::Merge(&data, zoneInfo, zoneCount); @@ -148,7 +156,7 @@ namespace Components } // This has to be called every time the cgame is reinitialized - void FastFiles::LoadDLCUIZones(Game::XZoneInfo *zoneInfo, unsigned int zoneCount, int sync) + void FastFiles::LoadDLCUIZones(Game::XZoneInfo* zoneInfo, unsigned int zoneCount, int sync) { std::vector data; Utils::Merge(&data, zoneInfo, zoneCount); @@ -173,7 +181,7 @@ namespace Components return FastFiles::LoadLocalizeZones(data.data(), data.size(), sync); } - void FastFiles::LoadGfxZones(Game::XZoneInfo *zoneInfo, unsigned int zoneCount, int sync) + void FastFiles::LoadGfxZones(Game::XZoneInfo* zoneInfo, unsigned int zoneCount, int sync) { std::vector data; Utils::Merge(&data, zoneInfo, zoneCount); @@ -187,7 +195,7 @@ namespace Components } // This has to be called every time fastfiles are loaded :D - void FastFiles::LoadLocalizeZones(Game::XZoneInfo *zoneInfo, unsigned int zoneCount, int sync) + void FastFiles::LoadLocalizeZones(Game::XZoneInfo* zoneInfo, unsigned int zoneCount, int sync) { std::vector data; Utils::Merge(&data, zoneInfo, zoneCount); @@ -209,6 +217,11 @@ namespace Components data.push_back(info); Game::DB_LoadXAssets(data.data(), data.size(), sync); + + Scheduler::OnGameInitialized([] + { + g_loadingInitialZones.set(false); + }, Scheduler::Pipeline::MAIN); } // Name is a bit weird, due to FasFileS and ExistS :P @@ -232,14 +245,14 @@ namespace Components const char* FastFiles::GetZoneLocation(const char* file) { - const char* dir = Dvar::Var("fs_basepath").get(); + const auto* dir = (*Game::fs_basepath)->current.string; std::vector paths; - auto modDir = Dvar::Var("fs_game").get(); + const std::string fsGame = (*Game::fs_gameDirVar)->current.string; - if ((file == "mod"s || file == "mod.ff"s) && !modDir.empty()) + if ((file == "mod"s || file == "mod.ff"s) && !fsGame.empty()) { - paths.push_back(std::format("{}\\", modDir)); + paths.push_back(std::format("{}\\", fsGame)); } if (Utils::String::StartsWith(file, "mp_")) @@ -390,7 +403,7 @@ namespace Components return Utils::Hook::Call(0x5BA240)(buffer, length, ivValue); } - int FastFiles::InflateInitDecrypt(z_streamp strm, const char *version, int stream_size) + static int InflateInitDecrypt(z_streamp strm, const char* version, int stream_size) { if (Zones::Version() >= 319) { @@ -398,7 +411,6 @@ namespace Components } return Utils::Hook::Call(0x4D8090)(strm, version, stream_size); - //return inflateInit_(strm, version, stream_size); } void FastFiles::AuthLoadInflateDecryptBaseFunc(unsigned char* buffer) @@ -433,7 +445,7 @@ namespace Components return std::min(partialProgress + (currentProgress * singleProgress), 1.0f); } - void FastFiles::LoadZonesStub(Game::XZoneInfo *zoneInfo, unsigned int zoneCount) + void FastFiles::LoadZonesStub(Game::XZoneInfo* zoneInfo, unsigned int zoneCount) { FastFiles::CurrentZone = 0; FastFiles::MaxZones = zoneCount; @@ -497,6 +509,7 @@ namespace Components FastFiles::FastFiles() { Dvar::Register("ui_zoneDebug", false, Game::DVAR_ARCHIVE, "Display current loaded zone."); + g_loadingInitialZones = Dvar::Register("g_loadingInitialZones", true, Game::DVAR_NONE, "Is loading initial zones"); // Fix XSurface assets Utils::Hook(0x0048E8A5, FastFiles::Load_XSurfaceArray, HOOK_CALL).install()->quick(); @@ -547,7 +560,7 @@ namespace Components Utils::Hook(0x4D02F0, FastFiles::AuthLoadInitCrypto, HOOK_CALL).install()->quick(); // Initial stage decryption - Utils::Hook(0x4D0306, FastFiles::InflateInitDecrypt, HOOK_CALL).install()->quick(); + Utils::Hook(0x4D0306, InflateInitDecrypt, HOOK_CALL).install()->quick(); // Hash bit decryption Utils::Hook(0x5B9958, FastFiles::AuthLoadInflateCompare, HOOK_CALL).install()->quick(); @@ -577,7 +590,7 @@ namespace Components if (FastFiles::Current().empty() || !Dvar::Var("ui_zoneDebug").get()) return; auto* const font = Game::R_RegisterFont("fonts/consoleFont", 0); - float color[4] = {1.0f, 1.0f, 1.0f, (Game::CL_IsCgameInitialized() ? 0.3f : 1.0f)}; + float color[4] = { 1.0f, 1.0f, 1.0f, (Game::CL_IsCgameInitialized() ? 0.3f : 1.0f) }; auto FFTotalSize = *reinterpret_cast(0x10AA5D8); auto FFCurrentOffset = *reinterpret_cast(0x10AA608); @@ -614,11 +627,11 @@ namespace Components while (!Game::Sys_IsDatabaseReady()) std::this_thread::sleep_for(100ms); }); -#ifdef DEBUG +#ifdef _DEBUG // ZoneBuilder debugging Utils::IO::WriteFile("userraw/logs/iw4_reads.log", "", false); Utils::Hook(0x4A8FA0, FastFiles::LogStreamRead, HOOK_JUMP).install()->quick(); - Utils::Hook(0x4BCB62, []() + Utils::Hook(0x4BCB62, [] { FastFiles::StreamRead = true; Utils::Hook::Call(0x4B8DB0)(true); // currently set to Load_GfxWorld diff --git a/src/Components/Modules/FastFiles.hpp b/src/Components/Modules/FastFiles.hpp index a1b4c235..24f0a03a 100644 --- a/src/Components/Modules/FastFiles.hpp +++ b/src/Components/Modules/FastFiles.hpp @@ -18,6 +18,8 @@ namespace Components static unsigned char ZoneKey[1191]; + static symmetric_CTR CurrentCTR; + private: union Key { @@ -38,8 +40,9 @@ namespace Components static char LastByteRead; + static Dvar::Var g_loadingInitialZones; + static Key CurrentKey; - static symmetric_CTR CurrentCTR; static std::vector ZonePaths; static const char* GetZoneLocation(const char* file); static void LoadInitialZones(Game::XZoneInfo *zoneInfo, unsigned int zoneCount, int sync); @@ -55,7 +58,6 @@ namespace Components static int AuthLoadInflateCompare(unsigned char* buffer, int length, unsigned char* ivValue); static void AuthLoadInflateDecryptBase(); static void AuthLoadInflateDecryptBaseFunc(unsigned char* buffer); - static int InflateInitDecrypt(z_streamp strm, const char *version, int stream_size); static void LoadZonesStub(Game::XZoneInfo *zoneInfo, unsigned int zoneCount); diff --git a/src/Components/Modules/FileSystem.cpp b/src/Components/Modules/FileSystem.cpp index 46ae0ca3..9762fce4 100644 --- a/src/Components/Modules/FileSystem.cpp +++ b/src/Components/Modules/FileSystem.cpp @@ -8,7 +8,7 @@ namespace Components void FileSystem::File::read(Game::FsThread thread) { - std::lock_guard _(FileSystem::FSMutex); + std::lock_guard _(FSMutex); assert(!filePath.empty()); @@ -38,14 +38,14 @@ namespace Components { this->buffer.clear(); - Game::RawFile* rawfile = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_RAWFILE, this->filePath.data()).rawfile; + auto* rawfile = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_RAWFILE, this->filePath.data()).rawfile; if (!rawfile || Game::DB_IsXAssetDefault(Game::XAssetType::ASSET_TYPE_RAWFILE, this->filePath.data())) return; this->buffer.resize(Game::DB_GetRawFileLen(rawfile)); Game::DB_GetRawBuffer(rawfile, this->buffer.data(), static_cast(this->buffer.size())); } - FileSystem::FileReader::FileReader(const std::string& file) : handle(0), name(file) + FileSystem::FileReader::FileReader(std::string file) : handle(0), name(std::move(file)) { this->size = Game::FS_FOpenFileReadCurrentThread(this->name.data(), &this->handle); } @@ -58,25 +58,25 @@ namespace Components } } - bool FileSystem::FileReader::exists() + bool FileSystem::FileReader::exists() const noexcept { return (this->size >= 0 && this->handle); } - std::string FileSystem::FileReader::getName() + std::string FileSystem::FileReader::getName() const { return this->name; } - int FileSystem::FileReader::getSize() + int FileSystem::FileReader::getSize() const noexcept { return this->size; } - std::string FileSystem::FileReader::getBuffer() + std::string FileSystem::FileReader::getBuffer() const { Utils::Memory::Allocator allocator; - if (!this->exists()) return std::string(); + if (!this->exists()) return {}; const auto position = Game::FS_FTell(this->handle); this->seek(0, Game::FS_SEEK_SET); @@ -93,9 +93,9 @@ namespace Components return {buffer, static_cast(this->size)}; } - bool FileSystem::FileReader::read(void* buffer, size_t _size) + bool FileSystem::FileReader::read(void* buffer, std::size_t _size) const noexcept { - if (!this->exists() || static_cast(this->size) < _size || Game::FS_Read(buffer, _size, this->handle) != static_cast(_size)) + if (!this->exists() || static_cast(this->size) < _size || Game::FS_Read(buffer, static_cast(_size), this->handle) != static_cast(_size)) { return false; } @@ -103,7 +103,7 @@ namespace Components return true; } - void FileSystem::FileReader::seek(int offset, int origin) + void FileSystem::FileReader::seek(int offset, int origin) const { if (this->exists()) { @@ -111,11 +111,11 @@ namespace Components } } - void FileSystem::FileWriter::write(const std::string& data) + void FileSystem::FileWriter::write(const std::string& data) const { if (this->handle) { - Game::FS_Write(data.data(), data.size(), this->handle); + Game::FS_Write(data.data(), static_cast(data.size()), this->handle); } } @@ -204,27 +204,26 @@ namespace Components bool FileSystem::_DeleteFile(const std::string& folder, const std::string& file) { - char path[MAX_PATH] = {0}; - Game::FS_BuildPathToFile(Dvar::Var("fs_basepath").get(), reinterpret_cast(0x63D0BB8), Utils::String::VA("%s/%s", folder.data(), file.data()), reinterpret_cast(&path)); + char path[MAX_PATH]{}; + Game::FS_BuildPathToFile((*Game::fs_basepath)->current.string, reinterpret_cast(0x63D0BB8), Utils::String::VA("%s/%s", folder.data(), file.data()), reinterpret_cast(&path)); return Game::FS_Remove(path); } int FileSystem::ReadFile(const char* path, char** buffer) { if (!buffer) return -1; - else *buffer = nullptr; if (!path) return -1; - std::lock_guard _(FileSystem::Mutex); - FileSystem::FileReader reader(path); + std::lock_guard _(Mutex); + FileReader reader(path); int size = reader.getSize(); if (reader.exists() && size >= 0) { - *buffer = FileSystem::AllocateFile(size + 1); + *buffer = AllocateFile(size + 1); if (reader.read(*buffer, size)) return size; - FileSystem::FreeFile(*buffer); + FreeFile(*buffer); *buffer = nullptr; } @@ -233,19 +232,19 @@ namespace Components char* FileSystem::AllocateFile(int size) { - return FileSystem::MemAllocator.allocateArray(size); + return MemAllocator.allocateArray(size); } void FileSystem::FreeFile(void* buffer) { - FileSystem::MemAllocator.free(buffer); + MemAllocator.free(buffer); } void FileSystem::RegisterFolder(const char* folder) { - const auto fs_cdpath = Dvar::Var("fs_cdpath").get(); - const auto fs_basepath = Dvar::Var("fs_basepath").get(); - const auto fs_homepath = Dvar::Var("fs_homepath").get(); + const std::string fs_cdpath = (*Game::fs_cdpath)->current.string; + const std::string fs_basepath = (*Game::fs_basepath)->current.string; + const std::string fs_homepath = (*Game::fs_homepath)->current.string; if (!fs_cdpath.empty()) Game::FS_AddLocalizedGameDirectory(fs_cdpath.data(), folder); if (!fs_basepath.empty()) Game::FS_AddLocalizedGameDirectory(fs_basepath.data(), folder); @@ -256,10 +255,10 @@ namespace Components { if (ZoneBuilder::IsEnabled()) { - FileSystem::RegisterFolder("zonedata"); + RegisterFolder("zonedata"); } - FileSystem::RegisterFolder("userraw"); + RegisterFolder("userraw"); } __declspec(naked) void FileSystem::StartupStub() @@ -286,34 +285,34 @@ namespace Components void FileSystem::FsStartupSync(const char* a1) { - std::lock_guard _(FileSystem::FSMutex); + std::lock_guard _(FSMutex); return Utils::Hook::Call(0x4823A0)(a1); // FS_Startup } - void FileSystem::FsRestartSync(int a1, int a2) + void FileSystem::FsRestartSync(int localClientNum, int checksumFeed) { - std::lock_guard _(FileSystem::FSMutex); + std::lock_guard _(FSMutex); Maps::GetUserMap()->freeIwd(); - Utils::Hook::Call(0x461A50)(a1, a2); // FS_Restart + Utils::Hook::Call(0x461A50)(localClientNum, checksumFeed); // FS_Restart Maps::GetUserMap()->reloadIwd(); } - void FileSystem::FsShutdownSync(int a1) + void FileSystem::FsShutdownSync(int closemfp) { - std::lock_guard _(FileSystem::FSMutex); + std::lock_guard _(FSMutex); Maps::GetUserMap()->freeIwd(); - Utils::Hook::Call(0x4A46C0)(a1); // FS_Shutdown + Utils::Hook::Call(0x4A46C0)(closemfp); // FS_Shutdown } void FileSystem::DelayLoadImagesSync() { - std::lock_guard _(FileSystem::FSMutex); + std::lock_guard _(FSMutex); return Utils::Hook::Call(0x494060)(); // DB_LoadDelayedImages } - int FileSystem::LoadTextureSync(Game::GfxImageLoadDef **loadDef, Game::GfxImage *image) + int FileSystem::LoadTextureSync(Game::GfxImageLoadDef** loadDef, Game::GfxImage* image) { - std::lock_guard _(FileSystem::FSMutex); + std::lock_guard _(FSMutex); return Game::Load_Texture(loadDef, image); } @@ -331,23 +330,20 @@ namespace Components FileSystem::FileSystem() { - FileSystem::MemAllocator.clear(); - // Thread safe file system interaction - Utils::Hook(0x4F4BFF, FileSystem::AllocateFile, HOOK_CALL).install()->quick(); - //Utils::Hook(Game::FS_ReadFile, FileSystem::ReadFile, HOOK_JUMP).install()->quick(); - Utils::Hook(Game::FS_FreeFile, FileSystem::FreeFile, HOOK_JUMP).install()->quick(); + Utils::Hook(0x4F4BFF, AllocateFile, HOOK_CALL).install()->quick(); + Utils::Hook(Game::FS_FreeFile, FreeFile, HOOK_JUMP).install()->quick(); // Filesystem config checks - Utils::Hook(0x6098FD, FileSystem::ExecIsFSStub, HOOK_CALL).install()->quick(); + Utils::Hook(0x6098FD, ExecIsFSStub, HOOK_CALL).install()->quick(); // Don't strip the folders from the config name (otherwise our ExecIsFSStub fails) Utils::Hook::Nop(0x6098F2, 5); // Register additional folders - Utils::Hook(0x482647, FileSystem::StartupStub, HOOK_JUMP).install()->quick(); + Utils::Hook(0x482647, StartupStub, HOOK_JUMP).install()->quick(); - // exec whitelist removal (YAYFINITY WARD) + // exec whitelist removal Utils::Hook::Nop(0x609685, 5); Utils::Hook::Nop(0x60968C, 2); @@ -355,30 +351,30 @@ namespace Components Utils::Hook::Nop(0x642A4B, 5); // Ignore bad magic, when trying to free hunk when it's already cleared - Utils::Hook::Set(0x49AACE, 0xC35E); + Utils::Hook::Set(0x49AACE, 0xC35E); // Synchronize filesystem starts - Utils::Hook(0x4290C6, FileSystem::FsStartupSync, HOOK_CALL).install()->quick(); // FS_InitFilesystem - Utils::Hook(0x461A88, FileSystem::FsStartupSync, HOOK_CALL).install()->quick(); // FS_Restart + Utils::Hook(0x4290C6, FsStartupSync, HOOK_CALL).install()->quick(); // FS_InitFilesystem + Utils::Hook(0x461A88, FsStartupSync, HOOK_CALL).install()->quick(); // FS_Restart // Synchronize filesystem restarts - Utils::Hook(0x4A745B, FileSystem::FsRestartSync, HOOK_CALL).install()->quick(); // SV_SpawnServer - Utils::Hook(0x4C8609, FileSystem::FsRestartSync, HOOK_CALL).install()->quick(); // FS_ConditionalRestart - Utils::Hook(0x5AC68E, FileSystem::FsRestartSync, HOOK_CALL).install()->quick(); // CL_ParseServerMessage + Utils::Hook(0x4A745B, FsRestartSync, HOOK_CALL).install()->quick(); // SV_SpawnServer + Utils::Hook(0x4C8609, FsRestartSync, HOOK_CALL).install()->quick(); // FS_ConditionalRestart + Utils::Hook(0x5AC68E, FsRestartSync, HOOK_CALL).install()->quick(); // CL_ParseServerMessage // Synchronize filesystem stops - Utils::Hook(0x461A55, FileSystem::FsShutdownSync, HOOK_CALL).install()->quick(); // FS_Restart - Utils::Hook(0x4D40DB, FileSystem::FsShutdownSync, HOOK_CALL).install()->quick(); // Com_Quitf + Utils::Hook(0x461A55, FsShutdownSync, HOOK_CALL).install()->quick(); // FS_Restart + Utils::Hook(0x4D40DB, FsShutdownSync, HOOK_CALL).install()->quick(); // Com_Quitf // Synchronize db image loading - Utils::Hook(0x415AB8, FileSystem::DelayLoadImagesSync, HOOK_CALL).install()->quick(); - Utils::Hook(0x4D32BC, FileSystem::LoadTextureSync, HOOK_CALL).install()->quick(); + Utils::Hook(0x415AB8, DelayLoadImagesSync, HOOK_CALL).install()->quick(); + Utils::Hook(0x4D32BC, LoadTextureSync, HOOK_CALL).install()->quick(); // Handle IWD freeing - Utils::Hook(0x642F60, FileSystem::IwdFreeStub, HOOK_CALL).install()->quick(); + Utils::Hook(0x642F60, IwdFreeStub, HOOK_CALL).install()->quick(); // Set the working dir based on info from the Xlabs launcher - Utils::Hook(0x4326E0, FileSystem::Sys_DefaultInstallPath_Hk, HOOK_JUMP).install()->quick(); + Utils::Hook(0x4326E0, Sys_DefaultInstallPath_Hk, HOOK_JUMP).install()->quick(); } FileSystem::~FileSystem() diff --git a/src/Components/Modules/FileSystem.hpp b/src/Components/Modules/FileSystem.hpp index bcdbd0e1..3a403027 100644 --- a/src/Components/Modules/FileSystem.hpp +++ b/src/Components/Modules/FileSystem.hpp @@ -10,9 +10,9 @@ namespace Components public: virtual ~AbstractFile() = default; - virtual bool exists() = 0; - virtual std::string getName() = 0; - virtual std::string& getBuffer() = 0; + [[nodiscard]] virtual bool exists() const noexcept = 0; + [[nodiscard]] virtual std::string getName() const = 0; + [[nodiscard]] virtual std::string& getBuffer() = 0; virtual explicit operator bool() { @@ -27,9 +27,9 @@ namespace Components File(std::string file) : filePath{std::move(file)} { this->read(); } File(std::string file, Game::FsThread thread) : filePath{std::move(file)} { this->read(thread); } - bool exists() override { return !this->buffer.empty(); } - std::string getName() override { return this->filePath; } - std::string& getBuffer() override { return this->buffer; } + [[nodiscard]] bool exists() const noexcept override { return !this->buffer.empty(); } + [[nodiscard]] std::string getName() const override { return this->filePath; } + [[nodiscard]] std::string& getBuffer() override { return this->buffer; } private: std::string filePath; @@ -44,9 +44,9 @@ namespace Components RawFile() = default; RawFile(std::string file) : filePath(std::move(file)) { this->read(); } - bool exists() override { return !this->buffer.empty(); } - std::string getName() override { return this->filePath; } - std::string& getBuffer() override { return this->buffer; } + [[nodiscard]] bool exists() const noexcept override { return !this->buffer.empty(); } + [[nodiscard]] std::string getName() const override { return this->filePath; } + [[nodiscard]] std::string& getBuffer() override { return this->buffer; } private: std::string filePath; @@ -58,16 +58,16 @@ namespace Components class FileReader { public: - FileReader() : handle(0), size(-1), name() {} - FileReader(const std::string& file); + FileReader() : handle(0), size(-1) {} + FileReader(std::string file); ~FileReader(); - bool exists(); - std::string getName(); - std::string getBuffer(); - int getSize(); - bool read(void* buffer, size_t size); - void seek(int offset, int origin); + [[nodiscard]] bool exists() const noexcept; + [[nodiscard]] std::string getName() const; + [[nodiscard]] std::string getBuffer() const; + [[nodiscard]] int getSize() const noexcept; + bool read(void* buffer, std::size_t size) const noexcept; + void seek(int offset, int origin) const; private: int handle; @@ -81,7 +81,7 @@ namespace Components FileWriter(std::string file, bool append = false) : handle(0), filePath(std::move(file)) { this->open(append); } ~FileWriter() { this->close(); } - void write(const std::string& data); + void write(const std::string& data) const; private: int handle; @@ -115,10 +115,10 @@ namespace Components static int ExecIsFSStub(const char* execFilename); static void FsStartupSync(const char* a1); - static void FsRestartSync(int a1, int a2); - static void FsShutdownSync(int a1); + static void FsRestartSync(int localClientNum, int checksumFeed); + static void FsShutdownSync(int closemfp); static void DelayLoadImagesSync(); - static int LoadTextureSync(Game::GfxImageLoadDef **loadDef, Game::GfxImage *image); + static int LoadTextureSync(Game::GfxImageLoadDef** loadDef, Game::GfxImage* image); static void IwdFreeStub(Game::iwd_t* iwd); diff --git a/src/Components/Modules/Flags.hpp b/src/Components/Modules/Flags.hpp index b8ee410d..5d97f740 100644 --- a/src/Components/Modules/Flags.hpp +++ b/src/Components/Modules/Flags.hpp @@ -5,8 +5,6 @@ namespace Components class Flags : public Component { public: - Flags() = default; - static bool HasFlag(const std::string& flag); private: diff --git a/src/Components/Modules/Friends.cpp b/src/Components/Modules/Friends.cpp index db4a9c27..b222ef40 100644 --- a/src/Components/Modules/Friends.cpp +++ b/src/Components/Modules/Friends.cpp @@ -1,4 +1,11 @@ #include + +#pragma warning(push) +#pragma warning(disable: 4100) +#include +#pragma warning(pop) + +#include "Party.hpp" #include "UIFeeder.hpp" namespace Components @@ -556,8 +563,7 @@ namespace Components { Friends::LoggedOn = false; - if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled()) - return; + if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled()) return; Friends::UIStreamFriendly = Dvar::Register("ui_streamFriendly", false, Game::DVAR_ARCHIVE, "Stream friendly UI"); Friends::CLAnonymous = Dvar::Register("cl_anonymous", false, Game::DVAR_ARCHIVE, "Enable invisible mode for Steam"); diff --git a/src/Components/Modules/GSC/GSC.cpp b/src/Components/Modules/GSC/GSC.cpp index 0cf74f21..46715f33 100644 --- a/src/Components/Modules/GSC/GSC.cpp +++ b/src/Components/Modules/GSC/GSC.cpp @@ -3,19 +3,25 @@ #include "Int64.hpp" #include "IO.hpp" #include "Script.hpp" +#include "ScriptError.hpp" #include "ScriptExtension.hpp" #include "ScriptPatches.hpp" #include "ScriptStorage.hpp" +#include "String.hpp" +#include "UserInfo.hpp" -namespace Components +namespace Components::GSC { GSC::GSC() { Loader::Register(new Int64()); Loader::Register(new IO()); Loader::Register(new Script()); + Loader::Register(new ScriptError()); Loader::Register(new ScriptExtension()); Loader::Register(new ScriptPatches()); Loader::Register(new ScriptStorage()); + Loader::Register(new String()); + Loader::Register(new UserInfo()); } } diff --git a/src/Components/Modules/GSC/GSC.hpp b/src/Components/Modules/GSC/GSC.hpp index 05d8fc69..666550ea 100644 --- a/src/Components/Modules/GSC/GSC.hpp +++ b/src/Components/Modules/GSC/GSC.hpp @@ -1,6 +1,6 @@ #pragma once -namespace Components +namespace Components::GSC { class GSC : public Component { diff --git a/src/Components/Modules/GSC/IO.cpp b/src/Components/Modules/GSC/IO.cpp index 5116d32c..203fcbbc 100644 --- a/src/Components/Modules/GSC/IO.cpp +++ b/src/Components/Modules/GSC/IO.cpp @@ -2,7 +2,7 @@ #include "IO.hpp" #include "Script.hpp" -namespace Components +namespace Components::GSC { const char* IO::QueryStrings[] = { R"(..)", R"(../)", R"(..\)" }; @@ -14,15 +14,15 @@ namespace Components auto* text = Game::Scr_GetString(1); auto* mode = Game::Scr_GetString(2); - if (path == nullptr) + if (!path) { - Game::Scr_ParamError(0, "^1FileWrite: filepath is not defined!\n"); + Game::Scr_ParamError(0, "FileWrite: filepath is not defined!"); return; } - if (text == nullptr || mode == nullptr) + if (!text || !mode) { - Game::Scr_Error("^1FileWrite: Illegal parameters!\n"); + Game::Scr_Error("FileWrite: Illegal parameters!"); return; } @@ -57,9 +57,9 @@ namespace Components { const auto* path = Game::Scr_GetString(0); - if (path == nullptr) + if (!path) { - Game::Scr_ParamError(0, "^1FileRead: filepath is not defined!\n"); + Game::Scr_ParamError(0, "FileRead: filepath is not defined!"); return; } @@ -74,22 +74,25 @@ namespace Components const auto* scriptData = Utils::String::VA("%s/%s", "scriptdata", path); - if (!FileSystem::FileReader(scriptData).exists()) + const auto file = FileSystem::FileReader(scriptData); + if (!file.exists()) { Logger::PrintError(Game::CON_CHANNEL_ERROR, "FileRead: file '{}' not found!\n", scriptData); return; } - Game::Scr_AddString(FileSystem::FileReader(scriptData).getBuffer().data()); + auto buffer = file.getBuffer(); + buffer = buffer.substr(0, 1024 - 1); // 1024 is the max string size for the SL system + Game::Scr_AddString(buffer.data()); }); Script::AddFunction("FileExists", [] // gsc: FileExists() { const auto* path = Game::Scr_GetString(0); - if (path == nullptr) + if (!path) { - Game::Scr_ParamError(0, "^1FileExists: filepath is not defined!\n"); + Game::Scr_ParamError(0, "FileExists: filepath is not defined!"); return; } @@ -103,7 +106,6 @@ namespace Components } const auto* scriptData = Utils::String::VA("%s/%s", "scriptdata", path); - Game::Scr_AddBool(FileSystem::FileReader(scriptData).exists()); }); @@ -111,9 +113,9 @@ namespace Components { const auto* path = Game::Scr_GetString(0); - if (path == nullptr) + if (!path) { - Game::Scr_ParamError(0, "^1FileRemove: filepath is not defined!\n"); + Game::Scr_ParamError(0, "FileRemove: filepath is not defined!"); return; } @@ -121,7 +123,7 @@ namespace Components { if (std::strstr(path, QueryStrings[i]) != nullptr) { - Logger::Print("^1FileRemove: directory traversal is not allowed!\n"); + Logger::Print("FileRemove: directory traversal is not allowed!\n"); return; } } diff --git a/src/Components/Modules/GSC/IO.hpp b/src/Components/Modules/GSC/IO.hpp index d9a2a758..a4df0788 100644 --- a/src/Components/Modules/GSC/IO.hpp +++ b/src/Components/Modules/GSC/IO.hpp @@ -1,6 +1,6 @@ #pragma once -namespace Components +namespace Components::GSC { class IO : public Component { diff --git a/src/Components/Modules/GSC/Int64.cpp b/src/Components/Modules/GSC/Int64.cpp index 1d4247b2..f091fead 100644 --- a/src/Components/Modules/GSC/Int64.cpp +++ b/src/Components/Modules/GSC/Int64.cpp @@ -4,7 +4,7 @@ #define INT64_OPERATION(expr) [](const std::int64_t a, [[maybe_unused]] const std::int64_t b) { return expr; } -namespace Components +namespace Components::GSC { std::unordered_map Int64::Operations = { diff --git a/src/Components/Modules/GSC/Int64.hpp b/src/Components/Modules/GSC/Int64.hpp index cbdc79f5..5187e395 100644 --- a/src/Components/Modules/GSC/Int64.hpp +++ b/src/Components/Modules/GSC/Int64.hpp @@ -1,6 +1,6 @@ #pragma once -namespace Components +namespace Components::GSC { class Int64 : public Component { diff --git a/src/Components/Modules/GSC/Script.cpp b/src/Components/Modules/GSC/Script.cpp index e662ccf6..7cd888d3 100644 --- a/src/Components/Modules/GSC/Script.cpp +++ b/src/Components/Modules/GSC/Script.cpp @@ -1,208 +1,14 @@ #include #include "Script.hpp" -namespace Components +namespace Components::GSC { std::vector Script::CustomScrFunctions; std::vector Script::CustomScrMethods; - std::string Script::ScriptName; - std::vector Script::ScriptNameStack; - unsigned short Script::FunctionName; - std::unordered_map Script::ScriptBaseProgramNum; - int Script::LastFrameTime = -1; - - std::unordered_map Script::ReplacedFunctions; - const char* Script::ReplacedPos = nullptr; - std::unordered_map Script::ScriptMainHandles; std::unordered_map Script::ScriptInitHandles; - void Script::FunctionError() - { - const auto* funcName = Game::SL_ConvertToString(FunctionName); - - Game::Scr_ShutdownAllocNode(); - - Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "\n"); - Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "******* script compile error *******\n"); - Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "Error: unknown function {} in {}\n", funcName, ScriptName); - Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "************************************\n"); - - Logger::Error(Game::ERR_SCRIPT_DROP, "script compile error\nunknown function {}\n{}\n\n", funcName, ScriptName); - } - - __declspec(naked) void Script::StoreFunctionNameStub() - { - __asm - { - mov eax, [esp - 8h] - mov FunctionName, ax - - sub esp, 0Ch - push 0 - push edi - - mov eax, 612DB6h - jmp eax - } - } - - void Script::RuntimeError(const char* codePos, unsigned int index, const char* msg, const char* dialogMessage) - { - // Allow error messages to be printed if developer mode is on - // Should check scrVarPub.developer but it's absent - // in this version of the game so let's check the dvar - if (!Game::scrVmPub->terminal_error && !(*Game::com_developer)->current.integer) - return; - - // If were are developing let's call RuntimeErrorInternal - // scrVmPub.debugCode seems to be always false - if (Game::scrVmPub->debugCode || Game::scrVarPub->developer_script) - { - Game::RuntimeErrorInternal(Game::CON_CHANNEL_PARSERSCRIPT, codePos, index, msg); - } - else - { - Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "{}\n", msg); - } - - // Let's not throw error unless we have to - if (Game::scrVmPub->terminal_error) - { - if (dialogMessage == nullptr) - dialogMessage = ""; - - Logger::Error(Game::ERR_SCRIPT_DROP, "\x15script runtime error\n(see console for details)\n{}\n{}", msg, dialogMessage); - } - } - - void Script::StoreScriptName(const char* name) - { - ScriptNameStack.push_back(ScriptName); - ScriptName = name; - - if (!Utils::String::EndsWith(ScriptName, ".gsc")) - { - ScriptName.append(".gsc"); - } - } - - __declspec(naked) void Script::StoreScriptNameStub() - { - __asm - { - pushad - - lea ecx, [esp + 30h] - push ecx - - call StoreScriptName - add esp, 4h - - popad - - push ebp - mov ebp, ds:1CDEAA8h - - push 427DC3h - retn - } - } - - void Script::RestoreScriptName() - { - ScriptName = ScriptNameStack.back(); - ScriptNameStack.pop_back(); - } - - __declspec(naked) void Script::RestoreScriptNameStub() - { - __asm - { - pushad - call RestoreScriptName - popad - - mov ds:1CDEAA8h, ebp - - push 427E77h - retn - } - } - - void Script::PrintSourcePos(const char* filename, unsigned int offset) - { - FileSystem::File script(filename); - - if (script.exists()) - { - std::string buffer = script.getBuffer(); - Utils::String::Replace(buffer, "\t", " "); - - auto line = 1, lineOffset = 0, inlineOffset = 0; - - for (size_t i = 0; i < buffer.size(); ++i) - { - // Terminate line - if (i == offset) - { - while (buffer[i] != '\r' && buffer[i] != '\n' && buffer[i] != '\0') - { - ++i; - } - - buffer[i] = '\0'; - break; - } - - if (buffer[i] == '\n') - { - ++line; - lineOffset = static_cast(i); // Includes the line break! - inlineOffset = 0; - } - else - { - ++inlineOffset; - } - } - - Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "in file {}, line {}:", filename, line); - Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "{}\n", buffer.substr(lineOffset)); - - for (auto i = 0; i < (inlineOffset - 1); ++i) - { - Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, " "); - } - - Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "*\n"); - } - else - { - Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "in file {}, offset {}\n", filename, offset); - } - } - - void Script::CompileError(unsigned int offset, const char* message, ...) - { - char msgbuf[1024] = {0}; - va_list va; - va_start(va, message); - _vsnprintf_s(msgbuf, _TRUNCATE, message, va); - va_end(va); - - Game::Scr_ShutdownAllocNode(); - - Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "\n"); - Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "******* script compile error *******\n"); - Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "Error: {} ", msgbuf); - PrintSourcePos(ScriptName.data(), offset); - Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "************************************\n\n"); - - Logger::Error(Game::ERR_SCRIPT_DROP, "script compile error\n{}\n{}\n(see console for actual details)\n", msgbuf, ScriptName); - } - void Script::Scr_LoadGameType_Stub() { for (const auto& handle : ScriptMainHandles) @@ -246,10 +52,14 @@ namespace Components const auto* scriptFile = files[i]; Logger::Print("Loading script {}...\n", scriptFile); - sprintf_s(path, "%s/%s", "scripts", scriptFile); + const auto len = sprintf_s(path, "%s/%s", "scripts", scriptFile); + if (len == -1) + { + continue; + } // Scr_LoadScriptInternal will add the '.gsc' suffix so we remove it - path[std::strlen(path) - 4] = '\0'; + path[len - 4] = '\0'; if (!Game::Scr_LoadScript(path)) { @@ -376,77 +186,6 @@ namespace Components return Utils::Hook::Call(0x5FA360)(pName, type); // Player_GetMethod } - void Script::StoreScriptBaseProgramNum() - { - ScriptBaseProgramNum.insert_or_assign(Utils::Hook::Get(0x1CFEEF8), ScriptName); - } - - void Script::Scr_PrintPrevCodePos(int scriptPos) - { - auto bestCodePos = -1, nextCodePos = -1, offset = -1; - std::string file; - - for (const auto& [key, value] : ScriptBaseProgramNum) - { - const auto codePos = key; - - if (codePos > scriptPos) - { - if (nextCodePos == -1 || codePos < nextCodePos) - nextCodePos = codePos; - - continue; - } - - if (codePos < bestCodePos) - continue; - - bestCodePos = codePos; - - file = value; - offset = scriptPos - bestCodePos; - } - - if (bestCodePos == -1) - return; - - Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "\n@ {} ({} - {})\n", scriptPos, bestCodePos, nextCodePos); - Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "in {} ({} through the source)\n\n", file, ((offset * 100.0f) / (nextCodePos - bestCodePos))); - } - - __declspec(naked) void Script::Scr_PrintPrevCodePosStub() - { - __asm - { - push esi - call Scr_PrintPrevCodePos - add esp, 4h - - pop esi - retn - } - } - - __declspec(naked) void Script::StoreScriptBaseProgramNumStub() - { - __asm - { - // execute our hook - pushad - call StoreScriptBaseProgramNum - popad - - // execute overwritten code caused by the jump hook - sub eax, ds:201A460h // gScrVarPub_programBuffer - add esp, 0Ch - mov ds:1CFEEF8h, eax // gScrCompilePub_programLen - - // jump back to the original code - push 426C3Bh - retn - } - } - unsigned int Script::SetExpFogStub() { if (Game::Scr_GetNumParam() == 6) @@ -462,99 +201,11 @@ namespace Components return Game::Scr_GetNumParam(); } - const char* Script::GetCodePosForParam(int index) - { - if (static_cast(index) >= Game::scrVmPub->outparamcount) - { - Game::Scr_ParamError(static_cast(index), "^1GetCodePosForParam: Index is out of range!\n"); - return ""; - } - - const auto* value = &Game::scrVmPub->top[-index]; - - if (value->type != Game::VAR_FUNCTION) - { - Game::Scr_ParamError(static_cast(index), "^1GetCodePosForParam: Expects a function as parameter!\n"); - return ""; - } - - return value->u.codePosValue; - } - - void Script::GetReplacedPos(const char* pos) - { - if (ReplacedFunctions.contains(pos)) - { - ReplacedPos = ReplacedFunctions[pos]; - } - } - - void Script::SetReplacedPos(const char* what, const char* with) - { - if (what[0] == '\0' || with[0] == '\0') - { - Logger::Warning(Game::CON_CHANNEL_SCRIPT, "Invalid parameters passed to ReplacedFunctions\n"); - return; - } - - if (ReplacedFunctions.contains(what)) - { - Logger::Warning(Game::CON_CHANNEL_SCRIPT, "ReplacedFunctions already contains codePosValue for a function\n"); - } - - ReplacedFunctions[what] = with; - } - - __declspec(naked) void Script::VMExecuteInternalStub() - { - __asm - { - pushad - - push edx - call GetReplacedPos - - pop edx - popad - - cmp ReplacedPos, 0 - jne SetPos - - movzx eax, byte ptr [edx] - inc edx - - Loc1: - cmp eax, 0x8B - - push ecx - - mov ecx, 0x2045094 - mov [ecx], eax - - mov ecx, 0x2040CD4 - mov [ecx], edx - - pop ecx - - push 0x61E944 - retn - - SetPos: - mov edx, ReplacedPos - mov ReplacedPos, 0 - - movzx eax, byte ptr [edx] - inc edx - - jmp Loc1 - } - } - Game::client_t* Script::GetClient(const Game::gentity_t* ent) { assert(ent); - if (ent->client == nullptr) + if (!ent->client) { Game::Scr_ObjectError(Utils::String::VA("Entity %i is not a player", ent->s.number)); return nullptr; @@ -569,90 +220,32 @@ namespace Components return &Game::svs_clients[ent->s.number]; } - void Script::AddFunctions() + Game::gentity_s* Script::Scr_GetPlayerEntity(Game::scr_entref_t entref) { - AddFunction("ReplaceFunc", [] // gsc: ReplaceFunc(, ) + if (entref.classnum) { - if (Game::Scr_GetNumParam() != 2) - { - Game::Scr_Error("^1ReplaceFunc: Needs two parameters!\n"); - return; - } + Game::Scr_ObjectError("not an entity"); + return nullptr; + } - const auto what = GetCodePosForParam(0); - const auto with = GetCodePosForParam(1); + assert(entref.entnum < Game::MAX_GENTITIES); - SetReplacedPos(what, with); - }); - - // System time - AddFunction("GetSystemMilliseconds", [] // gsc: GetSystemMilliseconds() + auto* ent = &Game::g_entities[entref.entnum]; + if (!ent->client) { - SYSTEMTIME time; - GetSystemTime(&time); + Game::Scr_ObjectError(Utils::String::VA("entity %i is not a player", entref.entnum)); + return nullptr; + } - Game::Scr_AddInt(time.wMilliseconds); - }); - - // Executes command to the console - AddFunction("Exec", [] // gsc: Exec() - { - const auto str = Game::Scr_GetString(0); - - if (str == nullptr) - { - Game::Scr_ParamError(0, "^1Exec: Illegal parameter!\n"); - return; - } - - Command::Execute(str, false); - }); - - // Allow printing to the console even when developer is 0 - AddFunction("PrintConsole", [] // gsc: PrintConsole() - { - for (std::size_t i = 0; i < Game::Scr_GetNumParam(); ++i) - { - const auto* str = Game::Scr_GetString(i); - - if (str == nullptr) - { - Game::Scr_ParamError(i, "^1PrintConsole: Illegal parameter!\n"); - return; - } - - Logger::Print(Game::level->scriptPrintChannel, "{}", str); - } - }); - - // PlayerCmd_AreControlsFrozen GSC function from Black Ops 2 - AddMethod("AreControlsFrozen", [](Game::scr_entref_t entref) // Usage: self AreControlsFrozen(); - { - const auto* ent = Scr_GetPlayerEntity(entref); - - Game::Scr_AddBool((ent->client->flags & Game::PLAYER_FLAG_FROZEN) != 0); - }); + return ent; } Script::Script() { - Utils::Hook(0x612DB0, StoreFunctionNameStub, HOOK_JUMP).install()->quick(); - Utils::Hook(0x427E71, RestoreScriptNameStub, HOOK_JUMP).install()->quick(); - Utils::Hook(0x427DBC, StoreScriptNameStub, HOOK_JUMP).install()->quick(); - Utils::Hook(0x426C2D, StoreScriptBaseProgramNumStub, HOOK_JUMP).install()->quick(); - Utils::Hook(0x42281B, Scr_PrintPrevCodePosStub, HOOK_JUMP).install()->quick(); - - Utils::Hook(0x61E3AD, RuntimeError, HOOK_CALL).install()->quick(); - Utils::Hook(0x621976, RuntimeError, HOOK_CALL).install()->quick(); - Utils::Hook(0x62246E, RuntimeError, HOOK_CALL).install()->quick(); // Skip check in GScr_CheckAllowedToSetPersistentData to prevent log spam in RuntimeError. // On IW5 the function is entirely nullsubbed Utils::Hook::Set(0x5F8DBF, 0xEB); - Utils::Hook(0x612E8D, FunctionError, HOOK_CALL).install()->quick(); - Utils::Hook(0x612EA2, FunctionError, HOOK_CALL).install()->quick(); - Utils::Hook(0x434260, CompileError, HOOK_JUMP).install()->quick(); - Utils::Hook(0x48EFFE, Scr_LoadGameType_Stub, HOOK_CALL).install()->quick(); Utils::Hook(0x48F008, Scr_StartupGameType_Stub, HOOK_CALL).install()->quick(); Utils::Hook(0x45D44A, GScr_LoadGameTypeScript_Stub, HOOK_CALL).install()->quick(); @@ -662,49 +255,5 @@ namespace Components Utils::Hook(0x4EC8DD, BuiltIn_GetMethodStub, HOOK_CALL).install()->quick(); // Scr_GetMethod Utils::Hook(0x5F41A3, SetExpFogStub, HOOK_CALL).install()->quick(); - - Utils::Hook(0x61E92E, VMExecuteInternalStub, HOOK_JUMP).install()->quick(); - Utils::Hook::Nop(0x61E933, 1); - - Scheduler::Loop([] - { - if (!Game::SV_Loaded()) - return; - - const auto nowMs = Game::Sys_Milliseconds(); - - if (LastFrameTime != -1) - { - const auto timeTaken = (nowMs - LastFrameTime) * static_cast((*Game::com_timescale)->current.value); - - if (timeTaken >= 500) - { - Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "Hitch warning: {} msec frame time\n", timeTaken); - } - } - - LastFrameTime = nowMs; - }, Scheduler::Pipeline::SERVER); - -#ifdef _DEBUG - AddFunction("DebugBox", [] - { - const auto* message = Game::Scr_GetString(0); - - if (message == nullptr) - { - Game::Scr_Error("^1DebugBox: Illegal parameter!\n"); - } - - MessageBoxA(nullptr, message, "DEBUG", MB_OK); - }, true); -#endif - - AddFunctions(); - - Events::OnVMShutdown([] - { - ReplacedFunctions.clear(); - }); } } diff --git a/src/Components/Modules/GSC/Script.hpp b/src/Components/Modules/GSC/Script.hpp index c3a27a00..7ca0cb24 100644 --- a/src/Components/Modules/GSC/Script.hpp +++ b/src/Components/Modules/GSC/Script.hpp @@ -1,6 +1,6 @@ #pragma once -namespace Components +namespace Components::GSC { class Script : public Component { @@ -15,29 +15,8 @@ namespace Components static void AddMethMultiple(Game::BuiltinMethod func, bool type, scriptNames); static Game::client_t* GetClient(const Game::gentity_t* gentity); - - static const char* GetCodePosForParam(int index); - // Probably a macro 'originally' but this is fine - static Game::gentity_s* Scr_GetPlayerEntity(Game::scr_entref_t entref) - { - if (entref.classnum != 0) - { - Game::Scr_ObjectError("not an entity"); - return nullptr; - } - - assert(entref.entnum < Game::MAX_GENTITIES); - - auto* ent = &Game::g_entities[entref.entnum]; - if (ent->client == nullptr) - { - Game::Scr_ObjectError(Utils::String::VA("entity %i is not a player", entref.entnum)); - return nullptr; - } - - return ent; - } + static Game::gentity_s* Scr_GetPlayerEntity(Game::scr_entref_t entref); private: struct ScriptFunction @@ -57,31 +36,9 @@ namespace Components static std::vector CustomScrFunctions; static std::vector CustomScrMethods; - static std::string ScriptName; - static std::vector ScriptNameStack; - static unsigned short FunctionName; - static std::unordered_map ScriptBaseProgramNum; - static int LastFrameTime; - static std::unordered_map ScriptMainHandles; static std::unordered_map ScriptInitHandles; - static std::unordered_map ReplacedFunctions; - static const char* ReplacedPos; - - static void CompileError(unsigned int offset, const char* message, ...); - static void PrintSourcePos(const char* filename, unsigned int offset); - - static void FunctionError(); - static void StoreFunctionNameStub(); - static void RuntimeError(const char* codePos, unsigned int index, const char* msg, const char* dialogMessage); - - static void StoreScriptName(const char* name); - static void StoreScriptNameStub(); - - static void RestoreScriptName(); - static void RestoreScriptNameStub(); - static void Scr_LoadGameType_Stub(); static void Scr_StartupGameType_Stub(); static void GScr_LoadGameTypeScript_Stub(); @@ -89,17 +46,6 @@ namespace Components static Game::BuiltinFunction BuiltIn_GetFunctionStub(const char** pName, int* type); static Game::BuiltinMethod BuiltIn_GetMethodStub(const char** pName, int* type); - static void StoreScriptBaseProgramNumStub(); - static void StoreScriptBaseProgramNum(); - static void Scr_PrintPrevCodePosStub(); - static void Scr_PrintPrevCodePos(int); - static unsigned int SetExpFogStub(); - - static void GetReplacedPos(const char* pos); - static void SetReplacedPos(const char* what, const char* with); - static void VMExecuteInternalStub(); - - static void AddFunctions(); }; } diff --git a/src/Components/Modules/GSC/ScriptError.cpp b/src/Components/Modules/GSC/ScriptError.cpp new file mode 100644 index 00000000..8b4def7b --- /dev/null +++ b/src/Components/Modules/GSC/ScriptError.cpp @@ -0,0 +1,1123 @@ +#include +#include "Game/Engine/Hunk.hpp" + +#include "ScriptError.hpp" + +#define SCRIPT_ERROR_PATCH + +namespace Components::GSC +{ + using namespace Utils::String; + + int ScriptError::developer_; + + Game::scrParserGlob_t ScriptError::scrParserGlob; + Game::scrParserPub_t ScriptError::scrParserPub; + + int ScriptError::Scr_IsInOpcodeMemory(const char* pos) + { + assert(Game::scrVarPub->programBuffer); + assert(pos); + + return pos - Game::scrVarPub->programBuffer < static_cast(Game::scrCompilePub->programLen); + } + + void ScriptError::AddOpcodePos(unsigned int sourcePos, int type) + { + Game::OpcodeLookup* opcodeLookup; + Game::SourceLookup* sourcePosLookup; + + if (!developer_) + { + return; + } + + if (Game::scrCompilePub->developer_statement == 2) + { + assert(!Game::scrVarPub->developer_script); + return; + } + + if (Game::scrCompilePub->developer_statement == 3) + { + return; + } + + if (!Game::scrCompilePub->allowedBreakpoint) + { + type &= ~Game::SOURCE_TYPE_BREAKPOINT; + } + + assert(scrParserGlob.opcodeLookup); + assert(scrParserGlob.sourcePosLookup); + assert(Game::scrCompilePub->opcodePos); + + auto size = sizeof(Game::OpcodeLookup) * (scrParserGlob.opcodeLookupLen + 1); + if (size > scrParserGlob.opcodeLookupMaxSize) + { + if (scrParserGlob.opcodeLookupMaxSize >= Game::MAX_OPCODE_LOOKUP_SIZE) + { + Game::Sys_Error("MAX_OPCODE_LOOKUP_SIZE exceeded"); + } + + Game::Z_VirtualCommit((char*)scrParserGlob.opcodeLookup + scrParserGlob.opcodeLookupMaxSize, 0x20000); + scrParserGlob.opcodeLookupMaxSize += 0x20000; + assert(size <= scrParserGlob.opcodeLookupMaxSize); + } + + size = sizeof(Game::SourceLookup) * (scrParserGlob.sourcePosLookupLen + 1); + if (size > scrParserGlob.sourcePosLookupMaxSize) + { + if (scrParserGlob.sourcePosLookupMaxSize >= Game::MAX_SOURCEPOS_LOOKUP_SIZE) + { + Game::Sys_Error("MAX_SOURCEPOS_LOOKUP_SIZE exceeded"); + } + + Game::Z_VirtualCommit((char*)scrParserGlob.sourcePosLookup + scrParserGlob.sourcePosLookupMaxSize, 0x20000); + scrParserGlob.sourcePosLookupMaxSize += 0x20000; + assert(size <= scrParserGlob.sourcePosLookupMaxSize); + } + + if (scrParserGlob.currentCodePos == Game::scrCompilePub->opcodePos) + { + assert(scrParserGlob.currentSourcePosCount); + --scrParserGlob.opcodeLookupLen; + opcodeLookup = &scrParserGlob.opcodeLookup[scrParserGlob.opcodeLookupLen]; + assert(opcodeLookup->sourcePosIndex + scrParserGlob.currentSourcePosCount == scrParserGlob.sourcePosLookupLen); + assert(opcodeLookup->codePos == (char*)scrParserGlob.currentCodePos); + } + else + { + scrParserGlob.currentSourcePosCount = 0; + scrParserGlob.currentCodePos = Game::scrCompilePub->opcodePos; + opcodeLookup = &scrParserGlob.opcodeLookup[scrParserGlob.opcodeLookupLen]; + opcodeLookup->sourcePosIndex = scrParserGlob.sourcePosLookupLen; + opcodeLookup->codePos = scrParserGlob.currentCodePos; + } + + auto sourcePosLookupIndex = scrParserGlob.currentSourcePosCount + opcodeLookup->sourcePosIndex; + sourcePosLookup = &scrParserGlob.sourcePosLookup[sourcePosLookupIndex]; + sourcePosLookup->sourcePos = sourcePos; + + if (sourcePos == static_cast(-1)) + { + assert(scrParserGlob.delayedSourceIndex == -1); + assert(type & Game::SOURCE_TYPE_BREAKPOINT); + scrParserGlob.delayedSourceIndex = static_cast(sourcePosLookupIndex); + } + else if (sourcePos == static_cast(-2)) + { + scrParserGlob.threadStartSourceIndex = static_cast(sourcePosLookupIndex); + } + else if (scrParserGlob.delayedSourceIndex >= 0 && (type & Game::SOURCE_TYPE_BREAKPOINT)) + { + scrParserGlob.sourcePosLookup[scrParserGlob.delayedSourceIndex].sourcePos = sourcePos; + scrParserGlob.delayedSourceIndex = -1; + } + + sourcePosLookup->type |= type; + ++scrParserGlob.currentSourcePosCount; + opcodeLookup->sourcePosCount = static_cast(scrParserGlob.currentSourcePosCount); + ++scrParserGlob.opcodeLookupLen; + ++scrParserGlob.sourcePosLookupLen; + } + + void ScriptError::RemoveOpcodePos() + { + if (!developer_) + { + return; + } + + if (Game::scrCompilePub->developer_statement == 2) + { + assert(!Game::scrVarPub->developer_script); + return; + } + + assert(scrParserGlob.opcodeLookup); + assert(scrParserGlob.sourcePosLookup); + assert(Game::scrCompilePub->opcodePos); + assert(scrParserGlob.sourcePosLookupLen); + + --scrParserGlob.sourcePosLookupLen; + assert(scrParserGlob.opcodeLookupLen); + + --scrParserGlob.opcodeLookupLen; + assert(scrParserGlob.currentSourcePosCount); + --scrParserGlob.currentSourcePosCount; + + auto* opcodeLookup = &scrParserGlob.opcodeLookup[scrParserGlob.opcodeLookupLen]; + + assert(scrParserGlob.currentCodePos == Game::scrCompilePub->opcodePos); + assert(opcodeLookup->sourcePosIndex + scrParserGlob.currentSourcePosCount == scrParserGlob.sourcePosLookupLen); + assert(opcodeLookup->codePos == (char*)scrParserGlob.currentCodePos); + + if (!scrParserGlob.currentSourcePosCount) + { + scrParserGlob.currentCodePos = nullptr; + } + + opcodeLookup->sourcePosCount = static_cast(scrParserGlob.currentSourcePosCount); + } + + void ScriptError::AddThreadStartOpcodePos(unsigned int sourcePos) + { + if (!developer_) + { + return; + } + + if (Game::scrCompilePub->developer_statement == 2) + { + assert(!Game::scrVarPub->developer_script); + } + else + { + assert(scrParserGlob.threadStartSourceIndex >= 0); + auto* sourcePosLookup = &scrParserGlob.sourcePosLookup[scrParserGlob.threadStartSourceIndex]; + sourcePosLookup->sourcePos = sourcePos; + assert(!sourcePosLookup->type); + sourcePosLookup->type = 8; + scrParserGlob.threadStartSourceIndex = -1; + } + } + + int ScriptError::Scr_GetLineNum(unsigned int bufferIndex, unsigned int sourcePos) + { + const char* startLine; + int col; + + assert(developer_); + return Scr_GetLineNumInternal(scrParserPub.sourceBufferLookup[bufferIndex].sourceBuf, sourcePos, &startLine, &col, nullptr); + } + + unsigned int ScriptError::Scr_GetPrevSourcePos(const char* codePos, unsigned int index) + { + return scrParserGlob.sourcePosLookup[index + Scr_GetPrevSourcePosOpcodeLookup(codePos)->sourcePosIndex].sourcePos; + } + + Game::OpcodeLookup* ScriptError::Scr_GetPrevSourcePosOpcodeLookup(const char* codePos) + { + assert(Scr_IsInOpcodeMemory(codePos)); + assert(scrParserGlob.opcodeLookup); + + unsigned int low = 0; + unsigned int high = scrParserGlob.opcodeLookupLen - 1; + while (low <= high) + { + unsigned int middle = (high + low) >> 1; + if (codePos < scrParserGlob.opcodeLookup[middle].codePos) + { + high = middle - 1; + } + else + { + low = middle + 1; + if (low == scrParserGlob.opcodeLookupLen || codePos < scrParserGlob.opcodeLookup[low].codePos) + { + return &scrParserGlob.opcodeLookup[middle]; + } + } + } + + AssertUnreachable; + return nullptr; + } + + void ScriptError::Scr_CopyFormattedLine(char* line, const char* rawLine) + { + auto len = static_cast(std::strlen(rawLine)); + if (len >= 1024) + { + len = 1024 - 1; + } + + for (auto i = 0; i < len; ++i) + { + if (rawLine[i] == '\t') + { + line[i] = ' '; + } + else + { + line[i] = rawLine[i]; + } + } + + if (line[len - 1] == '\r') + { + line[len - 1] = '\0'; + } + + line[len] = '\0'; + } + + int ScriptError::Scr_GetLineNumInternal(const char* buf, unsigned int sourcePos, const char** startLine, int* col, [[maybe_unused]] Game::SourceBufferInfo* binfo) + { + assert(buf); + + *startLine = buf; + unsigned int lineNum = 0; + while (sourcePos) + { + if (!*buf) + { + *startLine = buf + 1; + ++lineNum; + } + ++buf; + --sourcePos; + } + + *col = buf - *startLine; + return static_cast(lineNum); + } + + unsigned int ScriptError::Scr_GetSourceBuffer(const char* codePos) + { + unsigned int bufferIndex; + + assert(Scr_IsInOpcodeMemory(codePos)); + assert(scrParserPub.sourceBufferLookupLen > 0); + + for (bufferIndex = scrParserPub.sourceBufferLookupLen - 1; bufferIndex; --bufferIndex) + { + if (!scrParserPub.sourceBufferLookup[bufferIndex].codePos) + { + continue; + } + + if (scrParserPub.sourceBufferLookup[bufferIndex].codePos > codePos) + { + continue; + } + + break; + } + + return bufferIndex; + } + + void ScriptError::Scr_PrintPrevCodePos(int channel, const char* codePos, unsigned int index) + { + if (!codePos) + { + Game::Com_PrintMessage(channel, "\n", 0); + return; + } + + if (codePos == Game::g_EndPos) + { + Game::Com_PrintMessage(channel, "\n", 0); + return; + } + + if (!developer_) + { + if (Scr_IsInOpcodeMemory(codePos - 1)) + { + Game::Com_PrintMessage(channel, VA("@ %d\n", codePos - Game::scrVarPub->programBuffer), 0); + return; + } + } + else + { + if (Game::scrVarPub->programBuffer && Scr_IsInOpcodeMemory(codePos)) + { + auto bufferIndex = Scr_GetSourceBuffer(codePos - 1); + Scr_PrintSourcePos(channel, scrParserPub.sourceBufferLookup[bufferIndex].buf, scrParserPub.sourceBufferLookup[bufferIndex].sourceBuf, Scr_GetPrevSourcePos(codePos - 1, index)); + return; + } + } + + Game::Com_PrintMessage(channel, VA("%s\n\n", codePos), 0); + } + + int ScriptError::Scr_GetLineInfo(const char* buf, unsigned int sourcePos, int* col, char* line, Game::SourceBufferInfo* binfo) + { + const char* startLine; + unsigned int lineNum; + + if (buf) + { + lineNum = Scr_GetLineNumInternal(buf, sourcePos, &startLine, col, binfo); + } + else + { + lineNum = 0; + startLine = ""; + *col = 0; + } + + Scr_CopyFormattedLine(line, startLine); + return static_cast(lineNum); + } + + void ScriptError::Scr_PrintSourcePos(int channel, const char* filename, const char* buf, unsigned int sourcePos) + { + char line[1024]; + int col; + + assert(filename); + auto lineNum = Scr_GetLineInfo(buf, sourcePos, &col, line, nullptr); + + Game::Com_PrintMessage(channel, VA("(file '%s'%s, line %d)\n", filename, scrParserGlob.saveSourceBufferLookup ? " (savegame)" : "", lineNum + 1), 0); + Game::Com_PrintMessage(channel, VA("%s\n", line), 0); + + for (auto i = 0; i < col; ++i) + { + Game::Com_PrintMessage(channel, " ", 0); + } + + Game::Com_PrintMessage(channel, "*\n", 0); + } + + void ScriptError::RuntimeErrorInternal(int channel, const char* codePos, unsigned int index, const char* msg) + { + assert(Scr_IsInOpcodeMemory(codePos)); + + Game::Com_PrintError(channel, "\n******* script runtime error *******\n%s: ", msg); + Scr_PrintPrevCodePos(channel, codePos, index); + + if (Game::scrVmPub->function_count) + { + for (auto i = Game::scrVmPub->function_count - 1; i >= 1; --i) + { + Game::Com_PrintError(channel, "called from:\n"); + Scr_PrintPrevCodePos(0, Game::scrVmPub->function_frame_start[i].fs.pos, Game::scrVmPub->function_frame_start[i].fs.localId == 0); + } + + Game::Com_PrintError(channel, "started from:\n"); + Scr_PrintPrevCodePos(0, Game::scrVmPub->function_frame_start[0].fs.pos, 1); + } + + Game::Com_PrintError(channel, "************************************\n"); + } + + void ScriptError::RuntimeError(const char* codePos, unsigned int index, const char* msg, const char* dialogMessage) + { + bool abort_on_error; + const char* dialogMessageSeparator; + + if (!developer_) + { + assert(Scr_IsInOpcodeMemory(codePos)); + if (!(*Game::com_developer)->current.enabled) + { + return; + } + } + + if (Game::scrVmPub->debugCode) + { + Game::Com_Printf(Game::CON_CHANNEL_PARSERSCRIPT, "%s\n", msg); + if (!Game::scrVmPub->terminal_error) + { + return; + } + goto error; + } + + abort_on_error = Game::scrVmPub->terminal_error; + RuntimeErrorInternal(abort_on_error ? Game::CON_CHANNEL_LOGFILEONLY : Game::CON_CHANNEL_PARSERSCRIPT, codePos, index, msg); + if (abort_on_error) + { + error: + if (!dialogMessage) + { + dialogMessage = ""; + dialogMessageSeparator = ""; + } + else + { + dialogMessageSeparator = "\n"; + } + + Game::Com_Error(Game::scrVmPub->terminal_error ? Game::ERR_SCRIPT_DROP : Game::ERR_SCRIPT, "\x15script runtime error\n(see console for details)\n%s%s%s", msg, dialogMessageSeparator, dialogMessage); + } + } + + void ScriptError::CompileError(unsigned int sourcePos, const char* msg, ...) + { + char line[1024]; + char text[1024]; + int col; + va_list argptr; + + va_start(argptr, msg); + vsnprintf_s(text, _TRUNCATE, msg, argptr); + va_end(argptr); + + if (Game::scrVarPub->evaluate) + { + if (!Game::scrVarPub->error_message) + { + Game::scrVarPub->error_message = VA("%s", text); + } + } + else + { + Game::Scr_ShutdownAllocNode(); + Game::Com_PrintError(Game::CON_CHANNEL_PARSERSCRIPT, "\n"); + Game::Com_PrintError(Game::CON_CHANNEL_PARSERSCRIPT, "******* script compile error *******\n"); + + if (!developer_ || !scrParserPub.sourceBuf) + { + Game::Com_PrintError(Game::CON_CHANNEL_PARSERSCRIPT, "%s\n", text); + line[0] = '\0'; + + Game::Com_Printf(Game::CON_CHANNEL_PARSERSCRIPT, "************************************\n"); + Game::Com_Error(Game::ERR_SCRIPT_DROP, "\x15" "script compile error\n%s\n%s\n(see console for details)\n", text, line); + } + else + { + assert(scrParserPub.sourceBuf); + Game::Com_PrintError(Game::CON_CHANNEL_PARSERSCRIPT, "%s: ", text); + + Scr_PrintSourcePos(Game::CON_CHANNEL_PARSERSCRIPT, scrParserPub.scriptfilename, scrParserPub.sourceBuf, sourcePos); + auto lineNumber = Scr_GetLineInfo(scrParserPub.sourceBuf, sourcePos, &col, line, nullptr); + Game::Com_Error(Game::ERR_SCRIPT_DROP, "\x15" "script compile error\n%s\n%s(%d):\n %s\n(see console for details)\n", text, scrParserPub.scriptfilename, lineNumber, line); + } + } + } + + void ScriptError::CompileError2(const char* codePos, const char* msg, ...) + { + char line[1024]; + char text[1024]; + va_list argptr; + + assert(!Game::scrVarPub->evaluate); + assert(Scr_IsInOpcodeMemory(codePos)); + + Game::Com_PrintError(Game::CON_CHANNEL_PARSERSCRIPT, "\n"); + Game::Com_PrintError(Game::CON_CHANNEL_PARSERSCRIPT, "******* script compile error *******\n"); + + va_start(argptr, msg); + vsnprintf_s(text, _TRUNCATE, msg, argptr); + va_end(argptr); + + Game::Com_PrintError(Game::CON_CHANNEL_PARSERSCRIPT, "%s: ", text); + + Scr_PrintPrevCodePos(Game::CON_CHANNEL_PARSERSCRIPT, codePos, 0); + + Game::Com_Printf(Game::CON_CHANNEL_PARSERSCRIPT, "************************************\n"); + + Scr_GetTextSourcePos(scrParserPub.sourceBuf, codePos, line); + + Game::Com_Error(Game::ERR_SCRIPT_DROP, "\x15" "script compile error\n%s\n%s\n(see console for details)\n", text, line); + } + + void ScriptError::Scr_GetTextSourcePos([[maybe_unused]] const char* buf, const char* codePos, char* line) + { + int col; + + if (developer_ && codePos && codePos != Game::g_EndPos && Game::scrVarPub->programBuffer && Scr_IsInOpcodeMemory(codePos)) + { + auto bufferIndex = Scr_GetSourceBuffer(codePos - 1); + Scr_GetLineInfo(scrParserPub.sourceBufferLookup[bufferIndex].sourceBuf, Scr_GetPrevSourcePos(codePos - 1, 0), &col, line, nullptr); + } + else + { + *line = '\0'; + } + } + + void ScriptError::Scr_InitOpcodeLookup() + { + assert(!scrParserGlob.opcodeLookup); + assert(!scrParserGlob.sourcePosLookup); + assert(!scrParserPub.sourceBufferLookup); + + if (!developer_) + { + return; + } + + scrParserGlob.delayedSourceIndex = -1; + scrParserGlob.opcodeLookupMaxSize = 0; + scrParserGlob.opcodeLookupLen = 0; + scrParserGlob.opcodeLookup = static_cast(Game::Z_VirtualReserve(Game::MAX_OPCODE_LOOKUP_SIZE)); + + scrParserGlob.sourcePosLookupMaxSize = 0; + scrParserGlob.sourcePosLookupLen = 0; + scrParserGlob.sourcePosLookup = static_cast(Game::Z_VirtualReserve(Game::MAX_SOURCEPOS_LOOKUP_SIZE)); + scrParserGlob.currentCodePos = nullptr; + scrParserGlob.currentSourcePosCount = 0; + scrParserGlob.sourceBufferLookupMaxSize = 0; + + scrParserPub.sourceBufferLookupLen = 0; + scrParserPub.sourceBufferLookup = static_cast(Game::Z_VirtualReserve(Game::MAX_SOURCEBUF_LOOKUP_SIZE)); + } + + void ScriptError::Scr_ShutdownOpcodeLookup() + { + if (scrParserGlob.opcodeLookup) + { + Z_VirtualFree(scrParserGlob.opcodeLookup); + scrParserGlob.opcodeLookup = nullptr; + } + + if (scrParserGlob.sourcePosLookup) + { + Z_VirtualFree(scrParserGlob.sourcePosLookup); + scrParserGlob.sourcePosLookup = nullptr; + } + + if (scrParserPub.sourceBufferLookup) + { + for (unsigned int i = 0; i < scrParserPub.sourceBufferLookupLen; ++i) + { + Game::Engine::Hunk_FreeDebugMem(scrParserPub.sourceBufferLookup[i].buf); + } + + Z_VirtualFree(scrParserPub.sourceBufferLookup); + scrParserPub.sourceBufferLookup = nullptr; + } + + if (scrParserGlob.saveSourceBufferLookup) + { + for (unsigned int i = 0; i < scrParserGlob.saveSourceBufferLookupLen; ++i) + { + if (scrParserGlob.saveSourceBufferLookup[i].sourceBuf) + { + Game::Engine::Hunk_FreeDebugMem(scrParserGlob.saveSourceBufferLookup[i].buf); + } + } + + Game::Engine::Hunk_FreeDebugMem(scrParserGlob.saveSourceBufferLookup); + scrParserGlob.saveSourceBufferLookup = nullptr; + } + } + + __declspec(naked) void ScriptError::EmitThreadInternal_Stub() + { + __asm + { + pushad + + push [esp + 0x20 + 0x8] // sourcePos + call AddThreadStartOpcodePos + add esp, 0x4 + + popad + + cmp ds:0x1CFEEF0, 2 + + push 0x61A687 + ret + } + } + + Game::SourceBufferInfo* ScriptError::Scr_GetNewSourceBuffer() + { + assert(scrParserPub.sourceBufferLookup); + + auto size = sizeof(Game::SourceBufferInfo) * (scrParserPub.sourceBufferLookupLen + 1); + if (size > scrParserGlob.sourceBufferLookupMaxSize) + { + if (scrParserGlob.sourceBufferLookupMaxSize >= Game::MAX_SOURCEBUF_LOOKUP_SIZE) + { + Game::Sys_Error("MAX_SOURCEBUF_LOOKUP_SIZE exceeded"); + } + + Game::Z_VirtualCommit((char*)scrParserPub.sourceBufferLookup + scrParserGlob.sourceBufferLookupMaxSize, 0x20000); + scrParserGlob.sourceBufferLookupMaxSize += 0x20000; + assert(size <= scrParserGlob.sourceBufferLookupMaxSize); + } + + return &scrParserPub.sourceBufferLookup[scrParserPub.sourceBufferLookupLen++]; + } + + void ScriptError::Scr_AddSourceBufferInternal(const char* extFilename, const char* codePos, char* sourceBuf, int len, bool doEolFixup, bool archive) + { + int i; + + if (!scrParserPub.sourceBufferLookup) + { + scrParserPub.sourceBuf = nullptr; + return; + } + + assert((len >= -1)); + assert((len >= 0) || !sourceBuf); + + auto strLen = std::strlen(extFilename) + 1; + auto newLen = strLen + len + 2; + auto* buf = static_cast(Game::Engine::Hunk_AllocDebugMem(static_cast(newLen))); // Scr_AddSourceBufferInternal + + strcpy(buf, extFilename); + auto* sourceBuf2 = sourceBuf ? buf + strLen : nullptr; + auto* source = sourceBuf; + auto* dest = sourceBuf2; + + if (doEolFixup) + { + for (i = 0; i <= len; ++i) + { + const auto c = *source++; + if (c == '\n' || c == '\r' && *source != '\n') + { + *dest = 0; + } + else + { + *dest = c; + } + ++dest; + } + } + else + { + for (i = 0; i <= len; ++i) + { + const auto c = *source; + ++source; + *dest = c; + ++dest; + } + } + + auto* bufferInfo = Scr_GetNewSourceBuffer(); + bufferInfo->codePos = codePos; + bufferInfo->buf = buf; + bufferInfo->sourceBuf = sourceBuf2; + bufferInfo->len = len; + bufferInfo->sortedIndex = -1; + bufferInfo->archive = archive; + + if (sourceBuf2) + { + scrParserPub.sourceBuf = sourceBuf2; + } + } + + char* ScriptError::Scr_ReadFile_FastFile([[maybe_unused]] const char* filename, const char* extFilename, const char* codePos, bool archive) + { + auto* rawfile = Game::DB_FindXAssetHeader(Game::ASSET_TYPE_RAWFILE, extFilename).rawfile; + if (Game::DB_IsXAssetDefault(Game::ASSET_TYPE_RAWFILE, extFilename)) + { + Scr_AddSourceBufferInternal(extFilename, codePos, nullptr, -1, true, true); + return nullptr; + } + + const auto len = Game::DB_GetRawFileLen(rawfile); + auto* sourceBuf = static_cast(Game::Hunk_AllocateTempMemoryHigh(len)); // Scr_ReadFile_FastFile + Game::DB_GetRawBuffer(rawfile, sourceBuf, len); + Scr_AddSourceBufferInternal(extFilename, codePos, sourceBuf, len, true, archive); + return sourceBuf; + } + + char* ScriptError::Scr_ReadFile_LoadObj([[maybe_unused]] const char* filename, const char* extFilename, const char* codePos, bool archive) + { + int f; + + auto len = Game::FS_FOpenFileByMode(extFilename, &f, Game::FS_READ); + if (len < 0) + { + Scr_AddSourceBufferInternal(extFilename, codePos, nullptr, -1, true, archive); + return nullptr; + } + + *Game::g_loadedImpureScript = true; + + auto* sourceBuf = static_cast(Game::Hunk_AllocateTempMemoryHigh(len + 1)); + Game::FS_Read(sourceBuf, len, f); + sourceBuf[len] = '\0'; + + Game::FS_FCloseFile(f); + Scr_AddSourceBufferInternal(extFilename, codePos, sourceBuf, len, true, archive); + + return sourceBuf; + } + + char* ScriptError::Scr_ReadFile(const char* filename, const char* extFilename, const char* codePos, bool archive) + { + int file; + + if (Game::FS_FOpenFileRead(extFilename, &file) < 0) + { + return Scr_ReadFile_FastFile(filename, extFilename, codePos, archive); + } + + Game::FS_FCloseFile(file); + return Scr_ReadFile_LoadObj(filename, extFilename, codePos, archive); + } + + char* ScriptError::Scr_AddSourceBuffer(const char* filename, const char* extFilename, const char* codePos, bool archive) + { + char* sourceBuf; + + if (archive && scrParserGlob.saveSourceBufferLookup) + { + assert(scrParserGlob.saveSourceBufferLookupLen > 0); + --scrParserGlob.saveSourceBufferLookupLen; + + auto* saveSourceBuffer = scrParserGlob.saveSourceBufferLookup + scrParserGlob.saveSourceBufferLookupLen; + const auto len = saveSourceBuffer->len; + assert(len >= -1); + + if (len < 0) + { + Scr_AddSourceBufferInternal(extFilename, codePos, nullptr, -1, true, archive); + sourceBuf = nullptr; + } + else + { + sourceBuf = static_cast(Game::Hunk_AllocateTempMemoryHigh(len + 1)); + + const char* source = saveSourceBuffer->sourceBuf; + auto* dest = sourceBuf; + for (int i = 0; i < len; ++i) + { + const auto c = *source++; + *dest = c ? c : '\n'; + dest++; + } + + *dest = '\0'; + Scr_AddSourceBufferInternal(extFilename, codePos, sourceBuf, len, false, archive); + } + + return sourceBuf; + } + + return Scr_ReadFile(filename, extFilename, codePos, archive); + } + + unsigned int ScriptError::Scr_LoadScriptInternal_Hk(const char* filename, Game::PrecacheEntry* entries, int entriesCount) + { + char extFilename[MAX_QPATH]; + Game::sval_u parseData; + + assert(Game::scrCompilePub->script_loading); + assert(std::strlen(filename) < MAX_QPATH); + + const auto name = Game::Scr_CreateCanonicalFilename(filename); + + if (Game::FindVariable(Game::scrCompilePub->loadedscripts, name)) + { + Game::SL_RemoveRefToString(name); + auto filePosPtr = Game::FindVariable(Game::scrCompilePub->scriptsPos, name); + return filePosPtr ? Game::FindObject(Game::scrCompilePub->scriptsPos, filePosPtr) : 0; + } + + const auto scriptId = Game::GetNewVariable(Game::scrCompilePub->loadedscripts, name); + Game::SL_RemoveRefToString(name); + + sprintf_s(extFilename, "%s.gsc", Game::SL_ConvertToString(static_cast(name))); + + const auto* oldSourceBuf = scrParserPub.sourceBuf; + auto* sourceBuffer = Scr_AddSourceBuffer(Game::SL_ConvertToString(static_cast(name)), extFilename, Game::TempMalloc(0), true); + + if (!sourceBuffer) + { + return 0; + } + + const auto oldAnimTreeNames = Game::scrAnimPub->animTreeNames; + Game::scrAnimPub->animTreeNames = 0; + Game::scrCompilePub->far_function_count = 0; + + const auto* oldFilename = scrParserPub.scriptfilename; + scrParserPub.scriptfilename = extFilename; + + Game::scrCompilePub->in_ptr = "+"; + Game::scrCompilePub->in_ptr_valid = false; + Game::scrCompilePub->parseBuf = sourceBuffer; + + Game::ScriptParse(&parseData, 0); + Game::scrCompilePub->parseBuf = nullptr; + + const auto filePosId = Game::GetObject(Game::scrCompilePub->scriptsPos, Game::GetVariable(Game::scrCompilePub->scriptsPos, name)); + const auto fileCountId = Game::GetObject(Game::scrCompilePub->scriptsCount, Game::GetVariable(Game::scrCompilePub->scriptsCount, name)); + + Game::ScriptCompile(parseData.node, filePosId, fileCountId, scriptId, entries, entriesCount); + + Game::RemoveVariable(Game::scrCompilePub->scriptsCount, name); + + scrParserPub.scriptfilename = oldFilename; + scrParserPub.sourceBuf = oldSourceBuf; + + Game::scrAnimPub->animTreeNames = oldAnimTreeNames; + + return filePosId; + } + + void ScriptError::Scr_Settings_Hk([[maybe_unused]] int developer, int developer_script, int abort_on_error) + { + assert(!abort_on_error || developer); + developer_ = (*Game::com_developer)->current.enabled; + Game::scrVarPub->developer_script = developer_script != 0; + Game::scrVmPub->abort_on_error = abort_on_error != 0; + } + + void ScriptError::MT_Reset_Stub() + { + Utils::Hook::Call(0x4D9620)(); + + Scr_InitOpcodeLookup(); + Game::Engine::Hunk_InitDebugMemory(); + } + + void ScriptError::SL_ShutdownSystem_Stub(unsigned int user) + { + Utils::Hook::Call(0x4F46D0)(user); + + Scr_ShutdownOpcodeLookup(); + Game::Engine::Hunk_ShutdownDebugMemory(); + } + + ScriptError::ScriptError() + { +#ifdef SCRIPT_ERROR_PATCH + std::vector> patches; + patches.reserve(200); + + const auto p = [&patches](const std::size_t a, void* b) + { + patches.emplace_back(a, b); + }; + + p(0x44AC44, Scr_Settings_Hk); + p(0x60BE0B, Scr_Settings_Hk); + + p(0x4656A7, MT_Reset_Stub); // Scr_BeginLoadScripts + p(0x42904B, SL_ShutdownSystem_Stub); // Scr_FreeScripts + + p(0x426C8A, Scr_LoadScriptInternal_Hk); // ScriptCompile + p(0x45D959, Scr_LoadScriptInternal_Hk); // Scr_LoadScript + + p(0x61E3AD, RuntimeError); // VM_Notify + p(0x621976, RuntimeError); // VM_Execute + p(0x62246E, RuntimeError); // VM_Execute_0 + + p(0x611F7C, CompileError2); // Scr_CheckAnimsDefined + p(0x612E76, CompileError2); // LinkThread + p(0x612E8D, CompileError2); // ^^ + p(0x612EA2, CompileError2); // ^^ + + Utils::Hook(0x4227E0, Scr_PrintPrevCodePos, HOOK_JUMP).install()->quick(); + Utils::Hook(0x61A680, EmitThreadInternal_Stub, HOOK_JUMP).install()->quick(); + Utils::Hook::Nop(0x61A680 + 5, 2); + + p(0x61228C, AddOpcodePos); // EmitGetInteger + p(0x6122C0, AddOpcodePos); // ^^ + p(0x6121FF, AddOpcodePos); // ^^ + p(0x612223, AddOpcodePos); // ^^ + p(0x612259, AddOpcodePos); // ^^ + p(0x6122ED, AddOpcodePos); // ^^ + + p(0x6128BE, AddOpcodePos); // EmitValue + + p(0x6128F3, AddOpcodePos); // EmitGetFloat + + p(0x612702, AddOpcodePos); // EmitGetString + + p(0x612772, AddOpcodePos); // EmitGetIString + + p(0x6127E4, AddOpcodePos); // EmitGetVector + + p(0x612843, AddOpcodePos); // EmitGetAnimation + + p(0x6166DD, AddOpcodePos); // EmitPreFunctionCall + + p(0x617D10, AddOpcodePos); // EmitOrEvalVariableExpression + + p(0x615160, AddOpcodePos); // EmitOrEvalLocalVariable + + p(0x6155B3, AddOpcodePos); // EmitArrayVariable + p(0x6155BF, AddOpcodePos); // ^^ + + p(0x6170D7, AddOpcodePos); // EmitBoolAndExpression + + p(0x614971, AddOpcodePos); // ?? + + p(0x6151B6, AddOpcodePos); // EmitLocalVariableRef + + p(0x619CC1, AddOpcodePos); // EmitArrayVariableRef + p(0x619CC9, AddOpcodePos); // ^^ + + p(0x615261, AddOpcodePos); // EmitGameRef + + p(0x615309, AddOpcodePos); // EmitClearArray + Utils::Hook(0x615319, AddOpcodePos, HOOK_JUMP).install()->quick(); // EmitClearArray + + p(0x614D79, AddOpcodePos); // ?? + + p(0x6153B9, AddOpcodePos); // ?? + + p(0x614B74, AddOpcodePos); // ?? + + p(0x614ED9, AddOpcodePos); // ?? + + p(0x614E29, AddOpcodePos); // ?? + + p(0x614CC9, AddOpcodePos); // ?? + + p(0x614C19, AddOpcodePos); // ?? + + p(0x6167A5, AddOpcodePos); // ?? + + p(0x614AB1, AddOpcodePos); // ?? + + p(0x614A11, AddOpcodePos); // ?? + + p(0x616FB7, AddOpcodePos); // EmitBoolOrExpression + + p(0x6171EE, AddOpcodePos); // EmitOrEvalBinaryOperatorExpression + + p(0x616E6B, AddOpcodePos); // EmitOrEvalPrimitiveExpressionList + + p(0x618199, AddOpcodePos); // EmitReturnStatement + + p(0x61A2F2, AddOpcodePos); // EmitEndStatement + + p(0x61826A, AddOpcodePos); // EmitWaitStatement + p(0x618272, AddOpcodePos); // ^^ + p(0x61827E, AddOpcodePos); // ^^ + + p(0x61832E, RemoveOpcodePos); // EmitIfStatement (EmitOpcode inlined?) + p(0x618366, AddOpcodePos); // ^^ + p(0x6183C8, AddOpcodePos); // ^^ + + p(0x6184A6, RemoveOpcodePos); // EmitIfElseStatement (EmitOpcode inlined?) + p(0x6184DD, AddOpcodePos); // ^^ + p(0x618601, AddOpcodePos); // ^^ + p(0x618569, AddOpcodePos); // ^^ + p(0x618685, AddOpcodePos); // ^^ + + p(0x618876, RemoveOpcodePos); // EmitWhileStatement (EmitOpcode inlined?) + p(0x6188AD, AddOpcodePos); // ^^ + p(0x6189F5, AddOpcodePos); // ^^ + p(0x618A09, AddOpcodePos); // ^^ + + p(0x618CC2, RemoveOpcodePos); // EmitForStatement (EmitOpcode inlined?) + p(0x618CF9, AddOpcodePos); // ^^ + p(0x618EAE, AddOpcodePos); // ^^ + p(0x618EC2, AddOpcodePos); // ^^ + + p(0x61A0C7, AddOpcodePos); // EmitIncStatement + p(0x61A0DA, AddOpcodePos); // ^^ + + p(0x61A1A7, AddOpcodePos); // EmitDecStatement + p(0x61A1BA, AddOpcodePos); // ^^ + + p(0x61A239, AddOpcodePos); // EmitBinaryEqualsOperatorExpression + p(0x61A253, AddOpcodePos); // ^^ + + p(0x61909D, AddOpcodePos); // EmitWaittillStatement + p(0x6190A5, AddOpcodePos); // ^^ + p(0x6190B1, AddOpcodePos); // ^^ + p(0x6190BE, AddOpcodePos); // ^^ + + p(0x6148D0, AddOpcodePos); // EmitSafeSetWaittillVariableField + + p(0x6192B3, AddOpcodePos); // EmitWaittillmatchStatement + p(0x6192BB, AddOpcodePos); // ^^ + p(0x6192C7, AddOpcodePos); // ^^ + p(0x6192D5, AddOpcodePos); // ^^ + p(0x6192EC, AddOpcodePos); // ^^ + + p(0x617412, AddOpcodePos); // EmitWaittillFrameEnd + p(0x61741A, AddOpcodePos); // ^^ + + p(0x61944B, AddOpcodePos); // EmitNotifyStatement + p(0x619551, AddOpcodePos); // ^^ + p(0x61955F, AddOpcodePos); // ^^ + p(0x61956B, AddOpcodePos); // ^^ + + p(0x61965E, AddOpcodePos); // EmitEndOnStatement + p(0x61966A, AddOpcodePos); // ^^ + + p(0x619835, AddOpcodePos); // EmitSwitchStatement + + p(0x617860, AddOpcodePos); // EmitBreakStatement + + p(0x617990, AddOpcodePos); // EmitContinueStatement + + p(0x61A70D, AddOpcodePos); // EmitThreadInternal + p(0x61A716, AddOpcodePos); // ^^ + + p(0x617BB8, AddOpcodePos); // InitThread + p(0x617BC0, AddOpcodePos); // ^^ + + p(0x6157B7, AddOpcodePos); // EmitGetFunction + p(0x615997, AddOpcodePos); // ^^ + p(0x6158D7, AddOpcodePos); // ^^ + + p(0x616BBD, AddOpcodePos); // EmitMethod + p(0x616C2E, AddOpcodePos); // ^^ + + p(0x615A42, RemoveOpcodePos); // ?? (EmitOpcode inlined?) + p(0x615AF7, RemoveOpcodePos); // ^^ + p(0x615B78, AddOpcodePos); // ^^ + + p(0x615D0D, AddOpcodePos); // ?? + p(0x615D19, AddOpcodePos); // ^^ + + p(0x6163FD, AddOpcodePos); // ?? + p(0x616420, AddOpcodePos); // ^^ + + p(0x615E5B, RemoveOpcodePos); // ?? (EmitOpcode inlined?) + p(0x615E93, AddOpcodePos); // ^^ + + p(0x6161C5, AddOpcodePos); // ?? + + p(0x61645D, AddOpcodePos); // ?? + p(0x616480, AddOpcodePos); // ^^ + + p(0x615FEB, RemoveOpcodePos); // ?? (EmitOpcode inlined?) + p(0x616023, AddOpcodePos); // ^^ + + p(0x616365, AddOpcodePos); // ?? + + p(0x616605, AddOpcodePos); // ?? + + p(0x6167F2, AddOpcodePos); // EmitCallBuiltinMethodOpcode + + p(0x617C6F, AddOpcodePos); // EmitClearFieldVariable + + p(0x617482, AddOpcodePos); // EmitSafeSetVariableField + + p(0x617B61, AddOpcodePos); // EmitFormalParameterList + + p(0x6154F4, AddOpcodePos); // ?? + p(0x61551B, AddOpcodePos); // ^^ + p(0x61554D, AddOpcodePos); // ^^ + + p(0x6150C1, AddOpcodePos); // EmitAnimObject + + p(0x615021, AddOpcodePos); // EmitLevelObject + + p(0x614F81, AddOpcodePos); // ?? + + p(0x612CF2, AddOpcodePos); // EmitFunction + + p(0x619FFA, AddOpcodePos); // ?? + + p(0x61407E, RemoveOpcodePos); // EmitOpcode + p(0x61409F, RemoveOpcodePos); // ^^ + p(0x6140C0, RemoveOpcodePos); // ^^ + p(0x6140D7, RemoveOpcodePos); // ^^ + p(0x614164, RemoveOpcodePos); // ^^ + p(0x61417A, RemoveOpcodePos); // ^^ + p(0x61419D, RemoveOpcodePos); // ^^ + p(0x6141B4, RemoveOpcodePos); // ^^ + p(0x6141CE, RemoveOpcodePos); // ^^ + p(0x6141F9, RemoveOpcodePos); // ^^ + p(0x614214, RemoveOpcodePos); // ^^ + p(0x614277, RemoveOpcodePos); // ^^ + p(0x61428E, RemoveOpcodePos); // ^^ + p(0x6142CD, RemoveOpcodePos); // ^^ + + for (const auto& patch : patches) + { + Utils::Hook(patch.first, patch.second, HOOK_CALL).install()->quick(); + } + + Utils::Hook(0x434260, CompileError, HOOK_JUMP).install()->quick(); +#endif + } +} diff --git a/src/Components/Modules/GSC/ScriptError.hpp b/src/Components/Modules/GSC/ScriptError.hpp new file mode 100644 index 00000000..3cc61caa --- /dev/null +++ b/src/Components/Modules/GSC/ScriptError.hpp @@ -0,0 +1,60 @@ +#pragma once + +namespace Components::GSC +{ + class ScriptError : public Component + { + public: + ScriptError(); + + static int Scr_IsInOpcodeMemory(const char* pos); + static int Scr_GetLineNum(unsigned int bufferIndex, unsigned int sourcePos); + + static void RuntimeError(const char* codePos, unsigned int index, const char* msg, const char* dialogMessage); + + private: + // Replacement for variables not present in currently available structs + static int developer_; + + static Game::scrParserGlob_t scrParserGlob; + static Game::scrParserPub_t scrParserPub; + + static void AddOpcodePos(unsigned int sourcePos, int type); + static void RemoveOpcodePos(); + static void AddThreadStartOpcodePos(unsigned int sourcePos); + + static unsigned int Scr_GetPrevSourcePos(const char* codePos, unsigned int index); + static Game::OpcodeLookup* Scr_GetPrevSourcePosOpcodeLookup(const char* codePos); + static void Scr_CopyFormattedLine(char* line, const char* rawLine); + static int Scr_GetLineNumInternal(const char* buf, unsigned int sourcePos, const char** startLine, int* col, Game::SourceBufferInfo* binfo); + static unsigned int Scr_GetSourceBuffer(const char* codePos); + static void Scr_PrintPrevCodePos(int channel, const char* codePos, unsigned int index); + static int Scr_GetLineInfo(const char* buf, unsigned int sourcePos, int* col, char* line, Game::SourceBufferInfo* binfo); + static void Scr_PrintSourcePos(int channel, const char* filename, const char* buf, unsigned int sourcePos); + + static void RuntimeErrorInternal(int channel, const char* codePos, unsigned int index, const char* msg); + + static void CompileError(unsigned int sourcePos, const char* msg, ...); + static void CompileError2(const char* codePos, const char* msg, ...); + + static void Scr_GetTextSourcePos(const char* buf, const char* codePos, char* line); + + static void Scr_InitOpcodeLookup(); + static void Scr_ShutdownOpcodeLookup(); + + static void EmitThreadInternal_Stub(); + + static Game::SourceBufferInfo* Scr_GetNewSourceBuffer(); + static void Scr_AddSourceBufferInternal(const char* extFilename, const char* codePos, char* sourceBuf, int len, bool doEolFixup, bool archive); + static char* Scr_ReadFile_FastFile(const char* filename, const char* extFilename, const char* codePos, bool archive); + static char* Scr_ReadFile_LoadObj(const char* filename, const char* extFilename, const char* codePos, bool archive); + static char* Scr_ReadFile(const char* filename, const char* extFilename, const char* codePos, bool archive); + static char* Scr_AddSourceBuffer(const char* filename, const char* extFilename, const char* codePos, bool archive); + static unsigned int Scr_LoadScriptInternal_Hk(const char* filename, Game::PrecacheEntry* entries, int entriesCount); + + static void Scr_Settings_Hk(int developer, int developer_script, int abort_on_error); + + static void MT_Reset_Stub(); + static void SL_ShutdownSystem_Stub(unsigned int user); + }; +} diff --git a/src/Components/Modules/GSC/ScriptExtension.cpp b/src/Components/Modules/GSC/ScriptExtension.cpp index 0b30a4f0..635f1e82 100644 --- a/src/Components/Modules/GSC/ScriptExtension.cpp +++ b/src/Components/Modules/GSC/ScriptExtension.cpp @@ -2,11 +2,14 @@ #include "ScriptExtension.hpp" #include "Script.hpp" -namespace Components +namespace Components::GSC { std::unordered_map ScriptExtension::CustomEntityFields; std::unordered_map ScriptExtension::CustomClientFields; + std::unordered_map ScriptExtension::ReplacedFunctions; + const char* ScriptExtension::ReplacedPos = nullptr; + void ScriptExtension::AddEntityField(const char* name, Game::fieldtype_t type, const Game::ScriptCallbackEnt& setter, const Game::ScriptCallbackEnt& getter) { @@ -106,80 +109,96 @@ namespace Components Game::Scr_GetEntityField(entnum, offset); } + const char* ScriptExtension::GetCodePosForParam(int index) + { + if (static_cast(index) >= Game::scrVmPub->outparamcount) + { + Game::Scr_ParamError(static_cast(index), "GetCodePosForParam: Index is out of range!"); + return ""; + } + + const auto* value = &Game::scrVmPub->top[-index]; + + if (value->type != Game::VAR_FUNCTION) + { + Game::Scr_ParamError(static_cast(index), "GetCodePosForParam: Expects a function as parameter!"); + return ""; + } + + return value->u.codePosValue; + } + + void ScriptExtension::GetReplacedPos(const char* pos) + { + if (ReplacedFunctions.contains(pos)) + { + ReplacedPos = ReplacedFunctions[pos]; + } + } + + void ScriptExtension::SetReplacedPos(const char* what, const char* with) + { + if (!*what || !*with) + { + Logger::Warning(Game::CON_CHANNEL_SCRIPT, "Invalid parameters passed to ReplacedFunctions\n"); + return; + } + + if (ReplacedFunctions.contains(what)) + { + Logger::Warning(Game::CON_CHANNEL_SCRIPT, "ReplacedFunctions already contains codePosValue for a function\n"); + } + + ReplacedFunctions[what] = with; + } + + __declspec(naked) void ScriptExtension::VMExecuteInternalStub() + { + __asm + { + pushad + + push edx + call GetReplacedPos + + pop edx + popad + + cmp ReplacedPos, 0 + jne SetPos + + movzx eax, byte ptr [edx] + inc edx + + Loc1: + cmp eax, 0x8B + + push ecx + + mov ecx, 0x2045094 + mov [ecx], eax + + mov ecx, 0x2040CD4 + mov [ecx], edx + + pop ecx + + push 0x61E944 + ret + + SetPos: + mov edx, ReplacedPos + mov ReplacedPos, 0 + + movzx eax, byte ptr [edx] + inc edx + + jmp Loc1 + } + } + void ScriptExtension::AddFunctions() { - // Misc functions - Script::AddFunction("ToUpper", [] // gsc: ToUpper() - { - const auto scriptValue = Game::Scr_GetConstString(0); - const auto* string = Game::SL_ConvertToString(scriptValue); - - char out[1024] = {0}; // 1024 is the max for a string in this SL system - bool changed = false; - - size_t i = 0; - while (i < sizeof(out)) - { - const auto value = *string; - const auto result = static_cast(std::toupper(static_cast(value))); - out[i] = result; - - if (value != result) - changed = true; - - if (result == '\0') // Finished converting string - break; - - ++string; - ++i; - } - - // Null terminating character was overwritten - if (i >= sizeof(out)) - { - Game::Scr_Error("string too long"); - return; - } - - if (changed) - { - Game::Scr_AddString(out); - } - else - { - Game::SL_AddRefToString(scriptValue); - Game::Scr_AddConstString(scriptValue); - Game::SL_RemoveRefToString(scriptValue); - } - }); - - // Func present on IW5 - Script::AddFunction("StrICmp", [] // gsc: StrICmp(, ) - { - const auto value1 = Game::Scr_GetConstString(0); - const auto value2 = Game::Scr_GetConstString(1); - - const auto result = _stricmp(Game::SL_ConvertToString(value1), - Game::SL_ConvertToString(value2)); - - Game::Scr_AddInt(result); - }); - - // Func present on IW5 - Script::AddFunction("IsEndStr", [] // gsc: IsEndStr(, ) - { - const auto* str = Game::Scr_GetString(0); - const auto* suffix = Game::Scr_GetString(1); - - if (str == nullptr || suffix == nullptr) - { - Game::Scr_Error("^1IsEndStr: Illegal parameters!\n"); - return; - } - - Game::Scr_AddBool(Utils::String::EndsWith(str, suffix)); - }); - Script::AddFunction("IsArray", [] // gsc: IsArray() { auto type = Game::Scr_GetType(0); @@ -200,40 +219,56 @@ namespace Components Game::Scr_AddBool(result); }); - // Func present on IW5 - Script::AddFunction("CastFloat", [] // gsc: CastFloat() + Script::AddFunction("ReplaceFunc", [] // gsc: ReplaceFunc(, ) { - switch (Game::Scr_GetType(0)) + if (Game::Scr_GetNumParam() != 2) { - case Game::VAR_STRING: - Game::Scr_AddFloat(static_cast(std::atof(Game::Scr_GetString(0)))); - break; - case Game::VAR_FLOAT: - Game::Scr_AddFloat(Game::Scr_GetFloat(0)); - break; - case Game::VAR_INTEGER: - Game::Scr_AddFloat(static_cast(Game::Scr_GetInt(0))); - break; - default: - Game::Scr_ParamError(0, Utils::String::VA("cannot cast %s to float", Game::Scr_GetTypeName(0))); - break; - } - }); - - Script::AddFunction("Strtol", [] // gsc: Strtol(, ) - { - const auto* input = Game::Scr_GetString(0); - const auto base = Game::Scr_GetInt(1); - - char* end; - const auto result = std::strtol(input, &end, base); - if (input == end) - { - Game::Scr_ParamError(0, "cannot cast string to int"); + Game::Scr_Error("ReplaceFunc: Needs two parameters!"); + return; } - Game::Scr_AddInt(result); + const auto what = GetCodePosForParam(0); + const auto with = GetCodePosForParam(1); + + SetReplacedPos(what, with); }); + + + Script::AddFunction("GetSystemMilliseconds", [] // gsc: GetSystemMilliseconds() + { + SYSTEMTIME time; + GetSystemTime(&time); + + Game::Scr_AddInt(time.wMilliseconds); + }); + + Script::AddFunction("Exec", [] // gsc: Exec() + { + const auto* str = Game::Scr_GetString(0); + if (!str) + { + Game::Scr_ParamError(0, "Exec: Illegal parameter!"); + return; + } + + Command::Execute(str, false); + }); + + // Allow printing to the console even when developer is 0 + Script::AddFunction("PrintConsole", [] // gsc: PrintConsole() + { + for (std::size_t i = 0; i < Game::Scr_GetNumParam(); ++i) + { + const auto* str = Game::Scr_GetString(i); + if (!str) + { + Game::Scr_ParamError(i, "PrintConsole: Illegal parameter!"); + return; + } + + Logger::Print(Game::level->scriptPrintChannel, "{}", str); + } + }); } void ScriptExtension::AddMethods() @@ -241,20 +276,31 @@ namespace Components // ScriptExtension methods Script::AddMethod("GetIp", [](const Game::scr_entref_t entref) // gsc: self GetIp() { - const auto* ent = Game::GetPlayerEntity(entref); + const auto* ent = Script::Scr_GetPlayerEntity(entref); const auto* client = Script::GetClient(ent); std::string ip = Game::NET_AdrToString(client->header.netchan.remoteAddress); - if (const auto pos = ip.find_first_of(":"); pos != std::string::npos) - ip.erase(ip.begin() + pos, ip.end()); // Erase port + const auto extractIPAddress = [](const std::string& input) -> std::string + { + const auto colonPos = input.find(':'); + if (colonPos == std::string::npos) + { + return input; + } + + auto ipAddress = input.substr(0, colonPos); + return ipAddress; + }; + + ip = extractIPAddress(ip); Game::Scr_AddString(ip.data()); }); Script::AddMethod("GetPing", [](const Game::scr_entref_t entref) // gsc: self GetPing() { - const auto* ent = Game::GetPlayerEntity(entref); + const auto* ent = Script::Scr_GetPlayerEntity(entref); const auto* client = Script::GetClient(ent); Game::Scr_AddInt(client->ping); @@ -266,11 +312,18 @@ namespace Components ping = std::clamp(ping, 0, 999); - const auto* ent = Game::GetPlayerEntity(entref); + const auto* ent = Script::Scr_GetPlayerEntity(entref); auto* client = Script::GetClient(ent); client->ping = ping; }); + + // PlayerCmd_AreControlsFrozen GSC function from Black Ops 2 + Script::AddMethod("AreControlsFrozen", [](Game::scr_entref_t entref) // Usage: self AreControlsFrozen(); + { + const auto* ent = Script::Scr_GetPlayerEntity(entref); + Game::Scr_AddBool((ent->client->flags & Game::PF_FROZEN) != 0); + }); } void ScriptExtension::AddEntityFields() @@ -311,5 +364,13 @@ namespace Components Utils::Hook(0x41BED2, Scr_SetObjectFieldStub, HOOK_CALL).install()->quick(); // SetEntityFieldValue Utils::Hook(0x5FBF01, Scr_SetClientFieldStub, HOOK_CALL).install()->quick(); // Scr_SetObjectField Utils::Hook(0x4FF413, Scr_GetEntityFieldStub, HOOK_CALL).install()->quick(); // Scr_GetObjectField + + Utils::Hook(0x61E92E, VMExecuteInternalStub, HOOK_JUMP).install()->quick(); + Utils::Hook::Nop(0x61E933, 1); + + Events::OnVMShutdown([] + { + ReplacedFunctions.clear(); + }); } } diff --git a/src/Components/Modules/GSC/ScriptExtension.hpp b/src/Components/Modules/GSC/ScriptExtension.hpp index c153f24c..abd0a273 100644 --- a/src/Components/Modules/GSC/ScriptExtension.hpp +++ b/src/Components/Modules/GSC/ScriptExtension.hpp @@ -1,6 +1,6 @@ #pragma once -namespace Components +namespace Components::GSC { class ScriptExtension : public Component { @@ -10,10 +10,15 @@ namespace Components static void AddEntityField(const char* name, Game::fieldtype_t type, const Game::ScriptCallbackEnt& setter, const Game::ScriptCallbackEnt& getter); static void AddClientField(const char* name, Game::fieldtype_t type, const Game::ScriptCallbackClient& setter, const Game::ScriptCallbackClient& getter); + static const char* GetCodePosForParam(int index); + private: static std::unordered_map CustomEntityFields; static std::unordered_map CustomClientFields; + static std::unordered_map ReplacedFunctions; + static const char* ReplacedPos; + static void GScr_AddFieldsForEntityStub(); // Two hooks because it makes our code cleaner (luckily functions were not inlined) @@ -23,6 +28,10 @@ namespace Components // One hook because functions were inlined static void Scr_GetEntityFieldStub(int entnum, int offset); + static void GetReplacedPos(const char* pos); + static void SetReplacedPos(const char* what, const char* with); + static void VMExecuteInternalStub(); + static void AddFunctions(); static void AddMethods(); static void AddEntityFields(); diff --git a/src/Components/Modules/GSC/ScriptPatches.cpp b/src/Components/Modules/GSC/ScriptPatches.cpp index 3c2b6197..94f4b30a 100644 --- a/src/Components/Modules/GSC/ScriptPatches.cpp +++ b/src/Components/Modules/GSC/ScriptPatches.cpp @@ -4,7 +4,7 @@ using namespace Utils::String; -namespace Components +namespace Components::GSC { constexpr auto offset = 511; @@ -24,7 +24,7 @@ namespace Components { if (Game::Scr_GetNumParam() < 3) { - Game::Scr_Error("USAGE: tableLookupIStringByRow( filename, rowNum, returnValueColumnNum )\n"); + Game::Scr_Error("USAGE: tableLookupIStringByRow( filename, rowNum, returnValueColumnNum )"); return; } @@ -34,9 +34,9 @@ namespace Components const auto* table = Game::DB_FindXAssetHeader(Game::ASSET_TYPE_STRINGTABLE, fileName).stringTable; - if (table == nullptr) + if (!table) { - Game::Scr_ParamError(0, Utils::String::VA("%s does not exist\n", fileName)); + Game::Scr_ParamError(0, Utils::String::VA("%s does not exist", fileName)); return; } diff --git a/src/Components/Modules/GSC/ScriptPatches.hpp b/src/Components/Modules/GSC/ScriptPatches.hpp index 6d69a2fd..124cb1c9 100644 --- a/src/Components/Modules/GSC/ScriptPatches.hpp +++ b/src/Components/Modules/GSC/ScriptPatches.hpp @@ -1,6 +1,6 @@ #pragma once -namespace Components +namespace Components::GSC { class ScriptPatches : public Component { diff --git a/src/Components/Modules/GSC/ScriptStorage.cpp b/src/Components/Modules/GSC/ScriptStorage.cpp index e89425d7..18ef508f 100644 --- a/src/Components/Modules/GSC/ScriptStorage.cpp +++ b/src/Components/Modules/GSC/ScriptStorage.cpp @@ -1,8 +1,9 @@ #include + #include "ScriptStorage.hpp" #include "Script.hpp" -namespace Components +namespace Components::GSC { std::unordered_map ScriptStorage::Data; @@ -13,9 +14,9 @@ namespace Components const auto* key = Game::Scr_GetString(0); const auto* value = Game::Scr_GetString(1); - if (key == nullptr || value == nullptr) + if (!key || !value) { - Game::Scr_Error("^1StorageSet: Illegal parameters!\n"); + Game::Scr_Error("StorageSet: Illegal parameters!"); return; } @@ -26,15 +27,15 @@ namespace Components { const auto* key = Game::Scr_GetString(0); - if (key == nullptr) + if (!key) { - Game::Scr_ParamError(0, "^1StorageRemove: Illegal parameter!\n"); + Game::Scr_ParamError(0, "StorageRemove: Illegal parameter!"); return; } if (!Data.contains(key)) { - Game::Scr_Error(Utils::String::VA("^1StorageRemove: Store does not have key '%s'!\n", key)); + Game::Scr_Error(Utils::String::VA("StorageRemove: Store does not have key '%s'!", key)); return; } @@ -45,15 +46,15 @@ namespace Components { const auto* key = Game::Scr_GetString(0); - if (key == nullptr) + if (!key) { - Game::Scr_ParamError(0, "^1StorageGet: Illegal parameter!\n"); + Game::Scr_ParamError(0, "StorageGet: Illegal parameter!"); return; } if (!Data.contains(key)) { - Game::Scr_Error(Utils::String::VA("^1StorageGet: Store does not have key '%s'!\n", key)); + Game::Scr_Error(Utils::String::VA("StorageGet: Store does not have key '%s'!", key)); } const auto& data = Data.at(key); @@ -64,9 +65,9 @@ namespace Components { const auto* key = Game::Scr_GetString(0); - if (key == nullptr) + if (!key) { - Game::Scr_ParamError(0, "^1StorageHas: Illegal parameter!\n"); + Game::Scr_ParamError(0, "StorageHas: Illegal parameter!"); return; } @@ -77,7 +78,7 @@ namespace Components { if (Data.empty()) { - Game::Scr_Error("^1StorageDump: ScriptStorage is empty!\n"); + Game::Scr_Error("StorageDump: ScriptStorage is empty!"); return; } diff --git a/src/Components/Modules/GSC/ScriptStorage.hpp b/src/Components/Modules/GSC/ScriptStorage.hpp index 089e1f45..37618221 100644 --- a/src/Components/Modules/GSC/ScriptStorage.hpp +++ b/src/Components/Modules/GSC/ScriptStorage.hpp @@ -1,6 +1,6 @@ #pragma once -namespace Components +namespace Components::GSC { class ScriptStorage : public Component { diff --git a/src/Components/Modules/GSC/String.cpp b/src/Components/Modules/GSC/String.cpp new file mode 100644 index 00000000..bb497a69 --- /dev/null +++ b/src/Components/Modules/GSC/String.cpp @@ -0,0 +1,150 @@ +#include + +#include "Script.hpp" +#include "String.hpp" + +namespace Components::GSC +{ + void String::AddScriptFunctions() + { + Script::AddFunction("ToUpper", [] // gsc: ToUpper() + { + const auto scriptValue = Game::Scr_GetConstString(0); + const auto* string = Game::SL_ConvertToString(scriptValue); + + char out[1024]{}; // 1024 is the max for a string in this SL system + bool changed = false; + + std::size_t i = 0; + while (i < sizeof(out)) + { + const auto value = *string; + const auto result = static_cast(std::toupper(static_cast(value))); + out[i] = result; + + if (value != result) + { + changed = true; + } + + if (result == '\0') // Finished converting string + { + break; + } + + ++string; + ++i; + } + + // Null terminating character was overwritten + if (i >= sizeof(out)) + { + Game::Scr_Error("string too long"); + return; + } + + if (changed) + { + Game::Scr_AddString(out); + } + else + { + Game::SL_AddRefToString(scriptValue); + Game::Scr_AddConstString(scriptValue); + Game::SL_RemoveRefToString(scriptValue); + } + }); + + Script::AddFunction("GetChar", [] + { + const auto* str = Game::Scr_GetString(0); + const auto index = Game::Scr_GetInt(1); + + if (!str) + { + Game::Scr_Error("GetChar: Illegal parameter!"); + return; + } + + if (static_cast(index) >= std::strlen(str)) + { + Game::Scr_Error("GetChar: char index is out of bounds"); + } + + Game::Scr_AddInt(str[index]); + }); + + // Func present on IW5 + Script::AddFunction("StrICmp", [] // gsc: StrICmp(, ) + { + const auto* string1 = Game::SL_ConvertToString(Game::Scr_GetConstString(0)); + const auto* string2 = Game::SL_ConvertToString(Game::Scr_GetConstString(1)); + + Game::Scr_AddInt(_stricmp(string1, string2)); + }); + + // Func present on IW5 + Script::AddFunction("IsEndStr", [] // gsc: IsEndStr(, ) + { + const auto* str = Game::Scr_GetString(0); + const auto* suffix = Game::Scr_GetString(1); + + if (!str || !suffix) + { + Game::Scr_Error("IsEndStr: Illegal parameters!"); + return; + } + + const auto str_len = std::strlen(str); + const auto suffix_len = std::strlen(suffix); + + if (suffix_len > str_len) + { + Game::Scr_AddBool(0); + return; + } + + Game::Scr_AddBool(std::memcmp(str + str_len - suffix_len, suffix, suffix_len) == 0); + }); + + // Func present on IW5 + Script::AddFunction("CastFloat", [] // gsc: CastFloat() + { + switch (Game::Scr_GetType(0)) + { + case Game::VAR_STRING: + Game::Scr_AddFloat(static_cast(std::atof(Game::Scr_GetString(0)))); + break; + case Game::VAR_FLOAT: + Game::Scr_AddFloat(Game::Scr_GetFloat(0)); + break; + case Game::VAR_INTEGER: + Game::Scr_AddFloat(static_cast(Game::Scr_GetInt(0))); + break; + default: + Game::Scr_ParamError(0, Utils::String::VA("cannot cast %s to float", Game::Scr_GetTypeName(0))); + break; + } + }); + + Script::AddFunction("Strtol", [] // gsc: Strtol(, ) + { + const auto* input = Game::Scr_GetString(0); + const auto base = Game::Scr_GetInt(1); + + char* end; + const auto result = std::strtol(input, &end, base); + if (input == end) + { + Game::Scr_ParamError(0, "cannot cast string to int"); + } + + Game::Scr_AddInt(result); + }); + } + + String::String() + { + AddScriptFunctions(); + } +} diff --git a/src/Components/Modules/GSC/String.hpp b/src/Components/Modules/GSC/String.hpp new file mode 100644 index 00000000..215d08e0 --- /dev/null +++ b/src/Components/Modules/GSC/String.hpp @@ -0,0 +1,13 @@ +#pragma once + +namespace Components::GSC +{ + class String : public Component + { + public: + String(); + + private: + static void AddScriptFunctions(); + }; +} diff --git a/src/Components/Modules/UserInfo.cpp b/src/Components/Modules/GSC/UserInfo.cpp similarity index 78% rename from src/Components/Modules/UserInfo.cpp rename to src/Components/Modules/GSC/UserInfo.cpp index 4b71931b..aede628b 100644 --- a/src/Components/Modules/UserInfo.cpp +++ b/src/Components/Modules/GSC/UserInfo.cpp @@ -1,9 +1,10 @@ #include +#include + +#include "Script.hpp" #include "UserInfo.hpp" -#include "GSC/Script.hpp" - -namespace Components +namespace Components::GSC { std::unordered_map UserInfo::UserInfoOverrides; @@ -48,12 +49,12 @@ namespace Components { Script::AddMethod("SetName", [](Game::scr_entref_t entref) // gsc: self SetName() { - const auto* ent = Game::GetPlayerEntity(entref); + const auto* ent = Script::Scr_GetPlayerEntity(entref); const auto* name = Game::Scr_GetString(0); - if (name == nullptr) + if (!name) { - Game::Scr_ParamError(0, "^1SetName: Illegal parameter!\n"); + Game::Scr_ParamError(0, "SetName: Illegal parameter!"); return; } @@ -64,7 +65,7 @@ namespace Components Script::AddMethod("ResetName", [](Game::scr_entref_t entref) // gsc: self ResetName() { - const auto* ent = Game::GetPlayerEntity(entref); + const auto* ent = Script::Scr_GetPlayerEntity(entref); Logger::Debug("Resetting name of {}", ent->s.number); UserInfoOverrides[ent->s.number].erase("name"); @@ -73,12 +74,12 @@ namespace Components Script::AddMethod("SetClanTag", [](Game::scr_entref_t entref) // gsc: self SetClanTag() { - const auto* ent = Game::GetPlayerEntity(entref); + const auto* ent = Script::Scr_GetPlayerEntity(entref); const auto* clanName = Game::Scr_GetString(0); - if (clanName == nullptr) + if (!clanName) { - Game::Scr_ParamError(0, "^1SetClanTag: Illegal parameter!\n"); + Game::Scr_ParamError(0, "SetClanTag: Illegal parameter!"); return; } @@ -89,7 +90,7 @@ namespace Components Script::AddMethod("ResetClanTag", [](Game::scr_entref_t entref) // gsc: self ResetClanTag() { - const auto* ent = Game::GetPlayerEntity(entref); + const auto* ent = Script::Scr_GetPlayerEntity(entref); Logger::Debug("Resetting clanName of {}", ent->s.number); UserInfoOverrides[ent->s.number].erase("clanAbbrev"); @@ -104,15 +105,7 @@ namespace Components AddScriptMethods(); - Events::OnVMShutdown([] - { - ClearAllOverrides(); - }); - - Events::OnClientDisconnect([](const int clientNum) - { - // Clear the overrides for UserInfo - ClearClientOverrides(clientNum); - }); + Events::OnVMShutdown(ClearAllOverrides); + Events::OnClientDisconnect(ClearClientOverrides); } } diff --git a/src/Components/Modules/UserInfo.hpp b/src/Components/Modules/GSC/UserInfo.hpp similarity index 94% rename from src/Components/Modules/UserInfo.hpp rename to src/Components/Modules/GSC/UserInfo.hpp index ac2d7d15..d96d96ed 100644 --- a/src/Components/Modules/UserInfo.hpp +++ b/src/Components/Modules/GSC/UserInfo.hpp @@ -1,6 +1,6 @@ #pragma once -namespace Components +namespace Components::GSC { class UserInfo : public Component { diff --git a/src/Components/Modules/Gamepad.cpp b/src/Components/Modules/Gamepad.cpp index edb34245..2f00d7de 100644 --- a/src/Components/Modules/Gamepad.cpp +++ b/src/Components/Modules/Gamepad.cpp @@ -314,11 +314,11 @@ namespace Components } } - bool Gamepad::GPad_Check(const int gamePadIndex, const int portIndex) + bool Gamepad::GPad_Check(const int localClientNum, const int portIndex) { - AssertIn(gamePadIndex, Game::MAX_GPAD_COUNT); + AssertIn(localClientNum, Game::MAX_GPAD_COUNT); - auto& gamePad = gamePads[gamePadIndex]; + auto& gamePad = gamePads[localClientNum]; if (XInputGetCapabilities(portIndex, XINPUT_FLAG_GAMEPAD, &gamePad.caps) == ERROR_SUCCESS) { @@ -334,11 +334,12 @@ namespace Components void Gamepad::GPad_RefreshAll() { auto currentGamePadNum = 0; - for (auto currentPort = 0; currentPort < XUSER_MAX_COUNT && currentGamePadNum < Game::MAX_GPAD_COUNT; currentPort++) { if (GPad_Check(currentGamePadNum, currentPort)) - currentGamePadNum++; + { + ++currentGamePadNum; + } } } @@ -347,34 +348,45 @@ namespace Components const auto err = target - current; float step; if (err <= 0.0f) + { step = -rate * deltaTime; + } else + { step = rate * deltaTime; + } if (std::fabs(err) <= 0.001f) + { return target; + } if (std::fabs(step) <= std::fabs(err)) + { return current + step; + } return target; } bool Gamepad::AimAssist_DoBoundsIntersectCenterBox(const float* clipMins, const float* clipMaxs, const float clipHalfWidth, const float clipHalfHeight) { - return clipHalfWidth >= clipMins[0] && clipMaxs[0] >= -clipHalfWidth - && clipHalfHeight >= clipMins[1] && clipMaxs[1] >= -clipHalfHeight; + return clipHalfWidth >= clipMins[0] && clipMaxs[0] >= -clipHalfWidth && clipHalfHeight >= clipMins[1] && clipMaxs[1] >= -clipHalfHeight; } bool Gamepad::AimAssist_IsPlayerUsingOffhand(Game::AimAssistPlayerState* ps) { // Check offhand flag if ((ps->weapFlags & Game::PWF_USING_OFFHAND) == 0) + { return false; + } // If offhand weapon has no id we are not using one if (!ps->weapIndex) + { return false; + } const auto* weaponDef = Game::BG_GetWeaponDef(static_cast(ps->weapIndex)); @@ -399,13 +411,17 @@ namespace Components const Game::AimScreenTarget* Gamepad::AimAssist_GetTargetFromEntity(const Game::AimAssistGlobals* aaGlob, const int entIndex) { if (entIndex == Game::AIM_TARGET_INVALID) + { return nullptr; + } for (auto targetIndex = 0; targetIndex < aaGlob->screenTargetCount; targetIndex++) { const auto* currentTarget = &aaGlob->screenTargets[targetIndex]; if (currentTarget->entIndex == entIndex) + { return currentTarget; + } } return nullptr; @@ -417,25 +433,32 @@ namespace Components const auto screenTarget = AimAssist_GetTargetFromEntity(aaGlob, prevTargetEnt); if (screenTarget && (range * range) > screenTarget->distSqr && AimAssist_DoBoundsIntersectCenterBox(screenTarget->clipMins, screenTarget->clipMaxs, regionWidth, regionHeight)) + { return screenTarget; + } return AimAssist_GetBestTarget(aaGlob, range, regionWidth, regionHeight); } - bool Gamepad::AimAssist_IsLockonActive(const int gamePadIndex) + bool Gamepad::AimAssist_IsLockonActive(const int localClientNum) { - AssertIn(gamePadIndex, Game::MAX_GPAD_COUNT); - - auto& aaGlob = Game::aaGlobArray[gamePadIndex]; + AssertIn(localClientNum, Game::MAX_GPAD_COUNT); if (!aim_lockon_enabled.get() || !gpad_lockon_enabled.get()) + { return false; + } + auto& aaGlob = Game::aaGlobArray[localClientNum]; if (AimAssist_IsPlayerUsingOffhand(&aaGlob.ps)) + { return false; + } if (aaGlob.autoAimActive || aaGlob.autoMeleeState == Game::AIM_MELEE_STATE_UPDATING) + { return false; + } return true; } @@ -452,18 +475,26 @@ namespace Components aaGlob.lockOnTargetEnt = Game::AIM_TARGET_INVALID; if (!AimAssist_IsLockonActive(input->localClientNum)) + { return; + } const auto* weaponDef = Game::BG_GetWeaponDef(static_cast(aaGlob.ps.weapIndex)); if (weaponDef->requireLockonToFire) + { return; + } const auto deflection = aim_lockon_deflection.get(); if (deflection > std::fabs(input->pitchAxis) && deflection > std::fabs(input->yawAxis) && deflection > std::fabs(input->rightAxis)) + { return; + } if (!aaGlob.ps.weapIndex) + { return; + } const auto aimAssistRange = AimAssist_Lerp(weaponDef->aimAssistRange, weaponDef->aimAssistRangeAds, aaGlob.adsLerp) * aim_aimAssistRangeScale.get(); const auto screenTarget = AimAssist_GetPrevOrBestTarget(&aaGlob, aimAssistRange, aaGlob.tweakables.lockOnRegionWidth, aaGlob.tweakables.lockOnRegionHeight, prevTargetEnt); @@ -524,35 +555,53 @@ namespace Components const auto absYawAxis = std::fabs(*yawAxis); if (absPitchAxis <= absYawAxis) + { *pitchAxis = (1.0f - (absYawAxis - absPitchAxis)) * *pitchAxis; + } else + { *yawAxis = (1.0f - (absPitchAxis - absYawAxis)) * *yawAxis; + } } } bool Gamepad::AimAssist_IsSlowdownActive(const Game::AimAssistPlayerState* ps) { if (!aim_slowdown_enabled.get() || !gpad_slowdown_enabled.get()) + { return false; + } if (!ps->weapIndex) + { return false; + } const auto* weaponDef = Game::BG_GetWeaponDef(static_cast(ps->weapIndex)); if (weaponDef->requireLockonToFire) + { return false; + } if (ps->linkFlags & Game::PLF_WEAPONVIEW_ONLY) + { return false; + } if (ps->weaponState >= Game::WEAPON_STUNNED_START && ps->weaponState <= Game::WEAPON_STUNNED_END) + { return false; + } if (ps->eFlags & (Game::EF_VEHICLE_ACTIVE | Game::EF_TURRET_ACTIVE_DUCK | Game::EF_TURRET_ACTIVE_PRONE)) + { return false; + } if (!ps->hasAmmo) + { return false; + } return true; } @@ -570,7 +619,9 @@ namespace Components *yawScale = 1.0f; if (!AimAssist_IsSlowdownActive(&aaGlob.ps)) + { return; + } const auto* weaponDef = Game::BG_GetWeaponDef(static_cast(aaGlob.ps.weapIndex)); const auto aimAssistRange = AimAssist_Lerp(weaponDef->aimAssistRange, weaponDef->aimAssistRangeAds, aaGlob.adsLerp) * aim_aimAssistRangeScale.get(); @@ -583,7 +634,9 @@ namespace Components } if (AimAssist_IsPlayerUsingOffhand(&aaGlob.ps)) + { *pitchScale = 1.0f; + } } float Gamepad::AimAssist_Lerp(const float from, const float to, const float fraction) @@ -793,16 +846,17 @@ namespace Components return !Game::Key_IsCatcherActive(localClientNum, Game::KEYCATCH_MASK_ANY) || Game::UI_GetActiveMenu(localClientNum) == Game::UIMENU_SCOREBOARD; } - float Gamepad::CL_GamepadAxisValue(const int gamePadIndex, const Game::GamepadVirtualAxis virtualAxis) + float Gamepad::CL_GamepadAxisValue(const int localClientNum, const Game::GamepadVirtualAxis virtualAxis) { assert(virtualAxis > Game::GPAD_VIRTAXIS_NONE && virtualAxis < Game::GPAD_VIRTAXIS_COUNT); - const auto& gamePadGlobal = gamePadGlobals[gamePadIndex]; - + const auto& gamePadGlobal = gamePadGlobals[localClientNum]; const auto& [physicalAxis, mapType] = gamePadGlobal.axes.virtualAxes[virtualAxis]; if (physicalAxis <= Game::GPAD_PHYSAXIS_NONE || physicalAxis >= Game::GPAD_PHYSAXIS_COUNT) + { return 0.0f; + } auto axisDeflection = gamePadGlobal.axes.axesValues[physicalAxis]; @@ -812,9 +866,13 @@ namespace Components float otherAxisDeflection; if (otherAxisSameStick <= Game::GPAD_PHYSAXIS_NONE || otherAxisSameStick >= Game::GPAD_PHYSAXIS_COUNT) + { otherAxisDeflection = 0.0f; + } else + { otherAxisDeflection = gamePadGlobal.axes.axesValues[otherAxisSameStick]; + } axisDeflection = std::sqrt(axisDeflection * axisDeflection + otherAxisDeflection * otherAxisDeflection) * axisDeflection; } @@ -827,26 +885,23 @@ namespace Components return static_cast(std::clamp(value, std::numeric_limits::min(), std::numeric_limits::max())); } - void Gamepad::CL_GamepadMove(const int gamePadIndex, Game::usercmd_s* cmd, const float frameTimeBase) + void Gamepad::CL_GamepadMove(const int localClientNum, const float frameTimeBase, Game::usercmd_s* cmd) { - AssertIn(gamePadIndex, Game::MAX_GPAD_COUNT); + AssertIn(localClientNum, Game::MAX_GPAD_COUNT); - auto& gamePad = gamePads[gamePadIndex]; - auto& clientActive = Game::clients[gamePadIndex]; + auto& gamePad = gamePads[localClientNum]; + auto& clientActive = Game::clients[localClientNum]; - if (!gpad_enabled.get() || !gamePad.enabled) - return; - - auto pitch = CL_GamepadAxisValue(gamePadIndex, Game::GPAD_VIRTAXIS_PITCH); + auto pitch = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_PITCH); if (!input_invertPitch.get()) pitch *= -1; - auto yaw = -CL_GamepadAxisValue(gamePadIndex, Game::GPAD_VIRTAXIS_YAW); - auto forward = CL_GamepadAxisValue(gamePadIndex, Game::GPAD_VIRTAXIS_FORWARD); - auto side = CL_GamepadAxisValue(gamePadIndex, Game::GPAD_VIRTAXIS_SIDE); + auto yaw = -CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_YAW); + auto forward = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_FORWARD); + auto side = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_SIDE); // The game implements an attack axis at this location. This axis is unused however so for this patch it was not implemented. - //auto attack = CL_GamepadAxisValue(gamePadIndex, Game::GPAD_VIRTAXIS_ATTACK); + //auto attack = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_ATTACK); auto moveScale = static_cast(std::numeric_limits::max()); @@ -865,28 +920,36 @@ namespace Components cmd->forwardmove = ClampChar(cmd->forwardmove + forwardMove); // Swap attack and throw buttons when using controller and akimbo to match "left trigger"="left weapon" and "right trigger"="right weapon" - if(gamePad.inUse && clientActive.snap.ps.weapCommon.lastWeaponHand == Game::WEAPON_HAND_LEFT) + if (gamePad.inUse && clientActive.snap.ps.weapCommon.lastWeaponHand == Game::WEAPON_HAND_LEFT) { auto oldButtons = cmd->buttons; if (oldButtons & Game::CMD_BUTTON_ATTACK) + { cmd->buttons |= Game::CMD_BUTTON_THROW; + } else + { cmd->buttons &= ~Game::CMD_BUTTON_THROW; + } if (oldButtons & Game::CMD_BUTTON_THROW) + { cmd->buttons |= Game::CMD_BUTTON_ATTACK; + } else + { cmd->buttons &= ~Game::CMD_BUTTON_ATTACK; + } } // Check for frozen controls. Flag name should start with PMF_ - if (CG_ShouldUpdateViewAngles(gamePadIndex) && (clientActive.snap.ps.pm_flags & Game::PMF_FROZEN) == 0) + if (CG_ShouldUpdateViewAngles(localClientNum) && (clientActive.snap.ps.pm_flags & Game::PMF_FROZEN) == 0) { Game::AimInput aimInput{}; Game::AimOutput aimOutput{}; aimInput.deltaTime = frameTimeBase; aimInput.buttons = cmd->buttons; - aimInput.localClientNum = gamePadIndex; + aimInput.localClientNum = localClientNum; aimInput.deltaTimeScaled = static_cast(Game::cls->frametime) * 0.001f; aimInput.pitch = clientActive.clViewangles[0]; aimInput.pitchAxis = pitch; @@ -902,23 +965,32 @@ namespace Components } } - constexpr auto CL_MouseMove = 0x5A6240; + void Gamepad::CL_MouseMove(const int localClientNum, Game::usercmd_s* cmd, const float frametime_base) + { + auto& gamePad = gamePads[localClientNum]; + if (!gamePad.inUse) + { + Game::CL_MouseMove(localClientNum, cmd, frametime_base); + } + else if (gpad_enabled.get() && gamePad.enabled) + { + CL_GamepadMove(localClientNum, frametime_base, cmd); + } + } + __declspec(naked) void Gamepad::CL_MouseMove_Stub() { __asm { - // Prepare args for our function call - push [esp+0x4] // frametime_base + pushad + + push [esp + 0x20 + 0x4] // frametime_base push ebx // cmd push eax // localClientNum - - push [esp+0x8] // restore frametime_base on the stack call CL_MouseMove - add esp,4 + add esp, 0xC - // Call our function, the args were already prepared earlier - call CL_GamepadMove - add esp,0xC + popad ret } @@ -970,14 +1042,16 @@ namespace Components || key >= Game::K_FIRSTGAMEPADBUTTON_RANGE_3 && key <= Game::K_LASTGAMEPADBUTTON_RANGE_3; } - void Gamepad::CL_GamepadResetMenuScrollTime(const int gamePadIndex, const int key, const bool down, const unsigned time) + void Gamepad::CL_GamepadResetMenuScrollTime(const int localClientNum, const int key, const bool down, const unsigned time) { - AssertIn(gamePadIndex, Game::MAX_GPAD_COUNT); + AssertIn(localClientNum, Game::MAX_GPAD_COUNT); - auto& gamePadGlobal = gamePadGlobals[gamePadIndex]; + auto& gamePadGlobal = gamePadGlobals[localClientNum]; if (!down) + { return; + } const auto scrollDelayFirst = gpad_menu_scroll_delay_first.get(); for (const auto scrollButton : menuScrollButtonList) @@ -990,12 +1064,12 @@ namespace Components } } - void Gamepad::CL_GamepadGenerateAPad(const int gamePadIndex, const Game::GamepadPhysicalAxis physicalAxis, unsigned time) + void Gamepad::CL_GamepadGenerateAPad(const int localClientNum, const Game::GamepadPhysicalAxis physicalAxis, unsigned time) { - AssertIn(gamePadIndex, Game::MAX_GPAD_COUNT); + AssertIn(localClientNum, Game::MAX_GPAD_COUNT); assert(physicalAxis >= 0 && physicalAxis < Game::GPAD_PHYSAXIS_COUNT); - auto& gamePad = gamePads[gamePadIndex]; + auto& gamePad = gamePads[localClientNum]; const auto stick = stickForAxis[physicalAxis]; const auto stickIndex = stick & Game::GPAD_VALUE_MASK; @@ -1007,34 +1081,34 @@ namespace Components if (gamePad.stickDown[stickIndex][Game::GPAD_STICK_POS]) { const Game::GamePadButtonEvent event = gamePad.stickDownLast[stickIndex][Game::GPAD_STICK_POS] ? Game::GPAD_BUTTON_UPDATE : Game::GPAD_BUTTON_PRESSED; - CL_GamepadButtonEvent(gamePadIndex, mapping.posCode, event, time); + CL_GamepadButtonEvent(localClientNum, mapping.posCode, event, time); } else if (gamePad.stickDown[stickIndex][Game::GPAD_STICK_NEG]) { const Game::GamePadButtonEvent event = gamePad.stickDownLast[stickIndex][Game::GPAD_STICK_NEG] ? Game::GPAD_BUTTON_UPDATE : Game::GPAD_BUTTON_PRESSED; - CL_GamepadButtonEvent(gamePadIndex, mapping.negCode, event, time); + CL_GamepadButtonEvent(localClientNum, mapping.negCode, event, time); } else if (gamePad.stickDownLast[stickIndex][Game::GPAD_STICK_POS]) { - CL_GamepadButtonEvent(gamePadIndex, mapping.posCode, Game::GPAD_BUTTON_RELEASED, time); + CL_GamepadButtonEvent(localClientNum, mapping.posCode, Game::GPAD_BUTTON_RELEASED, time); } else if (gamePad.stickDownLast[stickIndex][Game::GPAD_STICK_NEG]) { - CL_GamepadButtonEvent(gamePadIndex, mapping.negCode, Game::GPAD_BUTTON_RELEASED, time); + CL_GamepadButtonEvent(localClientNum, mapping.negCode, Game::GPAD_BUTTON_RELEASED, time); } } } - void Gamepad::CL_GamepadEvent(const int gamePadIndex, const Game::GamepadPhysicalAxis physicalAxis, const float value, const unsigned time) + void Gamepad::CL_GamepadEvent(const int localClientNum, const Game::GamepadPhysicalAxis physicalAxis, const float value, const unsigned time) { - AssertIn(gamePadIndex, Game::MAX_GPAD_COUNT); + AssertIn(localClientNum, Game::MAX_GPAD_COUNT); assert(physicalAxis >= 0 && physicalAxis < Game::GPAD_PHYSAXIS_COUNT); - auto& gamePad = gamePads[gamePadIndex]; - auto& gamePadGlobal = gamePadGlobals[gamePadIndex]; + auto& gamePad = gamePads[localClientNum]; + auto& gamePadGlobal = gamePadGlobals[localClientNum]; gamePadGlobal.axes.axesValues[physicalAxis] = value; - CL_GamepadGenerateAPad(gamePadIndex, physicalAxis, time); + CL_GamepadGenerateAPad(localClientNum, physicalAxis, time); if (std::fabs(value) > 0.0f) { @@ -1043,12 +1117,12 @@ namespace Components } } - void Gamepad::UI_GamepadKeyEvent(const int gamePadIndex, const int key, const bool down) + void Gamepad::UI_GamepadKeyEvent(const int localClientNum, const int key, const bool down) { // If we are currently capturing a key for menu bind inputs then do not map keys and pass to game if (*Game::g_waitingForKey) { - Game::UI_KeyEvent(gamePadIndex, key, down); + Game::UI_KeyEvent(localClientNum, key, down); return; } @@ -1056,24 +1130,24 @@ namespace Components { if (mapping.controllerKey == key) { - Game::UI_KeyEvent(gamePadIndex, mapping.pcKey, down); + Game::UI_KeyEvent(localClientNum, mapping.pcKey, down); return; } } // No point in sending unmapped controller keystrokes to the key event handler since it doesn't know how to use it anyway - // Game::UI_KeyEvent(gamePadIndex, key, down); + // Game::UI_KeyEvent(localClientNum, key, down); } - bool Gamepad::Scoreboard_HandleInput(int gamePadIndex, int key) + bool Gamepad::Scoreboard_HandleInput(int localClientNum, int key) { - AssertIn(gamePadIndex, Game::STATIC_MAX_LOCAL_CLIENTS); + AssertIn(localClientNum, Game::STATIC_MAX_LOCAL_CLIENTS); - auto& keyState = Game::playerKeys[gamePadIndex]; + auto& keyState = Game::playerKeys[localClientNum]; - if (keyState.keys[key].binding && strcmp(keyState.keys[key].binding, "togglescores") == 0) + if (keyState.keys[key].binding && std::strcmp(keyState.keys[key].binding, "togglescores") == 0) { - Game::Cbuf_AddText(gamePadIndex, "togglescores\n"); + Game::Cbuf_AddText(localClientNum, "togglescores\n"); return true; } @@ -1092,13 +1166,13 @@ namespace Components } } - bool Gamepad::CL_CheckForIgnoreDueToRepeat(const int gamePadIndex, const int key, const int repeatCount, const unsigned time) + bool Gamepad::CL_CheckForIgnoreDueToRepeat(const int localClientNum, const int key, const int repeatCount, const unsigned time) { - AssertIn(gamePadIndex, Game::STATIC_MAX_LOCAL_CLIENTS); + AssertIn(localClientNum, Game::STATIC_MAX_LOCAL_CLIENTS); - auto& gamePadGlobal = gamePadGlobals[gamePadIndex]; + auto& gamePadGlobal = gamePadGlobals[localClientNum]; - if (Game::Key_IsCatcherActive(gamePadIndex, Game::KEYCATCH_UI)) + if (Game::Key_IsCatcherActive(localClientNum, Game::KEYCATCH_UI)) { const int scrollDelayFirst = gpad_menu_scroll_delay_first.get(); const int scrollDelayRest = gpad_menu_scroll_delay_rest.get(); @@ -1126,32 +1200,36 @@ namespace Components return repeatCount > 1; } - void Gamepad::CL_GamepadButtonEvent(const int gamePadIndex, const int key, const Game::GamePadButtonEvent buttonEvent, const unsigned time) + void Gamepad::CL_GamepadButtonEvent(const int localClientNum, const int key, const Game::GamePadButtonEvent buttonEvent, const unsigned time) { - AssertIn(gamePadIndex, Game::MAX_GPAD_COUNT); + AssertIn(localClientNum, Game::MAX_GPAD_COUNT); const auto pressed = buttonEvent == Game::GPAD_BUTTON_PRESSED; const auto pressedOrUpdated = pressed || buttonEvent == Game::GPAD_BUTTON_UPDATE; - auto& keyState = Game::playerKeys[gamePadIndex]; + auto& keyState = Game::playerKeys[localClientNum]; keyState.keys[key].down = pressedOrUpdated; if (pressedOrUpdated) { if (++keyState.keys[key].repeats == 1) + { keyState.anyKeyDown++; + } } else if (buttonEvent == Game::GPAD_BUTTON_RELEASED && keyState.keys[key].repeats > 0) { keyState.keys[key].repeats = 0; if (--keyState.anyKeyDown < 0) + { keyState.anyKeyDown = 0; + } } - if (pressedOrUpdated && CL_CheckForIgnoreDueToRepeat(gamePadIndex, key, keyState.keys[key].repeats, time)) + if (pressedOrUpdated && CL_CheckForIgnoreDueToRepeat(localClientNum, key, keyState.keys[key].repeats, time)) return; - if (Game::Key_IsCatcherActive(gamePadIndex, Game::KEYCATCH_LOCATION_SELECTION) && pressedOrUpdated) + if (Game::Key_IsCatcherActive(localClientNum, Game::KEYCATCH_LOCATION_SELECTION) && pressedOrUpdated) { if (key == Game::K_BUTTON_B || keyState.keys[key].binding && strcmp(keyState.keys[key].binding, "+actionslot 4") == 0) { @@ -1164,11 +1242,13 @@ namespace Components return; } - const auto activeMenu = Game::UI_GetActiveMenu(gamePadIndex); - if(activeMenu == Game::UIMENU_SCOREBOARD) + const auto activeMenu = Game::UI_GetActiveMenu(localClientNum); + if (activeMenu == Game::UIMENU_SCOREBOARD) { - if (buttonEvent == Game::GPAD_BUTTON_PRESSED && Scoreboard_HandleInput(gamePadIndex, key)) + if (buttonEvent == Game::GPAD_BUTTON_PRESSED && Scoreboard_HandleInput(localClientNum, key)) + { return; + } } keyState.locSelInputState = Game::LOC_SEL_INPUT_NONE; @@ -1178,9 +1258,9 @@ namespace Components char cmd[1024]; if (pressedOrUpdated) { - if (Game::Key_IsCatcherActive(gamePadIndex, Game::KEYCATCH_UI)) + if (Game::Key_IsCatcherActive(localClientNum, Game::KEYCATCH_UI)) { - UI_GamepadKeyEvent(gamePadIndex, key, pressedOrUpdated); + UI_GamepadKeyEvent(localClientNum, key, pressedOrUpdated); return; } @@ -1189,11 +1269,11 @@ namespace Components if (keyBinding[0] == '+') { sprintf_s(cmd, "%s %i %i\n", keyBinding, key, time); - Game::Cbuf_AddText(gamePadIndex, cmd); + Game::Cbuf_AddText(localClientNum, cmd); } else { - Game::Cbuf_InsertText(gamePadIndex, keyBinding); + Game::Cbuf_InsertText(localClientNum, keyBinding); } } } @@ -1202,34 +1282,36 @@ namespace Components if (keyBinding && keyBinding[0] == '+') { sprintf_s(cmd, "-%s %i %i\n", &keyBinding[1], key, time); - Game::Cbuf_AddText(gamePadIndex, cmd); + Game::Cbuf_AddText(localClientNum, cmd); } - if (Game::Key_IsCatcherActive(gamePadIndex, Game::KEYCATCH_UI)) + if (Game::Key_IsCatcherActive(localClientNum, Game::KEYCATCH_UI)) { - UI_GamepadKeyEvent(gamePadIndex, key, pressedOrUpdated); + UI_GamepadKeyEvent(localClientNum, key, pressedOrUpdated); } } } - void Gamepad::CL_GamepadButtonEventForPort(const int gamePadIndex, const int key, const Game::GamePadButtonEvent buttonEvent, const unsigned time) + void Gamepad::CL_GamepadButtonEventForPort(const int localClientNum, const int key, const Game::GamePadButtonEvent buttonEvent, const unsigned time) { - AssertIn(gamePadIndex, Game::MAX_GPAD_COUNT); + AssertIn(localClientNum, Game::MAX_GPAD_COUNT); - auto& gamePad = gamePads[gamePadIndex]; + auto& gamePad = gamePads[localClientNum]; gamePad.inUse = true; gpad_in_use.setRaw(true); - if (Game::Key_IsCatcherActive(gamePadIndex, Game::KEYCATCH_UI)) - CL_GamepadResetMenuScrollTime(gamePadIndex, key, buttonEvent == Game::GPAD_BUTTON_PRESSED, time); + if (Game::Key_IsCatcherActive(localClientNum, Game::KEYCATCH_UI)) + { + CL_GamepadResetMenuScrollTime(localClientNum, key, buttonEvent == Game::GPAD_BUTTON_PRESSED, time); + } - CL_GamepadButtonEvent(gamePadIndex, key, buttonEvent, time); + CL_GamepadButtonEvent(localClientNum, key, buttonEvent, time); } void Gamepad::GPad_ConvertStickToFloat(const short x, const short y, float& outX, float& outY) { - if(x == 0 && y == 0) + if (x == 0 && y == 0) { outX = 0.0f; outY = 0.0f; @@ -1246,31 +1328,36 @@ namespace Components if (gpad_stick_deadzone_min.get() <= len) { if (1.0f - gpad_stick_deadzone_max.get() >= len) + { len = (len - gpad_stick_deadzone_min.get()) / (1.0f - deadZoneTotal); + } else + { len = 1.0f; + } } else + { len = 0.0f; + } outX = stickVec[0] * len; outY = stickVec[1] * len; } - float Gamepad::GPad_GetStick(const int gamePadIndex, const Game::GamePadStick stick) + float Gamepad::GPad_GetStick(const int localClientNum, const Game::GamePadStick stick) { - AssertIn(gamePadIndex, Game::MAX_GPAD_COUNT); + AssertIn(localClientNum, Game::MAX_GPAD_COUNT); assert(stick & Game::GPAD_STICK_MASK); - auto& gamePad = gamePads[gamePadIndex]; - + auto& gamePad = gamePads[localClientNum]; return gamePad.sticks[stick]; } - float Gamepad::GPad_GetButton(const int gamePadIndex, Game::GamePadButton button) + float Gamepad::GPad_GetButton(const int localClientNum, Game::GamePadButton button) { - AssertIn(gamePadIndex, Game::MAX_GPAD_COUNT); - auto& gamePad = gamePads[gamePadIndex]; + AssertIn(localClientNum, Game::MAX_GPAD_COUNT); + auto& gamePad = gamePads[localClientNum]; float value = 0.0f; @@ -1291,12 +1378,12 @@ namespace Components return value; } - bool Gamepad::GPad_IsButtonPressed(const int gamePadIndex, Game::GamePadButton button) + bool Gamepad::GPad_IsButtonPressed(const int localClientNum, Game::GamePadButton button) { - AssertIn(gamePadIndex, Game::MAX_GPAD_COUNT); + AssertIn(localClientNum, Game::MAX_GPAD_COUNT); assert(button & (Game::GPAD_DIGITAL_MASK | Game::GPAD_ANALOG_MASK)); - auto& gamePad = gamePads[gamePadIndex]; + auto& gamePad = gamePads[localClientNum]; bool down = false; bool lastDown = false; @@ -1322,16 +1409,16 @@ namespace Components return down && !lastDown; } - bool Gamepad::GPad_ButtonRequiresUpdates(const int gamePadIndex, Game::GamePadButton button) + bool Gamepad::GPad_ButtonRequiresUpdates(const int localClientNum, Game::GamePadButton button) { - return (button & Game::GPAD_ANALOG_MASK || button & Game::GPAD_DPAD_MASK) && GPad_GetButton(gamePadIndex, button) > 0.0f; + return (button & Game::GPAD_ANALOG_MASK || button & Game::GPAD_DPAD_MASK) && GPad_GetButton(localClientNum, button) > 0.0f; } - bool Gamepad::GPad_IsButtonReleased(int gamePadIndex, Game::GamePadButton button) + bool Gamepad::GPad_IsButtonReleased(int localClientNum, Game::GamePadButton button) { - AssertIn(gamePadIndex, Game::MAX_GPAD_COUNT); + AssertIn(localClientNum, Game::MAX_GPAD_COUNT); - auto& gamePad = gamePads[gamePadIndex]; + auto& gamePad = gamePads[localClientNum]; bool down = false; bool lastDown = false; @@ -1358,24 +1445,27 @@ namespace Components return !down && lastDown; } - void Gamepad::GPad_UpdateSticksDown(const int gamePadIndex) + void Gamepad::GPad_UpdateSticksDown(const int localClientNum) { - AssertIn(gamePadIndex, Game::MAX_GPAD_COUNT); - - auto& gamePad = gamePads[gamePadIndex]; + AssertIn(localClientNum, Game::MAX_GPAD_COUNT); for (auto stickIndex = 0u; stickIndex < std::extent_v; stickIndex++) { for (auto dir = 0; dir < Game::GPAD_STICK_DIR_COUNT; dir++) { + auto& gamePad = gamePads[localClientNum]; gamePad.stickDownLast[stickIndex][dir] = gamePad.stickDown[stickIndex][dir]; auto threshold = gpad_stick_pressed.get(); if (gamePad.stickDownLast[stickIndex][dir]) + { threshold -= gpad_stick_pressed_hysteresis.get(); + } else + { threshold += gpad_stick_pressed_hysteresis.get(); + } if (dir == Game::GPAD_STICK_POS) { @@ -1390,11 +1480,11 @@ namespace Components } } - void Gamepad::GPad_UpdateSticks(const int gamePadIndex, const XINPUT_GAMEPAD& state) + void Gamepad::GPad_UpdateSticks(const int localClientNum, const XINPUT_GAMEPAD& state) { - AssertIn(gamePadIndex, Game::MAX_GPAD_COUNT); + AssertIn(localClientNum, Game::MAX_GPAD_COUNT); - auto& gamePad = gamePads[gamePadIndex]; + auto& gamePad = gamePads[localClientNum]; Game::vec2_t lVec, rVec; GPad_ConvertStickToFloat(state.sThumbLX, state.sThumbLY, lVec[0], lVec[1]); @@ -1409,7 +1499,7 @@ namespace Components gamePad.lastSticks[3] = gamePad.sticks[3]; gamePad.sticks[3] = rVec[1]; - GPad_UpdateSticksDown(gamePadIndex); + GPad_UpdateSticksDown(localClientNum); if (gpad_debug.get()) { @@ -1422,21 +1512,26 @@ namespace Components } } - void Gamepad::GPad_UpdateDigitals(const int gamePadIndex, const XINPUT_GAMEPAD& state) + void Gamepad::GPad_UpdateDigitals(const int localClientNum, const XINPUT_GAMEPAD& state) { - AssertIn(gamePadIndex, Game::MAX_GPAD_COUNT); + AssertIn(localClientNum, Game::MAX_GPAD_COUNT); - auto& gamePad = gamePads[gamePadIndex]; + auto& gamePad = gamePads[localClientNum]; gamePad.lastDigitals = gamePad.digitals; gamePad.digitals = state.wButtons; const auto leftDeflect = gpad_button_lstick_deflect_max.get(); if (std::fabs(gamePad.sticks[0]) > leftDeflect || std::fabs(gamePad.sticks[1]) > leftDeflect) + { gamePad.digitals &= ~static_cast(XINPUT_GAMEPAD_LEFT_THUMB); + } + const auto rightDeflect = gpad_button_rstick_deflect_max.get(); if (std::fabs(gamePad.sticks[2]) > leftDeflect || std::fabs(gamePad.sticks[3]) > rightDeflect) + { gamePad.digitals &= ~static_cast(XINPUT_GAMEPAD_RIGHT_THUMB); + } if (gpad_debug.get()) { @@ -1444,24 +1539,27 @@ namespace Components } } - void Gamepad::GPad_UpdateAnalogs(const int gamePadIndex, const XINPUT_GAMEPAD& state) + void Gamepad::GPad_UpdateAnalogs(const int localClientNum, const XINPUT_GAMEPAD& state) { - AssertIn(gamePadIndex, Game::MAX_GPAD_COUNT); + AssertIn(localClientNum, Game::MAX_GPAD_COUNT); - auto& gamePad = gamePads[gamePadIndex]; + auto& gamePad = gamePads[localClientNum]; const auto buttonDeadZone = gpad_button_deadzone.get(); gamePad.lastAnalogs[0] = gamePad.analogs[0]; gamePad.analogs[0] = static_cast(state.bLeftTrigger) / static_cast(std::numeric_limits::max()); if (gamePad.analogs[0] < buttonDeadZone) + { gamePad.analogs[0] = 0.0f; - + } gamePad.lastAnalogs[1] = gamePad.analogs[1]; gamePad.analogs[1] = static_cast(state.bRightTrigger) / static_cast(std::numeric_limits::max()); if (gamePad.analogs[1] < buttonDeadZone) + { gamePad.analogs[1] = 0.0f; + } if (gpad_debug.get()) { @@ -1473,19 +1571,23 @@ namespace Components { GPad_RefreshAll(); - for (auto currentGamePadIndex = 0; currentGamePadIndex < Game::MAX_GPAD_COUNT; currentGamePadIndex++) + for (auto localClientNum = 0; localClientNum < Game::MAX_GPAD_COUNT; ++localClientNum) { - const auto& gamePad = gamePads[currentGamePadIndex]; + const auto& gamePad = gamePads[localClientNum]; if (!gamePad.enabled) + { continue; + } XINPUT_STATE inputState; if (XInputGetState(gamePad.portIndex, &inputState) != ERROR_SUCCESS) + { continue; + } - GPad_UpdateSticks(currentGamePadIndex, inputState.Gamepad); - GPad_UpdateDigitals(currentGamePadIndex, inputState.Gamepad); - GPad_UpdateAnalogs(currentGamePadIndex, inputState.Gamepad); + GPad_UpdateSticks(localClientNum, inputState.Gamepad); + GPad_UpdateDigitals(localClientNum, inputState.Gamepad); + GPad_UpdateAnalogs(localClientNum, inputState.Gamepad); } } @@ -1498,53 +1600,42 @@ namespace Components const auto time = Game::Sys_Milliseconds(); bool gpadPresent = false; - for (auto gamePadIndex = 0; gamePadIndex < Game::MAX_GPAD_COUNT; gamePadIndex++) + for (auto localClientNum = 0; localClientNum < Game::MAX_GPAD_COUNT; ++localClientNum) { - const auto& gamePad = gamePads[gamePadIndex]; - - if (gamePad.enabled) + const auto& gamePad = gamePads[localClientNum]; + if (!gamePad.enabled) { - gpadPresent = true; - const auto lx = GPad_GetStick(gamePadIndex, Game::GPAD_LX); - const auto ly = GPad_GetStick(gamePadIndex, Game::GPAD_LY); - const auto rx = GPad_GetStick(gamePadIndex, Game::GPAD_RX); - const auto ry = GPad_GetStick(gamePadIndex, Game::GPAD_RY); - const auto leftTrig = GPad_GetButton(gamePadIndex, Game::GPAD_L_TRIG); - const auto rightTrig = GPad_GetButton(gamePadIndex, Game::GPAD_R_TRIG); + continue; + } - CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_LSTICK_X, lx, time); - CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_LSTICK_Y, ly, time); - CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_RSTICK_X, rx, time); - CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_RSTICK_Y, ry, time); - CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_LTRIGGER, leftTrig, time); - CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_RTRIGGER, rightTrig, time); + gpadPresent = true; + const auto lx = GPad_GetStick(localClientNum, Game::GPAD_LX); + const auto ly = GPad_GetStick(localClientNum, Game::GPAD_LY); + const auto rx = GPad_GetStick(localClientNum, Game::GPAD_RX); + const auto ry = GPad_GetStick(localClientNum, Game::GPAD_RY); + const auto leftTrig = GPad_GetButton(localClientNum, Game::GPAD_L_TRIG); + const auto rightTrig = GPad_GetButton(localClientNum, Game::GPAD_R_TRIG); - for (const auto& buttonMapping : buttonList) + CL_GamepadEvent(localClientNum, Game::GPAD_PHYSAXIS_LSTICK_X, lx, time); + CL_GamepadEvent(localClientNum, Game::GPAD_PHYSAXIS_LSTICK_Y, ly, time); + CL_GamepadEvent(localClientNum, Game::GPAD_PHYSAXIS_RSTICK_X, rx, time); + CL_GamepadEvent(localClientNum, Game::GPAD_PHYSAXIS_RSTICK_Y, ry, time); + CL_GamepadEvent(localClientNum, Game::GPAD_PHYSAXIS_LTRIGGER, leftTrig, time); + CL_GamepadEvent(localClientNum, Game::GPAD_PHYSAXIS_RTRIGGER, rightTrig, time); + + for (const auto& buttonMapping : buttonList) + { + if (GPad_IsButtonPressed(localClientNum, buttonMapping.padButton)) { - if (GPad_IsButtonPressed(gamePadIndex, buttonMapping.padButton)) - { - CL_GamepadButtonEventForPort( - gamePadIndex, - buttonMapping.code, - Game::GPAD_BUTTON_PRESSED, - time); - } - else if (GPad_ButtonRequiresUpdates(gamePadIndex, buttonMapping.padButton)) - { - CL_GamepadButtonEventForPort( - gamePadIndex, - buttonMapping.code, - Game::GPAD_BUTTON_UPDATE, - time); - } - else if (GPad_IsButtonReleased(gamePadIndex, buttonMapping.padButton)) - { - CL_GamepadButtonEventForPort( - gamePadIndex, - buttonMapping.code, - Game::GPAD_BUTTON_RELEASED, - time); - } + CL_GamepadButtonEventForPort(localClientNum, buttonMapping.code, Game::GPAD_BUTTON_PRESSED, time); + } + else if (GPad_ButtonRequiresUpdates(localClientNum, buttonMapping.padButton)) + { + CL_GamepadButtonEventForPort(localClientNum, buttonMapping.code, Game::GPAD_BUTTON_UPDATE, time); + } + else if (GPad_IsButtonReleased(localClientNum, buttonMapping.padButton)) + { + CL_GamepadButtonEventForPort(localClientNum, buttonMapping.code, Game::GPAD_BUTTON_RELEASED, time); } } } @@ -1559,19 +1650,19 @@ namespace Components IN_GamePadsMove(); } - void Gamepad::Gamepad_WriteBindings(const int gamePadIndex, const int handle) + void Gamepad::Gamepad_WriteBindings(const int localClientNum, const int handle) { - AssertIn(gamePadIndex, Game::MAX_GPAD_COUNT); + AssertIn(localClientNum, Game::MAX_GPAD_COUNT); - auto& gamePadGlobal = gamePadGlobals[gamePadIndex]; + auto& gamePadGlobal = gamePadGlobals[localClientNum]; Game::FS_Printf(handle, "unbindallaxis\n"); - for (auto virtualAxisIndex = 0u; virtualAxisIndex < Game::GPAD_VIRTAXIS_COUNT; virtualAxisIndex++) + for (auto virtualAxisIndex = 0u; virtualAxisIndex < Game::GPAD_VIRTAXIS_COUNT; ++virtualAxisIndex) { const auto& axisMapping = gamePadGlobal.axes.virtualAxes[virtualAxisIndex]; - if (axisMapping.physicalAxis <= Game::GPAD_PHYSAXIS_NONE || axisMapping.physicalAxis >= Game::GPAD_PHYSAXIS_COUNT - || axisMapping.mapType <= Game::GPAD_MAP_NONE || axisMapping.mapType >= Game::GPAD_MAP_COUNT) + if (axisMapping.physicalAxis <= Game::GPAD_PHYSAXIS_NONE || axisMapping.physicalAxis >= Game::GPAD_PHYSAXIS_COUNT || + axisMapping.mapType <= Game::GPAD_MAP_NONE || axisMapping.mapType >= Game::GPAD_MAP_COUNT) { continue; } @@ -1612,14 +1703,14 @@ namespace Components } } - void Gamepad::Gamepad_BindAxis(const int gamePadIndex, const Game::GamepadPhysicalAxis realIndex, const Game::GamepadVirtualAxis axisIndex, const Game::GamepadMapping mapType) + void Gamepad::Gamepad_BindAxis(const int localClientNum, const Game::GamepadPhysicalAxis realIndex, const Game::GamepadVirtualAxis axisIndex, const Game::GamepadMapping mapType) { - AssertIn(gamePadIndex, Game::MAX_GPAD_COUNT); + AssertIn(localClientNum, Game::MAX_GPAD_COUNT); assert(realIndex > Game::GPAD_PHYSAXIS_NONE && realIndex < Game::GPAD_PHYSAXIS_COUNT); assert(axisIndex > Game::GPAD_VIRTAXIS_NONE && axisIndex < Game::GPAD_VIRTAXIS_COUNT); assert(mapType > Game::GPAD_MAP_NONE && mapType < Game::GPAD_MAP_COUNT); - auto& gamePadGlobal = gamePadGlobals[gamePadIndex]; + auto& gamePadGlobal = gamePadGlobals[localClientNum]; gamePadGlobal.axes.virtualAxes[axisIndex].physicalAxis = realIndex; gamePadGlobal.axes.virtualAxes[axisIndex].mapType = mapType; @@ -1628,10 +1719,12 @@ namespace Components Game::GamepadPhysicalAxis Gamepad::StringToPhysicalAxis(const char* str) { - for (auto i = 0u; i < std::extent_v; i++) + for (std::size_t i = 0; i < std::extent_v; i++) { - if (strcmp(str, physicalAxisNames[i]) == 0) + if (std::strcmp(str, physicalAxisNames[i]) == 0) + { return static_cast(i); + } } return Game::GPAD_PHYSAXIS_NONE; @@ -1639,10 +1732,12 @@ namespace Components Game::GamepadVirtualAxis Gamepad::StringToVirtualAxis(const char* str) { - for (auto i = 0u; i < std::extent_v; i++) + for (std::size_t i = 0; i < std::extent_v; ++i) { - if (strcmp(str, virtualAxisNames[i]) == 0) + if (std::strcmp(str, virtualAxisNames[i]) == 0) + { return static_cast(i); + } } return Game::GPAD_VIRTAXIS_NONE; @@ -1650,10 +1745,12 @@ namespace Components Game::GamepadMapping Gamepad::StringToGamePadMapping(const char* str) { - for (auto i = 0u; i < std::extent_v; i++) + for (std::size_t i = 0; i < std::extent_v; ++i) { - if (strcmp(str, gamePadMappingTypeNames[i]) == 0) + if (std::strcmp(str, gamePadMappingTypeNames[i]) == 0) + { return static_cast(i); + } } return Game::GPAD_MAP_NONE; @@ -1720,12 +1817,16 @@ namespace Components void Gamepad::Scores_Toggle_f(Command::Params*) { - if(Game::cgArray[0].nextSnap) + if (Game::cgArray[0].nextSnap) { if (Game::UI_GetActiveMenu(0) != Game::UIMENU_SCOREBOARD) + { Game::CG_ScoresDown_f(); + } else + { Game::CG_ScoresUp_f(); + } } } @@ -1791,10 +1892,15 @@ namespace Components const char* Gamepad::GetGamePadCommand(const char* command) { - if (strcmp(command, "+activate") == 0 || strcmp(command, "+reload") == 0) + if (std::strcmp(command, "+activate") == 0 || std::strcmp(command, "+reload") == 0) + { return "+usereload"; - if (strcmp(command, "+melee_breath") == 0) + } + + if (std::strcmp(command, "+melee_breath") == 0) + { return "+holdbreath"; + } return command; } @@ -1812,14 +1918,18 @@ namespace Components for (auto keyNum = 0; keyNum < Game::K_LAST_KEY; keyNum++) { if (!Key_IsValidGamePadChar(keyNum)) + { continue; + } if (Game::playerKeys[0].keys[keyNum].binding && strcmp(Game::playerKeys[0].keys[keyNum].binding, gamePadCmd) == 0) { (*keys)[keyCount++] = keyNum; if (keyCount >= 2) + { return keyCount; + } } } } @@ -1828,14 +1938,18 @@ namespace Components for (auto keyNum = 0; keyNum < Game::K_LAST_KEY; keyNum++) { if (Key_IsValidGamePadChar(keyNum)) + { continue; + } if (Game::playerKeys[0].keys[keyNum].binding && strcmp(Game::playerKeys[0].keys[keyNum].binding, cmd) == 0) { (*keys)[keyCount++] = keyNum; if (keyCount >= 2) + { return keyCount; + } } } } @@ -1866,8 +1980,10 @@ namespace Components void Gamepad::Key_SetBinding_Hk(const int localClientNum, const int keyNum, const char* binding) { - if(Key_IsValidGamePadChar(keyNum)) + if (Key_IsValidGamePadChar(keyNum)) + { gpad_buttonConfig.set("custom"); + } Game::Key_SetBinding(localClientNum, keyNum, binding); } @@ -1911,8 +2027,10 @@ namespace Components Game::keyname_t* Gamepad::GetLocalizedKeyNameMap() { - if(gpad_style.get()) + if (gpad_style.get()) + { return combinedLocalizedKeyNamesPs3; + } return combinedLocalizedKeyNamesXenon; } @@ -1938,18 +2056,18 @@ namespace Components void Gamepad::CreateKeyNameMap() { - memcpy(combinedKeyNames, Game::keyNames, sizeof(Game::keyname_t) * Game::KEY_NAME_COUNT); - memcpy(&combinedKeyNames[Game::KEY_NAME_COUNT], extendedKeyNames, sizeof(Game::keyname_t) * std::extent_v); + std::memcpy(combinedKeyNames, Game::keyNames, sizeof(Game::keyname_t) * Game::KEY_NAME_COUNT); + std::memcpy(&combinedKeyNames[Game::KEY_NAME_COUNT], extendedKeyNames, sizeof(Game::keyname_t) * std::extent_v); combinedKeyNames[std::extent_v - 1] = {nullptr, 0}; - memcpy(combinedLocalizedKeyNamesXenon, Game::localizedKeyNames, sizeof(Game::keyname_t) * Game::LOCALIZED_KEY_NAME_COUNT); - memcpy(&combinedLocalizedKeyNamesXenon[Game::LOCALIZED_KEY_NAME_COUNT], extendedLocalizedKeyNamesXenon, - sizeof(Game::keyname_t) * std::extent_v); + std::memcpy(combinedLocalizedKeyNamesXenon, Game::localizedKeyNames, sizeof(Game::keyname_t) * Game::LOCALIZED_KEY_NAME_COUNT); + std::memcpy(&combinedLocalizedKeyNamesXenon[Game::LOCALIZED_KEY_NAME_COUNT], extendedLocalizedKeyNamesXenon, sizeof(Game::keyname_t) * std::extent_v); + combinedLocalizedKeyNamesXenon[std::extent_v - 1] = {nullptr, 0}; - memcpy(combinedLocalizedKeyNamesPs3, Game::localizedKeyNames, sizeof(Game::keyname_t) * Game::LOCALIZED_KEY_NAME_COUNT); - memcpy(&combinedLocalizedKeyNamesPs3[Game::LOCALIZED_KEY_NAME_COUNT], extendedLocalizedKeyNamesPs3, - sizeof(Game::keyname_t) * std::extent_v); + std::memcpy(combinedLocalizedKeyNamesPs3, Game::localizedKeyNames, sizeof(Game::keyname_t) * Game::LOCALIZED_KEY_NAME_COUNT); + std::memcpy(&combinedLocalizedKeyNamesPs3[Game::LOCALIZED_KEY_NAME_COUNT], extendedLocalizedKeyNamesPs3, sizeof(Game::keyname_t) * std::extent_v); + combinedLocalizedKeyNamesPs3[std::extent_v - 1] = {nullptr, 0}; Utils::Hook::Set(0x4A780A, combinedKeyNames); @@ -1961,7 +2079,9 @@ namespace Components Gamepad::Gamepad() { if (ZoneBuilder::IsEnabled()) + { return; + } // Initialize gamepad environment Utils::Hook(0x4059FE, CG_RegisterDvars_Hk, HOOK_CALL).install()->quick(); @@ -1993,7 +2113,9 @@ namespace Components Command::Add("togglescores", Scores_Toggle_f); if (Dedicated::IsEnabled()) + { return; + } // Gamepad on frame hook Utils::Hook(0x475E9E, IN_Frame_Hk, HOOK_CALL).install()->quick(); @@ -2021,7 +2143,7 @@ namespace Components // Add gamepad inputs to location selection (eg airstrike location) handling Utils::Hook(0x5A6D72, CG_HandleLocationSelectionInput_Stub, HOOK_CALL).install()->quick(); - // Add gamepad inputs to usercmds + // Add gamepad inputs to user commands if it is enabled Utils::Hook(0x5A6DAE, CL_MouseMove_Stub, HOOK_CALL).install()->quick(); } } diff --git a/src/Components/Modules/Gamepad.hpp b/src/Components/Modules/Gamepad.hpp index 45969762..5ff132d2 100644 --- a/src/Components/Modules/Gamepad.hpp +++ b/src/Components/Modules/Gamepad.hpp @@ -125,7 +125,7 @@ namespace Components static const Game::AimScreenTarget* AimAssist_GetBestTarget(const Game::AimAssistGlobals* aaGlob, float range, float regionWidth, float regionHeight); static const Game::AimScreenTarget* AimAssist_GetTargetFromEntity(const Game::AimAssistGlobals* aaGlob, int entIndex); static const Game::AimScreenTarget* AimAssist_GetPrevOrBestTarget(const Game::AimAssistGlobals* aaGlob, float range, float regionWidth, float regionHeight, int prevTargetEnt); - static bool AimAssist_IsLockonActive(int gamePadIndex); + static bool AimAssist_IsLockonActive(int localClientNum); static void AimAssist_ApplyLockOn(const Game::AimInput* input, Game::AimOutput* output); static void AimAssist_CalcAdjustedAxis(const Game::AimInput* input, float* pitchAxis, float* yawAxis); static bool AimAssist_IsSlowdownActive(const Game::AimAssistPlayerState* ps); @@ -139,47 +139,48 @@ namespace Components static bool CG_HandleLocationSelectionInput_GamePad(int localClientNum, Game::usercmd_s* cmd); static void CG_HandleLocationSelectionInput_Stub(); static bool CG_ShouldUpdateViewAngles(int localClientNum); - static float CL_GamepadAxisValue(int gamePadIndex, Game::GamepadVirtualAxis virtualAxis); + static float CL_GamepadAxisValue(int localClientNum, Game::GamepadVirtualAxis virtualAxis); static char ClampChar(int value); - static void CL_GamepadMove(int gamePadIndex, Game::usercmd_s* cmd, float frameTimeBase); + static void CL_GamepadMove(int localClientNum, float frameTimeBase, Game::usercmd_s* cmd); + static void CL_MouseMove(int localClientNum, Game::usercmd_s* cmd, float frametime_base); static void CL_MouseMove_Stub(); static bool Gamepad_ShouldUse(const Game::gentity_s* playerEnt, unsigned useTime); static void Player_UseEntity_Stub(); static bool Key_IsValidGamePadChar(int key); - static void CL_GamepadResetMenuScrollTime(int gamePadIndex, int key, bool down, unsigned int time); - static bool Scoreboard_HandleInput(int gamePadIndex, int key); - static bool CL_CheckForIgnoreDueToRepeat(int gamePadIndex, int key, int repeatCount, unsigned int time); - static void UI_GamepadKeyEvent(int gamePadIndex, int key, bool down); - static void CL_GamepadGenerateAPad(int gamePadIndex, Game::GamepadPhysicalAxis physicalAxis, unsigned time); - static void CL_GamepadEvent(int gamePadIndex, Game::GamepadPhysicalAxis physicalAxis, float value, unsigned time); - static void CL_GamepadButtonEvent(int gamePadIndex, int key, Game::GamePadButtonEvent buttonEvent, unsigned time); - static void CL_GamepadButtonEventForPort(int gamePadIndex, int key, Game::GamePadButtonEvent buttonEvent, unsigned int time); + static void CL_GamepadResetMenuScrollTime(int localClientNum, int key, bool down, unsigned int time); + static bool Scoreboard_HandleInput(int localClientNum, int key); + static bool CL_CheckForIgnoreDueToRepeat(int localClientNum, int key, int repeatCount, unsigned int time); + static void UI_GamepadKeyEvent(int localClientNum, int key, bool down); + static void CL_GamepadGenerateAPad(int localClientNum, Game::GamepadPhysicalAxis physicalAxis, unsigned time); + static void CL_GamepadEvent(int localClientNum, Game::GamepadPhysicalAxis physicalAxis, float value, unsigned time); + static void CL_GamepadButtonEvent(int localClientNum, int key, Game::GamePadButtonEvent buttonEvent, unsigned time); + static void CL_GamepadButtonEventForPort(int localClientNum, int key, Game::GamePadButtonEvent buttonEvent, unsigned int time); static void GPad_ConvertStickToFloat(short x, short y, float& outX, float& outY); - static float GPad_GetStick(int gamePadIndex, Game::GamePadStick stick); - static float GPad_GetButton(int gamePadIndex, Game::GamePadButton button); - static bool GPad_IsButtonPressed(int gamePadIndex, Game::GamePadButton button); - static bool GPad_ButtonRequiresUpdates(int gamePadIndex, Game::GamePadButton button); - static bool GPad_IsButtonReleased(int gamePadIndex, Game::GamePadButton button); + static float GPad_GetStick(int localClientNum, Game::GamePadStick stick); + static float GPad_GetButton(int localClientNum, Game::GamePadButton button); + static bool GPad_IsButtonPressed(int localClientNum, Game::GamePadButton button); + static bool GPad_ButtonRequiresUpdates(int localClientNum, Game::GamePadButton button); + static bool GPad_IsButtonReleased(int localClientNum, Game::GamePadButton button); - static void GPad_UpdateSticksDown(int gamePadIndex); - static void GPad_UpdateSticks(int gamePadIndex, const XINPUT_GAMEPAD& state); - static void GPad_UpdateDigitals(int gamePadIndex, const XINPUT_GAMEPAD& state); - static void GPad_UpdateAnalogs(int gamePadIndex, const XINPUT_GAMEPAD& state); + static void GPad_UpdateSticksDown(int localClientNum); + static void GPad_UpdateSticks(int localClientNum, const XINPUT_GAMEPAD& state); + static void GPad_UpdateDigitals(int localClientNum, const XINPUT_GAMEPAD& state); + static void GPad_UpdateAnalogs(int localClientNum, const XINPUT_GAMEPAD& state); - static bool GPad_Check(int gamePadIndex, int portIndex); + static bool GPad_Check(int localClientNum, int portIndex); static void GPad_RefreshAll(); static void GPad_UpdateAll(); static void IN_GamePadsMove(); static void IN_Frame_Hk(); - static void Gamepad_WriteBindings(int gamePadIndex, int handle); + static void Gamepad_WriteBindings(int localClientNum, int handle); static void Key_WriteBindings_Hk(int localClientNum, int handle); static void Com_WriteConfiguration_Modified_Stub(); - static void Gamepad_BindAxis(int gamePadIndex, Game::GamepadPhysicalAxis realIndex, Game::GamepadVirtualAxis axisIndex, Game::GamepadMapping mapType); + static void Gamepad_BindAxis(int localClientNum, Game::GamepadPhysicalAxis realIndex, Game::GamepadVirtualAxis axisIndex, Game::GamepadMapping mapType); static Game::GamepadPhysicalAxis StringToPhysicalAxis(const char* str); static Game::GamepadVirtualAxis StringToVirtualAxis(const char* str); static Game::GamepadMapping StringToGamePadMapping(const char* str); diff --git a/src/Components/Modules/IPCPipe.cpp b/src/Components/Modules/IPCPipe.cpp index 1de08efb..a7e79a40 100644 --- a/src/Components/Modules/IPCPipe.cpp +++ b/src/Components/Modules/IPCPipe.cpp @@ -1,4 +1,5 @@ #include +#include namespace Components { @@ -7,7 +8,7 @@ namespace Components #pragma region Pipe - Pipe::Pipe() : connectCallback(0), pipe(INVALID_HANDLE_VALUE), threadAttached(false), type(IPCTYPE_NONE), reconnectAttempt(0) + Pipe::Pipe() : connectCallback(nullptr), pipe(INVALID_HANDLE_VALUE), threadAttached(false), type(IPCTYPE_NONE), reconnectAttempt(0) { this->destroy(); } @@ -66,7 +67,7 @@ namespace Components if (!Loader::IsPerformingUnitTests()) { this->threadAttached = true; - this->thread = std::thread(Pipe::ReceiveThread, this); + this->thread = std::thread(ReceiveThread, this); } Logger::Print("Pipe successfully created\n"); @@ -92,7 +93,7 @@ namespace Components { if (this->type != IPCTYPE_CLIENT || this->pipe == INVALID_HANDLE_VALUE) return false; - Pipe::Packet _packet; + Packet _packet; strcpy_s(_packet.command, command.data()); strcpy_s(_packet.buffer, data.data()); @@ -102,15 +103,9 @@ namespace Components void Pipe::destroy() { - //this->Type = IPCTYPE_NONE; - - //*this->PipeFile = 0; - //*this->PipeName = 0; - if (this->pipe && INVALID_HANDLE_VALUE != this->pipe) { CancelIoEx(this->pipe, nullptr); - //DeleteFileA(this->pipeFile); if (this->type == IPCTYPE_SERVER) DisconnectNamedPipe(this->pipe); @@ -133,11 +128,11 @@ namespace Components void Pipe::setName(const std::string& name) { - memset(this->pipeName, 0, sizeof(this->pipeName)); - memset(this->pipeFile, 0, sizeof(this->pipeFile)); + ZeroMemory(this->pipeName, sizeof(this->pipeName)); + ZeroMemory(this->pipeFile, sizeof(this->pipeFile)); strncpy_s(this->pipeName, name.data(), sizeof(this->pipeName)); - sprintf_s(this->pipeFile, sizeof(this->pipeFile), "\\\\.\\Pipe\\%s", this->pipeName); + sprintf_s(this->pipeFile, "\\\\.\\Pipe\\%s", this->pipeName); } void Pipe::ReceiveThread(Pipe* pipe) @@ -157,7 +152,7 @@ namespace Components while (pipe->threadAttached && pipe->pipe && pipe->pipe != INVALID_HANDLE_VALUE) { - BOOL bResult = ReadFile(pipe->pipe, &pipe->packet, sizeof(pipe->packet), &cbBytes, nullptr); + auto bResult = ReadFile(pipe->pipe, &pipe->packet, sizeof(pipe->packet), &cbBytes, nullptr); if (bResult && cbBytes) { @@ -168,7 +163,7 @@ namespace Components } else if (pipe->threadAttached && pipe->pipe != INVALID_HANDLE_VALUE) { - Logger::Print("Failed to read from client through pipe\n"); + Logger::PrintError(Game::CON_CHANNEL_ERROR, "Failed to read from client through pipe\n"); DisconnectNamedPipe(pipe->pipe); ConnectNamedPipe(pipe->pipe, nullptr); @@ -186,20 +181,20 @@ namespace Components { if (Singleton::IsFirstInstance()) { - IPCPipe::ClientPipe.connect(IPC_PIPE_NAME_CLIENT); + ClientPipe.connect(IPC_PIPE_NAME_CLIENT); } } // Writes to the process on the other end of the pipe bool IPCPipe::Write(const std::string& command, const std::string& data) { - return IPCPipe::ClientPipe.write(command, data); + return ClientPipe.write(command, data); } // Installs a callback for receiving commands from the process on the other end of the pipe - void IPCPipe::On(const std::string& command, Utils::Slot callback) + void IPCPipe::On(const std::string& command, const Utils::Slot& callback) { - IPCPipe::ServerPipe.setCallback(command, callback); + ServerPipe.setCallback(command, callback); } IPCPipe::IPCPipe() @@ -207,37 +202,37 @@ namespace Components if (Dedicated::IsEnabled() || Loader::IsPerformingUnitTests() || ZoneBuilder::IsEnabled()) return; // Server pipe - IPCPipe::ServerPipe.onConnect(IPCPipe::ConnectClient); - IPCPipe::ServerPipe.create((Singleton::IsFirstInstance() ? IPC_PIPE_NAME_SERVER : IPC_PIPE_NAME_CLIENT)); + ServerPipe.onConnect(ConnectClient); + ServerPipe.create((Singleton::IsFirstInstance() ? IPC_PIPE_NAME_SERVER : IPC_PIPE_NAME_CLIENT)); // Connect second instance's client pipe to first instance's server pipe if (!Singleton::IsFirstInstance()) { - IPCPipe::ClientPipe.connect(IPC_PIPE_NAME_SERVER); + ClientPipe.connect(IPC_PIPE_NAME_SERVER); } - IPCPipe::On("ping", [](const std::string& data) + On("ping", [](const std::string& data) { Logger::Print("Received ping form pipe, sending pong!\n"); - IPCPipe::Write("pong", data); + Write("pong", data); }); - IPCPipe::On("pong", [](const std::string& /*data*/) + On("pong", []([[maybe_unused]] const std::string& data) { Logger::Print("Received pong form pipe!\n"); }); // Test pipe functionality by sending pings - Command::Add("ipcping", [](Command::Params*) + Command::Add("ipcping", []([[maybe_unused]] Command::Params* params) { Logger::Print("Sending ping to pipe!\n"); - IPCPipe::Write("ping", ""); + Write("ping", ""); }); } void IPCPipe::preDestroy() { - IPCPipe::ServerPipe.destroy(); - IPCPipe::ClientPipe.destroy(); + ServerPipe.destroy(); + ClientPipe.destroy(); } } diff --git a/src/Components/Modules/IPCPipe.hpp b/src/Components/Modules/IPCPipe.hpp index 14218cdd..1a9d0d08 100644 --- a/src/Components/Modules/IPCPipe.hpp +++ b/src/Components/Modules/IPCPipe.hpp @@ -51,8 +51,8 @@ namespace Components Type type; Packet packet; - char pipeName[MAX_PATH]; - char pipeFile[MAX_PATH]; + char pipeName[MAX_PATH]{}; + char pipeFile[MAX_PATH]{}; unsigned int reconnectAttempt; void setName(const std::string& name); @@ -68,7 +68,7 @@ namespace Components void preDestroy() override; static bool Write(const std::string& command, const std::string& data); - static void On(const std::string& command, Utils::Slot callback); + static void On(const std::string& command, const Utils::Slot& callback); private: static Pipe ServerPipe; diff --git a/src/Components/Modules/Localization.cpp b/src/Components/Modules/Localization.cpp index 15001840..cdf8d8d8 100644 --- a/src/Components/Modules/Localization.cpp +++ b/src/Components/Modules/Localization.cpp @@ -246,7 +246,7 @@ namespace Components for (i = 0; i < iTokenLen - 2; ++i) { - if (!std::strncmp(&szTokenBuf[i], "&&", 2) && std::isdigit(szTokenBuf[i + 2])) + if (!std::strncmp(&szTokenBuf[i], "&&", 2) && std::isdigit(static_cast(szTokenBuf[i + 2]))) { if (bInsertEnabled) { @@ -268,7 +268,7 @@ namespace Components { for (i = 0; i < iLen - 2; ++i) { - if (!std::strncmp(&pszString[i], "&&", 2) && std::isdigit(pszString[i + 2])) + if (!std::strncmp(&pszString[i], "&&", 2) && std::isdigit(static_cast(pszString[i + 2]))) { const auto digit = pszString[i + 2] - 48; if (!digit) diff --git a/src/Components/Modules/Logger.cpp b/src/Components/Modules/Logger.cpp index c1e446b7..8d9cdc99 100644 --- a/src/Components/Modules/Logger.cpp +++ b/src/Components/Modules/Logger.cpp @@ -80,11 +80,12 @@ namespace Components void Logger::ErrorInternal(const Game::errorParm_t error, const std::string_view& fmt, std::format_args&& args) { + const auto msg = std::vformat(fmt, args); + #ifdef _DEBUG if (IsDebuggerPresent()) __debugbreak(); #endif - const auto msg = std::vformat(fmt, args); Game::Com_Error(error, "%s", msg.data()); } @@ -95,7 +96,7 @@ namespace Components ++(*Game::com_errorPrintsCount); MessagePrint(channel, msg); - if (Game::cls->uiStarted != 0 && (*Game::com_fixedConsolePosition == 0)) + if (Game::cls->uiStarted && (*Game::com_fixedConsolePosition == 0)) { Game::CL_ConsoleFixPosition(); } diff --git a/src/Components/Modules/MapRotation.cpp b/src/Components/Modules/MapRotation.cpp index 1367ed41..8ed6ad4a 100644 --- a/src/Components/Modules/MapRotation.cpp +++ b/src/Components/Modules/MapRotation.cpp @@ -5,6 +5,7 @@ namespace Components { Dvar::Var MapRotation::SVRandomMapRotation; Dvar::Var MapRotation::SVDontRotate; + Dvar::Var MapRotation::SVNextMap; MapRotation::RotationData MapRotation::DedicatedRotation; @@ -39,6 +40,11 @@ namespace Components return this->rotationEntries_.at(index); } + MapRotation::RotationData::rotationEntry& MapRotation::RotationData::peekNextEntry() + { + return this->rotationEntries_.at(this->index_); + } + void MapRotation::RotationData::parse(const std::string& data) { const auto tokens = Utils::String::Split(data, ' '); @@ -54,7 +60,7 @@ namespace Components } else { - throw ParseRotationError(); + throw MapRotationParseError(); } } } @@ -89,7 +95,6 @@ namespace Components } } - auto mapRotationJson = nlohmann::json { {"maps", mapVector}, @@ -101,16 +106,6 @@ namespace Components void MapRotation::LoadRotation(const std::string& data) { - static auto loaded = false; - - if (loaded) - { - // Load the rotation once - return; - } - - loaded = true; - try { DedicatedRotation.parse(data); @@ -125,6 +120,15 @@ namespace Components void MapRotation::LoadMapRotation() { + static auto loaded = false; + if (loaded) + { + // Load the rotation once + return; + } + + loaded = true; + const std::string mapRotation = (*Game::sv_mapRotation)->current.string; // People may have sv_mapRotation empty because they only use 'addMap' or 'addGametype' if (!mapRotation.empty()) @@ -164,6 +168,12 @@ namespace Components return DedicatedRotation.contains(key, value); } + nlohmann::json MapRotation::to_json() + { + assert(!DedicatedRotation.empty()); + return DedicatedRotation.to_json(); + } + bool MapRotation::ShouldRotate() { if (!Dedicated::IsEnabled() && SVDontRotate.get()) @@ -175,7 +185,7 @@ namespace Components if (Dvar::Var("party_enable").get() && Dvar::Var("party_host").get()) { - Logger::Print(Game::CON_CHANNEL_SERVER, "Not performing map rotation as we are hosting a party!\n"); + Logger::Warning(Game::CON_CHANNEL_SERVER, "Not performing map rotation as we are hosting a party!\n"); return false; } @@ -199,7 +209,7 @@ namespace Components void MapRotation::ApplyGametype(const std::string& gametype) { assert(!gametype.empty()); - Dvar::Var("g_gametype").set(gametype); + Game::Dvar_SetStringByName("g_gametype", gametype.data()); } void MapRotation::RestartCurrentMap() @@ -220,29 +230,33 @@ namespace Components assert(!rotation.empty()); // Continue to apply gametype until a map is found - auto foundMap = false; - std::size_t i = 0; - while (!foundMap && i < rotation.getEntriesSize()) + while (i < rotation.getEntriesSize()) { const auto& entry = rotation.getNextEntry(); - if (entry.first == "map"s) { - Logger::Debug("Loading new map: '{}'", entry.second); + Logger::Print("Loading new map: '{}'", entry.second); ApplyMap(entry.second); // Map was found so we exit the loop - foundMap = true; + break; } - else if (entry.first == "gametype"s) + + if (entry.first == "gametype"s) { - Logger::Debug("Applying new gametype: '{}'", entry.second); + Logger::Print("Applying new gametype: '{}'", entry.second); ApplyGametype(entry.second); } ++i; } + + if (i == rotation.getEntriesSize()) + { + Logger::PrintError(Game::CON_CHANNEL_ERROR, "Map rotation does not contain any map. Restarting\n"); + RestartCurrentMap(); + } } void MapRotation::ApplyMapRotationCurrent(const std::string& data) @@ -275,6 +289,32 @@ namespace Components ApplyRotation(rotationCurrent); } + void MapRotation::SetNextMap(RotationData& rotation) + { + assert(!rotation.empty()); + + const auto& entry = rotation.peekNextEntry(); + if (entry.first == "map"s) + { + SVNextMap.set(entry.second); + } + else + { + ClearNextMap(); + } + } + + void MapRotation::SetNextMap(const char* value) + { + assert(value); + SVNextMap.set(value); + } + + void MapRotation::ClearNextMap() + { + SVNextMap.set(""); + } + void MapRotation::RandomizeMapRotation() { if (SVRandomMapRotation.get()) @@ -303,6 +343,7 @@ namespace Components { Logger::Debug("Applying {}", (*Game::sv_mapRotationCurrent)->name); ApplyMapRotationCurrent(mapRotationCurrent); + ClearNextMap(); return; } @@ -311,12 +352,21 @@ namespace Components { Logger::Print(Game::CON_CHANNEL_SERVER, "{} is empty or contains invalid data. Restarting map\n", (*Game::sv_mapRotation)->name); RestartCurrentMap(); + SetNextMap("map_restart"); return; } RandomizeMapRotation(); ApplyRotation(DedicatedRotation); + SetNextMap(DedicatedRotation); + } + + void MapRotation::RegisterMapRotationDvars() + { + SVRandomMapRotation = Dvar::Register("sv_randomMapRotation", false, Game::DVAR_ARCHIVE, "Randomize map rotation when true"); + SVDontRotate = Dvar::Register("sv_dontRotate", false, Game::DVAR_NONE, "Do not perform map rotation"); + SVNextMap = Dvar::Register("sv_nextMap", "", Game::DVAR_SERVERINFO, ""); } MapRotation::MapRotation() @@ -324,10 +374,7 @@ namespace Components AddMapRotationCommands(); Utils::Hook::Set(0x4152E8, SV_MapRotate_f); - SVRandomMapRotation = Dvar::Register("sv_randomMapRotation", false, - Game::DVAR_ARCHIVE, "Randomize map rotation when true"); - SVDontRotate = Dvar::Register("sv_dontRotate", false, - Game::DVAR_NONE, "Do not perform map rotation"); + Events::OnDvarInit(RegisterMapRotationDvars); } bool MapRotation::unitTest() diff --git a/src/Components/Modules/MapRotation.hpp b/src/Components/Modules/MapRotation.hpp index 09da7517..68e51b23 100644 --- a/src/Components/Modules/MapRotation.hpp +++ b/src/Components/Modules/MapRotation.hpp @@ -9,12 +9,14 @@ namespace Components static bool Contains(const std::string& key, const std::string& value); + static nlohmann::json to_json(); + bool unitTest() override; private: - struct ParseRotationError : public std::exception + struct MapRotationParseError : public std::exception { - const char* what() const noexcept override { return "Parse Rotation Error"; } + [[nodiscard]] const char* what() const noexcept override { return "Map Rotation Parse Error"; } }; class RotationData @@ -32,6 +34,7 @@ namespace Components [[nodiscard]] std::size_t getEntriesSize() const noexcept; rotationEntry& getNextEntry(); + rotationEntry& peekNextEntry(); void parse(const std::string& data); @@ -49,6 +52,7 @@ namespace Components // Rotation Dvars static Dvar::Var SVRandomMapRotation; static Dvar::Var SVDontRotate; + static Dvar::Var SVNextMap; // Holds the parsed data from sv_mapRotation static RotationData DedicatedRotation; @@ -58,6 +62,7 @@ namespace Components // Use these commands before SV_MapRotate_f is called static void AddMapRotationCommands(); + static void RegisterMapRotationDvars(); static bool ShouldRotate(); static void ApplyMap(const std::string& map); @@ -65,6 +70,11 @@ namespace Components static void RestartCurrentMap(); static void ApplyRotation(RotationData& rotation); static void ApplyMapRotationCurrent(const std::string& data); + + // Utils functions + static void SetNextMap(RotationData& rotation); // Only call this after ApplyRotation + static void SetNextMap(const char* value); + static void ClearNextMap(); static void RandomizeMapRotation(); static void SV_MapRotate_f(); diff --git a/src/Components/Modules/Maps.cpp b/src/Components/Modules/Maps.cpp index b3786320..48ee62c8 100644 --- a/src/Components/Modules/Maps.cpp +++ b/src/Components/Modules/Maps.cpp @@ -1,8 +1,9 @@ #include + +#include "FastFiles.hpp" #include "RawFiles.hpp" #include "StartupMessages.hpp" #include "Theatre.hpp" -#include "UIFeeder.hpp" namespace Components { @@ -10,6 +11,7 @@ namespace Components std::string Maps::CurrentMainZone; std::vector> Maps::DependencyList; std::vector Maps::CurrentDependencies; + std::vector Maps::FoundCustomMaps; Dvar::Var Maps::RListSModels; @@ -34,7 +36,7 @@ namespace Components if (this->isValid() && !this->searchPath.iwd) { auto iwdName = std::format("{}.iwd", this->mapname); - auto path = std::format("{}\\usermaps\\{}\\{}", Dvar::Var("fs_basepath").get(), this->mapname, iwdName); + auto path = std::format("{}\\usermaps\\{}\\{}", (*Game::fs_basepath)->current.string, this->mapname, iwdName); if (Utils::IO::FileExists(path)) { @@ -79,7 +81,7 @@ namespace Components this->wasFreed = true; // Unchain our searchpath - for (Game::searchpath_t** pathPtr = Game::fs_searchpaths; *pathPtr; pathPtr = &(*pathPtr)->next) + for (auto** pathPtr = Game::fs_searchpaths; *pathPtr; pathPtr = &(*pathPtr)->next) { if (*pathPtr == &this->searchPath) { @@ -104,8 +106,8 @@ namespace Components if (Maps::UserMap.isValid()) { - const std::string mapname = Maps::UserMap.getName(); - const auto* arena = Utils::String::VA("usermaps/%s/%s.arena", mapname.data(), mapname.data()); + const auto mapname = Maps::UserMap.getName(); + const auto arena = GetArenaPath(mapname); if (Utils::IO::FileExists(arena)) { @@ -182,17 +184,17 @@ namespace Components void Maps::OverrideMapEnts(Game::MapEnts* ents) { auto callback = [] (Game::XAssetHeader header, void* ents) - { - Game::MapEnts* mapEnts = reinterpret_cast(ents); - Game::clipMap_t* clipMap = header.clipMap; + { + Game::MapEnts* mapEnts = reinterpret_cast(ents); + Game::clipMap_t* clipMap = header.clipMap; - if (clipMap && mapEnts && !_stricmp(mapEnts->name, clipMap->name)) - { - clipMap->mapEnts = mapEnts; - //*Game::marMapEntsPtr = mapEnts; - //Game::G_SpawnEntitiesFromString(); - } - }; + if (clipMap && mapEnts && !_stricmp(mapEnts->name, clipMap->name)) + { + clipMap->mapEnts = mapEnts; + //*Game::marMapEntsPtr = mapEnts; + //Game::G_SpawnEntitiesFromString(); + } + }; // Internal doesn't lock the thread, as locking is impossible, due to executing this in the thread that holds the current lock Game::DB_EnumXAssets_Internal(Game::XAssetType::ASSET_TYPE_CLIPMAP_MP, callback, ents, true); @@ -337,12 +339,33 @@ namespace Components return (Utils::String::StartsWith(entity, "dyn_") || Utils::String::StartsWith(entity, "node_") || Utils::String::StartsWith(entity, "actor_")); } + std::unordered_map Maps::ParseCustomMapArena(const std::string& singleMapArena) + { + static const std::regex regex(" (\\w*) *\"?((?:\\w| )*)\"?"); + std::unordered_map arena; + + std::smatch m; + + std::string::const_iterator search_start(singleMapArena.cbegin()); + + while (std::regex_search(search_start, singleMapArena.cend(), m, regex)) + { + if (m.size() > 2) + { + arena.emplace(m[1].str(), m[2].str()); + search_start = m.suffix().first; + } + } + + return arena; + } + Maps::MapDependencies Maps::GetDependenciesForMap(const std::string& map) { std::string teamAxis = "opforce_composite"; std::string teamAllies = "us_army"; - Maps::MapDependencies dependencies{}; + Maps::MapDependencies dependencies; // True by default - cause some maps won't have an arenafile entry dependencies.requiresTeamZones = true; @@ -577,13 +600,53 @@ namespace Components return Utils::IO::DirectoryExists(std::format("usermaps/{}", mapname)) && Utils::IO::FileExists(std::format("usermaps/{}/{}.ff", mapname, mapname)); } + void Maps::ScanCustomMaps() + { + FoundCustomMaps.clear(); + Logger::Print("Looking for custom maps...\n"); + + std::filesystem::path basePath = (*Game::fs_basepath)->current.string; + basePath /= "usermaps"; + + if (!std::filesystem::exists(basePath)) + { + return; + } + + const auto entries = Utils::IO::ListFiles(basePath); + + for (const auto& entry : entries) + { + if (entry.is_directory()) + { + auto zoneName = entry.path().filename().string(); + auto mapPath = std::format("{}\\{}.ff", entry.path().string(), zoneName); + if (Utils::IO::FileExists(mapPath)) + { + FoundCustomMaps.push_back(zoneName); + Logger::Print("Discovered custom map {}\n", zoneName); + } + } + } + } + + std::string Maps::GetArenaPath(const std::string& mapName) + { + return std::format("usermaps/{}/{}.arena", mapName, mapName); + } + + const std::vector& Maps::GetCustomMaps() + { + return FoundCustomMaps; + } + Game::XAssetEntry* Maps::GetAssetEntryPool() { return *reinterpret_cast(0x48E6F4); } // dlcIsTrue serves as a check if the map is a custom map and if it's missing - bool Maps::CheckMapInstalled(const char* mapname, bool error, bool dlcIsTrue) + bool Maps::CheckMapInstalled(const std::string& mapname, bool error, bool dlcIsTrue) { if (FastFiles::Exists(mapname)) return true; @@ -591,12 +654,12 @@ namespace Components { for (auto map : pack.maps) { - if (map == std::string(mapname)) + if (map == mapname) { if (error) { Logger::Error(Game::ERR_DISCONNECT, "Missing DLC pack {} ({}) containing map {} ({}).\nPlease download it to play this map.", - pack.name, pack.index, Game::UI_LocalizeMapName(mapname), mapname); + pack.name, pack.index, Game::UI_LocalizeMapName(mapname.data()), mapname); } return dlcIsTrue; @@ -665,12 +728,25 @@ namespace Components return Utils::Hook::Call(0x5050C0)(ent); } - int16 Maps::CM_TriggerModelBounds(int modelPointer, Game::Bounds* bounds) { -#ifdef DEBUG - Game::MapEnts* ents = *reinterpret_cast(0x1AA651C); // Use me for debugging - (void)ents; -#endif - return Utils::Hook::Call(0x4416C0)(modelPointer, bounds); + unsigned short Maps::CM_TriggerModelBounds_Hk(unsigned int triggerIndex, Game::Bounds* bounds) + { + + auto* ents = *reinterpret_cast(0x1AA651C); // Use me for debugging + + if (ents) + { + if (triggerIndex >= ents->trigger.count) + { + Logger::Error(Game::errorParm_t::ERR_DROP, "Invalid trigger index ({}) in entities exceeds the maximum trigger count ({}) defined in the clipmap. Check your map ents, or your clipmap!", triggerIndex, ents->trigger.count); + return 0; + } + else + { + return Utils::Hook::Call(0x4416C0)(triggerIndex, bounds); + } + } + + return 0; } Maps::Maps() @@ -682,13 +758,9 @@ namespace Components Maps::AddDlc({ 1, "Stimulus Pack", {"mp_complex", "mp_compact", "mp_storm", "mp_overgrown", "mp_crash"} }); Maps::AddDlc({ 2, "Resurgence Pack", {"mp_abandon", "mp_vacant", "mp_trailerpark", "mp_strike", "mp_fuel2"} }); - Maps::AddDlc({ 3, "Nuketown", {"mp_nuked"} }); - Maps::AddDlc({ 4, "Classics Pack #1", {"mp_cross_fire", "mp_cargoship", "mp_bloc"} }); - Maps::AddDlc({ 5, "Classics Pack #2", {"mp_killhouse", "mp_bog_sh"} }); - Maps::AddDlc({ 6, "Freighter", {"mp_cargoship_sh"} }); - Maps::AddDlc({ 7, "Resurrection Pack", {"mp_shipment_long", "mp_rust_long", "mp_firingrange"} }); - Maps::AddDlc({ 8, "Recycled Pack", {"mp_bloc_sh", "mp_crash_tropical", "mp_estate_tropical", "mp_fav_tropical", "mp_storm_spring"} }); - Maps::AddDlc({ 9, "Classics Pack #3", {"mp_farm", "mp_backlot", "mp_pipeline", "mp_countdown", "mp_crash_snow", "mp_carentan", "mp_broadcast", "mp_showdown", "mp_convoy"} }); + Maps::AddDlc({ 3, "IW4x Classics", {"mp_nuked", "mp_cross_fire", "mp_cargoship", "mp_bloc", "mp_killhouse", "mp_bog_sh", "mp_cargoship_sh", "mp_shipment_long", "mp_rust_long", "mp_firingrange", "mp_bloc_sh", "mp_crash_tropical", "mp_estate_tropical", "mp_fav_tropical", "mp_storm_spring"} }); + Maps::AddDlc({ 4, "Call Of Duty 4 Pack", {"mp_farm", "mp_backlot", "mp_pipeline", "mp_countdown", "mp_crash_snow", "mp_carentan", "mp_broadcast", "mp_showdown", "mp_convoy"} }); + Maps::AddDlc({ 5, "Modern Warfare 3 Pack", {"mp_dome", "mp_hardhat", "mp_paris", "mp_seatown", "mp_bravo", "mp_underground", "mp_plaza2", "mp_village", "mp_alpha"}}); Maps::UpdateDlcStatus(); @@ -713,11 +785,13 @@ namespace Components Utils::Hook(0x5EE577, Maps::G_SpawnTurretHook, HOOK_CALL).install()->quick(); Utils::Hook(0x44A4D5, Maps::G_SpawnTurretHook, HOOK_CALL).install()->quick(); + // Catch trigger errors before they're critical + Utils::Hook(0x5050D4, Maps::CM_TriggerModelBounds_Hk, HOOK_CALL).install()->quick(); + #ifdef DEBUG // Check trigger models Utils::Hook(0x5FC0F1, Maps::SV_SetTriggerModelHook, HOOK_CALL).install()->quick(); Utils::Hook(0x5FC2671, Maps::SV_SetTriggerModelHook, HOOK_CALL).install()->quick(); - Utils::Hook(0x5050D4, Maps::CM_TriggerModelBounds, HOOK_CALL).install()->quick(); #endif // @@ -784,7 +858,7 @@ namespace Components }, Scheduler::Pipeline::CLIENT, 10s); }); - if(Dedicated::IsEnabled()) + if (Dedicated::IsEnabled()) { Utils::Hook(0x4A7251, Maps::LoadNewMapCommand, HOOK_CALL).install()->quick(); } diff --git a/src/Components/Modules/Maps.hpp b/src/Components/Modules/Maps.hpp index ea47c28b..356cf6fe 100644 --- a/src/Components/Modules/Maps.hpp +++ b/src/Components/Modules/Maps.hpp @@ -43,7 +43,7 @@ namespace Components bool wasFreed; unsigned int hash; std::string mapname; - Game::searchpath_t searchPath; + Game::searchpath_s searchPath; }; Maps(); @@ -54,7 +54,7 @@ namespace Components static std::string CurrentMainZone; static const char* UserMapFiles[4]; - static bool CheckMapInstalled(const char* mapname, bool error = false, bool dlcIsTrue = false); + static bool CheckMapInstalled(const std::string& mapname, bool error = false, bool dlcIsTrue = false); static UserMapContainer* GetUserMap(); static unsigned int GetUsermapHash(const std::string& map); @@ -63,6 +63,12 @@ namespace Components static bool IsCustomMap(); static bool IsUserMap(const std::string& mapname); + static void ScanCustomMaps(); + static std::string GetArenaPath(const std::string& mapName); + static const std::vector& GetCustomMaps(); + + static std::unordered_map ParseCustomMapArena(const std::string& singleMapArena); + private: class DLC { @@ -85,6 +91,7 @@ namespace Components static std::vector> DependencyList; static std::vector CurrentDependencies; + static std::vector FoundCustomMaps; static Dvar::Var RListSModels; @@ -121,6 +128,6 @@ namespace Components static void G_SpawnTurretHook(Game::gentity_s* ent, int unk, int unk2); static bool SV_SetTriggerModelHook(Game::gentity_s* ent); - static int16 CM_TriggerModelBounds(int brushModelPointer, Game::Bounds* bounds); + static unsigned short CM_TriggerModelBounds_Hk(unsigned int brushModelPointer, Game::Bounds* bounds); }; } diff --git a/src/Components/Modules/Menus.cpp b/src/Components/Modules/Menus.cpp index e9adf249..0328a1bd 100644 --- a/src/Components/Modules/Menus.cpp +++ b/src/Components/Modules/Menus.cpp @@ -1,4 +1,8 @@ #include +#include "Party.hpp" + +#define MAX_SOURCEFILES 64 +#define DEFINEHASHSIZE 1024 namespace Components { @@ -34,25 +38,31 @@ namespace Components int Menus::ReserveSourceHandle() { // Check if a free slot is available - int i = 1; - for (; i < MAX_SOURCEFILES; ++i) + auto i = 1; + while (i < MAX_SOURCEFILES) { if (!Game::sourceFiles[i]) + { break; + } + + ++i; } if (i >= MAX_SOURCEFILES) + { return 0; + } // Reserve it, if yes - Game::sourceFiles[i] = reinterpret_cast(1); + Game::sourceFiles[i] = reinterpret_cast(1); return i; } - Game::script_t* Menus::LoadMenuScript(const std::string& name, const std::string& buffer) + Game::script_s* Menus::LoadMenuScript(const std::string& name, const std::string& buffer) { - auto* script = static_cast(Game::GetClearedMemory(sizeof(Game::script_t) + 1 + buffer.length())); + auto* script = static_cast(Game::GetClearedMemory(sizeof(Game::script_s) + 1 + buffer.length())); if (!script) return nullptr; strcpy_s(script->filename, sizeof(script->filename), name.data()); @@ -80,37 +90,28 @@ namespace Components int Menus::LoadMenuSource(const std::string& name, const std::string& buffer) { - Utils::Memory::Allocator* allocator = Utils::Memory::GetAllocator(); - const auto handle = ReserveSourceHandle(); if (!IsValidSourceHandle(handle)) return 0; // No free source slot! auto* script = LoadMenuScript(name, buffer); - if (!script) { Game::sourceFiles[handle] = nullptr; // Free reserved slot return 0; } - auto* source = allocator->allocate(); - if (!source) - { - Game::FreeMemory(script); - return 0; - } - + auto* source = static_cast(Game::GetMemory(sizeof(Game::source_s))); std::memset(source, 0, sizeof(Game::source_s)); script->next = nullptr; - strncpy_s(source->filename, "string", _TRUNCATE); + strncpy_s(source->filename, name.data(), _TRUNCATE); source->scriptstack = script; source->tokens = nullptr; source->defines = nullptr; source->indentstack = nullptr; source->skip = 0; - source->definehash = static_cast(Game::GetClearedMemory(1024 * sizeof(Game::define_s*))); + source->definehash = static_cast(Game::GetClearedMemory(DEFINEHASHSIZE * sizeof(Game::define_s*))); Game::sourceFiles[handle] = source; @@ -135,7 +136,7 @@ namespace Components return nullptr; } - Game::pc_token_t token; + Game::pc_token_s token; if (!Game::PC_ReadTokenHandle(handle, &token) || token.string[0] != '{') { allocator->free(menu->items); @@ -194,8 +195,8 @@ namespace Components if (!menuFile.exists()) return nullptr; - Game::pc_token_t token; - int handle = LoadMenuSource(menu, menuFile.getBuffer()); + Game::pc_token_s token; + const auto handle = LoadMenuSource(menu, menuFile.getBuffer()); if (IsValidSourceHandle(handle)) { @@ -258,7 +259,7 @@ namespace Components if (menuFile.exists()) { - Game::pc_token_t token; + Game::pc_token_s token; const auto handle = LoadMenuSource(menu, menuFile.getBuffer()); if (IsValidSourceHandle(handle)) @@ -433,7 +434,7 @@ namespace Components } newList->name = allocator->duplicateString(menuList->name); - newList->menuCount = size; + newList->menuCount = static_cast(size); // Copy new menus for (unsigned int i = 0; i < menus.size(); ++i) @@ -447,45 +448,61 @@ namespace Components return newList; } + void Menus::FreeScript(Game::script_s* script) + { + if (script->punctuationtable) + { + Game::FreeMemory(script->punctuationtable); + } + + Game::FreeMemory(script); + } + void Menus::FreeMenuSource(int handle) { - Utils::Memory::Allocator* allocator = Utils::Memory::GetAllocator(); - if (!IsValidSourceHandle(handle)) return; - Game::source_t* source = Game::sourceFiles[handle]; + auto* source = Game::sourceFiles[handle]; while (source->scriptstack) { - Game::script_t* script = source->scriptstack; + auto* script = source->scriptstack; source->scriptstack = source->scriptstack->next; - Game::FreeMemory(script); + FreeScript(script); } while (source->tokens) { - Game::token_t* token = source->tokens; + auto* token = source->tokens; source->tokens = source->tokens->next; + Game::FreeMemory(token); + --*Game::numtokens; } - while (source->defines) + for (auto i = 0; i < DEFINEHASHSIZE; ++i) { - Game::define_t* define = source->defines; - source->defines = source->defines->next; - Game::FreeMemory(define); + while (source->definehash[i]) + { + auto* define = source->definehash[i]; + source->definehash[i] = source->definehash[i]->hashnext; + Game::PC_FreeDefine(define); + } } while (source->indentstack) { - Game::indent_t* indent = source->indentstack; + auto* indent = source->indentstack; source->indentstack = source->indentstack->next; - allocator->free(indent); + Game::FreeMemory(indent); } - if (source->definehash) allocator->free(source->definehash); + if (source->definehash) + { + Game::FreeMemory(source->definehash); + } - allocator->free(source); + Game::FreeMemory(source); Game::sourceFiles[handle] = nullptr; } @@ -727,11 +744,11 @@ namespace Components { if (menu->window.name == "connect"s) // Check if we're supposed to draw the loadscreen { - Game::menuDef_t* originalConnect = AssetHandler::FindOriginalAsset(Game::XAssetType::ASSET_TYPE_MENU, "connect").menu; + const auto* originalConnect = AssetHandler::FindOriginalAsset(Game::XAssetType::ASSET_TYPE_MENU, "connect").menu; if (originalConnect == menu) // Check if we draw the original loadscreen { - if (MenuList.find("connect") != Menus::MenuList.end()) // Check if we have a custom loadscreen, to prevent drawing the original one on top + if (MenuList.contains("connect")) // Check if we have a custom load screen, to prevent drawing the original one on top { return false; } @@ -784,8 +801,8 @@ namespace Components if (Dedicated::IsEnabled()) return; // Intercept asset finding - AssetHandler::OnFind(Game::XAssetType::ASSET_TYPE_MENU, MenuFindHook); - AssetHandler::OnFind(Game::XAssetType::ASSET_TYPE_MENULIST, MenuListFindHook); + AssetHandler::OnFind(Game::ASSET_TYPE_MENU, MenuFindHook); + AssetHandler::OnFind(Game::ASSET_TYPE_MENULIST, MenuListFindHook); // Don't open connect menu // Utils::Hook::Nop(0x428E48, 5); diff --git a/src/Components/Modules/Menus.hpp b/src/Components/Modules/Menus.hpp index 45ff1639..793cc817 100644 --- a/src/Components/Modules/Menus.hpp +++ b/src/Components/Modules/Menus.hpp @@ -1,6 +1,5 @@ #pragma once -#define MAX_SOURCEFILES 64 #undef LoadMenu namespace Components @@ -17,7 +16,7 @@ namespace Components static Game::MenuList* LoadCustomMenuList(const std::string& menu, Utils::Memory::Allocator* allocator); static std::vector> LoadMenu(Game::menuDef_t* menudef); - static std::vector> LoadMenu(const std::string& file); + static std::vector> LoadMenu(const std::string& menu); private: static std::unordered_map MenuList; @@ -32,7 +31,7 @@ namespace Components static void SafeMergeMenus(std::vector>* menus, std::vector> newMenus); - static Game::script_t* LoadMenuScript(const std::string& name, const std::string& buffer); + static Game::script_s* LoadMenuScript(const std::string& name, const std::string& buffer); static int LoadMenuSource(const std::string& name, const std::string& buffer); static int ReserveSourceHandle(); @@ -40,6 +39,7 @@ namespace Components static Game::menuDef_t* ParseMenu(int handle); + static void FreeScript(Game::script_s* script); static void FreeMenuSource(int handle); static void FreeMenuList(Game::MenuList* menuList); diff --git a/src/Components/Modules/ModList.cpp b/src/Components/Modules/ModList.cpp index 2b3a07f6..13f6c3ab 100644 --- a/src/Components/Modules/ModList.cpp +++ b/src/Components/Modules/ModList.cpp @@ -43,7 +43,7 @@ namespace Components void ModList::UIScript_LoadMods([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) { - auto folder = Dvar::Var("fs_basepath").get() + "\\mods"; + auto folder = (*Game::fs_basepath)->current.string + "\\mods"s; Logger::Debug("Searching for mods in {}...", folder); ModList::Mods = FileSystem::GetSysFileList(folder, "", true); Logger::Debug("Found {} mods!", ModList::Mods.size()); @@ -59,8 +59,8 @@ namespace Components void ModList::UIScript_ClearMods([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) { - auto fsGame = Dvar::Var("fs_game"); - fsGame.set(""); + Game::Dvar_SetString(*Game::fs_gameDirVar, ""); + const_cast((*Game::fs_gameDirVar))->modified = true; if (Dvar::Var("cl_modVidRestart").get()) { @@ -74,8 +74,8 @@ namespace Components void ModList::RunMod(const std::string& mod) { - auto fsGame = Dvar::Var("fs_game"); - fsGame.set(std::format("mods/{}", mod)); + Game::Dvar_SetString(*Game::fs_gameDirVar, Utils::String::Format("mods/{}", mod)); + const_cast((*Game::fs_gameDirVar))->modified = true; if (Dvar::Var("cl_modVidRestart").get()) { diff --git a/src/Components/Modules/Network.cpp b/src/Components/Modules/Network.cpp index 9e415552..6b135803 100644 --- a/src/Components/Modules/Network.cpp +++ b/src/Components/Modules/Network.cpp @@ -91,7 +91,7 @@ namespace Components return {this->getCString()}; } - bool Network::Address::isLocal() + bool Network::Address::isLocal() const noexcept { // According to: https://en.wikipedia.org/wiki/Private_network @@ -112,7 +112,7 @@ namespace Components return false; } - bool Network::Address::isSelf() + bool Network::Address::isSelf() const noexcept { if (Game::NET_IsLocalAddress(this->address)) return true; // Loopback if (this->getPort() != GetPort()) return false; // Port not equal @@ -128,7 +128,7 @@ namespace Components return false; } - bool Network::Address::isLoopback() const + bool Network::Address::isLoopback() const noexcept { if (this->getIP().full == 0x100007f) // 127.0.0.1 { @@ -138,7 +138,7 @@ namespace Components return Game::NET_IsLocalAddress(this->address); } - bool Network::Address::isValid() const + bool Network::Address::isValid() const noexcept { return (this->getType() != Game::NA_BAD && this->getType() >= Game::NA_BOT && this->getType() <= Game::NA_IP); } @@ -225,9 +225,11 @@ namespace Components StartupSignal.clear(); } - unsigned short Network::GetPort() + std::uint16_t Network::GetPort() { - return static_cast(Dvar::Var(0x64A3004).get()); + assert((*Game::port)); + assert((*Game::port)->current.unsignedInt <= std::numeric_limits::max()); + return static_cast((*Game::port)->current.unsignedInt); } __declspec(naked) void Network::NetworkStartStub() @@ -331,22 +333,22 @@ namespace Components { AssertSize(Game::netadr_t, 20); - // maximum size in NET_OutOfBandPrint - Utils::Hook::Set(0x4AEF08, 0x1FFFC); - Utils::Hook::Set(0x4AEFA3, 0x1FFFC); + // Maximum size in NET_OutOfBandPrint + Utils::Hook::Set(0x4AEF08, 0x1FFFC); + Utils::Hook::Set(0x4AEFA3, 0x1FFFC); - // increase max port binding attempts from 10 to 100 - Utils::Hook::Set(0x4FD48A, 100); + // Increase max port binding attempts from 10 to 100 + Utils::Hook::Set(0x4FD48A, 100); - // increase cl_maxpackets limit - Utils::Hook::Set(0x4050A1, 125); + // Increase cl_maxpackets dvar limit + Utils::Hook::Set(0x4050A1, 125); - // increase snaps + // Increase snaps (disabled for unknown reasons) //Utils::Hook::Set(0x405357, 40); - // default maxpackets and snaps - Utils::Hook::Set(0x40535B, 30); - Utils::Hook::Set(0x4050A5, 125); + // Set default value of snaps and cl_maxpackets dvar + Utils::Hook::Set(0x40535B, 30); + Utils::Hook::Set(0x4050A5, 125); // Parse port as short in Net_AddrToString Utils::Hook::Set(0x4698E3, "%u.%u.%u.%u:%hu"); @@ -364,11 +366,29 @@ namespace Components Utils::Hook(0x5AA703, CL_HandleCommandStub, HOOK_JUMP).install()->quick(); // Disable unused OOB packets handlers just to be sure - Utils::Hook::Set(0x5AA5B6, 0xEB); // CL_SteamServerAuth - Utils::Hook::Set(0x5AA69F, 0xEB); // echo - Utils::Hook::Set(0x5AAA82, 0xEB); // SP - Utils::Hook::Set(0x5A9F18, 0xEB); // CL_VoiceConnectionTestPacket - Utils::Hook::Set(0x5A9FF3, 0xEB); // CL_HandleRelayPacket + Utils::Hook::Set(0x5AA5B6, 0xEB); // CL_SteamServerAuth + Utils::Hook::Set(0x5AA69F, 0xEB); // echo + Utils::Hook::Set(0x5AAA82, 0xEB); // SP + Utils::Hook::Set(0x5A9F18, 0xEB); // CL_VoiceConnectionTestPacket + Utils::Hook::Set(0x5A9FF3, 0xEB); // CL_HandleRelayPacket + + // Com_GetProtocol + Utils::Hook::Set(0x4FB501, PROTOCOL); + + // Set the default, min and max of the protocol dvar + Utils::Hook::Set(0x4D36A9, PROTOCOL); + Utils::Hook::Set(0x4D36AE, PROTOCOL); + Utils::Hook::Set(0x4D36B3, PROTOCOL); + + // Internal version is 99, most servers should accept it + Utils::Hook::Set(0x463C61, 208); // getBuildNumberAsInt + + // LSP disabled + Utils::Hook::Set(0x435950, 0xC3); // LSP HELLO + Utils::Hook::Set(0x49C220, 0xC3); // We wanted to send a logging packet, but we haven't connected to LSP! + Utils::Hook::Set(0x4BD900, 0xC3); // main LSP response func + Utils::Hook::Set(0x682170, 0xC3); // Telling LSP that we're playing a private match + Utils::Hook::Nop(0x4FD448, 5); // Don't create lsp_socket OnClientPacket("resolveAddress", [](const Address& address, [[maybe_unused]] const std::string& data) { diff --git a/src/Components/Modules/Network.hpp b/src/Components/Modules/Network.hpp index 388ba28c..9f62839b 100644 --- a/src/Components/Modules/Network.hpp +++ b/src/Components/Modules/Network.hpp @@ -37,10 +37,10 @@ namespace Components [[nodiscard]] const char* getCString() const; [[nodiscard]] std::string getString() const; - [[nodiscard]] bool isLocal(); - [[nodiscard]] bool isSelf(); - [[nodiscard]] bool isValid() const; - [[nodiscard]] bool isLoopback() const; + [[nodiscard]] bool isLocal() const noexcept; + [[nodiscard]] bool isSelf() const noexcept; + [[nodiscard]] bool isValid() const noexcept; + [[nodiscard]] bool isLoopback() const noexcept; private: Game::netadr_t address; @@ -52,7 +52,7 @@ namespace Components Network(); - static unsigned short GetPort(); + static std::uint16_t GetPort(); static void OnStart(const Utils::Slot& callback); @@ -94,8 +94,8 @@ namespace Components template <> struct std::hash { - std::size_t operator()(const Components::Network::Address& k) const noexcept + std::size_t operator()(const Components::Network::Address& x) const noexcept { - return std::hash()(k.getString()); + return std::hash()(*reinterpret_cast(&x.getIP().bytes[0])) ^ std::hash()(x.getPort()); } }; diff --git a/src/Components/Modules/NetworkDebug.cpp b/src/Components/Modules/NetworkDebug.cpp index 97c8bdfa..8b5e9e4e 100644 --- a/src/Components/Modules/NetworkDebug.cpp +++ b/src/Components/Modules/NetworkDebug.cpp @@ -35,6 +35,24 @@ namespace Components Game::FS_FreeFile(file); } + int NetworkDebug::I_stricmp_Stub(const char* s0, const char* s1) + { + assert(s0); + assert(s1); + + if (!s0) + { + return -1; + } + + if (!s1) + { + return 1; + } + + return Utils::Hook::Call(0x426080)(s0, s1, std::numeric_limits::max()); // I_strnicmp + } + NetworkDebug::NetworkDebug() { #ifdef _DEBUG @@ -42,6 +60,10 @@ namespace Components Command::Add("parseBadPacket", CL_ParseBadPacket_f); #endif + // Address "race" condition where commands received from RCon can be null + Utils::Hook(0x6094DA, I_stricmp_Stub, HOOK_CALL).install()->quick(); // Cmd_ExecuteServerString + Utils::Hook(0x6095D7, I_stricmp_Stub, HOOK_CALL).install()->quick(); // Cmd_ExecuteSingleCommand + // Backport updates from IW5 Utils::Hook::Set(0x45D112, "CL_PacketEvent - ignoring illegible message\n"); } diff --git a/src/Components/Modules/NetworkDebug.hpp b/src/Components/Modules/NetworkDebug.hpp index 34ec8344..48745360 100644 --- a/src/Components/Modules/NetworkDebug.hpp +++ b/src/Components/Modules/NetworkDebug.hpp @@ -11,5 +11,7 @@ namespace Components static void CL_ParseServerMessage_Hk(Game::msg_t* msg); static void CL_ParseBadPacket_f(); + + static int I_stricmp_Stub(const char* s0, const char* s1); }; } diff --git a/src/Components/Modules/News.cpp b/src/Components/Modules/News.cpp index 4f3c72df..e79ead0d 100644 --- a/src/Components/Modules/News.cpp +++ b/src/Components/Modules/News.cpp @@ -27,7 +27,7 @@ namespace Components } else { - Logger::Debug("Successfully fetched motd"); + Logger::Print("Successfully fetched motd"); } } @@ -77,7 +77,7 @@ namespace Components Changelog::LoadChangelog(); if (Terminate) return; - std::string data = Utils::Cache::GetFile("/iw4/motd.txt"); + const auto data = Utils::Cache::GetFile("/iw4/motd.txt"); if (!data.empty()) { Localization::Set("MPUI_MOTD_TEXT", data); @@ -90,7 +90,7 @@ namespace Components // Sleep for 3 minutes for (int i = 0; i < 180 && !Terminate; ++i) { - std::this_thread::sleep_for(1s); + Game::Sys_Sleep(1); } } } diff --git a/src/Components/Modules/Node.cpp b/src/Components/Modules/Node.cpp index 5ad1dc5d..7c796801 100644 --- a/src/Components/Modules/Node.cpp +++ b/src/Components/Modules/Node.cpp @@ -1,4 +1,9 @@ #include +#include +#include + +#include + #include "ServerList.hpp" #include "Session.hpp" diff --git a/src/Components/Modules/Party.cpp b/src/Components/Modules/Party.cpp index 4126726f..b77a70c3 100644 --- a/src/Components/Modules/Party.cpp +++ b/src/Components/Modules/Party.cpp @@ -1,16 +1,38 @@ #include +#include + #include "Download.hpp" #include "Gamepad.hpp" +#include "Party.hpp" #include "ServerList.hpp" #include "Stats.hpp" #include "Voice.hpp" #include +#define CL_MOD_LOADING + namespace Components { - Party::JoinContainer Party::Container; - std::map Party::LobbyMap; + class JoinContainer + { + public: + Network::Address target; + std::string challenge; + std::string motd; + DWORD joinTime; + bool valid; + int matchType; + + Utils::InfoString info; + + // Party-specific stuff + DWORD requestTime; + bool awaitingPlaylist; + }; + + static JoinContainer Container; + std::map Party::LobbyMap; Dvar::Var Party::PartyEnable; @@ -28,35 +50,36 @@ namespace Components Network::Address Party::Target() { - return Party::Container.target; + return Container.target; } void Party::Connect(Network::Address target) { Node::Add(target); - Party::Container.valid = true; - Party::Container.awaitingPlaylist = false; - Party::Container.joinTime = Game::Sys_Milliseconds(); - Party::Container.target = target; - Party::Container.challenge = Utils::Cryptography::Rand::GenerateChallenge(); + Container.valid = true; + Container.awaitingPlaylist = false; + Container.joinTime = Game::Sys_Milliseconds(); + Container.target = target; + Container.challenge = Utils::Cryptography::Rand::GenerateChallenge(); - Network::SendCommand(Party::Container.target, "getinfo", Party::Container.challenge); + Network::SendCommand(Container.target, "getinfo", Container.challenge); Command::Execute("openmenu popup_reconnectingtoparty"); } const char* Party::GetLobbyInfo(SteamID lobby, const std::string& key) { - if (Party::LobbyMap.contains(lobby.bits)) + if (LobbyMap.contains(lobby.bits)) { - Network::Address address = Party::LobbyMap[lobby.bits]; + Network::Address address = LobbyMap[lobby.bits]; - if (key == "addr") + if (key == "addr"s) { return Utils::String::VA("%d", address.getIP().full); } - else if (key == "port") + + if (key == "port"s) { return Utils::String::VA("%d", address.getPort()); } @@ -67,7 +90,7 @@ namespace Components void Party::RemoveLobby(SteamID lobby) { - Party::LobbyMap.erase(lobby.bits); + LobbyMap.erase(lobby.bits); } void Party::ConnectError(const std::string& message) @@ -79,17 +102,23 @@ namespace Components std::string Party::GetMotd() { - return Party::Container.motd; + return Container.motd; } - Game::dvar_t* Party::RegisterMinPlayers(const char* name, int /*value*/, int /*min*/, int max, unsigned __int16 flag, const char* description) + std::string Party::GetHostName() { - return Dvar::Register(name, 1, 1, max, Game::DVAR_INIT | flag, description).get(); + return Container.info.get("hostname"); + } + + int Party::GetMaxClients() + { + const auto value = Container.info.get("sv_maxclients"); + return std::strtol(value.data(), nullptr, 10); } bool Party::PlaylistAwaiting() { - return Party::Container.awaitingPlaylist; + return Container.awaitingPlaylist; } void Party::PlaylistContinue() @@ -99,20 +128,20 @@ namespace Components // Ensure we can join *Game::g_lobbyCreateInProgress = false; - Party::Container.awaitingPlaylist = false; + Container.awaitingPlaylist = false; - SteamID id = Party::GenerateLobbyId(); + SteamID id = GenerateLobbyId(); // Temporary workaround // TODO: Patch the 127.0.0.1 -> loopback mapping in the party code - if (Party::Container.target.isLoopback()) + if (Container.target.isLoopback()) { if (*Game::numIP) { - Party::Container.target.setIP(*Game::localIP); - Party::Container.target.setType(Game::netadrtype_t::NA_IP); + Container.target.setIP(*Game::localIP); + Container.target.setType(Game::netadrtype_t::NA_IP); - Logger::Print("Trying to connect to party with loopback address, using a local ip instead: {}\n", Party::Container.target.getString()); + Logger::Print("Trying to connect to party with loopback address, using a local ip instead: {}\n", Container.target.getString()); } else { @@ -120,17 +149,17 @@ namespace Components } } - Party::LobbyMap[id.bits] = Party::Container.target; + LobbyMap[id.bits] = Container.target; Game::Steam_JoinLobby(id, 0); } void Party::PlaylistError(const std::string& error) { - Party::Container.valid = false; - Party::Container.awaitingPlaylist = false; + Container.valid = false; + Container.awaitingPlaylist = false; - Party::ConnectError(error); + ConnectError(error); } DWORD Party::UIDvarIntStub(char* dvar) @@ -150,7 +179,7 @@ namespace Components bool Party::IsInUserMapLobby() { - return (Party::IsInLobby() && Maps::IsUserMap(Dvar::Var("ui_mapname").get())); + return (IsInLobby() && Maps::IsUserMap((*Game::ui_mapname)->current.string)); } bool Party::IsEnabled() @@ -160,7 +189,7 @@ namespace Components Party::Party() { - Party::PartyEnable = Dvar::Register("party_enable", Dedicated::IsEnabled(), Game::DVAR_NONE, "Enable party system"); + PartyEnable = Dvar::Register("party_enable", Dedicated::IsEnabled(), Game::DVAR_NONE, "Enable party system"); Dvar::Register("xblive_privatematch", true, Game::DVAR_INIT, ""); // various changes to SV_DirectConnect-y stuff to allow non-party joinees @@ -194,8 +223,8 @@ namespace Components // causes 'does current Steam lobby match' calls in Steam_JoinLobby to be ignored Utils::Hook::Set(0x49D007, 0xEB); - // functions checking party heartbeat timeouts, cause random issues - Utils::Hook::Nop(0x4E532D, 5); + // function checking party heartbeat timeouts, cause random issues + Utils::Hook::Nop(0x4E532D, 5); // PartyHost_TimeoutMembers // Steam_JoinLobby call causes migration Utils::Hook::Nop(0x5AF851, 5); @@ -227,7 +256,7 @@ namespace Components Utils::Hook::Nop(0x5A8E33, 11); // Enable XP Bar - Utils::Hook(0x62A2A7, Party::UIDvarIntStub, HOOK_CALL).install()->quick(); + Utils::Hook(0x62A2A7, UIDvarIntStub, HOOK_CALL).install()->quick(); // Set NAT to open Utils::Hook::Set(0x79D898, 1); @@ -238,7 +267,7 @@ namespace Components Utils::Hook::Nop(0x4077A1, 5); // PartyMigrate_Frame // Patch playlist stuff for non-party behavior - static Game::dvar_t* partyEnable = Party::PartyEnable.get(); + static Game::dvar_t* partyEnable = PartyEnable.get(); Utils::Hook::Set(0x4A4093, &partyEnable); Utils::Hook::Set(0x4573F1, &partyEnable); Utils::Hook::Set(0x5B1A0C, &partyEnable); @@ -248,12 +277,6 @@ namespace Components Utils::Hook::Xor(0x4573FA, 1); Utils::Hook::Xor(0x5B1A17, 1); - // Fix xstartlobby - //Utils::Hook::Set(0x5B71CD, 0xEB); - - // Patch party_minplayers to 1 and protect it - //Utils::Hook(0x4D5D51, Party::RegisterMinPlayers, HOOK_CALL).install()->quick(); - // Set ui_maxclients to sv_maxclients Utils::Hook::Set(0x42618F, "sv_maxclients"); Utils::Hook::Set(0x4D3756, "sv_maxclients"); @@ -267,9 +290,6 @@ namespace Components Utils::Hook::Xor(0x4D376D, Game::DVAR_LATCH); Utils::Hook::Xor(0x5E3789, Game::DVAR_LATCH); - // Patch Live_PlayerHasLoopbackAddr - //Utils::Hook::Set(0x418F30, 0x90C3C033); - Command::Add("connect", [](Command::Params* params) { if (params->size() < 2) @@ -284,33 +304,34 @@ namespace Components } else { - Party::Connect(Network::Address(params->get(1))); + Connect(Network::Address(params->get(1))); } }); + Command::Add("reconnect", [](Command::Params*) { - Party::Connect(Party::Container.target); + Connect(Container.target); }); if (!Dedicated::IsEnabled() && !ZoneBuilder::IsEnabled()) { Scheduler::Loop([] { - if (Party::Container.valid) + if (Container.valid) { - if ((Game::Sys_Milliseconds() - Party::Container.joinTime) > 10'000) + if ((Game::Sys_Milliseconds() - Container.joinTime) > 10'000) { - Party::Container.valid = false; - Party::ConnectError("Server connection timed out."); + Container.valid = false; + ConnectError("Server connection timed out."); } } - if (Party::Container.awaitingPlaylist) + if (Container.awaitingPlaylist) { - if ((Game::Sys_Milliseconds() - Party::Container.requestTime) > 5'000) + if ((Game::Sys_Milliseconds() - Container.requestTime) > 5'000) { - Party::Container.awaitingPlaylist = false; - Party::ConnectError("Playlist request timed out."); + Container.awaitingPlaylist = false; + ConnectError("Playlist request timed out."); } } @@ -321,7 +342,7 @@ namespace Components Network::OnClientPacket("getInfo", [](const Network::Address& address, [[maybe_unused]] const std::string& data) { auto botCount = 0; - auto clientCount = 0; + auto effectiveClientCount = 0; auto maxClientCount = *Game::svs_clientCount; const auto securityLevel = Dvar::Var("sv_securityLevel").get(); const auto* password = *Game::g_password ? (*Game::g_password)->current.string : ""; @@ -330,17 +351,25 @@ namespace Components { for (int i = 0; i < maxClientCount; ++i) { - if (Game::svs_clients[i].header.state >= Game::CS_CONNECTED) + if (Game::svs_clients[i].header.state < Game::CS_ACTIVE) continue; + if (!Game::svs_clients[i].gentity || !Game::svs_clients[i].gentity->client) continue; + + const auto* client = Game::svs_clients[i].gentity->client; + const auto team = client->sess.cs.team; + if (Game::svs_clients[i].bIsTestClient || team == Game::TEAM_SPECTATOR) { - if (Game::svs_clients[i].bIsTestClient) ++botCount; - else ++clientCount; + ++botCount; + } + else + { + ++effectiveClientCount; } } } else { - maxClientCount = Dvar::Var("party_maxplayers").get(); - clientCount = Game::PartyHost_CountMembers(reinterpret_cast(0x1081C00)); + maxClientCount = *Game::party_maxplayers ? (*Game::party_maxplayers)->current.integer : 18; + effectiveClientCount = Game::PartyHost_CountMembers(Game::g_lobbyData); } Utils::InfoString info; @@ -350,7 +379,7 @@ namespace Components info.set("gametype", (*Game::sv_gametype)->current.string); info.set("fs_game", (*Game::fs_gameDirVar)->current.string); info.set("xuid", Utils::String::VA("%llX", Steam::SteamUser()->GetSteamID().bits)); - info.set("clients", std::to_string(clientCount)); + info.set("clients", std::to_string(effectiveClientCount)); info.set("bots", std::to_string(botCount)); info.set("sv_maxclients", std::to_string(maxClientCount)); info.set("protocol", std::to_string(PROTOCOL)); @@ -365,7 +394,7 @@ namespace Components info.set("voiceChat", (Voice::SV_VoiceEnabled() ? "1" : "0")); // Ensure mapname is set - if (info.get("mapname").empty() || Party::IsInLobby()) + if (info.get("mapname").empty() || IsInLobby()) { info.set("mapname", Dvar::Var("ui_mapname").get()); } @@ -374,14 +403,14 @@ namespace Components { info.set("usermaphash", Utils::String::VA("%i", Maps::GetUserMap()->getHash())); } - else if (Party::IsInUserMapLobby()) + else if (IsInUserMapLobby()) { info.set("usermaphash", Utils::String::VA("%i", Maps::GetUsermapHash(info.get("mapname")))); } if (Dedicated::IsEnabled()) { - info.set("sv_motd", Dvar::Var("sv_motd").get()); + info.set("sv_motd", Dedicated::SVMOTD.get()); } // Set matchtype @@ -402,81 +431,81 @@ namespace Components info.set("matchtype", "0"); } - info.set("wwwDownload", (Dvar::Var("sv_wwwDownload").get() ? "1" : "0")); - info.set("wwwUrl", Dvar::Var("sv_wwwBaseUrl").get()); + info.set("wwwDownload", (Download::SV_wwwDownload.get() ? "1" : "0")); + info.set("wwwUrl", Download::SV_wwwBaseUrl.get()); Network::SendCommand(address, "infoResponse", "\\" + info.build()); }); Network::OnClientPacket("infoResponse", [](const Network::Address& address, [[maybe_unused]] const std::string& data) { - Utils::InfoString info(data); + const Utils::InfoString info(data); // Handle connection - if (Party::Container.valid) + if (Container.valid) { - if (Party::Container.target == address) + if (Container.target == address) { // Invalidate handler for future packets - Party::Container.valid = false; - Party::Container.info = info; + Container.valid = false; + Container.info = info; - Party::Container.matchType = atoi(info.get("matchtype").data()); - uint32_t securityLevel = static_cast(atoi(info.get("securityLevel").data())); + Container.matchType = std::strtol(info.get("matchtype").data(), nullptr, 10); + auto securityLevel = std::strtoul(info.get("securityLevel").data(), nullptr, 10); bool isUsermap = !info.get("usermaphash").empty(); - unsigned int usermapHash = atoi(info.get("usermaphash").data()); - + auto usermapHash = std::strtoul(info.get("usermaphash").data(), nullptr, 10); +#ifdef CL_MOD_LOADING std::string mod = (*Game::fs_gameDirVar)->current.string; - +#endif // set fast server stuff here so its updated when we go to download stuff if (info.get("wwwDownload") == "1"s) { - Dvar::Var("sv_wwwDownload").set(true); - Dvar::Var("sv_wwwBaseUrl").set(info.get("wwwUrl")); + Download::SV_wwwDownload.set(true); + Download::SV_wwwBaseUrl.set(info.get("wwwUrl")); } else { - Dvar::Var("sv_wwwDownload").set(false); - Dvar::Var("sv_wwwBaseUrl").set(""); + Download::SV_wwwDownload.set(false); + Download::SV_wwwBaseUrl.set(""); } - if (info.get("challenge") != Party::Container.challenge) + if (info.get("challenge") != Container.challenge) { - Party::ConnectError("Invalid join response: Challenge mismatch."); + ConnectError("Invalid join response: Challenge mismatch."); } else if (securityLevel > Auth::GetSecurityLevel()) { - //Party::ConnectError(Utils::VA("Your security level (%d) is lower than the server's (%d)", Auth::GetSecurityLevel(), securityLevel)); Command::Execute("closemenu popup_reconnectingtoparty"); Auth::IncreaseSecurityLevel(securityLevel, "reconnect"); } - else if (!Party::Container.matchType) + else if (!Container.matchType) { - Party::ConnectError("Server is not hosting a match."); + ConnectError("Server is not hosting a match."); } - else if (Party::Container.matchType > 2 || Party::Container.matchType < 0) + else if (Container.matchType > 2 || Container.matchType < 0) { - Party::ConnectError("Invalid join response: Unknown matchtype"); + ConnectError("Invalid join response: Unknown matchtype"); } - else if (Party::Container.info.get("mapname").empty() || Party::Container.info.get("gametype").empty()) + else if (Container.info.get("mapname").empty() || Container.info.get("gametype").empty()) { - Party::ConnectError("Invalid map or gametype."); + ConnectError("Invalid map or gametype."); } - else if (Party::Container.info.get("isPrivate") == "1"s && !Dvar::Var("password").get().length()) + else if (Container.info.get("isPrivate") == "1"s && !Dvar::Var("password").get().length()) { - Party::ConnectError("A password is required to join this server! Set it at the bottom of the serverlist."); + ConnectError("A password is required to join this server! Set it at the bottom of the serverlist."); } else if (isUsermap && usermapHash != Maps::GetUsermapHash(info.get("mapname"))) { Command::Execute("closemenu popup_reconnectingtoparty"); Download::InitiateMapDownload(info.get("mapname"), info.get("isPrivate") == "1"); } +#ifdef CL_MOD_LOADING else if (!info.get("fs_game").empty() && Utils::String::ToLower(mod) != Utils::String::ToLower(info.get("fs_game"))) { Command::Execute("closemenu popup_reconnectingtoparty"); Download::InitiateClientDownload(info.get("fs_game"), info.get("isPrivate") == "1"s); } - else if (!Dvar::Var("fs_game").get().empty() && info.get("fs_game").empty()) + else if ((*Game::fs_gameDirVar)->current.string[0] != '\0' && info.get("fs_game").empty()) { Game::Dvar_SetString(*Game::fs_gameDirVar, ""); @@ -487,18 +516,19 @@ namespace Components Command::Execute("reconnect", false); } +#endif else { - if (!Maps::CheckMapInstalled(Party::Container.info.get("mapname").data(), true)) return; + if (!Maps::CheckMapInstalled(Container.info.get("mapname"), true)) return; - Party::Container.motd = info.get("sv_motd"); + Container.motd = info.get("sv_motd"); - if (Party::Container.matchType == 1) // Party + if (Container.matchType == 1) // Party { // Send playlist request - Party::Container.requestTime = Game::Sys_Milliseconds(); - Party::Container.awaitingPlaylist = true; - Network::SendCommand(Party::Container.target, "getplaylist", Dvar::Var("password").get()); + Container.requestTime = Game::Sys_Milliseconds(); + Container.awaitingPlaylist = true; + Network::SendCommand(Container.target, "getplaylist", Dvar::Var("password").get()); // This is not a safe method // TODO: Fix actual error! @@ -507,11 +537,25 @@ namespace Components Command::Execute("disconnect", true); } } - else if (Party::Container.matchType == 2) // Match + else if (Container.matchType == 2) // Match { - if (atoi(Party::Container.info.get("clients").data()) >= atoi(Party::Container.info.get("sv_maxclients").data())) + int clients; + int maxClients; + + try { - Party::ConnectError("@EXE_SERVERISFULL"); + clients = std::stoi(Container.info.get("clients")); + maxClients = std::stoi(Container.info.get("sv_maxclients")); + } + catch ([[maybe_unused]] const std::exception& ex) + { + ConnectError("Invalid info string"); + return; + } + + if (clients >= maxClients) + { + ConnectError("@EXE_SERVERISFULL"); } else { @@ -520,7 +564,7 @@ namespace Components Game::Menus_CloseAll(Game::uiContext); Game::_XSESSION_INFO hostInfo; - Game::CL_ConnectFromParty(0, &hostInfo, *Party::Container.target.get(), 0, 0, Party::Container.info.get("mapname").data(), Party::Container.info.get("gametype").data()); + Game::CL_ConnectFromParty(0, &hostInfo, *Container.target.get(), 0, 0, Container.info.get("mapname").data(), Container.info.get("gametype").data()); } } } diff --git a/src/Components/Modules/Party.hpp b/src/Components/Modules/Party.hpp index 3287cecd..44b73477 100644 --- a/src/Components/Modules/Party.hpp +++ b/src/Components/Modules/Party.hpp @@ -24,34 +24,16 @@ namespace Components static bool IsEnabled(); static std::string GetMotd(); + static std::string GetHostName(); + static int GetMaxClients(); private: - class JoinContainer - { - public: - Network::Address target; - std::string challenge; - std::string motd; - DWORD joinTime; - bool valid; - int matchType; - - Utils::InfoString info; - - // Party-specific stuff - DWORD requestTime; - bool awaitingPlaylist; - }; - - static JoinContainer Container; - static std::map LobbyMap; + static std::map LobbyMap; static Dvar::Var PartyEnable; static SteamID GenerateLobbyId(); - static Game::dvar_t* RegisterMinPlayers(const char* name, int value, int min, int max, unsigned __int16 flag, const char* description); - static DWORD UIDvarIntStub(char* dvar); }; } diff --git a/src/Components/Modules/PlayerMovement.cpp b/src/Components/Modules/PlayerMovement.cpp index 749f15df..e933a255 100644 --- a/src/Components/Modules/PlayerMovement.cpp +++ b/src/Components/Modules/PlayerMovement.cpp @@ -15,6 +15,8 @@ namespace Components const Game::dvar_t* PlayerMovement::PlayerSpectateSpeedScale; const Game::dvar_t* PlayerMovement::BGBounces; const Game::dvar_t* PlayerMovement::BGBouncesAllAngles; + const Game::dvar_t* PlayerMovement::BGDisableLandingSlowdown; + const Game::dvar_t* PlayerMovement::BGBunnyHopAuto; const Game::dvar_t* PlayerMovement::PlayerDuckedSpeedScale; const Game::dvar_t* PlayerMovement::PlayerProneSpeedScale; @@ -105,27 +107,40 @@ namespace Components __asm { // Check the value of BGBounces - push ecx push eax mov eax, BGBounces - mov ecx, dword ptr [eax + 0x10] - test ecx, ecx + mov eax, dword ptr [eax + 0x10] + test eax, eax pop eax - pop ecx // Do not bounce if BGBounces is 0 jle noBounce + push eax + + mov eax, BGBouncesAllAngles + mov eax, dword ptr [eax + 0x10] + cmp eax, 2 + + pop eax + + // Do not apply all angles patch if BGBouncesAllAngles is not set to "all surfaces" + jne regularBounce + + push 0x4B1B7D + ret + // Bounce + regularBounce: push 0x4B1B34 ret noBounce: // Original game code cmp dword ptr [esp + 0x24], 0 - push 0x4B1B48 + push 0x4B1B32 ret } } @@ -133,10 +148,12 @@ namespace Components // Double bounces void PlayerMovement::Jump_ClearState_Hk(Game::playerState_s* ps) { - if (BGBounces->current.integer != DOUBLE) + if (BGBounces->current.integer == DOUBLE) { - Game::Jump_ClearState(ps); + return; } + + Game::Jump_ClearState(ps); } __declspec(naked) void PlayerMovement::PM_ProjectVelocityStub() @@ -145,19 +162,21 @@ namespace Components { push eax mov eax, BGBouncesAllAngles - cmp byte ptr [eax + 0x10], 1 + mov eax, dword ptr [eax + 0x10] + test eax, eax pop eax - je bounce + je noBounce + // Force the bounce + push 0x417B6F + ret + + noBounce: fstp ST(0) pop esi add esp, 0x10 ret - - bounce: - push 0x417B6F - ret } } @@ -200,6 +219,38 @@ namespace Components } } + void PlayerMovement::PM_CrashLand_Stub(const float* v, float scale, const float* result) + { + if (!BGDisableLandingSlowdown->current.enabled) + { + Utils::Hook::Call(0x4C12B0)(v, scale, result); + } + } + + __declspec(naked) void PlayerMovement::Jump_Check_Stub() + { + using namespace Game; + + __asm + { + push eax + mov eax, BGBunnyHopAuto + cmp byte ptr [eax + 0x10], 1 + pop eax + + je autoHop + + // Game's code + test dword ptr [ebp + 0x30], CMD_BUTTON_UP + push 0x4E9890 + ret + + autoHop: + push 0x4E989F + ret + } + } + void PlayerMovement::GScr_IsSprinting(const Game::scr_entref_t entref) { const auto* client = Game::GetEntity(entref)->client; @@ -240,8 +291,11 @@ namespace Components 3.0f, 0.001f, 1000.0f, Game::DVAR_CHEAT | Game::DVAR_CODINFO, "The speed at which noclip camera moves"); - BGBouncesAllAngles = Game::Dvar_RegisterBool("bg_bouncesAllAngles", - false, Game::DVAR_CODINFO, "Force bounce from all angles"); + BGDisableLandingSlowdown = Game::Dvar_RegisterBool("bg_disableLandingSlowdown", + false, Game::DVAR_CODINFO, "Toggle landing slowdown"); + + BGBunnyHopAuto = Game::Dvar_RegisterBool("bg_bunnyHopAuto", + false, Game::DVAR_CODINFO, "Constantly jump when holding space"); BGRocketJump = Dvar::Register("bg_rocketJump", false, Game::DVAR_CODINFO, "Enable CoD4 rocket jumps"); @@ -265,21 +319,29 @@ namespace Components AssertOffset(Game::playerState_s, eFlags, 0xB0); AssertOffset(Game::playerState_s, pm_flags, 0xC); - Scheduler::Once([] + Events::OnDvarInit([] { static const char* bg_bouncesValues[] = { "disabled", "enabled", "double", - nullptr + nullptr, }; - BGBounces = Game::Dvar_RegisterEnum("bg_bounces", - bg_bouncesValues, DISABLED, Game::DVAR_CODINFO, "Bounce glitch settings"); - }, Scheduler::Pipeline::MAIN); + static const char* bg_bouncesAllAnglesValues[] = + { + "disabled", + "simple", + "all surfaces", + nullptr, + }; - // Hook Dvar_RegisterFloat. Only thing that's changed is that the 0x80 flag is not used. + BGBounces = Game::Dvar_RegisterEnum("bg_bounces", bg_bouncesValues, DISABLED, Game::DVAR_CODINFO, "Bounce glitch settings"); + BGBouncesAllAngles = Game::Dvar_RegisterEnum("bg_bouncesAllAngles", bg_bouncesAllAnglesValues, DISABLED, Game::DVAR_CODINFO, "Force bounce from all angles"); + }); + + // Hook Dvar_RegisterFloat. Only thing that's changed is that the 0x80 flag is not used Utils::Hook(0x448990, Dvar_RegisterSpectateSpeedScale, HOOK_CALL).install()->quick(); // PM_CmdScaleForStance @@ -299,7 +361,7 @@ namespace Components // Rocket jump Utils::Hook(0x4A4F9B, Weapon_RocketLauncher_Fire_Hk, HOOK_CALL).install()->quick(); // FireWeapon - // Hook StuckInClient & CM_TransformedCapsuleTrace + // Hook StuckInClient & CM_TransformedCapsuleTrace // so we can prevent intersecting players from being pushed away from each other Utils::Hook(0x5D8153, StuckInClient_Hk, HOOK_CALL).install()->quick(); Utils::Hook(0x45A5BF, CM_TransformedCapsuleTrace_Hk, HOOK_CALL).install()->quick(); // SV_ClipMoveToEntity @@ -308,7 +370,10 @@ namespace Components Utils::Hook(0x573F39, PM_PlayerTraceStub, HOOK_CALL).install()->quick(); Utils::Hook(0x573E93, PM_PlayerTraceStub, HOOK_CALL).install()->quick(); - Script::AddMethod("IsSprinting", GScr_IsSprinting); + Utils::Hook(0x570020, PM_CrashLand_Stub, HOOK_CALL).install()->quick(); // Vec3Scale + Utils::Hook(0x4E9889, Jump_Check_Stub, HOOK_JUMP).install()->quick(); + + GSC::Script::AddMethod("IsSprinting", GScr_IsSprinting); RegisterMovementDvars(); } diff --git a/src/Components/Modules/PlayerMovement.hpp b/src/Components/Modules/PlayerMovement.hpp index d86d8394..9f87e2cd 100644 --- a/src/Components/Modules/PlayerMovement.hpp +++ b/src/Components/Modules/PlayerMovement.hpp @@ -8,7 +8,7 @@ namespace Components PlayerMovement(); private: - enum BouncesSettings { DISABLED, ENABLED, DOUBLE }; + enum BouncesSettings : int { DISABLED, ENABLED, DOUBLE }; static constexpr auto SURF_LADDER = 0x8; @@ -23,6 +23,8 @@ namespace Components static const Game::dvar_t* PlayerSpectateSpeedScale; static const Game::dvar_t* BGBounces; static const Game::dvar_t* BGBouncesAllAngles; + static const Game::dvar_t* BGDisableLandingSlowdown; + static const Game::dvar_t* BGBunnyHopAuto; static const Game::dvar_t* PlayerDuckedSpeedScale; static const Game::dvar_t* PlayerProneSpeedScale; @@ -45,6 +47,9 @@ namespace Components static int StuckInClient_Hk(Game::gentity_s* self); static void CM_TransformedCapsuleTrace_Hk(Game::trace_t* results, const float* start, const float* end, const Game::Bounds* bounds, const Game::Bounds* capsule, int contents, const float* origin, const float* angles); + static void PM_CrashLand_Stub(const float* v, float scale, const float* result); + static void Jump_Check_Stub(); + static void GScr_IsSprinting(Game::scr_entref_t entref); static const Game::dvar_t* Dvar_RegisterSpectateSpeedScale(const char* dvarName, float value, float min, float max, unsigned __int16 flags, const char* description); diff --git a/src/Components/Modules/PlayerName.cpp b/src/Components/Modules/PlayerName.cpp index 2d32fd47..fabe29bf 100644 --- a/src/Components/Modules/PlayerName.cpp +++ b/src/Components/Modules/PlayerName.cpp @@ -6,11 +6,11 @@ namespace Components { Dvar::Var PlayerName::sv_allowColoredNames; - void PlayerName::UserInfoCopy(char* buffer, const char* name, const size_t size) + void PlayerName::UserInfoCopy(char* buffer, const char* name, const int size) { if (!sv_allowColoredNames.get()) { - char nameBuffer[64] = {0}; + char nameBuffer[64]{}; TextRenderer::StripColors(name, nameBuffer, sizeof(nameBuffer)); TextRenderer::StripAllTextIcons(nameBuffer, buffer, size); } @@ -32,17 +32,18 @@ namespace Components { __asm { - mov eax, [esp + 4h] // length - - push eax + pushad + push [esp + 0x20 + 0x4] // length push ecx // name push edx // buffer call UserInfoCopy + add esp, 0xC - add esp, 0Ch - retn + popad + + ret } } @@ -58,7 +59,7 @@ namespace Components char* PlayerName::CleanStrStub(char* string) { - TextRenderer::StripColors(string, string, strlen(string) + 1); + TextRenderer::StripColors(string, string, std::strlen(string) + 1); return string; } @@ -69,9 +70,16 @@ namespace Components auto i = 0; while (i < size - 1 && dest[i] != '\0') { - if (dest[i] > 125 || dest[i] < 32 || dest[i] == '%') + // Check for various illegal characters + + if (dest[i] == '%') { - return false; // Illegal string + return false; + } + + if (std::iscntrl(static_cast(dest[i]))) + { + return false; } ++i; @@ -82,6 +90,8 @@ namespace Components __declspec(naked) void PlayerName::SV_UserinfoChangedStub() { + using namespace Game; + __asm { call CopyClientNameCheck @@ -94,8 +104,7 @@ namespace Components push 1 // tellThem push INVALID_NAME_MSG // reason push edi // drop - mov eax, 0x4D1600 // SV_DropClient - call eax + call SV_DropClient add esp, 0xC popad @@ -114,7 +123,8 @@ namespace Components Utils::Hook::Set(0x6258D0, 0xC3); // Allow colored names ingame. Hook placed in ClientUserinfoChanged - Utils::Hook(0x5D8B40, ClientCleanName, HOOK_JUMP).install()->quick(); + Utils::Hook(0x445301, ClientCleanName, HOOK_CALL).install()->quick(); + Utils::Hook(0x44533A, ClientCleanName, HOOK_CALL).install()->quick(); // Though, don't apply that to overhead names. Utils::Hook(0x581932, GetClientName, HOOK_CALL).install()->quick(); diff --git a/src/Components/Modules/PlayerName.hpp b/src/Components/Modules/PlayerName.hpp index ccf61ff4..fb8641b4 100644 --- a/src/Components/Modules/PlayerName.hpp +++ b/src/Components/Modules/PlayerName.hpp @@ -7,7 +7,7 @@ namespace Components public: PlayerName(); - static void UserInfoCopy(char* buffer, const char* name, size_t size); + static void UserInfoCopy(char* buffer, const char* name, int size); static int GetClientName(int localClientNum, int index, char* buf, int size); diff --git a/src/Components/Modules/Playlist.cpp b/src/Components/Modules/Playlist.cpp index 52aa12b5..6acb85d5 100644 --- a/src/Components/Modules/Playlist.cpp +++ b/src/Components/Modules/Playlist.cpp @@ -1,4 +1,9 @@ #include +#include + +#include + +#include "Party.hpp" #include "Playlist.hpp" namespace Components @@ -13,7 +18,7 @@ namespace Components if (*Game::s_havePlaylists) return; // Don't load playlists when dedi and no party - if (Dedicated::IsEnabled() && !Dvar::Var("party_enable").get()) + if (Dedicated::IsEnabled() && !Party::IsEnabled()) { *Game::s_havePlaylists = true; Dvar::Var("xblive_privateserver").set(true); diff --git a/src/Components/Modules/QuickPatch.cpp b/src/Components/Modules/QuickPatch.cpp index 5631fcca..41fc91b9 100644 --- a/src/Components/Modules/QuickPatch.cpp +++ b/src/Components/Modules/QuickPatch.cpp @@ -1,8 +1,12 @@ #include +#include + #include "QuickPatch.hpp" namespace Components { + Dvar::Var QuickPatch::UIMousePitch; + Dvar::Var QuickPatch::r_customAspectRatio; void QuickPatch::UnlockStats() @@ -246,7 +250,7 @@ namespace Components using namespace Game; static const char* msg = "SND_GetAliasOffset: Could not find sound alias '%s'"; - static const DWORD func = 0x4B22D0; // Com_Error + using namespace Game; __asm { @@ -268,7 +272,7 @@ namespace Components push [esi] // alias->aliasName push msg push ERR_DROP - call func // Going to longjmp back to safety + call Com_Error // Going to longjmp back to safety add esp, 0xC xor eax, eax @@ -316,31 +320,9 @@ namespace Components // Fix crash as nullptr goes unchecked Utils::Hook(0x437CAD, QuickPatch::SND_GetAliasOffset_Stub, HOOK_JUMP).install()->quick(); - // protocol version (workaround for hacks) - Utils::Hook::Set(0x4FB501, PROTOCOL); - - // protocol command - Utils::Hook::Set(0x4D36A9, PROTOCOL); - Utils::Hook::Set(0x4D36AE, PROTOCOL); - Utils::Hook::Set(0x4D36B3, PROTOCOL); - - // internal version is 99, most servers should accept it - Utils::Hook::Set(0x463C61, 208); - // remove system pre-init stuff (improper quit, disk full) Utils::Hook::Set(0x411350, 0xC3); - // remove STEAMSTART checking for DRM IPC - Utils::Hook::Nop(0x451145, 5); - Utils::Hook::Set(0x45114C, 0xEB); - - // LSP disabled - Utils::Hook::Set(0x435950, 0xC3); // LSP HELLO - Utils::Hook::Set(0x49C220, 0xC3); // We wanted to send a logging packet, but we haven't connected to LSP! - Utils::Hook::Set(0x4BD900, 0xC3); // main LSP response func - Utils::Hook::Set(0x682170, 0xC3); // Telling LSP that we're playing a private match - Utils::Hook::Nop(0x4FD448, 5); // Don't create lsp_socket - // Don't delete config files if corrupted Utils::Hook::Set(0x47DCB3, 0xEB); Utils::Hook::Set(0x4402B6, 0); @@ -391,12 +373,14 @@ namespace Components { std::thread([] { + Com_InitThreadData(); + // check natpmpstate // state 4 is no more devices to query while (Utils::Hook::Get(0x66CE200) < 4) { Utils::Hook::Call(0x4D7030)(); - std::this_thread::sleep_for(500ms); + Game::Sys_Sleep(500); } }).detach(); }, HOOK_JUMP).install()->quick(); @@ -447,7 +431,7 @@ namespace Components // vid_restart when ingame Utils::Hook::Nop(0x4CA1FA, 6); - // Filter log (initially com_logFilter, but I don't see why that dvar is needed) + // Filter log (initially com_logFilter, but I don't see why that dvar print is needed) // Seems like it's needed for B3, so there is a separate handling for dedicated servers in Dedicated.cpp if (!Dedicated::IsEnabled()) { @@ -517,16 +501,16 @@ namespace Components }, Scheduler::Pipeline::RENDERER); // Fix mouse pitch adjustments - Dvar::Register("ui_mousePitch", false, Game::DVAR_ARCHIVE, ""); + UIMousePitch = Dvar::Register("ui_mousePitch", false, Game::DVAR_ARCHIVE, ""); UIScript::Add("updateui_mousePitch", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) { - if (Dvar::Var("ui_mousePitch").get()) + if (UIMousePitch.get()) { - Dvar::Var("m_pitch").set(-0.022f); + Game::Dvar_SetFloatByName("m_pitch", -0.022f); } else { - Dvar::Var("m_pitch").set(0.022f); + Game::Dvar_SetFloatByName("m_pitch", 0.022f); } }); @@ -541,17 +525,25 @@ namespace Components } std::vector fastFiles; - - if (param->get(1) == "all"s) + if (std::strcmp(param->get(1), "all") == 0) { - for (const auto& f : Utils::IO::ListFiles("zone/english")) + for (const auto& entry : Utils::IO::ListFiles("zone/english", false)) + { + const auto& f = entry.path().string(); fastFiles.emplace_back(f.substr(7, f.length() - 10)); + } - for (const auto& f : Utils::IO::ListFiles("zone/dlc")) + for (const auto& entry : Utils::IO::ListFiles("zone/dlc", false)) + { + const auto& f = entry.path().string(); fastFiles.emplace_back(f.substr(3, f.length() - 6)); + } - for (const auto& f : Utils::IO::ListFiles("zone/patch")) + for (const auto& entry : Utils::IO::ListFiles("zone/patch", false)) + { + const auto& f = entry.path().string(); fastFiles.emplace_back(f.substr(5, f.length() - 8)); + } } else { @@ -597,7 +589,7 @@ namespace Components { Utils::IO::CreateDir("userraw/techsets"); Utils::Stream buffer(0x1000); - Game::MaterialTechniqueSet* dest = buffer.dest(); + auto* dest = buffer.dest(); buffer.save(asset.techniqueSet); if (asset.techniqueSet->name) @@ -608,18 +600,18 @@ namespace Components for (int i = 0; i < ARRAYSIZE(Game::MaterialTechniqueSet::techniques); ++i) { - Game::MaterialTechnique* technique = asset.techniqueSet->techniques[i]; + auto* technique = asset.techniqueSet->techniques[i]; if (technique) { // Size-check is obsolete, as the structure is dynamic buffer.align(Utils::Stream::ALIGN_4); - Game::MaterialTechnique* destTechnique = buffer.dest(); + auto* destTechnique = buffer.dest(); buffer.save(technique, 8); // Save_MaterialPassArray - Game::MaterialPass* destPasses = buffer.dest(); + auto* destPasses = buffer.dest(); buffer.saveArray(technique->passArray, technique->passCount); for (std::uint16_t j = 0; j < technique->passCount; ++j) @@ -680,7 +672,7 @@ namespace Components } }); -#ifdef DEBUG +#ifdef DEBUG_MAT_LOG AssetHandler::OnLoad([](Game::XAssetType type, Game::XAssetHeader asset, const std::string& /*name*/, bool* /*restrict*/) { if (type == Game::XAssetType::ASSET_TYPE_GFXWORLD) diff --git a/src/Components/Modules/QuickPatch.hpp b/src/Components/Modules/QuickPatch.hpp index 58679706..8f18c838 100644 --- a/src/Components/Modules/QuickPatch.hpp +++ b/src/Components/Modules/QuickPatch.hpp @@ -12,6 +12,8 @@ namespace Components static void UnlockStats(); private: + static Dvar::Var UIMousePitch; + static Dvar::Var r_customAspectRatio; static Game::dvar_t* Dvar_RegisterAspectRatioDvar(const char* dvarName, const char** valueList, int defaultIndex, unsigned __int16 flags, const char* description); static void SetAspectRatio_Stub(); diff --git a/src/Components/Modules/RCon.cpp b/src/Components/Modules/RCon.cpp index 56cf17ba..029c42bc 100644 --- a/src/Components/Modules/RCon.cpp +++ b/src/Components/Modules/RCon.cpp @@ -1,10 +1,15 @@ #include +#include + #include "RCon.hpp" +#include "Party.hpp" namespace Components { std::unordered_map RCon::RateLimit; + std::vector RCon::RconAddresses; + RCon::Container RCon::RconContainer; Utils::Cryptography::ECC::Key RCon::RconKey; @@ -12,6 +17,7 @@ namespace Components Dvar::Var RCon::RconPassword; Dvar::Var RCon::RconLogRequests; + Dvar::Var RCon::RconTimeout; void RCon::AddCommands() { @@ -72,6 +78,33 @@ namespace Components Network::SendCommand(target, "rconRequest"); } }); + + Command::AddSV("RconWhitelistAdd", [](Command::Params* params) + { + if (params->size() < 2) + { + Logger::Print("Usage: %s \n", params->get(0)); + return; + } + + Network::Address address(params->get(1)); + const auto hash = std::hash()(*reinterpret_cast(&address.getIP().bytes[0])); + + if (address.isValid() && std::ranges::find(RconAddresses, hash) == RconAddresses.end()) + { + RconAddresses.push_back(hash); + } + }); + } + + bool RCon::IsRateLimitCheckDisabled() + { + static std::optional flag; + if (!flag.has_value()) + { + flag.emplace(Flags::HasFlag("disable-rate-limit-check")); + } + return flag.value(); } bool RCon::RateLimitCheck(const Network::Address& address, const int time) @@ -85,8 +118,7 @@ namespace Components const auto lastTime = RateLimit[ip.full]; - // Only one request every 500ms - if (lastTime && (time - lastTime) < 500) + if (lastTime && (time - lastTime) < RconTimeout.get()) { return false; // Flooding } @@ -100,7 +132,7 @@ namespace Components for (auto i = RateLimit.begin(); i != RateLimit.end();) { // No longer at risk of flooding, remove - if ((time - i->second) > 500) + if ((time - i->second) > RconTimeout.get()) { i = RateLimit.erase(i); } @@ -113,7 +145,7 @@ namespace Components RCon::RCon() { - AddCommands(); + Events::OnSVInit(AddCommands); if (!Dedicated::IsEnabled()) { @@ -158,16 +190,23 @@ namespace Components RconContainer.timestamp = 0; - Scheduler::Once([] + Events::OnDvarInit([] { RconPassword = Dvar::Register("rcon_password", "", Game::DVAR_NONE, "The password for rcon"); - RconLogRequests = Dvar::Register("rcon_log_requests", false, Game::DVAR_NONE, "Print remote commands in the output log"); - }, Scheduler::Pipeline::MAIN); + RconLogRequests = Dvar::Register("rcon_log_requests", false, Game::DVAR_NONE, "Print remote commands in log"); + RconTimeout = Dvar::Register("rcon_timeout", 500, 100, 10000, Game::DVAR_NONE, ""); + }); Network::OnClientPacket("rcon", [](const Network::Address& address, [[maybe_unused]] const std::string& data) { + const auto hash = std::hash()(*reinterpret_cast(&address.getIP().bytes[0])); + if (!RconAddresses.empty() && std::ranges::find(RconAddresses, hash) == RconAddresses.end()) + { + return; + } + const auto time = Game::Sys_Milliseconds(); - if (!RateLimitCheck(address, time)) + if (!IsRateLimitCheckDisabled() && !RateLimitCheck(address, time)) { return; } diff --git a/src/Components/Modules/RCon.hpp b/src/Components/Modules/RCon.hpp index 22c8cfc0..907592c6 100644 --- a/src/Components/Modules/RCon.hpp +++ b/src/Components/Modules/RCon.hpp @@ -31,6 +31,8 @@ namespace Components static std::unordered_map RateLimit; + static std::vector RconAddresses; + static Container RconContainer; static Utils::Cryptography::ECC::Key RconKey; @@ -38,9 +40,11 @@ namespace Components static Dvar::Var RconPassword; static Dvar::Var RconLogRequests; + static Dvar::Var RconTimeout; static void AddCommands(); + static bool IsRateLimitCheckDisabled(); static bool RateLimitCheck(const Network::Address& address, int time); static void RateLimitCleanup(int time); }; diff --git a/src/Components/Modules/RawFiles.cpp b/src/Components/Modules/RawFiles.cpp index 95a8556f..a9f235e6 100644 --- a/src/Components/Modules/RawFiles.cpp +++ b/src/Components/Modules/RawFiles.cpp @@ -8,7 +8,7 @@ namespace Components auto fileHandle = 0; auto fileSize = Game::FS_FOpenFileRead(filename, &fileHandle); - if (fileHandle != 0) + if (fileHandle) { if ((fileSize + 1) <= size) { @@ -37,7 +37,7 @@ namespace Components auto fileHandle = 0; auto fileSize = Game::FS_FOpenFileRead(filename, &fileHandle); - if (fileHandle != 0) + if (fileHandle) { if (fileSize < 0x8000) { @@ -99,9 +99,8 @@ namespace Components const char* RawFiles::Com_LoadInfoString_Hk(const char* fileName, const char* fileDesc, const char* ident, char* loadBuffer) { - const char* buffer; - buffer = Com_LoadInfoString_LoadObj(fileName, fileDesc, ident, loadBuffer); + const auto* buffer = Com_LoadInfoString_LoadObj(fileName, fileDesc, ident, loadBuffer); if (!buffer) { buffer = Game::Com_LoadInfoString_FastFile(fileName, fileDesc, ident, loadBuffer); diff --git a/src/Components/Modules/Renderer.cpp b/src/Components/Modules/Renderer.cpp index 853e1d78..4eed9557 100644 --- a/src/Components/Modules/Renderer.cpp +++ b/src/Components/Modules/Renderer.cpp @@ -12,9 +12,12 @@ namespace Components Dvar::Var Renderer::r_drawSceneModelCollisions; Dvar::Var Renderer::r_drawModelBoundingBoxes; Dvar::Var Renderer::r_drawModelNames; + Dvar::Var Renderer::r_drawRunners; Dvar::Var Renderer::r_drawAABBTrees; Dvar::Var Renderer::r_playerDrawDebugDistance; Dvar::Var Renderer::r_forceTechnique; + Dvar::Var Renderer::r_listSamplers; + Dvar::Var Renderer::r_drawLights; float cyan[4] = { 0.0f, 0.5f, 0.5f, 1.0f }; float red[4] = { 1.0f, 0.0f, 0.0f, 1.0f }; @@ -118,30 +121,34 @@ namespace Components } } - void Renderer::R_TextureFromCodeError(const char* sampler, Game::GfxCmdBufState* state) + void Renderer::R_TextureFromCodeError(const char* sampler, Game::GfxCmdBufState* state, int samplerCode) { - Logger::Error(Game::ERR_FATAL, "Tried to use sampler '{}' when it isn't valid for material '{}' and technique '{}'", - sampler, state->material->info.name, state->technique->name); + Logger::Error(Game::ERR_FATAL, "Tried to use sampler '{}' ({}) at the wrong time! Additional info:\nMaterial: '{}'\nTechnique '{}'\nTechnique slot: {}\nTechnique flags: {}\nPass: {}\nPixel shader: '{}'\n", + samplerCode, sampler, state->material->info.name, state->technique->name, static_cast(state->techType), state->technique->flags, state->passIndex, state->pixelShader->name + ); } __declspec(naked) void Renderer::StoreGfxBufContextPtrStub1() { __asm { - // original code + // Game's code mov eax, dword ptr [eax * 4 + 0x66E600C] - // show error + // Show error pushad - push [esp + 0x24 + 0x20] + + push eax + push [esp + 0x20 + 0x24] push eax call R_TextureFromCodeError - add esp, 8 + add esp, 0xC + popad - // go back + // Jump back in push 0x54CAC1 - retn + ret } } @@ -154,10 +161,11 @@ namespace Components // show error pushad + push eax push ebx push edx call R_TextureFromCodeError - add esp, 8 + add esp, 0xC popad // go back @@ -172,41 +180,13 @@ namespace Components return Utils::Hook::Call(0x005033E0)(a1, a2, a3, Utils::String::VA("%s (^3%s^7)", mat->info.name, mat->techniqueSet->name), color, a6); } - void ListSamplers() - { - static auto* source = reinterpret_cast(0x6CAF080); - - Game::Font_s* font = Game::R_RegisterFont("fonts/smallFont", 0); - auto height = Game::R_TextHeight(font); - auto scale = 1.0f; - float color[4] = {0.0f, 1.0f, 0.0f, 1.0f}; - - for (std::size_t i = 0; i < 27; ++i) - { - if (source->input.codeImages[i] == nullptr) - { - color[0] = 1.f; - } - else - { - color[0] = 0.f; - } - - std::stringstream str; - str << std::format("{}/{:#X} => ", i, i) << (source->input.codeImages[i] == nullptr ? "---" : source->input.codeImages[i]->name) << " " << std::to_string(source->input.codeImageSamplerStates[i]); - Game::R_AddCmdDrawText(str.str().data(), std::numeric_limits::max(), font, 15.0f, (height * scale + 1) * (i + 1) + 14.0f, scale, scale, 0.0f, color, Game::ITEM_TEXTSTYLE_NORMAL); - } - } - void Renderer::DebugDrawTriggers() { if (!r_drawTriggers.get()) return; - auto entities = Game::g_entities; - for (std::size_t i = 0; i < Game::MAX_GENTITIES; ++i) { - auto* ent = &entities[i]; + auto* ent = &Game::g_entities[i]; if (ent->r.isInUse) { @@ -283,7 +263,7 @@ namespace Components if (!val) return; auto clientNum = Game::CG_GetClientNum(); - Game::gentity_t* clientEntity = &Game::g_entities[clientNum]; + auto* clientEntity = &Game::g_entities[clientNum]; // Ingame only & player only if (!Game::CL_IsCgameInitialized() || clientEntity->client == nullptr) @@ -354,14 +334,13 @@ namespace Components for (size_t i = 0; i < world->dpvs.smodelCount; i++) { auto staticModel = &world->dpvs.smodelDrawInsts[i]; - Game::Bounds* b = &world->dpvs.smodelInsts[i].bounds; + auto* b = &world->dpvs.smodelInsts[i].bounds; if (Utils::Maths::Vec3SqrDistance(playerPosition, staticModel->placement.origin) < sqrDist) { if (staticModel->model) { - - Game::R_AddDebugBounds(staticModelsColor, b); + Game::R_AddDebugBounds(staticModelsColor, b); } } } @@ -378,7 +357,7 @@ namespace Components if (!val) return; auto clientNum = Game::CG_GetClientNum(); - Game::gentity_t* clientEntity = &Game::g_entities[clientNum]; + auto* clientEntity = &Game::g_entities[clientNum]; // Ingame only & player only if (!Game::CL_IsCgameInitialized() || clientEntity->client == nullptr) @@ -458,6 +437,44 @@ namespace Components } } + void Renderer::DebugDrawRunners() + { + if (!Game::CL_IsCgameInitialized()) + { + return; + } + + if (!r_drawRunners.get()) + { + return; + } + + auto* fxSystem = reinterpret_cast(0x173F200); + + if (fxSystem) + { + for (auto i = 0; i < fxSystem->activeElemCount; i++) + { + auto* elem = &fxSystem->effects[i]; + if (elem->def) + { + Game::R_AddDebugString(sceneModelsColor, elem->frameNow.origin, 1.0f, elem->def->name); + } + } + } + + auto soundCount = *reinterpret_cast(0x7C5C90); + auto* sounds = reinterpret_cast(0x7C5CA0); + + for (auto i = 0; i < soundCount; i++) + { + if (sounds[i].aliasList) + { + Game::R_AddDebugString(staticModelsColor, sounds[i].origin, 1.0f, sounds[i].aliasList->aliasName); + } + } + } + void Renderer::DebugDrawAABBTrees() { if (!r_drawAABBTrees.get()) return; @@ -486,6 +503,116 @@ namespace Components } } + void Renderer::ListSamplers() + { + if (!r_listSamplers.get()) + { + return; + } + + static auto* source = reinterpret_cast(0x6CAF080); + + auto* font = Game::R_RegisterFont("fonts/smallFont", 0); + auto height = Game::R_TextHeight(font); + auto scale = 1.0f; + float color[] = {0.0f, 1.0f, 0.0f, 1.0f}; + + for (std::size_t i = 0; i < 27; ++i) + { + if (source->input.codeImages[i] == nullptr) + { + color[0] = 1.f; + } + else + { + color[0] = 0.f; + } + + const auto* str = Utils::String::Format("{}/{:#X} => {} {}", i, i, + (source->input.codeImages[i] == nullptr ? "---" : source->input.codeImages[i]->name), + std::to_string(source->input.codeImageSamplerStates[i]) + ); + + Game::R_AddCmdDrawText(str, std::numeric_limits::max(), font, 15.0f, (height * scale + 1) * (i + 1) + 14.0f, scale, scale, 0.0f, color, Game::ITEM_TEXTSTYLE_NORMAL); + } + } + + void Renderer::DrawPrimaryLights() + { + if (!r_drawLights.get()) + { + return; + } + + auto clientNum = Game::CG_GetClientNum(); + auto* clientEntity = &Game::g_entities[clientNum]; + + // Ingame only & player only + if (!Game::CL_IsCgameInitialized() || clientEntity->client == nullptr) + { + return; + } + + auto scene = Game::scene; + auto asset = Game::DB_FindXAssetEntry(Game::XAssetType::ASSET_TYPE_COMWORLD, Utils::String::VA("maps/mp/%s.d3dbsp", (*Game::sv_mapname)->current.string)); + + if (asset == nullptr) + { + return; + } + + auto world = asset->asset.header.comWorld; + + for (size_t i = 0; i < world->primaryLightCount; i++) + { + auto light = &world->primaryLights[i]; + + float to[3]; + to[0] = light->origin[0] + light->dir[0] * 10; + to[1] = light->origin[1] + light->dir[1] * 10; + to[2] = light->origin[2] + light->dir[2] * 10; + + auto n = light->defName == nullptr ? "NONE" : light->defName; + + auto str = std::format("LIGHT #{} ({})", i, n); + + float color[4]{}; + color[3] = 1.0f; + color[0] = light->color[0]; + color[1] = light->color[1]; + color[2] = light->color[2]; + + + Game::R_AddDebugLine(color, light->origin, to); + Game::R_AddDebugString(color, light->origin, 1.0f, str.data()); + } + + if (scene) + { + for (size_t i = 0; i < scene->addedLightCount; i++) + { + auto light = &scene->addedLight[i]; + + float color[4]{}; + color[3] = 1.0f; + color[0] = light->color[0]; + color[1] = light->color[1]; + color[2] = light->color[2]; + + float to[3]; + to[0] = light->origin[0] + light->dir[0] * 10; + to[1] = light->origin[1] + light->dir[1] * 10; + to[2] = light->origin[2] + light->dir[2] * 10; + + auto str = std::format("ADDED LIGHT #{}", i); + + Game::R_AddDebugLine(color, light->origin, to); + Game::R_AddDebugString(color, light->origin, 1.0f, str.data()); + + } + } + } + int Renderer::FixSunShadowPartitionSize(Game::GfxCamera* camera, Game::GfxSunShadowMapMetrics* mapMetrics, Game::GfxSunShadow* sunShadow, Game::GfxSunShadowClip* clip, float* partitionFraction) { auto result = Utils::Hook::Call(0x5463B0)(camera, mapMetrics, sunShadow, clip, partitionFraction); @@ -507,15 +634,23 @@ namespace Components { if (Game::CL_IsCgameInitialized()) { + DebugDrawRunners(); DebugDrawAABBTrees(); DebugDrawModelNames(); DebugDrawModelBoundingBoxes(); DebugDrawSceneModelCollisions(); DebugDrawTriggers(); ForceTechnique(); + ListSamplers(); + DrawPrimaryLights(); } }, Scheduler::Pipeline::RENDERER); +#ifdef _DEBUG + // Disable ATI Radeon 4000 optimization that crashes Pixwin + Utils::Hook::Set(0x5066F8, D3DFMT_UNKNOWN); +#endif + // COD4 Map Fixes // The day map porting is perfect we should be able to remove these Utils::Hook(0x546A09, FixSunShadowPartitionSize, HOOK_CALL).install()->quick(); @@ -550,7 +685,7 @@ namespace Components // End vid_restart Utils::Hook(0x4CA3A7, Renderer::PostVidRestartStub, HOOK_CALL).install()->quick(); - Scheduler::Once([] + Events::OnDvarInit([] { static const char* values[] = { @@ -565,10 +700,13 @@ namespace Components Renderer::r_drawSceneModelCollisions = Game::Dvar_RegisterBool("r_drawSceneModelCollisions", false, Game::DVAR_CHEAT, "Draw scene model collisions"); Renderer::r_drawTriggers = Game::Dvar_RegisterBool("r_drawTriggers", false, Game::DVAR_CHEAT, "Draw triggers"); Renderer::r_drawModelNames = Game::Dvar_RegisterEnum("r_drawModelNames", values, 0, Game::DVAR_CHEAT, "Draw all model names"); + Renderer::r_drawRunners = Game::Dvar_RegisterBool("r_drawRunners", false, Game::DVAR_NONE, "Draw active sound & fx runners"); Renderer::r_drawAABBTrees = Game::Dvar_RegisterBool("r_drawAabbTrees", false, Game::DVAR_CHEAT, "Draw aabb trees"); - Renderer::r_playerDrawDebugDistance = Game::Dvar_RegisterInt("r_drawDebugDistance", 1000, 0, 50000, Game::DVAR_ARCHIVE, "r_draw debug functions draw distance, relative to the player"); + Renderer::r_playerDrawDebugDistance = Game::Dvar_RegisterInt("r_drawDebugDistance", 1000, 0, 50000, Game::DVAR_ARCHIVE, "r_draw debug functions draw distance relative to the player"); Renderer::r_forceTechnique = Game::Dvar_RegisterInt("r_forceTechnique", 0, 0, 14, Game::DVAR_NONE, "Force a base technique on the renderer"); - }, Scheduler::Pipeline::MAIN); + Renderer::r_listSamplers = Game::Dvar_RegisterBool("r_listSamplers", false, Game::DVAR_NONE, "List samplers & sampler states"); + Renderer::r_drawLights = Game::Dvar_RegisterBool("r_drawLights", false, Game::DVAR_NONE, "Draw every comworld light in the level"); + }); } Renderer::~Renderer() diff --git a/src/Components/Modules/Renderer.hpp b/src/Components/Modules/Renderer.hpp index c856376e..327f11f6 100644 --- a/src/Components/Modules/Renderer.hpp +++ b/src/Components/Modules/Renderer.hpp @@ -28,7 +28,7 @@ namespace Components static void PostVidRestart(); static void PostVidRestartStub(); - static void R_TextureFromCodeError(const char* sampler, Game::GfxCmdBufState* state); + static void R_TextureFromCodeError(const char* sampler, Game::GfxCmdBufState* state, int samplerCode); static void StoreGfxBufContextPtrStub1(); static void StoreGfxBufContextPtrStub2(); @@ -38,8 +38,11 @@ namespace Components static void DebugDrawSceneModelCollisions(); static void DebugDrawModelBoundingBoxes(); static void DebugDrawModelNames(); + static void DebugDrawRunners(); static void DebugDrawAABBTrees(); static void ForceTechnique(); + static void ListSamplers(); + static void DrawPrimaryLights(); static int FixSunShadowPartitionSize(Game::GfxCamera* camera, Game::GfxSunShadowMapMetrics* mapMetrics, Game::GfxSunShadow* sunShadow, Game::GfxSunShadowClip* clip, float* partitionFraction); @@ -53,8 +56,11 @@ namespace Components static Dvar::Var r_drawSceneModelCollisions; static Dvar::Var r_drawModelBoundingBoxes; static Dvar::Var r_drawModelNames; + static Dvar::Var r_drawRunners; static Dvar::Var r_drawAABBTrees; static Dvar::Var r_playerDrawDebugDistance; static Dvar::Var r_forceTechnique; + static Dvar::Var r_listSamplers; + static Dvar::Var r_drawLights; }; } diff --git a/src/Components/Modules/Security.cpp b/src/Components/Modules/Security.cpp index 9c45e24a..a1854c30 100644 --- a/src/Components/Modules/Security.cpp +++ b/src/Components/Modules/Security.cpp @@ -55,7 +55,7 @@ namespace Components } const auto* dvar = Game::Dvar_FindVar(name); - if (dvar == nullptr) + if (!dvar) { // If it's not a dvar let it continue Game::CL_SelectStringTableEntryInDvar_f(); @@ -148,5 +148,15 @@ namespace Components // Fix packets causing buffer overflow Utils::Hook(0x6267E3, NET_DeferPacketToClientStub, HOOK_CALL).install()->quick(); + + // Prevent curl 7_19_4 from running + // Call to DL_Init from Live_Init + Utils::Hook::Nop(0x420937, 5); + // Call to DL_CheckOngoingDownloads from Live_Frame + Utils::Hook::Nop(0x40F8DB, 5); + // Call to LiveStorage_FetchPlaylists from Live_Frame + Utils::Hook::Nop(0x40F88C, 5); + // Call to LiveStorage_FetchPlaylists from Live_Init + Utils::Hook::Nop(0x420B54, 5); } } diff --git a/src/Components/Modules/ServerInfo.cpp b/src/Components/Modules/ServerInfo.cpp index 1dfcba0c..0172f4a5 100644 --- a/src/Components/Modules/ServerInfo.cpp +++ b/src/Components/Modules/ServerInfo.cpp @@ -1,5 +1,8 @@ #include +#include + #include "Gamepad.hpp" +#include "Party.hpp" #include "ServerInfo.hpp" #include "ServerList.hpp" #include "UIFeeder.hpp" @@ -11,28 +14,25 @@ namespace Components { ServerInfo::Container ServerInfo::PlayerContainer; - Game::dvar_t** ServerInfo::CGScoreboardHeight; - Game::dvar_t** ServerInfo::CGScoreboardWidth; - unsigned int ServerInfo::GetPlayerCount() { - return ServerInfo::PlayerContainer.playerList.size(); + return PlayerContainer.playerList.size(); } const char* ServerInfo::GetPlayerText(unsigned int index, int column) { - if (index < ServerInfo::PlayerContainer.playerList.size()) + if (index < PlayerContainer.playerList.size()) { switch (column) { case 0: return Utils::String::VA("%d", index); case 1: - return ServerInfo::PlayerContainer.playerList[index].name.data(); + return PlayerContainer.playerList[index].name.data(); case 2: - return Utils::String::VA("%d", ServerInfo::PlayerContainer.playerList[index].score); + return Utils::String::VA("%d", PlayerContainer.playerList[index].score); case 3: - return Utils::String::VA("%d", ServerInfo::PlayerContainer.playerList[index].ping); + return Utils::String::VA("%d", PlayerContainer.playerList[index].ping); default: break; } @@ -43,13 +43,13 @@ namespace Components void ServerInfo::SelectPlayer(unsigned int index) { - ServerInfo::PlayerContainer.currentPlayer = index; + PlayerContainer.currentPlayer = index; } void ServerInfo::ServerStatus([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) { - ServerInfo::PlayerContainer.currentPlayer = 0; - ServerInfo::PlayerContainer.playerList.clear(); + PlayerContainer.currentPlayer = 0; + PlayerContainer.playerList.clear(); auto* serverInfo = ServerList::GetCurrentServer(); @@ -75,8 +75,8 @@ namespace Components Dvar::Var("uiSi_ModName").set(serverInfo->mod.data() + 5); } - ServerInfo::PlayerContainer.target = serverInfo->addr; - Network::SendCommand(ServerInfo::PlayerContainer.target, "getstatus"); + PlayerContainer.target = serverInfo->addr; + Network::SendCommand(PlayerContainer.target, "getstatus"); } } @@ -87,22 +87,24 @@ namespace Components auto addressText = Network::Address(*Game::connectedHost).getString(); - if (addressText == "0.0.0.0:0" || addressText == "loopback") - addressText = "Listen Server"; + if (addressText == "0.0.0.0:0"s || addressText == "loopback"s) + { + addressText = "Listen Server"s; + } // Get x positions - auto y = (480.0f - (*ServerInfo::CGScoreboardHeight)->current.value) * 0.5f; - y += (*ServerInfo::CGScoreboardHeight)->current.value + 6.0f; + auto y = (480.0f - (*Game::cg_scoreboardHeight)->current.value) * 0.5f; + y += (*Game::cg_scoreboardHeight)->current.value + 6.0f; - const auto x = 320.0f - (*ServerInfo::CGScoreboardWidth)->current.value * 0.5f; - const auto x2 = 320.0f + (*ServerInfo::CGScoreboardWidth)->current.value * 0.5f; + const auto x = 320.0f - (*Game::cg_scoreboardWidth)->current.value * 0.5f; + const auto x2 = 320.0f + (*Game::cg_scoreboardWidth)->current.value * 0.5f; // Draw only when stream friendly ui is not enabled if (!Friends::UIStreamFriendly.get()) { constexpr auto fontSize = 0.35f; - Game::UI_DrawText(cxt, reinterpret_cast(0x7ED3F8), 0x7FFFFFFF, font, x, y, 0, 0, fontSize, reinterpret_cast(0x747F34), 3); - Game::UI_DrawText(cxt, addressText.data(), 0x7FFFFFFF, font, x2 - Game::UI_TextWidth(addressText.data(), 0, font, fontSize), y, 0, 0, fontSize, reinterpret_cast(0x747F34), 3); + Game::UI_DrawText(cxt, reinterpret_cast(0x7ED3F8), std::numeric_limits::max(), font, x, y, 0, 0, fontSize, reinterpret_cast(0x747F34), 3); + Game::UI_DrawText(cxt, addressText.data(), std::numeric_limits::max(), font, x2 - Game::UI_TextWidth(addressText.data(), 0, font, fontSize), y, 0, 0, fontSize, reinterpret_cast(0x747F34), 3); } } @@ -112,7 +114,7 @@ namespace Components { pushad push eax - call ServerInfo::DrawScoreboardInfo + call DrawScoreboardInfo pop eax popad @@ -140,7 +142,7 @@ namespace Components if (!maxClientCount) { - maxClientCount = Dvar::Var("party_maxplayers").get(); + maxClientCount = *Game::party_maxplayers ? (*Game::party_maxplayers)->current.integer : 18; } Utils::InfoString info(Game::Dvar_InfoString_Big(Game::DVAR_SERVERINFO)); @@ -158,7 +160,7 @@ namespace Components // Ensure mapname is set if (info.get("mapname").empty()) { - info.set("mapname", Dvar::Var("ui_mapname").get()); + info.set("mapname", (*Game::ui_mapname)->current.string); } // Set matchtype @@ -166,7 +168,7 @@ namespace Components // 1 - Party, use Steam_JoinLobby to connect // 2 - Match, use CL_ConnectFromParty to connect - if (Dvar::Var("party_enable").get() && Dvar::Var("party_host").get()) // Party hosting + if (Party::IsEnabled() && Dvar::Var("party_host").get()) // Party hosting { info.set("matchtype", "1"); } @@ -184,28 +186,25 @@ namespace Components ServerInfo::ServerInfo() { - ServerInfo::PlayerContainer.currentPlayer = 0; - - ServerInfo::CGScoreboardHeight = reinterpret_cast(0x9FD070); - ServerInfo::CGScoreboardWidth = reinterpret_cast(0x9FD0AC); + PlayerContainer.currentPlayer = 0; // Draw IP and hostname on the scoreboard - Utils::Hook(0x4FC6EA, ServerInfo::DrawScoreboardStub, HOOK_CALL).install()->quick(); + Utils::Hook(0x4FC6EA, DrawScoreboardStub, HOOK_CALL).install()->quick(); // Ignore native getStatus implementation Utils::Hook::Nop(0x62654E, 6); // Add uiscript - UIScript::Add("ServerStatus", ServerInfo::ServerStatus); + UIScript::Add("ServerStatus", ServerStatus); // Add uifeeder - UIFeeder::Add(13.0f, ServerInfo::GetPlayerCount, ServerInfo::GetPlayerText, ServerInfo::SelectPlayer); + UIFeeder::Add(13.0f, GetPlayerCount, GetPlayerText, SelectPlayer); Network::OnClientPacket("getStatus", [](const Network::Address& address, [[maybe_unused]] const std::string& data) { std::string playerList; - Utils::InfoString info = ServerInfo::GetInfo(); + Utils::InfoString info = GetInfo(); info.set("challenge", Utils::ParseChallenge(data)); for (std::size_t i = 0; i < Game::MAX_CLIENTS; ++i) @@ -216,7 +215,15 @@ namespace Components if (Dedicated::IsRunning()) { - if (Game::svs_clients[i].header.state < Game::CS_CONNECTED) continue; + if (Game::svs_clients[i].header.state < Game::CS_ACTIVE) continue; + if (!Game::svs_clients[i].gentity || !Game::svs_clients[i].gentity->client) continue; + + const auto* client = Game::svs_clients[i].gentity->client; + const auto team = client->sess.cs.team; + if (team == Game::TEAM_SPECTATOR) + { + continue; + } score = Game::SV_GameClientNum_Score(static_cast(i)); ping = Game::svs_clients[i].ping; @@ -239,12 +246,12 @@ namespace Components Network::OnClientPacket("statusResponse", [](const Network::Address& address, [[maybe_unused]] const std::string& data) { - if (ServerInfo::PlayerContainer.target != address) + if (PlayerContainer.target != address) { return; } - const Utils::InfoString info(data.substr(0, data.find_first_of("\n"))); + const Utils::InfoString info(data.substr(0, data.find_first_of('\n'))); Dvar::Var("uiSi_ServerName").set(info.get("sv_hostname")); Dvar::Var("uiSi_MaxClients").set(info.get("sv_maxclients")); @@ -278,7 +285,7 @@ namespace Components if (Utils::String::StartsWith(info.get("fs_game"), "mods/")) { - auto mod = info.get("fs_game"); + const auto mod = info.get("fs_game"); Dvar::Var("uiSi_ModName").set(mod.substr(5)); } @@ -288,23 +295,23 @@ namespace Components for (std::size_t i = 1; i < lines.size(); ++i) { - ServerInfo::Container::Player player; + Container::Player player; std::string currentData = lines[i]; if (currentData.size() < 3) continue; // Insert score - player.score = atoi(currentData.substr(0, currentData.find_first_of(" ")).data()); + player.score = atoi(currentData.substr(0, currentData.find_first_of(' ')).data()); // Remove score - currentData = currentData.substr(currentData.find_first_of(" ") + 1); + currentData = currentData.substr(currentData.find_first_of(' ') + 1); // Insert ping - player.ping = atoi(currentData.substr(0, currentData.find_first_of(" ")).data()); + player.ping = atoi(currentData.substr(0, currentData.find_first_of(' ')).data()); // Remove ping - currentData = currentData.substr(currentData.find_first_of(" ") + 1); + currentData = currentData.substr(currentData.find_first_of(' ') + 1); if (currentData[0] == '\"') { @@ -318,13 +325,13 @@ namespace Components player.name = currentData; - ServerInfo::PlayerContainer.playerList.push_back(player); + PlayerContainer.playerList.push_back(player); } }); } ServerInfo::~ServerInfo() { - ServerInfo::PlayerContainer.playerList.clear(); + PlayerContainer.playerList.clear(); } } diff --git a/src/Components/Modules/ServerInfo.hpp b/src/Components/Modules/ServerInfo.hpp index f09d87b6..336e035f 100644 --- a/src/Components/Modules/ServerInfo.hpp +++ b/src/Components/Modules/ServerInfo.hpp @@ -28,9 +28,6 @@ namespace Components Network::Address target; }; - static Game::dvar_t** CGScoreboardHeight; - static Game::dvar_t** CGScoreboardWidth; - static Container PlayerContainer; static void ServerStatus([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info); diff --git a/src/Components/Modules/ServerList.cpp b/src/Components/Modules/ServerList.cpp index e50011bc..ba51b830 100644 --- a/src/Components/Modules/ServerList.cpp +++ b/src/Components/Modules/ServerList.cpp @@ -1,5 +1,8 @@ #include +#include + #include "Discovery.hpp" +#include "Party.hpp" #include "ServerList.hpp" #include "UIFeeder.hpp" @@ -113,7 +116,7 @@ namespace Components { if (server->svRunning) { - if (!sorting && !Maps::CheckMapInstalled(server->mapname.data())) + if (!sorting && !Maps::CheckMapInstalled(server->mapname)) { return Utils::String::VA("^1%s", Game::UI_LocalizeMapName(server->mapname.data())); } @@ -191,7 +194,7 @@ namespace Components auto* list = GetList(); if (!list) return; - std::vector tempList(*list); + const std::vector tempList(*list); if (tempList.empty()) { @@ -206,7 +209,7 @@ namespace Components RefreshContainer.sendCount = 0; RefreshContainer.sentCount = 0; - for (auto& server : tempList) + for (const auto& server : tempList) { InsertRequest(server.addr); } @@ -271,13 +274,7 @@ namespace Components void ServerList::Refresh([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) { Dvar::Var("ui_serverSelected").set(false); - //Localization::Set("MPUI_SERVERQUERIED", "Sent requests: 0/0"); -#if 0 - OnlineList.clear(); - OfflineList.clear(); - FavouriteList.clear(); -#endif auto* list = GetList(); if (list) list->clear(); @@ -296,15 +293,15 @@ namespace Components } else if (IsOnlineList()) { - const auto masterPort = Dvar::Var("masterPort").get(); - const auto masterServerName = Dvar::Var("masterServerName").get(); + const auto masterPort = (*Game::com_masterPort)->current.integer; + const auto* masterServerName = (*Game::com_masterServerName)->current.string; // Check if our dvars can properly convert to a address Game::netadr_t masterServerAddr; if (!GetMasterServer(masterServerName, masterPort, masterServerAddr)) { Logger::Print("Could not resolve address for {}:{}", masterServerName, masterPort); - Toast::Show("cardicon_headshot", "^1Error", Utils::String::VA("Could not resolve address for %s:%i", masterServerName, masterPort), 5000); + Toast::Show("cardicon_headshot", "^1Error", std::format("Could not resolve address for {}:{}", masterServerName, masterPort), 5000); UseMasterServer = false; return; } @@ -315,10 +312,10 @@ namespace Components RefreshContainer.awatingList = true; RefreshContainer.awaitTime = Game::Sys_Milliseconds(); - RefreshContainer.host = Network::Address(Utils::String::VA("%s:%u", masterServerName, masterPort)); + RefreshContainer.host = Network::Address(std::format("{}:{}", masterServerName, masterPort)); - Logger::Print("Sending serverlist request to master\n"); - Network::SendCommand(RefreshContainer.host, "getservers", Utils::String::VA("IW4 %i full empty", PROTOCOL)); + Logger::Print("Sending server list request to master\n"); + Network::SendCommand(RefreshContainer.host, "getservers", std::format("IW4 {} full empty", PROTOCOL)); } else if (IsFavouriteList()) { @@ -414,8 +411,6 @@ namespace Components if (list) list->clear(); RefreshVisibleListInternal(UIScript::Token(), nullptr); - - Game::ShowMessageBox("Server removed from favourites.", "Success"); } void ServerList::LoadFavourties() @@ -468,7 +463,7 @@ namespace Components container.sent = false; container.target = address; - bool alreadyInserted = false; + auto alreadyInserted = false; for (auto &server : RefreshContainer.servers) { if (server.target == container.target) @@ -507,99 +502,104 @@ namespace Components for (auto i = RefreshContainer.servers.begin(); i != RefreshContainer.servers.end();) { // Our desired server - if ((i->target == address) && i->sent) + if ((i->target != address) || !i->sent) { - // Challenge did not match - if (i->challenge != info.get("challenge")) + ++i; + continue; + } + + // Challenge did not match + if (i->challenge != info.get("challenge")) + { + // Shall we remove the server from the queue? + // Better not, it might send a second response with the correct challenge. + // This might happen when users refresh twice (or more often) in a short period of time + break; + } + + ServerInfo server; + server.hostname = info.get("hostname"); + server.mapname = info.get("mapname"); + server.gametype = info.get("gametype"); + server.shortversion = info.get("shortversion"); + server.mod = info.get("fs_game"); + server.matchType = std::strtol(info.get("matchtype").data(), nullptr, 10); + server.clients = std::strtol(info.get("clients").data(), nullptr, 10); + server.bots = std::strtol(info.get("bots").data(), nullptr, 10); + server.securityLevel = std::strtol(info.get("securityLevel").data(), nullptr, 10); + server.maxClients = std::strtol(info.get("sv_maxclients").data(), nullptr, 10); + server.password = info.get("isPrivate") == "1"s; + server.aimassist = info.get("aimAssist") == "1"; + server.voice = info.get("voiceChat") == "1"s; + server.hardcore = info.get("hc") == "1"s; + server.svRunning = info.get("sv_running") == "1"s; + server.ping = (Game::Sys_Milliseconds() - i->sendTime); + server.addr = address; + + std::hash hashFn; + server.hash = hashFn(server); + + server.hostname = TextRenderer::StripMaterialTextIcons(server.hostname); + server.mapname = TextRenderer::StripMaterialTextIcons(server.mapname); + server.gametype = TextRenderer::StripMaterialTextIcons(server.gametype); + server.mod = TextRenderer::StripMaterialTextIcons(server.mod); + + // Remove server from queue + i = RefreshContainer.servers.erase(i); + + // Servers with more than 18 players or less than 0 players are faking for sure + // So lets ignore those + if (static_cast(server.clients) > Game::MAX_CLIENTS || static_cast(server.maxClients) > Game::MAX_CLIENTS) + { + return; + } + + // Check if already inserted and remove + auto* list = GetList(); + if (!list) return; + + std::size_t k = 0; + for (auto j = list->begin(); j != list->end(); ++k) + { + if (j->addr == address) { - // Shall we remove the server from the queue? - // Better not, it might send a second response with the correct challenge. - // This might happen when users refresh twice (or more often) in a short period of time - break; + j = list->erase(j); } - - ServerInfo server; - server.hostname = info.get("hostname"); - server.mapname = info.get("mapname"); - server.gametype = info.get("gametype"); - server.shortversion = info.get("shortversion"); - server.mod = info.get("fs_game"); - server.matchType = std::strtol(info.get("matchtype").data(), nullptr, 10); - server.clients = std::strtol(info.get("clients").data(), nullptr, 10); - server.bots = std::strtol(info.get("bots").data(), nullptr, 10); - server.securityLevel = std::strtol(info.get("securityLevel").data(), nullptr, 10); - server.maxClients = std::strtol(info.get("sv_maxclients").data(), nullptr, 10); - server.password = info.get("isPrivate") == "1"s; - server.aimassist = info.get("aimAssist") == "1"; - server.voice = info.get("voiceChat") == "1"s; - server.hardcore = info.get("hc") == "1"s; - server.svRunning = info.get("sv_running") == "1"s; - server.ping = (Game::Sys_Milliseconds() - i->sendTime); - server.addr = address; - - server.hostname = TextRenderer::StripMaterialTextIcons(server.hostname); - server.mapname = TextRenderer::StripMaterialTextIcons(server.mapname); - server.gametype = TextRenderer::StripMaterialTextIcons(server.gametype); - server.mod = TextRenderer::StripMaterialTextIcons(server.mod); - - // Remove server from queue - i = RefreshContainer.servers.erase(i); - - // Servers with more than 18 players or less than 0 players are faking for sure - // So lets ignore those - if (server.clients > 18 || server.maxClients > 18 || server.clients < 0 || server.maxClients < 0) + else { - return; + ++j; } + } - // Check if already inserted and remove - auto* list = GetList(); - if (!list) return; - - std::size_t k = 0; - for (auto j = list->begin(); j != list->end(); ++k) + // Also remove from visible list + for (auto j = VisibleList.begin(); j != VisibleList.end();) + { + if (*j == k) { - if (j->addr == address) - { - j = list->erase(j); - } - else - { - ++j; - } + j = VisibleList.erase(j); } - - // Also remove from visible list - for (auto j = VisibleList.begin(); j != VisibleList.end();) + else { - if (*j == k) - { - j = VisibleList.erase(j); - } - else - { - ++j; - } + ++j; } + } - if (info.get("gamename") == "IW4"s && server.matchType + if (info.get("gamename") == "IW4"s && server.matchType #if !defined(DEBUG) && defined(VERSION_FILTER) - && CompareVersion(server.shortversion, SHORTVERSION) + && CompareVersion(server.shortversion, SHORTVERSION) #endif - ) + ) + { + auto* lList = GetList(); + if (lList) { - auto* lList = GetList(); - if (lList) + if (!IsServerDuplicate(lList, server)) { lList->push_back(server); RefreshVisibleListInternal(UIScript::Token(), nullptr); } } } - else - { - ++i; - } } } @@ -623,7 +623,7 @@ namespace Components } catch (const std::exception& ex) { - Logger::Warning(Game::CON_CHANNEL_CONSOLEONLY, "{} while performing numeric comparison between {} and {}\n", ex.what(), subVersions1[i], subVersions2[i]); + Logger::PrintError(Game::CON_CHANNEL_ERROR, "{} while performing numeric comparison between {} and {}\n", ex.what(), subVersions1[i], subVersions2[i]); return false; } } @@ -631,6 +631,19 @@ namespace Components return true; } + bool ServerList::IsServerDuplicate(const std::vector* list, const ServerInfo& server) + { + for (auto l = list->begin(); l != list->end(); ++l) + { + if (l->hash == server.hash) + { + return true; + } + } + + return false; + } + ServerList::ServerInfo* ServerList::GetCurrentServer() { return GetServer(CurrentServer); @@ -720,13 +733,14 @@ namespace Components RefreshContainer.awatingList = false; Logger::Print("We haven't received a response from the master within {} seconds!\n", (Game::Sys_Milliseconds() - RefreshContainer.awaitTime) / 1000); - Toast::Show("cardicon_headshot", "^3Warning", "Failed to reach master server. Using node system instead.", 5000); + Toast::Show("net_disconnect", "^2Notice", "Master server could not be reached. Switching to decentralized network", 3000); UseMasterServer = false; Node::Synchronize(); } } + const auto challenge = Utils::Cryptography::Rand::GenerateChallenge(); auto requestLimit = NETServerQueryLimit.get(); for (std::size_t i = 0; i < RefreshContainer.servers.size() && requestLimit > 0; ++i) { @@ -738,14 +752,11 @@ namespace Components requestLimit--; server->sendTime = Game::Sys_Milliseconds(); - server->challenge = Utils::Cryptography::Rand::GenerateChallenge(); + server->challenge = challenge; ++RefreshContainer.sentCount; Network::SendCommand(server->target, "getinfo", server->challenge); - - // Display in the menu, like in CoD4 - Disabled to avoid spamming? - //Localization::Set("MPUI_SERVERQUERIED", Utils::String::VA("Sent requests: %d/%d", ServerList::RefreshContainer.sentCount, ServerList::RefreshContainer.sendCount)); } UpdateVisibleInfo(); @@ -781,17 +792,17 @@ namespace Components void ServerList::UpdateVisibleInfo() { - static int servers = 0; - static int players = 0; - static int bots = 0; + static auto servers = 0; + static auto players = 0; + static auto bots = 0; - auto list = GetList(); + auto* list = GetList(); if (list) { - int newSevers = list->size(); - int newPlayers = 0; - int newBots = 0; + auto newSevers = static_cast(list->size()); + auto newPlayers = 0; + auto newBots = 0; for (std::size_t i = 0; i < list->size(); ++i) { @@ -805,7 +816,7 @@ namespace Components players = newPlayers; bots = newBots; - Localization::Set("MPUI_SERVERQUERIED", Utils::String::VA("Servers: %i\nPlayers: %i (%i)", servers, players, bots)); + Localization::Set("MPUI_SERVERQUERIED", std::format("Servers: {}\nPlayers: {} ({})", servers, players, bots)); } } } @@ -818,8 +829,10 @@ namespace Components bool ServerList::IsServerListOpen() { auto* menu = Game::Menus_FindByName(Game::uiContext, "pc_join_unranked"); - if (!menu) + if (!menu) + { return false; + } return Game::Menu_IsVisible(Game::uiContext, menu); } @@ -831,7 +844,7 @@ namespace Components FavouriteList.clear(); VisibleList.clear(); - Scheduler::Once([] + Events::OnDvarInit([] { UIServerSelected = Dvar::Register("ui_serverSelected", false, Game::DVAR_NONE, "Whether a server has been selected in the serverlist"); @@ -842,7 +855,7 @@ namespace Components 1, 10, Dedicated::IsEnabled() ? Game::DVAR_NONE : Game::DVAR_ARCHIVE, "Amount of server queries per frame"); NETServerFrames = Dvar::Register("net_serverFrames", 30, 1, 60, Dedicated::IsEnabled() ? Game::DVAR_NONE : Game::DVAR_ARCHIVE, "Amount of server query frames per second"); - }, Scheduler::Pipeline::MAIN); + }); // Fix ui_netsource dvar Utils::Hook::Nop(0x4CDEEC, 5); // Don't reset the netsource when gametypes aren't loaded @@ -857,9 +870,9 @@ namespace Components std::lock_guard _(RefreshContainer.mutex); - int offset = 0; - auto count = RefreshContainer.servers.size(); - MasterEntry* entry = nullptr; + auto offset = 0; + const auto count = RefreshContainer.servers.size(); + MasterEntry* entry; // Find first entry do @@ -955,18 +968,6 @@ namespace Components } }); -#ifdef _DEBUG - Command::Add("playerCount", [](Command::Params*) - { - auto count = 0; - for (const auto& server : OnlineList) - { - count += server.clients; - } - - Logger::Debug("There are {} players playing", count); - }); -#endif // Add required ownerDraws UIScript::AddOwnerDraw(220, UpdateSource); UIScript::AddOwnerDraw(253, UpdateGameType); @@ -975,7 +976,7 @@ namespace Components Scheduler::Loop(Frame, Scheduler::Pipeline::CLIENT); } - ServerList::~ServerList() + void ServerList::preDestroy() { std::lock_guard _(RefreshContainer.mutex); RefreshContainer.awatingList = false; diff --git a/src/Components/Modules/ServerList.hpp b/src/Components/Modules/ServerList.hpp index 834f90e5..af088b1e 100644 --- a/src/Components/Modules/ServerList.hpp +++ b/src/Components/Modules/ServerList.hpp @@ -10,15 +10,15 @@ namespace Components public: typedef int(SortCallback)(const void*, const void*); - class ServerInfo + struct ServerInfo { - public: Network::Address addr; std::string hostname; std::string mapname; std::string gametype; std::string mod; std::string shortversion; + std::size_t hash; int clients; int bots; int maxClients; @@ -33,7 +33,8 @@ namespace Components }; ServerList(); - ~ServerList(); + + void preDestroy() override; static void Refresh([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info); static void RefreshVisibleList([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info); @@ -85,13 +86,13 @@ namespace Components uint16_t port; }; - bool IsEndToken() const + [[nodiscard]] bool IsEndToken() const noexcept { // End of transmission or file token return (token[0] == 'E' && token[1] == 'O' && (token[2] == 'T' || token[2] == 'F')); } - bool HasSeparator() const + [[nodiscard]] bool HasSeparator() const noexcept { return (token[6] == '\\'); } @@ -138,6 +139,7 @@ namespace Components static ServerInfo* GetServer(unsigned int index); static bool CompareVersion(const std::string& version1, const std::string& version2); + static bool IsServerDuplicate(const std::vector* list, const ServerInfo& server); static int SortKey; static bool SortAsc; @@ -159,3 +161,20 @@ namespace Components static bool IsServerListOpen(); }; } + +template <> +struct std::hash +{ + std::size_t operator()(const Components::ServerList::ServerInfo& x) const noexcept + { + std::size_t hash = 0; + + hash ^= std::hash()(x.hostname); + hash ^= std::hash()(x.mapname); + hash ^= std::hash()(x.mod); + hash ^= std::hash()(*reinterpret_cast(&x.addr.getIP().bytes[0])); + hash ^= x.clients; + + return hash; + } +}; diff --git a/src/Components/Modules/Session.cpp b/src/Components/Modules/Session.cpp index 096ccd24..6058f334 100644 --- a/src/Components/Modules/Session.cpp +++ b/src/Components/Modules/Session.cpp @@ -1,9 +1,11 @@ #include +#include + #include "Session.hpp" namespace Components { - bool Session::Terminate; + volatile bool Session::Terminate; std::thread Session::Thread; std::recursive_mutex Session::Mutex; @@ -142,11 +144,13 @@ namespace Components Session::Terminate = false; Session::Thread = std::thread([]() { + Com_InitThreadData(); + while (!Session::Terminate) { Session::RunFrame(); Session::HandleSignatures(); - std::this_thread::sleep_for(20ms); + Game::Sys_Sleep(20); } }); } diff --git a/src/Components/Modules/Session.hpp b/src/Components/Modules/Session.hpp index 83177d58..181d9ce6 100644 --- a/src/Components/Modules/Session.hpp +++ b/src/Components/Modules/Session.hpp @@ -38,7 +38,7 @@ namespace Components static void Handle(const std::string& packet, const Network::NetworkCallback& callback); private: - static bool Terminate; + static volatile bool Terminate; static std::thread Thread; static std::recursive_mutex Mutex; static std::unordered_map Sessions; diff --git a/src/Components/Modules/Singleton.cpp b/src/Components/Modules/Singleton.cpp index 3986a930..218e6825 100644 --- a/src/Components/Modules/Singleton.cpp +++ b/src/Components/Modules/Singleton.cpp @@ -19,7 +19,7 @@ namespace Components { printf("%s", "IW4x " VERSION " (built " __DATE__ " " __TIME__ ")\n"); printf("%d\n", REVISION); - std::exit(0); + ExitProcess(EXIT_SUCCESS); } Console::FreeNativeConsole(); @@ -30,7 +30,7 @@ namespace Components if (!FirstInstance && !ConnectProtocol::Used() && MessageBoxA(nullptr, "Do you want to start another instance?\nNot all features will be available!", "Game already running", MB_ICONEXCLAMATION | MB_YESNO) == IDNO) { - std::exit(0); + ExitProcess(EXIT_SUCCESS); } } } diff --git a/src/Components/Modules/StartupMessages.cpp b/src/Components/Modules/StartupMessages.cpp index b9b5765e..09bae507 100644 --- a/src/Components/Modules/StartupMessages.cpp +++ b/src/Components/Modules/StartupMessages.cpp @@ -6,37 +6,41 @@ namespace Components int StartupMessages::TotalMessages = -1; std::list StartupMessages::MessageList; + Dvar::Var StartupMessages::UIStartupMessage; + Dvar::Var StartupMessages::UIStartupMessageTitle; + Dvar::Var StartupMessages::UIStartupNextButtonText; + StartupMessages::StartupMessages() { - Scheduler::Once([] + Events::OnDvarInit([] { - Dvar::Register("ui_startupMessage", "", Game::DVAR_EXTERNAL | Game::DVAR_INIT, ""); - Dvar::Register("ui_startupMessageTitle", "", Game::DVAR_EXTERNAL | Game::DVAR_INIT, ""); - Dvar::Register("ui_startupNextButtonText", "", Game::DVAR_EXTERNAL | Game::DVAR_INIT, ""); - }, Scheduler::Pipeline::MAIN); + UIStartupMessage = Dvar::Register("ui_startupMessage", "", Game::DVAR_NONE, ""); + UIStartupMessageTitle = Dvar::Register("ui_startupMessageTitle", "", Game::DVAR_NONE, ""); + UIStartupNextButtonText = Dvar::Register("ui_startupNextButtonText", "", Game::DVAR_NONE, ""); + }); UIScript::Add("nextStartupMessage", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) { - if (!StartupMessages::MessageList.size()) return; + if (MessageList.empty()) return; - if (StartupMessages::TotalMessages < 1) + if (TotalMessages < 1) { - StartupMessages::TotalMessages = StartupMessages::MessageList.size(); + TotalMessages = static_cast(MessageList.size()); } - const auto& message = StartupMessages::MessageList.front(); + const auto& message = MessageList.front(); - Game::Dvar_SetStringByName("ui_startupMessage", message.data()); - Game::Dvar_SetStringByName("ui_startupMessageTitle", Utils::String::VA("Messages (%d/%d)", StartupMessages::TotalMessages - StartupMessages::MessageList.size(), StartupMessages::TotalMessages)); - Game::Dvar_SetStringByName("ui_startupNextButtonText", StartupMessages::MessageList.size() ? "Next" : "Close"); + UIStartupMessage.set(message); + UIStartupMessageTitle.set(std::format("Messages ({}/{})", TotalMessages - MessageList.size(), TotalMessages)); + UIStartupNextButtonText.set(MessageList.empty() ? "Close" : "Next"); Game::Cbuf_AddText(0, "openmenu startup_messages\n"); - StartupMessages::MessageList.pop_front(); + MessageList.pop_front(); }); } void StartupMessages::AddMessage(const std::string& message) { - StartupMessages::MessageList.push_back(message); + MessageList.push_back(message); } } diff --git a/src/Components/Modules/StartupMessages.hpp b/src/Components/Modules/StartupMessages.hpp index 7680c77a..fb06780c 100644 --- a/src/Components/Modules/StartupMessages.hpp +++ b/src/Components/Modules/StartupMessages.hpp @@ -12,5 +12,9 @@ namespace Components private: static int TotalMessages; static std::list MessageList; + + static Dvar::Var UIStartupMessage; + static Dvar::Var UIStartupMessageTitle; + static Dvar::Var UIStartupNextButtonText; }; } diff --git a/src/Components/Modules/Stats.cpp b/src/Components/Modules/Stats.cpp index a6ab031e..1f424688 100644 --- a/src/Components/Modules/Stats.cpp +++ b/src/Components/Modules/Stats.cpp @@ -79,9 +79,9 @@ namespace Components void Stats::AddScriptFunctions() { - Script::AddMethod("GetStat", [](const Game::scr_entref_t entref) + GSC::Script::AddMethod("GetStat", [](const Game::scr_entref_t entref) { - const auto* ent = Game::GetPlayerEntity(entref); + const auto* ent = GSC::Script::Scr_GetPlayerEntity(entref); const auto index = Game::Scr_GetInt(0); if (index < 0 || index > 3499) @@ -97,14 +97,14 @@ namespace Components Game::Scr_AddInt(Game::SV_GetClientStat(ent->s.number, index)); }); - Script::AddMethod("SetStat", [](const Game::scr_entref_t entref) + GSC::Script::AddMethod("SetStat", [](const Game::scr_entref_t entref) { - const auto* ent = Game::GetPlayerEntity(entref); + const auto* ent = GSC::Script::Scr_GetPlayerEntity(entref); const auto iNumParms = Game::Scr_GetNumParam(); if (iNumParms != 2) { - Game::Scr_Error(Utils::String::VA("GetStat: takes 2 arguments, got %u.\n", iNumParms)); + Game::Scr_Error(Utils::String::VA("GetStat: takes 2 arguments, got %u.", iNumParms)); } const auto index = Game::Scr_GetInt(0); @@ -179,7 +179,7 @@ namespace Components return; } - const auto index = std::atoi(params->get(1)); + const auto index = std::strtol(params->get(1), nullptr, 0); const auto stat = Game::LiveStorage_GetStat(0, index); Logger::Print(Game::CON_CHANNEL_SYSTEM, "Stat {}: {}\n", index, stat); }); diff --git a/src/Components/Modules/StructuredData.cpp b/src/Components/Modules/StructuredData.cpp index f60437c7..e8dd0ca1 100644 --- a/src/Components/Modules/StructuredData.cpp +++ b/src/Components/Modules/StructuredData.cpp @@ -158,6 +158,7 @@ namespace Components // 15 or more custom classes Utils::Hook::Set(0x60A2FE, NUM_CUSTOM_CLASSES); +#ifdef _DEBUG // Reset empty names Command::Add("checkClasses", [](Command::Params*) { @@ -168,7 +169,7 @@ namespace Components if (!*className) strcpy_s(className, 24, Game::SEH_StringEd_GetString(Utils::String::VA("CLASS_SLOT%i", i + 1))); } }); - +#endif return; } diff --git a/src/Components/Modules/TextRenderer.cpp b/src/Components/Modules/TextRenderer.cpp index baad1edb..6c677179 100644 --- a/src/Components/Modules/TextRenderer.cpp +++ b/src/Components/Modules/TextRenderer.cpp @@ -2,1645 +2,1717 @@ namespace Game { - float* con_screenMin = reinterpret_cast(0xA15F48); + float* con_screenMin = reinterpret_cast(0xA15F48); } namespace Components { - unsigned TextRenderer::colorTableDefault[TEXT_COLOR_COUNT] - { - ColorRgb(0, 0, 0), // TEXT_COLOR_BLACK - ColorRgb(255, 92, 92), // TEXT_COLOR_RED - ColorRgb(0, 255, 0), // TEXT_COLOR_GREEN - ColorRgb(255, 255, 0), // TEXT_COLOR_YELLOW - ColorRgb(0, 0, 255), // TEXT_COLOR_BLUE - ColorRgb(0, 255, 255), // TEXT_COLOR_LIGHT_BLUE - ColorRgb(255, 92, 255), // TEXT_COLOR_PINK - ColorRgb(255, 255, 255), // TEXT_COLOR_DEFAULT - ColorRgb(255, 255, 255), // TEXT_COLOR_AXIS - ColorRgb(255, 255, 255), // TEXT_COLOR_ALLIES - ColorRgb(255, 255, 255), // TEXT_COLOR_RAINBOW - ColorRgb(255, 255, 255), // TEXT_COLOR_SERVER - }; - - unsigned TextRenderer::colorTableNew[TEXT_COLOR_COUNT] - { - ColorRgb(0, 0, 0), // TEXT_COLOR_BLACK - ColorRgb(255, 49, 49), // TEXT_COLOR_RED - ColorRgb(134, 192, 0), // TEXT_COLOR_GREEN - ColorRgb(255, 173, 34), // TEXT_COLOR_YELLOW - ColorRgb(0, 135, 193), // TEXT_COLOR_BLUE - ColorRgb(32, 197, 255), // TEXT_COLOR_LIGHT_BLUE - ColorRgb(151, 80, 221), // TEXT_COLOR_PINK - ColorRgb(255, 255, 255), // TEXT_COLOR_DEFAULT - ColorRgb(255, 255, 255), // TEXT_COLOR_AXIS - ColorRgb(255, 255, 255), // TEXT_COLOR_ALLIES - ColorRgb(255, 255, 255), // TEXT_COLOR_RAINBOW - ColorRgb(255, 255, 255), // TEXT_COLOR_SERVER - }; - - unsigned(*TextRenderer::currentColorTable)[TEXT_COLOR_COUNT]; - TextRenderer::FontIconAutocompleteContext TextRenderer::autocompleteContextArray[FONT_ICON_ACI_COUNT]; - std::map TextRenderer::fontIconLookup; - std::vector TextRenderer::fontIconList; - - TextRenderer::BufferedLocalizedString TextRenderer::stringHintAutoComplete(REFERENCE_HINT_AUTO_COMPLETE, STRING_BUFFER_SIZE_SMALL); - TextRenderer::BufferedLocalizedString TextRenderer::stringHintModifier(REFERENCE_HINT_MODIFIER, STRING_BUFFER_SIZE_SMALL); - TextRenderer::BufferedLocalizedString TextRenderer::stringListHeader(REFERENCE_MODIFIER_LIST_HEADER, STRING_BUFFER_SIZE_SMALL); - TextRenderer::BufferedLocalizedString TextRenderer::stringListFlipHorizontal(REFERENCE_MODIFIER_LIST_FLIP_HORIZONTAL, STRING_BUFFER_SIZE_SMALL); - TextRenderer::BufferedLocalizedString TextRenderer::stringListFlipVertical(REFERENCE_MODIFIER_LIST_FLIP_VERTICAL, STRING_BUFFER_SIZE_SMALL); - TextRenderer::BufferedLocalizedString TextRenderer::stringListBig(REFERENCE_MODIFIER_LIST_BIG, STRING_BUFFER_SIZE_SMALL); - - Dvar::Var TextRenderer::cg_newColors; - Dvar::Var TextRenderer::cg_fontIconAutocomplete; - Dvar::Var TextRenderer::cg_fontIconAutocompleteHint; - Game::dvar_t* TextRenderer::sv_customTextColor; - Dvar::Var TextRenderer::r_colorBlind; - Game::dvar_t* TextRenderer::g_ColorBlind_MyTeam; - Game::dvar_t* TextRenderer::g_ColorBlind_EnemyTeam; - Game::dvar_t** TextRenderer::con_inputBoxColor = reinterpret_cast(0x9FD4BC); - - TextRenderer::BufferedLocalizedString::BufferedLocalizedString(const char* reference, const size_t bufferSize) - : stringReference(reference), - stringBuffer(std::make_unique(bufferSize)), - stringBufferSize(bufferSize), - stringWidth{-1} - { - - } - - void TextRenderer::BufferedLocalizedString::Cache() - { - const auto* formattingString = Game::UI_SafeTranslateString(stringReference); - - if (formattingString != nullptr) - { - strncpy(stringBuffer.get(), formattingString, stringBufferSize); - for (auto& width : stringWidth) - width = -1; - } - } - - const char* TextRenderer::BufferedLocalizedString::Format(const char* value) - { - const auto* formattingString = Game::UI_SafeTranslateString(stringReference); - if (formattingString == nullptr) - { - stringBuffer[0] = '\0'; - return stringBuffer.get(); - } - - Game::ConversionArguments conversionArguments{}; - conversionArguments.args[conversionArguments.argCount++] = value; - Game::UI_ReplaceConversions(formattingString, &conversionArguments, stringBuffer.get(), stringBufferSize); - - for (auto& width : stringWidth) - width = -1; - return stringBuffer.get(); - } - - const char* TextRenderer::BufferedLocalizedString::GetString() const - { - return stringBuffer.get(); - } - - int TextRenderer::BufferedLocalizedString::GetWidth(const FontIconAutocompleteInstance autocompleteInstance, Game::Font_s* font) - { - assert(autocompleteInstance < FONT_ICON_ACI_COUNT); - if (stringWidth[autocompleteInstance] < 0) - stringWidth[autocompleteInstance] = Game::R_TextWidth(GetString(), std::numeric_limits::max(), font); - - return stringWidth[autocompleteInstance]; - } - - TextRenderer::FontIconAutocompleteContext::FontIconAutocompleteContext() - : autocompleteActive(false), - inModifiers(false), - userClosed(false), - lastHash(0u), - results{}, - resultCount(0u), - hasMoreResults(false), - resultOffset(0u), - lastResultOffset(0u), - selectedOffset(0u), - maxFontIconWidth(0.0f), - maxMaterialNameWidth(0.0f), - stringSearchStartWith(REFERENCE_SEARCH_START_WITH, STRING_BUFFER_SIZE_BIG) - { - - } - - unsigned TextRenderer::HsvToRgb(HsvColor hsv) - { - unsigned rgb; - unsigned char region, p, q, t; - unsigned int h, s, v, remainder; - - if (hsv.s == 0) - { - rgb = ColorRgb(hsv.v, hsv.v, hsv.v); - return rgb; - } - - // converting to 16 bit to prevent overflow - h = hsv.h; - s = hsv.s; - v = hsv.v; - - region = static_cast(h / 43); - remainder = (h - (region * 43)) * 6; - - p = static_cast((v * (255 - s)) >> 8); - q = static_cast((v * (255 - ((s * remainder) >> 8))) >> 8); - t = static_cast((v * (255 - ((s * (255 - remainder)) >> 8))) >> 8); - - switch (region) - { - case 0: - rgb = ColorRgb(static_cast(v), t, p); - break; - case 1: - rgb = ColorRgb(q, static_cast(v), p); - break; - case 2: - rgb = ColorRgb(p, static_cast(v), t); - break; - case 3: - rgb = ColorRgb(p, q, static_cast(v)); - break; - case 4: - rgb = ColorRgb(t, p, static_cast(v)); - break; - default: - rgb = ColorRgb(static_cast(v), p, q); - break; - } - - return rgb; - } - - void TextRenderer::DrawAutocompleteBox(const FontIconAutocompleteContext& context, const float x, const float y, const float w, const float h, const float* color) - { - static constexpr float colorWhite[4] - { - 1.0f, - 1.0f, - 1.0f, - 1.0f - }; - const float borderColor[4] - { - color[0] * 0.5f, - color[1] * 0.5f, - color[2] * 0.5f, - color[3] - }; - - Game::R_AddCmdDrawStretchPic(x, y, w, h, 0.0, 0.0, 0.0, 0.0, color, Game::cls->whiteMaterial); - Game::R_AddCmdDrawStretchPic(x, y, FONT_ICON_AUTOCOMPLETE_BOX_BORDER, h, 0.0, 0.0, 0.0, 0.0, borderColor, Game::cls->whiteMaterial); - Game::R_AddCmdDrawStretchPic(x + w - FONT_ICON_AUTOCOMPLETE_BOX_BORDER, y, FONT_ICON_AUTOCOMPLETE_BOX_BORDER, h, 0.0, 0.0, 0.0, 0.0, borderColor, Game::cls->whiteMaterial); - Game::R_AddCmdDrawStretchPic(x, y, w, FONT_ICON_AUTOCOMPLETE_BOX_BORDER, 0.0, 0.0, 0.0, 0.0, borderColor, Game::cls->whiteMaterial); - Game::R_AddCmdDrawStretchPic(x, y + h - FONT_ICON_AUTOCOMPLETE_BOX_BORDER, w, FONT_ICON_AUTOCOMPLETE_BOX_BORDER, 0.0, 0.0, 0.0, 0.0, borderColor, Game::cls->whiteMaterial); - - if (context.resultOffset > 0) - { - Game::R_AddCmdDrawStretchPic(x + w - FONT_ICON_AUTOCOMPLETE_BOX_BORDER - FONT_ICON_AUTOCOMPLETE_ARROW_SIZE, - y + FONT_ICON_AUTOCOMPLETE_BOX_BORDER, - FONT_ICON_AUTOCOMPLETE_ARROW_SIZE, - FONT_ICON_AUTOCOMPLETE_ARROW_SIZE, - 1.0f, 1.0f, 0.0f, 0.0f, colorWhite, Game::sharedUiInfo->assets.scrollBarArrowDown); - } - if(context.hasMoreResults) - { - Game::R_AddCmdDrawStretchPic(x + w - FONT_ICON_AUTOCOMPLETE_BOX_BORDER - FONT_ICON_AUTOCOMPLETE_ARROW_SIZE, - y + h - FONT_ICON_AUTOCOMPLETE_BOX_BORDER - FONT_ICON_AUTOCOMPLETE_ARROW_SIZE, - FONT_ICON_AUTOCOMPLETE_ARROW_SIZE, - FONT_ICON_AUTOCOMPLETE_ARROW_SIZE, - 1.0f, 1.0f, 0.0f, 0.0f, colorWhite, Game::sharedUiInfo->assets.scrollBarArrowUp); - } - } - - void TextRenderer::UpdateAutocompleteContextResults(FontIconAutocompleteContext& context, Game::Font_s* font, const float textXScale) - { - context.resultCount = 0; - context.hasMoreResults = false; - context.lastResultOffset = context.resultOffset; - - auto skipCount = context.resultOffset; - - const auto queryLen = context.lastQuery.size(); - for(const auto& fontIconEntry : fontIconList) - { - const auto compareValue = fontIconEntry.iconName.compare(0, queryLen, context.lastQuery); - - if (compareValue == 0) - { - if (skipCount > 0) - { - skipCount--; - } - else if (context.resultCount < FontIconAutocompleteContext::MAX_RESULTS) - { - context.results[context.resultCount++] = { - Utils::String::VA(":%s:", fontIconEntry.iconName.data()), - fontIconEntry.iconName - }; - } - else - context.hasMoreResults = true; - } - else if (compareValue > 0) - break; - } - - context.maxFontIconWidth = 0; - context.maxMaterialNameWidth = 0; - for(auto resultIndex = 0u; resultIndex < context.resultCount; resultIndex++) - { - const auto& result = context.results[resultIndex]; - const auto fontIconWidth = static_cast(Game::R_TextWidth(result.fontIconName.c_str(), std::numeric_limits::max(), font)) * textXScale; - const auto materialNameWidth = static_cast(Game::R_TextWidth(result.materialName.c_str(), std::numeric_limits::max(), font)) * textXScale; - - if (fontIconWidth > context.maxFontIconWidth) - context.maxFontIconWidth = fontIconWidth; - if (materialNameWidth > context.maxMaterialNameWidth) - context.maxMaterialNameWidth = materialNameWidth; - } - } - - void TextRenderer::UpdateAutocompleteContext(FontIconAutocompleteContext& context, const Game::field_t* edit, Game::Font_s* font, const float textXScale) - { - int fontIconStart = -1; - auto inModifiers = false; - - for(auto i = 0; i < edit->cursor; i++) - { - const auto c = static_cast(edit->buffer[i]); - if (c == FONT_ICON_SEPARATOR_CHARACTER) - { - if(fontIconStart < 0) - { - fontIconStart = i + 1; - inModifiers = false; - } - else - { - fontIconStart = -1; - inModifiers = false; - } - } - else if(isspace(c)) - { - fontIconStart = -1; - inModifiers = false; - } - else if(c == FONT_ICON_MODIFIER_SEPARATOR_CHARACTER) - { - if (fontIconStart >= 0 && !inModifiers) - { - inModifiers = true; - } - else - { - fontIconStart = -1; - inModifiers = false; - } - } - } - - if(fontIconStart < 0 // Not in fonticon sequence - || fontIconStart == edit->cursor // Did not type the first letter yet - || !isalpha(static_cast(edit->buffer[fontIconStart])) // First letter of the icon is not alphabetic - || (fontIconStart > 1 && isalnum(static_cast(edit->buffer[fontIconStart - 2]))) // Letter before sequence is alnum - ) - { - context.autocompleteActive = false; - context.userClosed = false; - context.lastHash = 0; - context.resultCount = 0; - return; - } - - context.inModifiers = inModifiers; - - // Update scroll - if(context.selectedOffset < context.resultOffset) - context.resultOffset = context.selectedOffset; - else if(context.selectedOffset >= context.resultOffset + FontIconAutocompleteContext::MAX_RESULTS) - context.resultOffset = context.selectedOffset - (FontIconAutocompleteContext::MAX_RESULTS - 1); - - // If the user closed the context do not draw or update - if (context.userClosed) - return; - - context.autocompleteActive = true; - - // No need to update results when in modifiers - if (context.inModifiers) - return; - - // Check if results need updates - const auto currentFontIconHash = Game::R_HashString(&edit->buffer[fontIconStart], edit->cursor - fontIconStart); - if (currentFontIconHash == context.lastHash && context.lastResultOffset == context.resultOffset) - return; - - // If query was updated then reset scroll parameters - if(currentFontIconHash != context.lastHash) - { - context.resultOffset = 0; - context.selectedOffset = 0; - context.lastHash = currentFontIconHash; - } - - // Update results for query and scroll and update search string - context.lastQuery = std::string(&edit->buffer[fontIconStart], edit->cursor - fontIconStart); - context.stringSearchStartWith.Format(context.lastQuery.c_str()); - UpdateAutocompleteContextResults(context, font, textXScale); - } - - void TextRenderer::DrawAutocompleteModifiers(const FontIconAutocompleteInstance instance, const float x, const float y, Game::Font_s* font, const float textXScale, const float textYScale) - { - assert(instance < FONT_ICON_ACI_COUNT); - const auto& context = autocompleteContextArray[instance]; - - // Check which is the longest string to be able to calculate how big the box needs to be - const auto longestStringLength = std::max(std::max(std::max(stringListHeader.GetWidth(instance, font), stringListFlipHorizontal.GetWidth(instance, font)), - stringListFlipVertical.GetWidth(instance, font)), - stringListBig.GetWidth(instance, font)); - - // Draw background box - const auto boxWidth = static_cast(longestStringLength) * textXScale; - constexpr auto totalLines = 4u; - const auto lineHeight = static_cast(font->pixelHeight) * textYScale; - DrawAutocompleteBox(context, - x - FONT_ICON_AUTOCOMPLETE_BOX_PADDING, - y - FONT_ICON_AUTOCOMPLETE_BOX_PADDING, - boxWidth + FONT_ICON_AUTOCOMPLETE_BOX_PADDING * 2, - static_cast(totalLines) * lineHeight + FONT_ICON_AUTOCOMPLETE_BOX_PADDING * 2, - (*con_inputBoxColor)->current.vector); - - auto currentY = y + lineHeight; - - // Draw header line: "Following modifiers are available:" - Game::R_AddCmdDrawText(stringListHeader.GetString(), std::numeric_limits::max(), font, x, currentY, textXScale, textYScale, 0.0, TEXT_COLOR, 0); - currentY += lineHeight; - - // Draw modifier hints - Game::R_AddCmdDrawText(stringListFlipHorizontal.GetString(), std::numeric_limits::max(), font, x, currentY, textXScale, textYScale, 0.0, TEXT_COLOR, 0); - currentY += lineHeight; - Game::R_AddCmdDrawText(stringListFlipVertical.GetString(), std::numeric_limits::max(), font, x, currentY, textXScale, textYScale, 0.0, TEXT_COLOR, 0); - currentY += lineHeight; - Game::R_AddCmdDrawText(stringListBig.GetString(), std::numeric_limits::max(), font, x, currentY, textXScale, textYScale, 0.0, TEXT_COLOR, 0); - } - - void TextRenderer::DrawAutocompleteResults(const FontIconAutocompleteInstance instance, const float x, const float y, Game::Font_s* font, const float textXScale, const float textYScale) - { - assert(instance < FONT_ICON_ACI_COUNT); - auto& context = autocompleteContextArray[instance]; - - const auto hintEnabled = cg_fontIconAutocompleteHint.get(); - - // Check which is the longest string to be able to calculate how big the box needs to be - auto longestStringLength = context.stringSearchStartWith.GetWidth(instance, font); - if(hintEnabled) - longestStringLength = std::max(std::max(longestStringLength, stringHintAutoComplete.GetWidth(instance, font)), stringHintModifier.GetWidth(instance, font)); - - const auto colSpacing = FONT_ICON_AUTOCOMPLETE_COL_SPACING * textXScale; - const auto boxWidth = std::max(context.maxFontIconWidth + context.maxMaterialNameWidth + colSpacing, static_cast(longestStringLength) * textXScale); - const auto lineHeight = static_cast(font->pixelHeight) * textYScale; - - // Draw background box - const auto totalLines = 1u + context.resultCount + (hintEnabled ? 2u : 0u); - const auto arrowPadding = context.resultOffset > 0 || context.hasMoreResults ? FONT_ICON_AUTOCOMPLETE_ARROW_SIZE : 0.0f; - DrawAutocompleteBox(context, - x - FONT_ICON_AUTOCOMPLETE_BOX_PADDING, - y - FONT_ICON_AUTOCOMPLETE_BOX_PADDING, - boxWidth + FONT_ICON_AUTOCOMPLETE_BOX_PADDING * 2 + arrowPadding, - static_cast(totalLines) * lineHeight + FONT_ICON_AUTOCOMPLETE_BOX_PADDING * 2, - (*con_inputBoxColor)->current.vector); - - // Draw header line "Search results for: xyz" - auto currentY = y + lineHeight; - Game::R_AddCmdDrawText(context.stringSearchStartWith.GetString(), std::numeric_limits::max(), font, x, currentY, textXScale, textYScale, 0.0, TEXT_COLOR, 0); - currentY += lineHeight; - - // Draw search results - const auto selectedIndex = context.selectedOffset - context.resultOffset; - for(auto resultIndex = 0u; resultIndex < context.resultCount; resultIndex++) - { - const auto& result = context.results[resultIndex]; - Game::R_AddCmdDrawText(result.fontIconName.c_str(), std::numeric_limits::max(), font, x, currentY, textXScale, textYScale, 0.0, TEXT_COLOR, 0); - - if (selectedIndex == resultIndex) - Game::R_AddCmdDrawText(Utils::String::VA("^2%s", result.materialName.c_str()), std::numeric_limits::max(), font, x + context.maxFontIconWidth + colSpacing, currentY, textXScale, textYScale, 0.0, TEXT_COLOR, 0); - else - Game::R_AddCmdDrawText(result.materialName.c_str(), std::numeric_limits::max(), font, x + context.maxFontIconWidth + colSpacing, currentY, textXScale, textYScale, 0.0, TEXT_COLOR, 0); - currentY += lineHeight; - } - - // Draw extra hint if enabled - if(hintEnabled) - { - Game::R_AddCmdDrawText(stringHintAutoComplete.GetString(), std::numeric_limits::max(), font, x, currentY, textXScale, textYScale, 0.0, HINT_COLOR, 0); - currentY += lineHeight; - Game::R_AddCmdDrawText(stringHintModifier.GetString(), std::numeric_limits::max(), font, x, currentY, textXScale, textYScale, 0.0, HINT_COLOR, 0); - } - } - - void TextRenderer::DrawAutocomplete(const FontIconAutocompleteInstance instance, const float x, const float y, Game::Font_s* font, const float textXScale, const float textYScale) - { - assert(instance < FONT_ICON_ACI_COUNT); - const auto& context = autocompleteContextArray[instance]; - - if (context.inModifiers) - DrawAutocompleteModifiers(instance, x, y, font, textXScale, textYScale); - else - DrawAutocompleteResults(instance, x, y, font, textXScale, textYScale); - } - - void TextRenderer::Con_DrawInput_Hk(const int localClientNum) - { - // Call original function - Utils::Hook::Call(0x5A4480)(localClientNum); - - auto& autocompleteContext = autocompleteContextArray[FONT_ICON_ACI_CONSOLE]; - if (cg_fontIconAutocomplete.get() == false) - { - autocompleteContext.autocompleteActive = false; - return; - } - - UpdateAutocompleteContext(autocompleteContext, Game::g_consoleField, Game::cls->consoleFont, 1.0f); - if (autocompleteContext.autocompleteActive) - { - const auto x = Game::conDrawInputGlob->leftX; - const auto y = Game::con_screenMin[1] + 6.0f + static_cast(2 * Game::R_TextHeight(Game::cls->consoleFont)); - DrawAutocomplete(FONT_ICON_ACI_CONSOLE, x, y, Game::cls->consoleFont, 1.0f, 1.0f); - } - } - - void TextRenderer::Field_Draw_Say(const int localClientNum, Game::field_t* edit, const int x, const int y, const int horzAlign, const int vertAlign) - { - Game::Field_Draw(localClientNum, edit, x, y, horzAlign, vertAlign); - - auto& autocompleteContext = autocompleteContextArray[FONT_ICON_ACI_CHAT]; - if (cg_fontIconAutocomplete.get() == false) - { - autocompleteContext.autocompleteActive = false; - return; - } - - auto* screenPlacement = Game::ScrPlace_GetActivePlacement(localClientNum); - const auto scale = edit->charHeight / 48.0f; - auto* font = Game::UI_GetFontHandle(screenPlacement, 0, scale); - const auto normalizedScale = Game::R_NormalizedTextScale(font, scale); - auto xx = static_cast(x); - auto yy = static_cast(y); - yy += static_cast(Game::R_TextHeight(font)) * normalizedScale * 1.5f; - auto ww = normalizedScale; - auto hh = normalizedScale; - Game::ScrPlace_ApplyRect(screenPlacement, &xx, &yy, &ww, &hh, horzAlign, vertAlign); - - UpdateAutocompleteContext(autocompleteContext, edit, font, ww); - if (autocompleteContext.autocompleteActive) - { - DrawAutocomplete(FONT_ICON_ACI_CHAT, std::floor(xx), std::floor(yy), font, ww, hh); - } - } - - void TextRenderer::AutocompleteUp(FontIconAutocompleteContext& context) - { - if (context.selectedOffset > 0) - context.selectedOffset--; - } - - void TextRenderer::AutocompleteDown(FontIconAutocompleteContext& context) - { - if (context.resultCount < FontIconAutocompleteContext::MAX_RESULTS) - { - if (context.resultCount > 0 && context.selectedOffset < context.resultOffset + context.resultCount - 1) - context.selectedOffset++; - } - else if (context.selectedOffset == context.resultOffset + context.resultCount - 1) - { - if (context.hasMoreResults) - context.selectedOffset++; - } - else - { - context.selectedOffset++; - } - } - - void TextRenderer::AutocompleteFill(const FontIconAutocompleteContext& context, Game::ScreenPlacement* scrPlace, Game::field_t* edit, const bool closeFontIcon) - { - if (context.selectedOffset >= context.resultOffset + context.resultCount) - return; - - const auto selectedResultIndex = context.selectedOffset - context.resultOffset; - std::string remainingFillData = context.results[selectedResultIndex].materialName.substr(context.lastQuery.size()); - if (closeFontIcon) - remainingFillData += ":"; - const std::string moveData(&edit->buffer[edit->cursor]); - - const auto remainingBufferCharacters = std::extent_v - edit->cursor - moveData.size() - 1; - if(remainingFillData.size() > remainingBufferCharacters) - remainingFillData = remainingFillData.erase(remainingBufferCharacters); - - if(!remainingFillData.empty()) - { - strncpy(&edit->buffer[edit->cursor], remainingFillData.c_str(), remainingFillData.size()); - strncpy(&edit->buffer[edit->cursor + remainingFillData.size()], moveData.c_str(), moveData.size()); - edit->buffer[std::extent_v - 1] = '\0'; - edit->cursor += static_cast(remainingFillData.size()); - Game::Field_AdjustScroll(scrPlace, edit); - } - } - - bool TextRenderer::AutocompleteHandleKeyDown(FontIconAutocompleteContext& context, const int key, Game::ScreenPlacement* scrPlace, Game::field_t* edit) - { - switch (key) - { - case Game::K_UPARROW: - case Game::K_KP_UPARROW: - AutocompleteUp(context); - return true; - - case Game::K_DOWNARROW: - case Game::K_KP_DOWNARROW: - AutocompleteDown(context); - return true; - - case Game::K_ENTER: - case Game::K_KP_ENTER: - if(context.resultCount > 0) - { - AutocompleteFill(context, scrPlace, edit, true); - return true; - } - return false; - - case Game::K_TAB: - AutocompleteFill(context, scrPlace, edit, false); - return true; - - case Game::K_ESCAPE: - if (!context.userClosed) - { - context.autocompleteActive = false; - context.userClosed = true; - return true; - } - return false; - - default: - return false; - } - } - - bool TextRenderer::HandleFontIconAutocompleteKey(const int localClientNum, const FontIconAutocompleteInstance autocompleteInstance, const int key) - { - assert(autocompleteInstance < FONT_ICON_ACI_COUNT); - if (autocompleteInstance >= FONT_ICON_ACI_COUNT) - return false; - - auto& autocompleteContext = autocompleteContextArray[autocompleteInstance]; - if (!autocompleteContext.autocompleteActive) - return false; - - if(autocompleteInstance == FONT_ICON_ACI_CONSOLE) - return AutocompleteHandleKeyDown(autocompleteContext, key, Game::scrPlaceFull, Game::g_consoleField); - - if(autocompleteInstance == FONT_ICON_ACI_CHAT) - return AutocompleteHandleKeyDown(autocompleteContext, key, &Game::scrPlaceView[localClientNum], &Game::playerKeys[localClientNum].chatField); - - return false; - } - - void TextRenderer::Console_Key_Hk(const int localClientNum, const int key) - { - if (HandleFontIconAutocompleteKey(localClientNum, FONT_ICON_ACI_CONSOLE, key)) - return; - - Utils::Hook::Call(0x4311E0)(localClientNum, key); - } - - bool TextRenderer::ChatHandleKeyDown(const int localClientNum, const int key) - { - return HandleFontIconAutocompleteKey(localClientNum, FONT_ICON_ACI_CHAT, key); - } - - constexpr auto Message_Key = 0x5A7E50; - __declspec(naked) void TextRenderer::Message_Key_Stub() - { - __asm - { - pushad - - push eax - push edi - call ChatHandleKeyDown - add esp, 0x8 - test al,al - jnz skipHandling - - popad - call Message_Key - ret - - skipHandling: - popad - mov al, 1 - ret - } - } - - float TextRenderer::GetMonospaceWidth(Game::Font_s* font, int rendererFlags) - { - if(rendererFlags & Game::TEXT_RENDERFLAG_FORCEMONOSPACE) - return Game::R_GetCharacterGlyph(font, 'o')->dx; - - return 0.0f; - } - - void TextRenderer::GlowColor(Game::GfxColor* result, const Game::GfxColor baseColor, const Game::GfxColor forcedGlowColor, int renderFlags) - { - if (renderFlags & Game::TEXT_RENDERFLAG_GLOW_FORCE_COLOR) - { - result->array[0] = forcedGlowColor.array[0]; - result->array[1] = forcedGlowColor.array[1]; - result->array[2] = forcedGlowColor.array[2]; - } - else - { - result->array[0] = static_cast(std::floor(static_cast(static_cast(baseColor.array[0])) * 0.06f)); - result->array[1] = static_cast(std::floor(static_cast(static_cast(baseColor.array[1])) * 0.06f)); - result->array[2] = static_cast(std::floor(static_cast(static_cast(baseColor.array[2])) * 0.06f)); - } - } - - unsigned TextRenderer::R_FontGetRandomLetter(const int seed) - { - static constexpr char RANDOM_CHARACTERS[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890"; - return RANDOM_CHARACTERS[seed % (std::extent_v -1)]; - } - - void TextRenderer::DrawTextFxExtraCharacter(Game::Material* material, const int charIndex, const float x, const float y, const float w, const float h, const float sinAngle, const float cosAngle, const unsigned color) - { - Game::RB_DrawStretchPicRotate(material, x, y, w, h, static_cast(charIndex % 16) * 0.0625f, 0.0f, static_cast(charIndex % 16) * 0.0625f + 0.0625f, 1.0f, sinAngle, cosAngle, color); - } - - Game::GfxImage* TextRenderer::GetFontIconColorMap(const Game::Material* fontIconMaterial) - { - for (auto i = 0u; i < fontIconMaterial->textureCount; i++) - { - if (fontIconMaterial->textureTable[i].nameHash == COLOR_MAP_HASH) - return fontIconMaterial->textureTable[i].u.image; - } - - return nullptr; - } - - bool TextRenderer::IsFontIcon(const char*& text, FontIconInfo& fontIcon) - { - const auto* curPos = text; - - while (*curPos != ' ' && *curPos != FONT_ICON_SEPARATOR_CHARACTER && *curPos != 0 && *curPos != FONT_ICON_MODIFIER_SEPARATOR_CHARACTER) - curPos++; - - const auto* nameEnd = curPos; - - if(*curPos == FONT_ICON_MODIFIER_SEPARATOR_CHARACTER) - { - auto breakArgs = false; - while(!breakArgs) - { - curPos++; - switch(*curPos) - { - case FONT_ICON_MODIFIER_FLIP_HORIZONTALLY: - fontIcon.flipHorizontal = true; - break; - - case FONT_ICON_MODIFIER_FLIP_VERTICALLY: - fontIcon.flipVertical = true; - break; - - case FONT_ICON_MODIFIER_BIG: - fontIcon.big = true; - break; - - case FONT_ICON_SEPARATOR_CHARACTER: - breakArgs = true; - break; - - default: - return false; - } - } - } - - if (*curPos != FONT_ICON_SEPARATOR_CHARACTER) - return false; - - const std::string fontIconName(text, nameEnd - text); - - const auto foundFontIcon = fontIconLookup.find(fontIconName); - if (foundFontIcon == fontIconLookup.end()) - return false; - - auto& entry = foundFontIcon->second; - if(entry.material == nullptr) - { - auto* materialEntry = Game::DB_FindXAssetEntry(Game::XAssetType::ASSET_TYPE_MATERIAL, entry.materialName.data()); - if (materialEntry == nullptr) - return false; - auto* material = materialEntry->asset.header.material; - if (material == nullptr || material->techniqueSet == nullptr || material->techniqueSet->name == nullptr) - return false; - - if(strcmp(material->techniqueSet->name, "2d") != 0) - { - Logger::Print("^1Fonticon material '{}' does not have 2d techset!\n", material->info.name); - material = Game::DB_FindXAssetHeader(Game::ASSET_TYPE_MATERIAL, "default").material; - } - - entry.material = material; - } - - text = curPos + 1; - fontIcon.material = entry.material; - return true; - } - - float TextRenderer::GetNormalizedFontIconWidth(const FontIconInfo& fontIcon) - { - const auto* colorMap = GetFontIconColorMap(fontIcon.material); - if (colorMap == nullptr) - return 0; - const auto sizeMultiplier = fontIcon.big ? 1.5f : 1.0f; - auto colWidth = static_cast(colorMap->width); - auto colHeight = static_cast(colorMap->height); - if (fontIcon.material->info.textureAtlasColumnCount > 1) - colWidth /= static_cast(fontIcon.material->info.textureAtlasColumnCount); - if (fontIcon.material->info.textureAtlasRowCount > 1) - colHeight /= static_cast(fontIcon.material->info.textureAtlasRowCount); - return (colWidth / colHeight) * sizeMultiplier; - } - - float TextRenderer::GetFontIconWidth(const FontIconInfo& fontIcon, const Game::Font_s* font, const float xScale) - { - const auto* colorMap = GetFontIconColorMap(fontIcon.material); - if (colorMap == nullptr) - return 0; - const auto sizeMultiplier = fontIcon.big ? 1.5f : 1.0f; - auto colWidth = static_cast(colorMap->width); - auto colHeight = static_cast(colorMap->height); - if (fontIcon.material->info.textureAtlasColumnCount > 1) - colWidth /= static_cast(fontIcon.material->info.textureAtlasColumnCount); - if (fontIcon.material->info.textureAtlasRowCount > 1) - colHeight /= static_cast(fontIcon.material->info.textureAtlasRowCount); - return static_cast(font->pixelHeight) * (colWidth / colHeight) * xScale * sizeMultiplier; - } - - float TextRenderer::DrawFontIcon(const FontIconInfo& fontIcon, const float x, const float y, const float sinAngle, const float cosAngle, const Game::Font_s* font, const float xScale, const float yScale, const unsigned color) - { - const auto* colorMap = GetFontIconColorMap(fontIcon.material); - if (colorMap == nullptr) - return 0; - - float s0, t0, s1, t1; - if(fontIcon.flipHorizontal) - { - s0 = 1.0f; - s1 = 0.0f; - } - else - { - s0 = 0.0f; - s1 = 1.0f; - } - if(fontIcon.flipVertical) - { - t0 = 1.0f; - t1 = 0.0f; - } - else - { - t0 = 0.0f; - t1 = 1.0f; - } - Game::Material_Process2DTextureCoordsForAtlasing(fontIcon.material, &s0, &s1, &t0, &t1); - const auto sizeMultiplier = fontIcon.big ? 1.5f : 1.0f; - - auto colWidth = static_cast(colorMap->width); - auto colHeight = static_cast(colorMap->height); - if (fontIcon.material->info.textureAtlasColumnCount > 1) - colWidth /= static_cast(fontIcon.material->info.textureAtlasColumnCount); - if (fontIcon.material->info.textureAtlasRowCount > 1) - colHeight /= static_cast(fontIcon.material->info.textureAtlasRowCount); - - const auto h = static_cast(font->pixelHeight) * yScale * sizeMultiplier; - const auto w = static_cast(font->pixelHeight) * (colWidth / colHeight) * xScale * sizeMultiplier; - - const auto yy = y - (h + yScale * static_cast(font->pixelHeight)) * 0.5f; - Game::RB_DrawStretchPicRotate(fontIcon.material, x, yy, w, h, s0, t0, s1, t1, sinAngle, cosAngle, color); - - return w; - } - - float TextRenderer::DrawHudIcon(const char*& text, const float x, const float y, const float sinAngle, const float cosAngle, const Game::Font_s* font, const float xScale, const float yScale, const unsigned color) - { - float s0, s1, t0, t1; - - if(*text == '\x01') - { - s0 = 0.0; - t0 = 0.0; - s1 = 1.0; - t1 = 1.0; - } - else - { - s0 = 1.0; - t0 = 0.0; - s1 = 0.0; - t1 = 1.0; - } - text++; - - if (*text == 0) - return 0; - - const auto v12 = font->pixelHeight * (*text - 16) + 16; - const auto w = static_cast((((v12 >> 24) & 0x1F) + v12) >> 5) * xScale; - text++; - - if (*text == 0) - return 0; - - const auto h = static_cast((font->pixelHeight * (*text - 16) + 16) >> 5) * yScale; - text++; - - if (*text == 0) - return 0; - - const auto materialNameLen = static_cast(*text); - text++; - - for(auto i = 0u; i < materialNameLen; i++) - { - if (text[i] == 0) - return 0; - } - - const std::string materialName(text, materialNameLen); - text += materialNameLen; - - auto* material = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_MATERIAL, materialName.data()).material; - if (material == nullptr || material->techniqueSet == nullptr || material->techniqueSet->name == nullptr || strcmp(material->techniqueSet->name, "2d") != 0) - material = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_MATERIAL, "default").material; - - const auto yy = y - (h + yScale * static_cast(font->pixelHeight)) * 0.5f; - - Game::RB_DrawStretchPicRotate(material, x, yy, w, h, s0, t0, s1, t1, sinAngle, cosAngle, color); - - return w; - } - - void TextRenderer::RotateXY(const float cosAngle, const float sinAngle, const float pivotX, const float pivotY, const float x, const float y, float* outX, float* outY) - { - *outX = (x - pivotX) * cosAngle + pivotX - (y - pivotY) * sinAngle; - *outY = (y - pivotY) * cosAngle + pivotY + (x - pivotX) * sinAngle; - } - - void TextRenderer::DrawText2D(const char* text, float x, float y, Game::Font_s* font, float xScale, float yScale, float sinAngle, float cosAngle, Game::GfxColor color, int maxLength, int renderFlags, int cursorPos, char cursorLetter, float padding, Game::GfxColor glowForcedColor, int fxBirthTime, int fxLetterTime, int fxDecayStartTime, int fxDecayDuration, Game::Material* fxMaterial, Game::Material* fxMaterialGlow) - { - UpdateColorTable(); - - Game::GfxColor dropShadowColor{0}; - dropShadowColor.array[3] = color.array[3]; - - int randSeed = 1; - bool drawRandomCharAtEnd = false; - const auto forceMonospace = renderFlags & Game::TEXT_RENDERFLAG_FORCEMONOSPACE; - const auto monospaceWidth = GetMonospaceWidth(font, renderFlags); - auto* material = font->material; - Game::Material* glowMaterial = nullptr; - - bool decaying; - int decayTimeElapsed; - if(renderFlags & Game::TEXT_RENDERFLAG_FX_DECODE) - { - if (!Game::SetupPulseFXVars(text, maxLength, fxBirthTime, fxLetterTime, fxDecayStartTime, fxDecayDuration, &drawRandomCharAtEnd, &randSeed, &maxLength, &decaying, &decayTimeElapsed)) - return; - } - else - { - drawRandomCharAtEnd = false; - randSeed = 1; - decaying = false; - decayTimeElapsed = 0; - } - - Game::FontPassType passes[Game::FONTPASS_COUNT]; - unsigned passCount = 0; - - if(renderFlags & Game::TEXT_RENDERFLAG_OUTLINE) - { - if(renderFlags & Game::TEXT_RENDERFLAG_GLOW) - { - glowMaterial = font->glowMaterial; - passes[passCount++] = Game::FONTPASS_GLOW; - } - - passes[passCount++] = Game::FONTPASS_OUTLINE; - passes[passCount++] = Game::FONTPASS_NORMAL; - } - else - { - passes[passCount++] = Game::FONTPASS_NORMAL; - - if (renderFlags & Game::TEXT_RENDERFLAG_GLOW) - { - glowMaterial = font->glowMaterial; - passes[passCount++] = Game::FONTPASS_GLOW; - } - } - - const auto startX = x - xScale * 0.5f; - const auto startY = y - 0.5f * yScale; - - for(auto passIndex = 0u; passIndex < passCount; passIndex++) - { - float xRot, yRot; - const char* curText = text; - auto maxLengthRemaining = maxLength; - auto currentColor = color; - auto subtitleAllowGlow = false; - auto extraFxChar = 0; - auto drawExtraFxChar = false; - auto passRandSeed = randSeed; - auto count = 0; - auto xa = startX; - auto xy = startY; - - while(*curText && maxLengthRemaining) - { - if (passes[passIndex] == Game::FONTPASS_NORMAL && renderFlags & Game::TEXT_RENDERFLAG_CURSOR && count == cursorPos) - { - RotateXY(cosAngle, sinAngle, startX, startY, xa, xy, &xRot, &yRot); - Game::RB_DrawCursor(material, cursorLetter, xRot, yRot, sinAngle, cosAngle, font, xScale, yScale, color.packed); - } - - auto letter = Game::SEH_ReadCharFromString(&curText, nullptr); - - if(letter == '^' && *curText >= COLOR_FIRST_CHAR && *curText <= COLOR_LAST_CHAR) - { - const auto colorIndex = ColorIndexForChar(*curText); - subtitleAllowGlow = false; - if (colorIndex == TEXT_COLOR_DEFAULT) - { - currentColor = color; - } - else if (renderFlags & Game::TEXT_RENDERFLAG_SUBTITLETEXT && colorIndex == TEXT_COLOR_GREEN) - { - constexpr Game::GfxColor altColor{ MY_ALTCOLOR_TWO }; - subtitleAllowGlow = true; - // Swap r and b for whatever reason - currentColor.packed = ColorRgba(altColor.array[2], altColor.array[1], altColor.array[0], Game::ModulateByteColors(altColor.array[3], color.array[3])); - } - else - { - const Game::GfxColor colorTableColor{ (*currentColorTable)[colorIndex] }; - // Swap r and b for whatever reason - currentColor.packed = ColorRgba(colorTableColor.array[2], colorTableColor.array[1], colorTableColor.array[0], color.array[3]); - } - - if(!(renderFlags & Game::TEXT_RENDERFLAG_CURSOR && cursorPos > count && cursorPos < count + 2)) - { - curText++; - count += 2; - continue; - } - } - - auto finalColor = currentColor; - - if(letter == '^' && (*curText == '\x01' || *curText == '\x02')) - { - RotateXY(cosAngle, sinAngle, startX, startY, xa, xy, &xRot, &yRot); - xa += DrawHudIcon(curText, xRot, yRot, sinAngle, cosAngle, font, xScale, yScale, ColorRgba(255, 255, 255, finalColor.array[3])); - - if (renderFlags & Game::TEXT_RENDERFLAG_PADDING) - xa += xScale * padding; - ++count; - maxLengthRemaining--; - continue; - } - - if(letter == FONT_ICON_SEPARATOR_CHARACTER) - { - FontIconInfo fontIconInfo{}; - const char* fontIconEnd = curText; - if(IsFontIcon(fontIconEnd, fontIconInfo) && !(renderFlags & Game::TEXT_RENDERFLAG_CURSOR && cursorPos > count && cursorPos <= count + (fontIconEnd - curText))) - { - RotateXY(cosAngle, sinAngle, startX, startY, xa, xy, &xRot, &yRot); - - if(passes[passIndex] == Game::FONTPASS_NORMAL) - xa += DrawFontIcon(fontIconInfo, xRot, yRot, sinAngle, cosAngle, font, xScale, yScale, ColorRgba(255, 255, 255, finalColor.array[3])); - else - xa += GetFontIconWidth(fontIconInfo, font, xScale); - - if (renderFlags & Game::TEXT_RENDERFLAG_PADDING) - xa += xScale * padding; - count += (fontIconEnd - curText) + 1; - maxLengthRemaining--; - curText = fontIconEnd; - continue; - } - } - - if(drawRandomCharAtEnd && maxLengthRemaining == 1) - { - letter = R_FontGetRandomLetter(Game::RandWithSeed(&passRandSeed)); - - if(Game::RandWithSeed(&passRandSeed) % 2) - { - drawExtraFxChar = true; - letter = 'O'; - } - } - - if(letter == '\n') - { - xa = startX; - xy += static_cast(font->pixelHeight) * yScale; - continue; - } - - if(letter == '\r') - { - xy += static_cast(font->pixelHeight) * yScale; - continue; - } - - auto skipDrawing = false; - if(decaying) - { - char decayAlpha; - Game::GetDecayingLetterInfo(letter, &passRandSeed, decayTimeElapsed, fxBirthTime, fxDecayDuration, currentColor.array[3], &skipDrawing, &decayAlpha, &letter, &drawExtraFxChar); - finalColor.array[3] = decayAlpha; - } - - if(drawExtraFxChar) - { - auto tempSeed = passRandSeed; - extraFxChar = Game::RandWithSeed(&tempSeed); - } - - auto glyph = Game::R_GetCharacterGlyph(font, letter); - auto xAdj = static_cast(glyph->x0) * xScale; - auto yAdj = static_cast(glyph->y0) * yScale; - - if(!skipDrawing) - { - if (passes[passIndex] == Game::FONTPASS_NORMAL) - { - if (renderFlags & Game::TEXT_RENDERFLAG_DROPSHADOW) - { - auto ofs = 1.0f; - if (renderFlags & Game::TEXT_RENDERFLAG_DROPSHADOW_EXTRA) - ofs += 1.0f; - - xRot = xa + xAdj + ofs; - yRot = xy + yAdj + ofs; - RotateXY(cosAngle, sinAngle, startX, startY, xRot, yRot, &xRot, &yRot); - if (drawExtraFxChar) - DrawTextFxExtraCharacter(fxMaterial, extraFxChar, xRot, yRot, static_cast(glyph->pixelWidth) * xScale, static_cast(glyph->pixelHeight) * yScale, sinAngle, cosAngle, dropShadowColor.packed); - else - Game::RB_DrawChar(material, xRot, yRot, static_cast(glyph->pixelWidth) * xScale, static_cast(glyph->pixelHeight) * yScale, sinAngle, cosAngle, glyph, dropShadowColor.packed); - } - - RotateXY(cosAngle, sinAngle, startX, startY, xa + xAdj, xy + yAdj, &xRot, &yRot); - if (drawExtraFxChar) - DrawTextFxExtraCharacter(fxMaterial, extraFxChar, xRot, yRot, static_cast(glyph->pixelWidth) * xScale, static_cast(glyph->pixelHeight) * yScale, sinAngle, cosAngle, finalColor.packed); - else - Game::RB_DrawChar(material, xRot, yRot, static_cast(glyph->pixelWidth) * xScale, static_cast(glyph->pixelHeight) * yScale, sinAngle, cosAngle, glyph, finalColor.packed); - } - else if(passes[passIndex] == Game::FONTPASS_OUTLINE) - { - auto outlineSize = 1.0f; - if (renderFlags & Game::TEXT_RENDERFLAG_OUTLINE_EXTRA) - outlineSize = 1.3f; - - for (const auto offset : MY_OFFSETS) - { - RotateXY(cosAngle, sinAngle, startX, startY, xa + xAdj + outlineSize * offset[0], xy + yAdj + outlineSize * offset[1], &xRot, &yRot); - if (drawExtraFxChar) - DrawTextFxExtraCharacter(fxMaterial, extraFxChar, xRot, yRot, static_cast(glyph->pixelWidth) * xScale, static_cast(glyph->pixelHeight) * yScale, sinAngle, cosAngle, dropShadowColor.packed); - else - Game::RB_DrawChar(material, xRot, yRot, static_cast(glyph->pixelWidth) * xScale, static_cast(glyph->pixelHeight) * yScale, sinAngle, cosAngle, glyph, dropShadowColor.packed); - } - } - else if(passes[passIndex] == Game::FONTPASS_GLOW && ((renderFlags & Game::TEXT_RENDERFLAG_SUBTITLETEXT) == 0 || subtitleAllowGlow)) - { - GlowColor(&finalColor, finalColor, glowForcedColor, renderFlags); - - for (const auto offset : MY_OFFSETS) - { - RotateXY(cosAngle, sinAngle, startX, startY, xa + xAdj + 2.0f * offset[0] * xScale, xy + yAdj + 2.0f * offset[1] * yScale, &xRot, &yRot); - if (drawExtraFxChar) - DrawTextFxExtraCharacter(fxMaterialGlow, extraFxChar, xRot, yRot, static_cast(glyph->pixelWidth) * xScale, static_cast(glyph->pixelHeight) * yScale, sinAngle, cosAngle, finalColor.packed); - else - Game::RB_DrawChar(glowMaterial, xRot, yRot, static_cast(glyph->pixelWidth) * xScale, static_cast(glyph->pixelHeight) * yScale, sinAngle, cosAngle, glyph, finalColor.packed); - } - } - } - - if(forceMonospace) - xa += monospaceWidth * xScale; - else - xa += static_cast(glyph->dx) * xScale; - - if (renderFlags & Game::TEXT_RENDERFLAG_PADDING) - xa += xScale * padding; - - count++; - maxLengthRemaining--; - } - - if(renderFlags & Game::TEXT_RENDERFLAG_CURSOR && count == cursorPos) - { - RotateXY(cosAngle, sinAngle, startX, startY, xa, xy, &xRot, &yRot); - Game::RB_DrawCursor(material, cursorLetter, xRot, yRot, sinAngle, cosAngle, font, xScale, yScale, color.packed); - } - } - } - - int TextRenderer::R_TextWidth_Hk(const char* text, int maxChars, Game::Font_s* font) - { - auto lineWidth = 0; - auto maxWidth = 0; - - if (maxChars <= 0) - maxChars = std::numeric_limits::max(); - - if (text == nullptr) - return 0; - - auto count = 0; - while (text && *text && count < maxChars) - { - const auto letter = Game::SEH_ReadCharFromString(&text, nullptr); - if (letter == '\r' || letter == '\n') - { - lineWidth = 0; - } - else - { - if (letter == '^' && text) - { - if (*text >= COLOR_FIRST_CHAR && *text <= COLOR_LAST_CHAR) - { - text++; - continue; - } - - if (*text >= '\x01' && *text <= '\x02' && text[1] != '\0' && text[2] != '\0' && text[3] != '\0') - { - const auto width = text[1]; - const auto materialNameLength = text[3]; - - // This is how the game calculates width and height. Probably some 1 byte floating point number. - // Details to be investigated if necessary. - const auto v9 = font->pixelHeight * (width - 16) + 16; - const auto w = ((((v9 >> 24) & 0x1F) + v9) >> 5); - - lineWidth += w; - if (lineWidth > maxWidth) - maxWidth = lineWidth; - - text += 4; - for (auto currentLength = 0; currentLength < materialNameLength && *text; currentLength++) - text++; - continue; - } - } - - if (letter == FONT_ICON_SEPARATOR_CHARACTER) - { - FontIconInfo fontIconInfo{}; - const char* fontIconEnd = text; - if (IsFontIcon(fontIconEnd, fontIconInfo)) - { - lineWidth += static_cast(GetFontIconWidth(fontIconInfo, font, 1.0f)); - if (lineWidth > maxWidth) - maxWidth = lineWidth; - text = fontIconEnd; - continue; - } - } - - lineWidth += R_GetCharacterGlyph(font, letter)->dx; - if (lineWidth > maxWidth) - maxWidth = lineWidth; - count++; - } - } - - return maxWidth; - } - - unsigned int TextRenderer::ColorIndex(const char index) - { - auto result = index - '0'; - if (static_cast(result) >= TEXT_COLOR_COUNT || result < 0) result = 7; - return result; - } - - void TextRenderer::StripColors(const char* in, char* out, std::size_t max) - { - if (!in || !out) return; - - max--; - size_t current = 0; - while (*in != 0 && current < max) - { - const char index = *(in + 1); - if (*in == '^' && (ColorIndex(index) != 7 || index == '7')) - { - ++in; - } - else - { - *out = *in; - ++out; - ++current; - } - - ++in; - } - *out = '\0'; - } - - std::string TextRenderer::StripColors(const std::string& in) - { - char buffer[1024]{}; // 1024 is a lucky number in the engine - StripColors(in.data(), buffer, sizeof(buffer)); - return std::string(buffer); - } - - void TextRenderer::StripMaterialTextIcons(const char* in, char* out, std::size_t max) - { - if (!in || !out) return; - - max--; - size_t current = 0; - while (*in != 0 && current < max) - { - if (*in == '^' && (in[1] == '\x01' || in[1] == '\x02')) - { - in += 2; - - if (*in) // width - in++; - if (*in) // height - in++; - - if(*in) // material name length + material name characters - { - const auto materialNameLength = *in; - in++; - for(auto i = 0; i < materialNameLength; i++) - { - if (*in) - in++; - } - } - } - else - { - *out = *in; - ++out; - ++current; - ++in; - } - - } - *out = '\0'; - } - - std::string TextRenderer::StripMaterialTextIcons(const std::string& in) - { - char buffer[1000] = { 0 }; // Should be more than enough - StripAllTextIcons(in.data(), buffer, sizeof(buffer)); - return std::string(buffer); - } - - void TextRenderer::StripAllTextIcons(const char* in, char* out, std::size_t max) - { - if (!in || !out) return; - - max--; - size_t current = 0; - while (*in != 0 && current < max) - { - if (*in == '^' && (in[1] == '\x01' || in[1] == '\x02')) - { - in += 2; - - if (*in) // width - in++; - if (*in) // height - in++; - - if (*in) // material name length + material name characters - { - const auto materialNameLength = *in; - in++; - for (auto i = 0; i < materialNameLength; i++) - { - if (*in) - in++; - } - } - - continue; - } - - if (*in == FONT_ICON_SEPARATOR_CHARACTER) - { - const auto* fontIconEndPos = &in[1]; - FontIconInfo fontIcon{}; - if(IsFontIcon(fontIconEndPos, fontIcon)) - { - in = fontIconEndPos; - continue; - } - } - - *out = *in; - ++out; - ++current; - ++in; - } - - *out = '\0'; - } - - std::string TextRenderer::StripAllTextIcons(const std::string& in) - { - char buffer[1000] = { 0 }; // Should be more than enough - StripAllTextIcons(in.data(), buffer, sizeof(buffer)); - return std::string(buffer); - } - - int TextRenderer::SEH_PrintStrlenWithCursor(const char* string, const Game::field_t* field) - { - if (!string) - return 0; - - const auto cursorPos = field->cursor; - auto len = 0; - auto lenWithInvisibleTail = 0; - auto count = 0; - const auto* curText = string; - while(*curText) - { - const auto c = Game::SEH_ReadCharFromString(&curText, nullptr); - lenWithInvisibleTail = len; - - if (c == '^' && *curText >= COLOR_FIRST_CHAR && *curText <= COLOR_LAST_CHAR && !(cursorPos > count && cursorPos < count + 2)) - { - curText++; - count++; - } - else if(c != '\r' && c != '\n') - { - len++; - } - - count++; - lenWithInvisibleTail++; - } - - return lenWithInvisibleTail; - } - - __declspec(naked) void TextRenderer::Field_AdjustScroll_PrintLen_Stub() - { - __asm - { - push eax - pushad - - push esi - push [esp + 0x8 + 0x24] - call SEH_PrintStrlenWithCursor - add esp, 0x8 - mov [esp + 0x20], eax - - popad - pop eax - ret - } - } - - void TextRenderer::PatchColorLimit(const char limit) - { - Utils::Hook::Set(0x535629, limit); // DrawText2d - Utils::Hook::Set(0x4C1BE4, limit); // SEH_PrintStrlen - Utils::Hook::Set(0x4863DD, limit); // No idea :P - Utils::Hook::Set(0x486429, limit); // No idea :P - Utils::Hook::Set(0x49A5A8, limit); // No idea :P - Utils::Hook::Set(0x505721, limit); // R_TextWidth - Utils::Hook::Set(0x505801, limit); // No idea :P - Utils::Hook::Set(0x50597F, limit); // No idea :P - Utils::Hook::Set(0x5815DB, limit); // No idea :P - Utils::Hook::Set(0x592ED0, limit); // No idea :P - Utils::Hook::Set(0x5A2E2E, limit); // No idea :P - - Utils::Hook::Set(0x5A2733, static_cast(ColorIndexForChar(limit))); // No idea :P - } - - // Patches team overhead normally - bool TextRenderer::Dvar_GetUnpackedColorByName(const char* name, float* expandedColor) - { - if (r_colorBlind.get()) - { - const auto str = std::string(name); - if (str == "g_TeamColor_EnemyTeam") - { - // Dvar_GetUnpackedColor - const auto* colorblindEnemy = g_ColorBlind_EnemyTeam->current.color; - expandedColor[0] = static_cast(colorblindEnemy[0]) / 255.0f; - expandedColor[1] = static_cast(colorblindEnemy[1]) / 255.0f; - expandedColor[2] = static_cast(colorblindEnemy[2]) / 255.0f; - expandedColor[3] = static_cast(colorblindEnemy[3]) / 255.0f; - return false; - } - else if (str == "g_TeamColor_MyTeam") - { - // Dvar_GetUnpackedColor - const auto* colorblindAlly = g_ColorBlind_MyTeam->current.color; - expandedColor[0] = static_cast(colorblindAlly[0]) / 255.0f; - expandedColor[1] = static_cast(colorblindAlly[1]) / 255.0f; - expandedColor[2] = static_cast(colorblindAlly[2]) / 255.0f; - expandedColor[3] = static_cast(colorblindAlly[3]) / 255.0f; - return false; - } - } - - return true; - } - - __declspec(naked) void TextRenderer::GetUnpackedColorByNameStub() - { - __asm - { - push[esp + 8h] - push[esp + 8h] - call TextRenderer::Dvar_GetUnpackedColorByName - add esp, 8h - - test al, al - jnz continue - - retn - - continue: - push edi - mov edi, [esp + 8h] - push 406535h - retn - } - } - - void TextRenderer::UpdateColorTable() - { - if (cg_newColors.get()) - currentColorTable = &colorTableNew; - else - currentColorTable = &colorTableDefault; - - (*currentColorTable)[TEXT_COLOR_AXIS] = *reinterpret_cast(0x66E5F70); - (*currentColorTable)[TEXT_COLOR_ALLIES] = *reinterpret_cast(0x66E5F74); - (*currentColorTable)[TEXT_COLOR_RAINBOW] = HsvToRgb({ static_cast((Game::Sys_Milliseconds() / 200) % 256), 255,255 }); - (*currentColorTable)[TEXT_COLOR_SERVER] = sv_customTextColor->current.unsignedInt; - } - - void TextRenderer::InitFontIconStrings() - { - stringHintAutoComplete.Format("TAB"); - stringHintModifier.Format(Utils::String::VA("%c", FONT_ICON_MODIFIER_SEPARATOR_CHARACTER)); - stringListHeader.Cache(); - stringListFlipHorizontal.Format(Utils::String::VA("%c", FONT_ICON_MODIFIER_FLIP_HORIZONTALLY)); - stringListFlipVertical.Format(Utils::String::VA("%c", FONT_ICON_MODIFIER_FLIP_VERTICALLY)); - stringListBig.Format(Utils::String::VA("%c", FONT_ICON_MODIFIER_BIG)); - } - - void TextRenderer::InitFontIcons() - { - InitFontIconStrings(); - - fontIconList.clear(); - fontIconLookup.clear(); - - const auto fontIconTable = Game::DB_FindXAssetHeader(Game::ASSET_TYPE_STRINGTABLE, "mp/fonticons.csv").stringTable; - - if(fontIconTable->columnCount < 2 || fontIconTable->rowCount <= 0) - { - Logger::Print("^1Failed to load font icon table\n"); - return; - } - - fontIconList.reserve(fontIconTable->rowCount); - for(auto rowIndex = 0; rowIndex < fontIconTable->rowCount; rowIndex++) - { - const auto* columns = &fontIconTable->values[rowIndex * fontIconTable->columnCount]; - - if(columns[0].string == nullptr || columns[1].string == nullptr) - continue; - - if (columns[0].string[0] == '\0' || columns[1].string[1] == '\0') - continue; - - if (columns[0].string[0] == '#') - continue; - - FontIconTableEntry entry - { - columns[0].string, - columns[1].string, - nullptr - }; - - fontIconList.emplace_back(entry); - fontIconLookup.emplace(std::make_pair(entry.iconName, entry)); - } - - std::sort(fontIconList.begin(), fontIconList.end(), [](const FontIconTableEntry& a, const FontIconTableEntry& b) - { - return a.iconName < b.iconName; - }); - } - - void TextRenderer::UI_Init_Hk(const int localClientNum) - { - // Call original method - Utils::Hook::Call(0x4A57D0)(localClientNum); - - InitFontIcons(); - } - - TextRenderer::TextRenderer() - { - currentColorTable = &colorTableDefault; - - cg_newColors = Dvar::Register("cg_newColors", true, Game::DVAR_ARCHIVE, "Use Warfare 2 color code style."); - cg_fontIconAutocomplete = Dvar::Register("cg_fontIconAutocomplete", true, Game::DVAR_ARCHIVE, "Show autocomplete for fonticons when typing."); - cg_fontIconAutocompleteHint = Dvar::Register("cg_fontIconAutocompleteHint", true, Game::DVAR_ARCHIVE, "Show hint text in autocomplete for fonticons."); - sv_customTextColor = Game::Dvar_RegisterColor("sv_customTextColor", 1, 0.7f, 0, 1, Game::DVAR_CODINFO, "Color for the extended color code."); - - // Initialize font icons when initializing UI - Utils::Hook(0x4B5422, UI_Init_Hk, HOOK_CALL).install()->quick(); - - // Replace vanilla text drawing function with a reimplementation with extensions - Utils::Hook(0x535410, DrawText2D, HOOK_JUMP).install()->quick(); - - // Consider material text icons and font icons when calculating text width - Utils::Hook(0x5056C0, R_TextWidth_Hk, HOOK_JUMP).install()->quick(); - - // Patch ColorIndex - Utils::Hook(0x417770, ColorIndex, HOOK_JUMP).install()->quick(); - - // Add a colorblind mode for team colors - r_colorBlind = Dvar::Register("r_colorBlind", false, Game::DVAR_ARCHIVE, "Use color-blindness-friendly colors"); - // A dark red - g_ColorBlind_EnemyTeam = Game::Dvar_RegisterColor("g_ColorBlind_EnemyTeam", 0.659f, 0.088f, 0.145f, 1, Game::DVAR_ARCHIVE, "Enemy team color for colorblind mode"); - // A bright yellow - g_ColorBlind_MyTeam = Game::Dvar_RegisterColor("g_ColorBlind_MyTeam", 1, 0.859f, 0.125f, 1, Game::DVAR_ARCHIVE, "Ally team color for colorblind mode"); - - // Replace team colors with colorblind team colors when colorblind is enabled - Utils::Hook(0x406530, GetUnpackedColorByNameStub, HOOK_JUMP).install()->quick(); - - // Consider the cursor being inside the color escape sequence when getting the print length for a field - Utils::Hook(0x488CBD, Field_AdjustScroll_PrintLen_Stub, HOOK_CALL).install()->quick(); - - // Draw fonticon autocompletion for say field - Utils::Hook(0x4CA1BD, Field_Draw_Say, HOOK_CALL).install()->quick(); - - // Draw fonticon autocompletion for console field - Utils::Hook(0x5A50A5, Con_DrawInput_Hk, HOOK_CALL).install()->quick(); - Utils::Hook(0x5A50BB, Con_DrawInput_Hk, HOOK_CALL).install()->quick(); - - // Handle key inputs for console and chat - Utils::Hook(0x4F685C, Console_Key_Hk, HOOK_CALL).install()->quick(); - Utils::Hook(0x4F6694, Message_Key_Stub, HOOK_CALL).install()->quick(); - Utils::Hook(0x4F684C, Message_Key_Stub, HOOK_CALL).install()->quick(); + unsigned TextRenderer::colorTableDefault[TEXT_COLOR_COUNT] + { + ColorRgb(0, 0, 0), // TEXT_COLOR_BLACK + ColorRgb(255, 92, 92), // TEXT_COLOR_RED + ColorRgb(0, 255, 0), // TEXT_COLOR_GREEN + ColorRgb(255, 255, 0), // TEXT_COLOR_YELLOW + ColorRgb(0, 0, 255), // TEXT_COLOR_BLUE + ColorRgb(0, 255, 255), // TEXT_COLOR_LIGHT_BLUE + ColorRgb(255, 92, 255), // TEXT_COLOR_PINK + ColorRgb(255, 255, 255), // TEXT_COLOR_DEFAULT + ColorRgb(255, 255, 255), // TEXT_COLOR_AXIS + ColorRgb(255, 255, 255), // TEXT_COLOR_ALLIES + ColorRgb(255, 255, 255), // TEXT_COLOR_RAINBOW + ColorRgb(255, 255, 255), // TEXT_COLOR_SERVER + }; + + unsigned TextRenderer::colorTableNew[TEXT_COLOR_COUNT] + { + ColorRgb(0, 0, 0), // TEXT_COLOR_BLACK + ColorRgb(255, 49, 49), // TEXT_COLOR_RED + ColorRgb(134, 192, 0), // TEXT_COLOR_GREEN + ColorRgb(255, 173, 34), // TEXT_COLOR_YELLOW + ColorRgb(0, 135, 193), // TEXT_COLOR_BLUE + ColorRgb(32, 197, 255), // TEXT_COLOR_LIGHT_BLUE + ColorRgb(151, 80, 221), // TEXT_COLOR_PINK + ColorRgb(255, 255, 255), // TEXT_COLOR_DEFAULT + ColorRgb(255, 255, 255), // TEXT_COLOR_AXIS + ColorRgb(255, 255, 255), // TEXT_COLOR_ALLIES + ColorRgb(255, 255, 255), // TEXT_COLOR_RAINBOW + ColorRgb(255, 255, 255), // TEXT_COLOR_SERVER + }; + + unsigned(*TextRenderer::currentColorTable)[TEXT_COLOR_COUNT]; + TextRenderer::FontIconAutocompleteContext TextRenderer::autocompleteContextArray[FONT_ICON_ACI_COUNT]; + std::map TextRenderer::fontIconLookup; + std::vector TextRenderer::fontIconList; + + TextRenderer::BufferedLocalizedString TextRenderer::stringHintAutoComplete(REFERENCE_HINT_AUTO_COMPLETE, STRING_BUFFER_SIZE_SMALL); + TextRenderer::BufferedLocalizedString TextRenderer::stringHintModifier(REFERENCE_HINT_MODIFIER, STRING_BUFFER_SIZE_SMALL); + TextRenderer::BufferedLocalizedString TextRenderer::stringListHeader(REFERENCE_MODIFIER_LIST_HEADER, STRING_BUFFER_SIZE_SMALL); + TextRenderer::BufferedLocalizedString TextRenderer::stringListFlipHorizontal(REFERENCE_MODIFIER_LIST_FLIP_HORIZONTAL, STRING_BUFFER_SIZE_SMALL); + TextRenderer::BufferedLocalizedString TextRenderer::stringListFlipVertical(REFERENCE_MODIFIER_LIST_FLIP_VERTICAL, STRING_BUFFER_SIZE_SMALL); + TextRenderer::BufferedLocalizedString TextRenderer::stringListBig(REFERENCE_MODIFIER_LIST_BIG, STRING_BUFFER_SIZE_SMALL); + + Dvar::Var TextRenderer::cg_newColors; + Dvar::Var TextRenderer::cg_fontIconAutocomplete; + Dvar::Var TextRenderer::cg_fontIconAutocompleteHint; + Game::dvar_t* TextRenderer::sv_customTextColor; + Dvar::Var TextRenderer::r_colorBlind; + Game::dvar_t* TextRenderer::g_ColorBlind_MyTeam; + Game::dvar_t* TextRenderer::g_ColorBlind_EnemyTeam; + Game::dvar_t** TextRenderer::con_inputBoxColor = reinterpret_cast(0x9FD4BC); + + TextRenderer::BufferedLocalizedString::BufferedLocalizedString(const char* reference, const std::size_t bufferSize) + : stringReference(reference), + stringBuffer(std::make_unique(bufferSize)), + stringBufferSize(bufferSize), + stringWidth{-1} + { + + } + + void TextRenderer::BufferedLocalizedString::Cache() + { + const auto* formattingString = Game::UI_SafeTranslateString(stringReference); + + if (formattingString != nullptr) + { + strncpy_s(stringBuffer.get(), stringBufferSize, formattingString, _TRUNCATE); + for (auto& width : stringWidth) + { + width = -1; + } + } + } + + const char* TextRenderer::BufferedLocalizedString::Format(const char* value) + { + const auto* formattingString = Game::UI_SafeTranslateString(stringReference); + if (formattingString == nullptr) + { + stringBuffer[0] = '\0'; + return stringBuffer.get(); + } + + Game::ConversionArguments conversionArguments{}; + conversionArguments.args[conversionArguments.argCount++] = value; + Game::UI_ReplaceConversions(formattingString, &conversionArguments, stringBuffer.get(), stringBufferSize); + + for (auto& width : stringWidth) + { + width = -1; + } + + return stringBuffer.get(); + } + + const char* TextRenderer::BufferedLocalizedString::GetString() const + { + return stringBuffer.get(); + } + + int TextRenderer::BufferedLocalizedString::GetWidth(const FontIconAutocompleteInstance autocompleteInstance, Game::Font_s* font) + { + assert(autocompleteInstance < FONT_ICON_ACI_COUNT); + if (stringWidth[autocompleteInstance] < 0) + { + stringWidth[autocompleteInstance] = Game::R_TextWidth(GetString(), std::numeric_limits::max(), font); + } + + return stringWidth[autocompleteInstance]; + } + + TextRenderer::FontIconAutocompleteContext::FontIconAutocompleteContext() + : autocompleteActive(false), + inModifiers(false), + userClosed(false), + lastHash(0), + results{}, + resultCount(0), + hasMoreResults(false), + resultOffset(0), + lastResultOffset(0), + selectedOffset(0), + maxFontIconWidth(0.0f), + maxMaterialNameWidth(0.0f), + stringSearchStartWith(REFERENCE_SEARCH_START_WITH, STRING_BUFFER_SIZE_BIG) + { + + } + + unsigned TextRenderer::HsvToRgb(HsvColor hsv) + { + unsigned rgb; + unsigned char region, p, q, t; + unsigned int h, s, v, remainder; + + if (hsv.s == 0) + { + rgb = ColorRgb(hsv.v, hsv.v, hsv.v); + return rgb; + } + + // converting to 16 bit to prevent overflow + h = hsv.h; + s = hsv.s; + v = hsv.v; + + region = static_cast(h / 43); + remainder = (h - (region * 43)) * 6; + + p = static_cast((v * (255 - s)) >> 8); + q = static_cast((v * (255 - ((s * remainder) >> 8))) >> 8); + t = static_cast((v * (255 - ((s * (255 - remainder)) >> 8))) >> 8); + + switch (region) + { + case 0: + rgb = ColorRgb(static_cast(v), t, p); + break; + case 1: + rgb = ColorRgb(q, static_cast(v), p); + break; + case 2: + rgb = ColorRgb(p, static_cast(v), t); + break; + case 3: + rgb = ColorRgb(p, q, static_cast(v)); + break; + case 4: + rgb = ColorRgb(t, p, static_cast(v)); + break; + default: + rgb = ColorRgb(static_cast(v), p, q); + break; + } + + return rgb; + } + + void TextRenderer::DrawAutocompleteBox(const FontIconAutocompleteContext& context, const float x, const float y, const float w, const float h, const float* color) + { + const float borderColor[4] + { + color[0] * 0.5f, + color[1] * 0.5f, + color[2] * 0.5f, + color[3] + }; + + Game::R_AddCmdDrawStretchPic(x, y, w, h, 0.0, 0.0, 0.0, 0.0, color, Game::cls->whiteMaterial); + Game::R_AddCmdDrawStretchPic(x, y, FONT_ICON_AUTOCOMPLETE_BOX_BORDER, h, 0.0, 0.0, 0.0, 0.0, borderColor, Game::cls->whiteMaterial); + Game::R_AddCmdDrawStretchPic(x + w - FONT_ICON_AUTOCOMPLETE_BOX_BORDER, y, FONT_ICON_AUTOCOMPLETE_BOX_BORDER, h, 0.0, 0.0, 0.0, 0.0, borderColor, Game::cls->whiteMaterial); + Game::R_AddCmdDrawStretchPic(x, y, w, FONT_ICON_AUTOCOMPLETE_BOX_BORDER, 0.0, 0.0, 0.0, 0.0, borderColor, Game::cls->whiteMaterial); + Game::R_AddCmdDrawStretchPic(x, y + h - FONT_ICON_AUTOCOMPLETE_BOX_BORDER, w, FONT_ICON_AUTOCOMPLETE_BOX_BORDER, 0.0, 0.0, 0.0, 0.0, borderColor, Game::cls->whiteMaterial); + + if (context.resultOffset > 0) + { + Game::R_AddCmdDrawStretchPic(x + w - FONT_ICON_AUTOCOMPLETE_BOX_BORDER - FONT_ICON_AUTOCOMPLETE_ARROW_SIZE, + y + FONT_ICON_AUTOCOMPLETE_BOX_BORDER, + FONT_ICON_AUTOCOMPLETE_ARROW_SIZE, + FONT_ICON_AUTOCOMPLETE_ARROW_SIZE, + 1.0f, 1.0f, 0.0f, 0.0f, WHITE_COLOR, Game::sharedUiInfo->assets.scrollBarArrowDown); + } + + if (context.hasMoreResults) + { + Game::R_AddCmdDrawStretchPic(x + w - FONT_ICON_AUTOCOMPLETE_BOX_BORDER - FONT_ICON_AUTOCOMPLETE_ARROW_SIZE, + y + h - FONT_ICON_AUTOCOMPLETE_BOX_BORDER - FONT_ICON_AUTOCOMPLETE_ARROW_SIZE, + FONT_ICON_AUTOCOMPLETE_ARROW_SIZE, + FONT_ICON_AUTOCOMPLETE_ARROW_SIZE, + 1.0f, 1.0f, 0.0f, 0.0f, WHITE_COLOR, Game::sharedUiInfo->assets.scrollBarArrowUp); + } + } + + void TextRenderer::UpdateAutocompleteContextResults(FontIconAutocompleteContext& context, Game::Font_s* font, const float textXScale) + { + context.resultCount = 0; + context.hasMoreResults = false; + context.lastResultOffset = context.resultOffset; + + auto skipCount = context.resultOffset; + + const auto queryLen = context.lastQuery.size(); + for (const auto& fontIconEntry : fontIconList) + { + const auto compareValue = fontIconEntry.iconName.compare(0, queryLen, context.lastQuery); + + if (compareValue == 0) + { + if (skipCount > 0) + { + skipCount--; + } + else if (context.resultCount < FontIconAutocompleteContext::MAX_RESULTS) + { + context.results[context.resultCount++] = + { + Utils::String::VA(":%s:", fontIconEntry.iconName.data()), + fontIconEntry.iconName + }; + } + else + { + context.hasMoreResults = true; + } + } + else if (compareValue > 0) + { + break; + } + } + + context.maxFontIconWidth = 0; + context.maxMaterialNameWidth = 0; + for (auto resultIndex = 0u; resultIndex < context.resultCount; resultIndex++) + { + const auto& result = context.results[resultIndex]; + const auto fontIconWidth = static_cast(Game::R_TextWidth(result.fontIconName.c_str(), std::numeric_limits::max(), font)) * textXScale; + const auto materialNameWidth = static_cast(Game::R_TextWidth(result.materialName.c_str(), std::numeric_limits::max(), font)) * textXScale; + + if (fontIconWidth > context.maxFontIconWidth) + context.maxFontIconWidth = fontIconWidth; + if (materialNameWidth > context.maxMaterialNameWidth) + context.maxMaterialNameWidth = materialNameWidth; + } + } + + void TextRenderer::UpdateAutocompleteContext(FontIconAutocompleteContext& context, const Game::field_t* edit, Game::Font_s* font, const float textXScale) + { + int fontIconStart = -1; + auto inModifiers = false; + + for (auto i = 0; i < edit->cursor; i++) + { + const auto c = static_cast(edit->buffer[i]); + if (c == FONT_ICON_SEPARATOR_CHARACTER) + { + if (fontIconStart < 0) + { + fontIconStart = i + 1; + inModifiers = false; + } + else + { + fontIconStart = -1; + inModifiers = false; + } + } + else if (std::isspace(c)) + { + fontIconStart = -1; + inModifiers = false; + } + else if (c == FONT_ICON_MODIFIER_SEPARATOR_CHARACTER) + { + if (fontIconStart >= 0 && !inModifiers) + { + inModifiers = true; + } + else + { + fontIconStart = -1; + inModifiers = false; + } + } + } + + if (fontIconStart < 0 // Not in fonticon sequence + || fontIconStart == edit->cursor // Did not type the first letter yet + || !std::isalpha(static_cast(edit->buffer[fontIconStart])) // First letter of the icon is not alphabetic + || (fontIconStart > 1 && std::isalnum(static_cast(edit->buffer[fontIconStart - 2]))) // Letter before sequence is alnum + ) + { + context.autocompleteActive = false; + context.userClosed = false; + context.lastHash = 0; + context.resultCount = 0; + return; + } + + context.inModifiers = inModifiers; + + // Update scroll + if (context.selectedOffset < context.resultOffset) + { + context.resultOffset = context.selectedOffset; + } + else if(context.selectedOffset >= context.resultOffset + FontIconAutocompleteContext::MAX_RESULTS) + { + context.resultOffset = context.selectedOffset - (FontIconAutocompleteContext::MAX_RESULTS - 1); + } + + // If the user closed the context do not draw or update + if (context.userClosed) + { + return; + } + + context.autocompleteActive = true; + + // No need to update results when in modifiers + if (context.inModifiers) + { + return; + } + + // Check if results need updates + const auto currentFontIconHash = Game::R_HashString(&edit->buffer[fontIconStart], edit->cursor - fontIconStart); + if (currentFontIconHash == context.lastHash && context.lastResultOffset == context.resultOffset) + { + return; + } + + // If query was updated then reset scroll parameters + if (currentFontIconHash != context.lastHash) + { + context.resultOffset = 0; + context.selectedOffset = 0; + context.lastHash = currentFontIconHash; + } + + // Update results for query and scroll and update search string + context.lastQuery = std::string(&edit->buffer[fontIconStart], edit->cursor - fontIconStart); + context.stringSearchStartWith.Format(context.lastQuery.c_str()); + UpdateAutocompleteContextResults(context, font, textXScale); + } + + void TextRenderer::DrawAutocompleteModifiers(const FontIconAutocompleteInstance instance, const float x, const float y, Game::Font_s* font, const float textXScale, const float textYScale) + { + assert(instance < FONT_ICON_ACI_COUNT); + const auto& context = autocompleteContextArray[instance]; + + // Check which is the longest string to be able to calculate how big the box needs to be + const auto longestStringLength = std::max(std::max(std::max(stringListHeader.GetWidth(instance, font), stringListFlipHorizontal.GetWidth(instance, font)), + stringListFlipVertical.GetWidth(instance, font)), + stringListBig.GetWidth(instance, font)); + + // Draw background box + const auto boxWidth = static_cast(longestStringLength) * textXScale; + constexpr auto totalLines = 4u; + const auto lineHeight = static_cast(font->pixelHeight) * textYScale; + DrawAutocompleteBox(context, + x - FONT_ICON_AUTOCOMPLETE_BOX_PADDING, + y - FONT_ICON_AUTOCOMPLETE_BOX_PADDING, + boxWidth + FONT_ICON_AUTOCOMPLETE_BOX_PADDING * 2, + static_cast(totalLines) * lineHeight + FONT_ICON_AUTOCOMPLETE_BOX_PADDING * 2, + (*con_inputBoxColor)->current.vector); + + auto currentY = y + lineHeight; + + // Draw header line: "Following modifiers are available:" + Game::R_AddCmdDrawText(stringListHeader.GetString(), std::numeric_limits::max(), font, x, currentY, textXScale, textYScale, 0.0, TEXT_COLOR, 0); + currentY += lineHeight; + + // Draw modifier hints + Game::R_AddCmdDrawText(stringListFlipHorizontal.GetString(), std::numeric_limits::max(), font, x, currentY, textXScale, textYScale, 0.0, TEXT_COLOR, 0); + currentY += lineHeight; + Game::R_AddCmdDrawText(stringListFlipVertical.GetString(), std::numeric_limits::max(), font, x, currentY, textXScale, textYScale, 0.0, TEXT_COLOR, 0); + currentY += lineHeight; + Game::R_AddCmdDrawText(stringListBig.GetString(), std::numeric_limits::max(), font, x, currentY, textXScale, textYScale, 0.0, TEXT_COLOR, 0); + } + + void TextRenderer::DrawAutocompleteResults(const FontIconAutocompleteInstance instance, const float x, const float y, Game::Font_s* font, const float textXScale, const float textYScale) + { + assert(instance < FONT_ICON_ACI_COUNT); + auto& context = autocompleteContextArray[instance]; + + const auto hintEnabled = cg_fontIconAutocompleteHint.get(); + + // Check which is the longest string to be able to calculate how big the box needs to be + auto longestStringLength = context.stringSearchStartWith.GetWidth(instance, font); + if(hintEnabled) + longestStringLength = std::max(std::max(longestStringLength, stringHintAutoComplete.GetWidth(instance, font)), stringHintModifier.GetWidth(instance, font)); + + const auto colSpacing = FONT_ICON_AUTOCOMPLETE_COL_SPACING * textXScale; + const auto boxWidth = std::max(context.maxFontIconWidth + context.maxMaterialNameWidth + colSpacing, static_cast(longestStringLength) * textXScale); + const auto lineHeight = static_cast(font->pixelHeight) * textYScale; + + // Draw background box + const auto totalLines = 1u + context.resultCount + (hintEnabled ? 2u : 0u); + const auto arrowPadding = context.resultOffset > 0 || context.hasMoreResults ? FONT_ICON_AUTOCOMPLETE_ARROW_SIZE : 0.0f; + DrawAutocompleteBox(context, + x - FONT_ICON_AUTOCOMPLETE_BOX_PADDING, + y - FONT_ICON_AUTOCOMPLETE_BOX_PADDING, + boxWidth + FONT_ICON_AUTOCOMPLETE_BOX_PADDING * 2 + arrowPadding, + static_cast(totalLines) * lineHeight + FONT_ICON_AUTOCOMPLETE_BOX_PADDING * 2, + (*con_inputBoxColor)->current.vector); + + // Draw header line "Search results for: xyz" + auto currentY = y + lineHeight; + Game::R_AddCmdDrawText(context.stringSearchStartWith.GetString(), std::numeric_limits::max(), font, x, currentY, textXScale, textYScale, 0.0, TEXT_COLOR, 0); + currentY += lineHeight; + + // Draw search results + const auto selectedIndex = context.selectedOffset - context.resultOffset; + for (auto resultIndex = 0u; resultIndex < context.resultCount; resultIndex++) + { + const auto& result = context.results[resultIndex]; + Game::R_AddCmdDrawText(result.fontIconName.c_str(), std::numeric_limits::max(), font, x, currentY, textXScale, textYScale, 0.0, TEXT_COLOR, 0); + + if (selectedIndex == resultIndex) + Game::R_AddCmdDrawText(Utils::String::VA("^2%s", result.materialName.c_str()), std::numeric_limits::max(), font, x + context.maxFontIconWidth + colSpacing, currentY, textXScale, textYScale, 0.0, TEXT_COLOR, 0); + else + Game::R_AddCmdDrawText(result.materialName.c_str(), std::numeric_limits::max(), font, x + context.maxFontIconWidth + colSpacing, currentY, textXScale, textYScale, 0.0, TEXT_COLOR, 0); + currentY += lineHeight; + } + + // Draw extra hint if enabled + if (hintEnabled) + { + Game::R_AddCmdDrawText(stringHintAutoComplete.GetString(), std::numeric_limits::max(), font, x, currentY, textXScale, textYScale, 0.0, HINT_COLOR, 0); + currentY += lineHeight; + Game::R_AddCmdDrawText(stringHintModifier.GetString(), std::numeric_limits::max(), font, x, currentY, textXScale, textYScale, 0.0, HINT_COLOR, 0); + } + } + + void TextRenderer::DrawAutocomplete(const FontIconAutocompleteInstance instance, const float x, const float y, Game::Font_s* font, const float textXScale, const float textYScale) + { + assert(instance < FONT_ICON_ACI_COUNT); + const auto& context = autocompleteContextArray[instance]; + + if (context.inModifiers) + DrawAutocompleteModifiers(instance, x, y, font, textXScale, textYScale); + else + DrawAutocompleteResults(instance, x, y, font, textXScale, textYScale); + } + + void TextRenderer::Con_DrawInput_Hk(const int localClientNum) + { + // Call original function + Utils::Hook::Call(0x5A4480)(localClientNum); + + auto& autocompleteContext = autocompleteContextArray[FONT_ICON_ACI_CONSOLE]; + if (cg_fontIconAutocomplete.get() == false) + { + autocompleteContext.autocompleteActive = false; + return; + } + + UpdateAutocompleteContext(autocompleteContext, Game::g_consoleField, Game::cls->consoleFont, 1.0f); + if (autocompleteContext.autocompleteActive) + { + const auto x = Game::conDrawInputGlob->leftX; + const auto y = Game::con_screenMin[1] + 6.0f + static_cast(2 * Game::R_TextHeight(Game::cls->consoleFont)); + DrawAutocomplete(FONT_ICON_ACI_CONSOLE, x, y, Game::cls->consoleFont, 1.0f, 1.0f); + } + } + + void TextRenderer::Field_Draw_Say(const int localClientNum, Game::field_t* edit, const int x, const int y, const int horzAlign, const int vertAlign) + { + Game::Field_Draw(localClientNum, edit, x, y, horzAlign, vertAlign); + + auto& autocompleteContext = autocompleteContextArray[FONT_ICON_ACI_CHAT]; + if (cg_fontIconAutocomplete.get() == false) + { + autocompleteContext.autocompleteActive = false; + return; + } + + auto* screenPlacement = Game::ScrPlace_GetActivePlacement(localClientNum); + const auto scale = edit->charHeight / 48.0f; + auto* font = Game::UI_GetFontHandle(screenPlacement, 0, scale); + const auto normalizedScale = Game::R_NormalizedTextScale(font, scale); + auto xx = static_cast(x); + auto yy = static_cast(y); + yy += static_cast(Game::R_TextHeight(font)) * normalizedScale * 1.5f; + auto ww = normalizedScale; + auto hh = normalizedScale; + Game::ScrPlace_ApplyRect(screenPlacement, &xx, &yy, &ww, &hh, horzAlign, vertAlign); + + UpdateAutocompleteContext(autocompleteContext, edit, font, ww); + if (autocompleteContext.autocompleteActive) + { + DrawAutocomplete(FONT_ICON_ACI_CHAT, std::floor(xx), std::floor(yy), font, ww, hh); + } + } + + void TextRenderer::AutocompleteUp(FontIconAutocompleteContext& context) + { + if (context.selectedOffset > 0) + { + context.selectedOffset--; + } + } + + void TextRenderer::AutocompleteDown(FontIconAutocompleteContext& context) + { + if (context.resultCount < FontIconAutocompleteContext::MAX_RESULTS) + { + if (context.resultCount > 0 && context.selectedOffset < context.resultOffset + context.resultCount - 1) + { + ++context.selectedOffset; + } + } + else if (context.selectedOffset == context.resultOffset + context.resultCount - 1) + { + if (context.hasMoreResults) + { + ++context.selectedOffset; + } + } + else + { + context.selectedOffset++; + } + } + + void TextRenderer::AutocompleteFill(const FontIconAutocompleteContext& context, Game::ScreenPlacement* scrPlace, Game::field_t* edit, const bool closeFontIcon) + { + if (context.selectedOffset >= context.resultOffset + context.resultCount) + return; + + const auto selectedResultIndex = context.selectedOffset - context.resultOffset; + std::string remainingFillData = context.results[selectedResultIndex].materialName.substr(context.lastQuery.size()); + + if (closeFontIcon) + { + remainingFillData += ":"; + } + + const std::string moveData(&edit->buffer[edit->cursor]); + + const auto remainingBufferCharacters = std::extent_v - edit->cursor - moveData.size() - 1; + if (remainingFillData.size() > remainingBufferCharacters) + { + remainingFillData = remainingFillData.erase(remainingBufferCharacters); + } + + if (!remainingFillData.empty()) + { + strncpy(&edit->buffer[edit->cursor], remainingFillData.c_str(), remainingFillData.size()); + strncpy(&edit->buffer[edit->cursor + remainingFillData.size()], moveData.c_str(), moveData.size()); + edit->buffer[std::extent_v - 1] = '\0'; + edit->cursor += static_cast(remainingFillData.size()); + Game::Field_AdjustScroll(scrPlace, edit); + } + } + + bool TextRenderer::AutocompleteHandleKeyDown(FontIconAutocompleteContext& context, const int key, Game::ScreenPlacement* scrPlace, Game::field_t* edit) + { + switch (key) + { + case Game::K_UPARROW: + case Game::K_KP_UPARROW: + AutocompleteUp(context); + return true; + + case Game::K_DOWNARROW: + case Game::K_KP_DOWNARROW: + AutocompleteDown(context); + return true; + + case Game::K_ENTER: + case Game::K_KP_ENTER: + if(context.resultCount > 0) + { + AutocompleteFill(context, scrPlace, edit, true); + return true; + } + return false; + + case Game::K_TAB: + AutocompleteFill(context, scrPlace, edit, false); + return true; + + case Game::K_ESCAPE: + if (!context.userClosed) + { + context.autocompleteActive = false; + context.userClosed = true; + return true; + } + return false; + + default: + return false; + } + } + + bool TextRenderer::HandleFontIconAutocompleteKey(const int localClientNum, const FontIconAutocompleteInstance autocompleteInstance, const int key) + { + assert(autocompleteInstance < FONT_ICON_ACI_COUNT); + if (autocompleteInstance >= FONT_ICON_ACI_COUNT) + return false; + + auto& autocompleteContext = autocompleteContextArray[autocompleteInstance]; + if (!autocompleteContext.autocompleteActive) + return false; + + if (autocompleteInstance == FONT_ICON_ACI_CONSOLE) + return AutocompleteHandleKeyDown(autocompleteContext, key, Game::scrPlaceFull, Game::g_consoleField); + + if (autocompleteInstance == FONT_ICON_ACI_CHAT) + return AutocompleteHandleKeyDown(autocompleteContext, key, &Game::scrPlaceView[localClientNum], &Game::playerKeys[localClientNum].chatField); + + return false; + } + + void TextRenderer::Console_Key_Hk(const int localClientNum, const int key) + { + if (HandleFontIconAutocompleteKey(localClientNum, FONT_ICON_ACI_CONSOLE, key)) + return; + + Utils::Hook::Call(0x4311E0)(localClientNum, key); + } + + bool TextRenderer::ChatHandleKeyDown(const int localClientNum, const int key) + { + return HandleFontIconAutocompleteKey(localClientNum, FONT_ICON_ACI_CHAT, key); + } + + constexpr auto Message_Key = 0x5A7E50; + __declspec(naked) void TextRenderer::Message_Key_Stub() + { + __asm + { + pushad + + push eax + push edi + call ChatHandleKeyDown + add esp, 0x8 + test al,al + jnz skipHandling + + popad + call Message_Key + ret + + skipHandling: + popad + mov al, 1 + ret + } + } + + float TextRenderer::GetMonospaceWidth(Game::Font_s* font, int rendererFlags) + { + if (rendererFlags & Game::TEXT_RENDERFLAG_FORCEMONOSPACE) + { + return Game::R_GetCharacterGlyph(font, 'o')->dx; + } + + return 0.0f; + } + + void TextRenderer::GlowColor(Game::GfxColor* result, const Game::GfxColor baseColor, const Game::GfxColor forcedGlowColor, int renderFlags) + { + if (renderFlags & Game::TEXT_RENDERFLAG_GLOW_FORCE_COLOR) + { + result->array[0] = forcedGlowColor.array[0]; + result->array[1] = forcedGlowColor.array[1]; + result->array[2] = forcedGlowColor.array[2]; + } + else + { + result->array[0] = static_cast(std::floor(static_cast(static_cast(baseColor.array[0])) * 0.06f)); + result->array[1] = static_cast(std::floor(static_cast(static_cast(baseColor.array[1])) * 0.06f)); + result->array[2] = static_cast(std::floor(static_cast(static_cast(baseColor.array[2])) * 0.06f)); + } + } + + unsigned TextRenderer::R_FontGetRandomLetter(const int seed) + { + static constexpr char RANDOM_CHARACTERS[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890"; + return RANDOM_CHARACTERS[seed % (std::extent_v - 1)]; + } + + void TextRenderer::DrawTextFxExtraCharacter(Game::Material* material, const int charIndex, const float x, const float y, const float w, const float h, const float sinAngle, const float cosAngle, const unsigned color) + { + Game::RB_DrawStretchPicRotate(material, x, y, w, h, static_cast(charIndex % 16) * 0.0625f, 0.0f, static_cast(charIndex % 16) * 0.0625f + 0.0625f, 1.0f, sinAngle, cosAngle, color); + } + + Game::GfxImage* TextRenderer::GetFontIconColorMap(const Game::Material* fontIconMaterial) + { + for (auto i = 0u; i < fontIconMaterial->textureCount; i++) + { + if (fontIconMaterial->textureTable[i].nameHash == COLOR_MAP_HASH) + { + return fontIconMaterial->textureTable[i].u.image; + } + } + + return nullptr; + } + + bool TextRenderer::IsFontIcon(const char*& text, FontIconInfo& fontIcon) + { + const auto* curPos = text; + + while (*curPos != ' ' && *curPos != FONT_ICON_SEPARATOR_CHARACTER && *curPos != 0 && *curPos != FONT_ICON_MODIFIER_SEPARATOR_CHARACTER) + curPos++; + + const auto* nameEnd = curPos; + + if (*curPos == FONT_ICON_MODIFIER_SEPARATOR_CHARACTER) + { + auto breakArgs = false; + while (!breakArgs) + { + curPos++; + switch(*curPos) + { + case FONT_ICON_MODIFIER_FLIP_HORIZONTALLY: + fontIcon.flipHorizontal = true; + break; + + case FONT_ICON_MODIFIER_FLIP_VERTICALLY: + fontIcon.flipVertical = true; + break; + + case FONT_ICON_MODIFIER_BIG: + fontIcon.big = true; + break; + + case FONT_ICON_SEPARATOR_CHARACTER: + breakArgs = true; + break; + + default: + return false; + } + } + } + + if (*curPos != FONT_ICON_SEPARATOR_CHARACTER) + { + return false; + } + + const std::string fontIconName(text, nameEnd - text); + + const auto foundFontIcon = fontIconLookup.find(fontIconName); + if (foundFontIcon == fontIconLookup.end()) + { + return false; + } + + auto& entry = foundFontIcon->second; + if (entry.material == nullptr) + { + auto* materialEntry = Game::DB_FindXAssetEntry(Game::XAssetType::ASSET_TYPE_MATERIAL, entry.materialName.data()); + if (materialEntry == nullptr) + return false; + auto* material = materialEntry->asset.header.material; + if (material == nullptr || material->techniqueSet == nullptr || material->techniqueSet->name == nullptr) + return false; + + if (std::strcmp(material->techniqueSet->name, "2d") != 0) + { + Logger::PrintError(Game::CON_CHANNEL_ERROR, "Fonticon material '{}' does not have 2d techset!\n", material->info.name); + material = Game::DB_FindXAssetHeader(Game::ASSET_TYPE_MATERIAL, "default").material; + } + + entry.material = material; + } + + text = curPos + 1; + fontIcon.material = entry.material; + return true; + } + + float TextRenderer::GetNormalizedFontIconWidth(const FontIconInfo& fontIcon) + { + const auto* colorMap = GetFontIconColorMap(fontIcon.material); + if (colorMap == nullptr) + { + return 0.0f; + } + + const auto sizeMultiplier = fontIcon.big ? 1.5f : 1.0f; + auto colWidth = static_cast(colorMap->width); + auto colHeight = static_cast(colorMap->height); + if (fontIcon.material->info.textureAtlasColumnCount > 1) + colWidth /= static_cast(fontIcon.material->info.textureAtlasColumnCount); + if (fontIcon.material->info.textureAtlasRowCount > 1) + colHeight /= static_cast(fontIcon.material->info.textureAtlasRowCount); + return (colWidth / colHeight) * sizeMultiplier; + } + + float TextRenderer::GetFontIconWidth(const FontIconInfo& fontIcon, const Game::Font_s* font, const float xScale) + { + const auto* colorMap = GetFontIconColorMap(fontIcon.material); + if (colorMap == nullptr) + { + return 0.0f; + } + + const auto sizeMultiplier = fontIcon.big ? 1.5f : 1.0f; + auto colWidth = static_cast(colorMap->width); + auto colHeight = static_cast(colorMap->height); + if (fontIcon.material->info.textureAtlasColumnCount > 1) + colWidth /= static_cast(fontIcon.material->info.textureAtlasColumnCount); + if (fontIcon.material->info.textureAtlasRowCount > 1) + colHeight /= static_cast(fontIcon.material->info.textureAtlasRowCount); + return static_cast(font->pixelHeight) * (colWidth / colHeight) * xScale * sizeMultiplier; + } + + float TextRenderer::DrawFontIcon(const FontIconInfo& fontIcon, const float x, const float y, const float sinAngle, const float cosAngle, const Game::Font_s* font, const float xScale, const float yScale, const unsigned color) + { + const auto* colorMap = GetFontIconColorMap(fontIcon.material); + if (colorMap == nullptr) + { + return 0.0f; + } + + float s0, t0, s1, t1; + if (fontIcon.flipHorizontal) + { + s0 = 1.0f; + s1 = 0.0f; + } + else + { + s0 = 0.0f; + s1 = 1.0f; + } + if (fontIcon.flipVertical) + { + t0 = 1.0f; + t1 = 0.0f; + } + else + { + t0 = 0.0f; + t1 = 1.0f; + } + + Game::Material_Process2DTextureCoordsForAtlasing(fontIcon.material, &s0, &s1, &t0, &t1); + const auto sizeMultiplier = fontIcon.big ? 1.5f : 1.0f; + + auto colWidth = static_cast(colorMap->width); + auto colHeight = static_cast(colorMap->height); + if (fontIcon.material->info.textureAtlasColumnCount > 1) + colWidth /= static_cast(fontIcon.material->info.textureAtlasColumnCount); + if (fontIcon.material->info.textureAtlasRowCount > 1) + colHeight /= static_cast(fontIcon.material->info.textureAtlasRowCount); + + const auto h = static_cast(font->pixelHeight) * yScale * sizeMultiplier; + const auto w = static_cast(font->pixelHeight) * (colWidth / colHeight) * xScale * sizeMultiplier; + + const auto yy = y - (h + yScale * static_cast(font->pixelHeight)) * 0.5f; + Game::RB_DrawStretchPicRotate(fontIcon.material, x, yy, w, h, s0, t0, s1, t1, sinAngle, cosAngle, color); + + return w; + } + + float TextRenderer::DrawHudIcon(const char*& text, const float x, const float y, const float sinAngle, const float cosAngle, const Game::Font_s* font, const float xScale, const float yScale, const unsigned color) + { + float s0, s1, t0, t1; + + if (*text == '\x01') + { + s0 = 0.0; + t0 = 0.0; + s1 = 1.0; + t1 = 1.0; + } + else + { + s0 = 1.0; + t0 = 0.0; + s1 = 0.0; + t1 = 1.0; + } + + ++text; + + if (*text == 0) + { + return 0.0f; + } + + const auto v12 = font->pixelHeight * (*text - 16) + 16; + const auto w = static_cast((((v12 >> 24) & 0x1F) + v12) >> 5) * xScale; + ++text; + + if (*text == 0) + { + return 0.0f; + } + + const auto h = static_cast((font->pixelHeight * (*text - 16) + 16) >> 5) * yScale; + ++text; + + if (*text == 0) + { + return 0.0f; + } + + const auto materialNameLen = static_cast(*text); + text++; + + for (auto i = 0u; i < materialNameLen; i++) + { + if (text[i] == 0) + { + return 0.0f; + } + } + + const std::string materialName(text, materialNameLen); + text += materialNameLen; + + auto* material = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_MATERIAL, materialName.data()).material; + if (material == nullptr || material->techniqueSet == nullptr || material->techniqueSet->name == nullptr || strcmp(material->techniqueSet->name, "2d") != 0) + material = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_MATERIAL, "default").material; + + const auto yy = y - (h + yScale * static_cast(font->pixelHeight)) * 0.5f; + + Game::RB_DrawStretchPicRotate(material, x, yy, w, h, s0, t0, s1, t1, sinAngle, cosAngle, color); + + return w; + } + + void TextRenderer::RotateXY(const float cosAngle, const float sinAngle, const float pivotX, const float pivotY, const float x, const float y, float* outX, float* outY) + { + *outX = (x - pivotX) * cosAngle + pivotX - (y - pivotY) * sinAngle; + *outY = (y - pivotY) * cosAngle + pivotY + (x - pivotX) * sinAngle; + } + + void TextRenderer::DrawText2D(const char* text, float x, float y, Game::Font_s* font, float xScale, float yScale, float sinAngle, float cosAngle, Game::GfxColor color, int maxLength, int renderFlags, int cursorPos, char cursorLetter, float padding, Game::GfxColor glowForcedColor, int fxBirthTime, int fxLetterTime, int fxDecayStartTime, int fxDecayDuration, Game::Material* fxMaterial, Game::Material* fxMaterialGlow) + { + UpdateColorTable(); + + Game::GfxColor dropShadowColor{0}; + dropShadowColor.array[3] = color.array[3]; + + int randSeed = 1; + bool drawRandomCharAtEnd = false; + const auto forceMonospace = renderFlags & Game::TEXT_RENDERFLAG_FORCEMONOSPACE; + const auto monospaceWidth = GetMonospaceWidth(font, renderFlags); + auto* material = font->material; + Game::Material* glowMaterial = nullptr; + + bool decaying; + int decayTimeElapsed; + if(renderFlags & Game::TEXT_RENDERFLAG_FX_DECODE) + { + if (!Game::SetupPulseFXVars(text, maxLength, fxBirthTime, fxLetterTime, fxDecayStartTime, fxDecayDuration, &drawRandomCharAtEnd, &randSeed, &maxLength, &decaying, &decayTimeElapsed)) + return; + } + else + { + drawRandomCharAtEnd = false; + randSeed = 1; + decaying = false; + decayTimeElapsed = 0; + } + + Game::FontPassType passes[Game::FONTPASS_COUNT]; + unsigned passCount = 0; + + if(renderFlags & Game::TEXT_RENDERFLAG_OUTLINE) + { + if(renderFlags & Game::TEXT_RENDERFLAG_GLOW) + { + glowMaterial = font->glowMaterial; + passes[passCount++] = Game::FONTPASS_GLOW; + } + + passes[passCount++] = Game::FONTPASS_OUTLINE; + passes[passCount++] = Game::FONTPASS_NORMAL; + } + else + { + passes[passCount++] = Game::FONTPASS_NORMAL; + + if (renderFlags & Game::TEXT_RENDERFLAG_GLOW) + { + glowMaterial = font->glowMaterial; + passes[passCount++] = Game::FONTPASS_GLOW; + } + } + + const auto startX = x - xScale * 0.5f; + const auto startY = y - 0.5f * yScale; + + for (auto passIndex = 0u; passIndex < passCount; passIndex++) + { + float xRot, yRot; + const char* curText = text; + auto maxLengthRemaining = maxLength; + auto currentColor = color; + auto subtitleAllowGlow = false; + auto extraFxChar = 0; + auto drawExtraFxChar = false; + auto passRandSeed = randSeed; + auto count = 0; + auto xa = startX; + auto xy = startY; + + while (*curText && maxLengthRemaining) + { + if (passes[passIndex] == Game::FONTPASS_NORMAL && renderFlags & Game::TEXT_RENDERFLAG_CURSOR && count == cursorPos) + { + RotateXY(cosAngle, sinAngle, startX, startY, xa, xy, &xRot, &yRot); + Game::RB_DrawCursor(material, cursorLetter, xRot, yRot, sinAngle, cosAngle, font, xScale, yScale, color.packed); + } + + auto letter = Game::SEH_ReadCharFromString(&curText, nullptr); + + if (letter == '^' && *curText >= COLOR_FIRST_CHAR && *curText <= COLOR_LAST_CHAR) + { + const auto colorIndex = ColorIndexForChar(*curText); + subtitleAllowGlow = false; + if (colorIndex == TEXT_COLOR_DEFAULT) + { + currentColor = color; + } + else if (renderFlags & Game::TEXT_RENDERFLAG_SUBTITLETEXT && colorIndex == TEXT_COLOR_GREEN) + { + constexpr Game::GfxColor altColor{ MY_ALTCOLOR_TWO }; + subtitleAllowGlow = true; + // Swap r and b for whatever reason + currentColor.packed = ColorRgba(altColor.array[2], altColor.array[1], altColor.array[0], Game::ModulateByteColors(altColor.array[3], color.array[3])); + } + else + { + const Game::GfxColor colorTableColor{ (*currentColorTable)[colorIndex] }; + // Swap r and b for whatever reason + currentColor.packed = ColorRgba(colorTableColor.array[2], colorTableColor.array[1], colorTableColor.array[0], color.array[3]); + } + + if (!(renderFlags & Game::TEXT_RENDERFLAG_CURSOR && cursorPos > count && cursorPos < count + 2)) + { + curText++; + count += 2; + continue; + } + } + + auto finalColor = currentColor; + + if (letter == '^' && (*curText == '\x01' || *curText == '\x02')) + { + RotateXY(cosAngle, sinAngle, startX, startY, xa, xy, &xRot, &yRot); + xa += DrawHudIcon(curText, xRot, yRot, sinAngle, cosAngle, font, xScale, yScale, ColorRgba(255, 255, 255, finalColor.array[3])); + + if (renderFlags & Game::TEXT_RENDERFLAG_PADDING) + xa += xScale * padding; + ++count; + maxLengthRemaining--; + continue; + } + + if (letter == FONT_ICON_SEPARATOR_CHARACTER) + { + FontIconInfo fontIconInfo{}; + const char* fontIconEnd = curText; + if (IsFontIcon(fontIconEnd, fontIconInfo) && !(renderFlags & Game::TEXT_RENDERFLAG_CURSOR && cursorPos > count && cursorPos <= count + (fontIconEnd - curText))) + { + RotateXY(cosAngle, sinAngle, startX, startY, xa, xy, &xRot, &yRot); + + if(passes[passIndex] == Game::FONTPASS_NORMAL) + xa += DrawFontIcon(fontIconInfo, xRot, yRot, sinAngle, cosAngle, font, xScale, yScale, ColorRgba(255, 255, 255, finalColor.array[3])); + else + xa += GetFontIconWidth(fontIconInfo, font, xScale); + + if (renderFlags & Game::TEXT_RENDERFLAG_PADDING) + xa += xScale * padding; + count += (fontIconEnd - curText) + 1; + maxLengthRemaining--; + curText = fontIconEnd; + continue; + } + } + + if (drawRandomCharAtEnd && maxLengthRemaining == 1) + { + letter = R_FontGetRandomLetter(Game::RandWithSeed(&passRandSeed)); + + if(Game::RandWithSeed(&passRandSeed) % 2) + { + drawExtraFxChar = true; + letter = 'O'; + } + } + + if (letter == '\n') + { + xa = startX; + xy += static_cast(font->pixelHeight) * yScale; + continue; + } + + if (letter == '\r') + { + xy += static_cast(font->pixelHeight) * yScale; + continue; + } + + auto skipDrawing = false; + if (decaying) + { + char decayAlpha; + Game::GetDecayingLetterInfo(letter, &passRandSeed, decayTimeElapsed, fxBirthTime, fxDecayDuration, currentColor.array[3], &skipDrawing, &decayAlpha, &letter, &drawExtraFxChar); + finalColor.array[3] = decayAlpha; + } + + if (drawExtraFxChar) + { + auto tempSeed = passRandSeed; + extraFxChar = Game::RandWithSeed(&tempSeed); + } + + auto glyph = Game::R_GetCharacterGlyph(font, letter); + auto xAdj = static_cast(glyph->x0) * xScale; + auto yAdj = static_cast(glyph->y0) * yScale; + + if (!skipDrawing) + { + if (passes[passIndex] == Game::FONTPASS_NORMAL) + { + if (renderFlags & Game::TEXT_RENDERFLAG_DROPSHADOW) + { + auto ofs = 1.0f; + if (renderFlags & Game::TEXT_RENDERFLAG_DROPSHADOW_EXTRA) + ofs += 1.0f; + + xRot = xa + xAdj + ofs; + yRot = xy + yAdj + ofs; + RotateXY(cosAngle, sinAngle, startX, startY, xRot, yRot, &xRot, &yRot); + if (drawExtraFxChar) + DrawTextFxExtraCharacter(fxMaterial, extraFxChar, xRot, yRot, static_cast(glyph->pixelWidth) * xScale, static_cast(glyph->pixelHeight) * yScale, sinAngle, cosAngle, dropShadowColor.packed); + else + Game::RB_DrawChar(material, xRot, yRot, static_cast(glyph->pixelWidth) * xScale, static_cast(glyph->pixelHeight) * yScale, sinAngle, cosAngle, glyph, dropShadowColor.packed); + } + + RotateXY(cosAngle, sinAngle, startX, startY, xa + xAdj, xy + yAdj, &xRot, &yRot); + if (drawExtraFxChar) + DrawTextFxExtraCharacter(fxMaterial, extraFxChar, xRot, yRot, static_cast(glyph->pixelWidth) * xScale, static_cast(glyph->pixelHeight) * yScale, sinAngle, cosAngle, finalColor.packed); + else + Game::RB_DrawChar(material, xRot, yRot, static_cast(glyph->pixelWidth) * xScale, static_cast(glyph->pixelHeight) * yScale, sinAngle, cosAngle, glyph, finalColor.packed); + } + else if (passes[passIndex] == Game::FONTPASS_OUTLINE) + { + auto outlineSize = 1.0f; + if (renderFlags & Game::TEXT_RENDERFLAG_OUTLINE_EXTRA) + outlineSize = 1.3f; + + for (const auto offset : MY_OFFSETS) + { + RotateXY(cosAngle, sinAngle, startX, startY, xa + xAdj + outlineSize * offset[0], xy + yAdj + outlineSize * offset[1], &xRot, &yRot); + if (drawExtraFxChar) + DrawTextFxExtraCharacter(fxMaterial, extraFxChar, xRot, yRot, static_cast(glyph->pixelWidth) * xScale, static_cast(glyph->pixelHeight) * yScale, sinAngle, cosAngle, dropShadowColor.packed); + else + Game::RB_DrawChar(material, xRot, yRot, static_cast(glyph->pixelWidth) * xScale, static_cast(glyph->pixelHeight) * yScale, sinAngle, cosAngle, glyph, dropShadowColor.packed); + } + } + else if(passes[passIndex] == Game::FONTPASS_GLOW && ((renderFlags & Game::TEXT_RENDERFLAG_SUBTITLETEXT) == 0 || subtitleAllowGlow)) + { + GlowColor(&finalColor, finalColor, glowForcedColor, renderFlags); + + for (const auto offset : MY_OFFSETS) + { + RotateXY(cosAngle, sinAngle, startX, startY, xa + xAdj + 2.0f * offset[0] * xScale, xy + yAdj + 2.0f * offset[1] * yScale, &xRot, &yRot); + if (drawExtraFxChar) + DrawTextFxExtraCharacter(fxMaterialGlow, extraFxChar, xRot, yRot, static_cast(glyph->pixelWidth) * xScale, static_cast(glyph->pixelHeight) * yScale, sinAngle, cosAngle, finalColor.packed); + else + Game::RB_DrawChar(glowMaterial, xRot, yRot, static_cast(glyph->pixelWidth) * xScale, static_cast(glyph->pixelHeight) * yScale, sinAngle, cosAngle, glyph, finalColor.packed); + } + } + } + + if (forceMonospace) + xa += monospaceWidth * xScale; + else + xa += static_cast(glyph->dx) * xScale; + + if (renderFlags & Game::TEXT_RENDERFLAG_PADDING) + xa += xScale * padding; + + count++; + maxLengthRemaining--; + } + + if (renderFlags & Game::TEXT_RENDERFLAG_CURSOR && count == cursorPos) + { + RotateXY(cosAngle, sinAngle, startX, startY, xa, xy, &xRot, &yRot); + Game::RB_DrawCursor(material, cursorLetter, xRot, yRot, sinAngle, cosAngle, font, xScale, yScale, color.packed); + } + } + } + + int TextRenderer::R_TextWidth_Hk(const char* text, int maxChars, Game::Font_s* font) + { + auto lineWidth = 0; + auto maxWidth = 0; + + if (maxChars <= 0) + maxChars = std::numeric_limits::max(); + + if (text == nullptr) + return 0; + + auto count = 0; + while (text && *text && count < maxChars) + { + const auto letter = Game::SEH_ReadCharFromString(&text, nullptr); + if (letter == '\r' || letter == '\n') + { + lineWidth = 0; + } + else + { + if (letter == '^' && text) + { + if (*text >= COLOR_FIRST_CHAR && *text <= COLOR_LAST_CHAR) + { + text++; + continue; + } + + if (*text >= '\x01' && *text <= '\x02' && text[1] != '\0' && text[2] != '\0' && text[3] != '\0') + { + const auto width = text[1]; + const auto materialNameLength = text[3]; + + // This is how the game calculates width and height. Probably some 1 byte floating point number. + // Details to be investigated if necessary. + const auto v9 = font->pixelHeight * (width - 16) + 16; + const auto w = ((((v9 >> 24) & 0x1F) + v9) >> 5); + + lineWidth += w; + if (lineWidth > maxWidth) + { + maxWidth = lineWidth; + } + + text += 4; + for (auto currentLength = 0; currentLength < materialNameLength && *text; currentLength++) + { + ++text; + } + continue; + } + } + + if (letter == FONT_ICON_SEPARATOR_CHARACTER) + { + FontIconInfo fontIconInfo{}; + const char* fontIconEnd = text; + if (IsFontIcon(fontIconEnd, fontIconInfo)) + { + lineWidth += static_cast(GetFontIconWidth(fontIconInfo, font, 1.0f)); + if (lineWidth > maxWidth) + { + maxWidth = lineWidth; + } + text = fontIconEnd; + continue; + } + } + + lineWidth += R_GetCharacterGlyph(font, letter)->dx; + if (lineWidth > maxWidth) + { + maxWidth = lineWidth; + } + count++; + } + } + + return maxWidth; + } + + unsigned int TextRenderer::ColorIndex(const char index) + { + auto result = index - '0'; + if (static_cast(result) >= TEXT_COLOR_COUNT || result < 0) result = 7; + return result; + } + + void TextRenderer::StripColors(const char* in, char* out, std::size_t max) + { + if (!in || !out) return; + + max--; + size_t current = 0; + while (*in != 0 && current < max) + { + const char index = *(in + 1); + if (*in == '^' && (ColorIndex(index) != 7 || index == '7')) + { + ++in; + } + else + { + *out = *in; + ++out; + ++current; + } + + ++in; + } + *out = '\0'; + } + + std::string TextRenderer::StripColors(const std::string& in) + { + char buffer[1024]{}; // 1024 is a lucky number in the engine + StripColors(in.data(), buffer, sizeof(buffer)); + return {buffer}; + } + + void TextRenderer::StripMaterialTextIcons(const char* in, char* out, std::size_t max) + { + if (!in || !out) return; + + --max; + std::size_t current = 0; + while (*in != 0 && current < max) + { + if (*in == '^' && (in[1] == '\x01' || in[1] == '\x02')) + { + in += 2; + + if (*in) // width + { + ++in; + } + + if (*in) // height + { + ++in; + } + + if (*in) // material name length + material name characters + { + const auto materialNameLength = *in; + ++in; + for (auto i = 0; i < materialNameLength; i++) + { + if (*in) + { + ++in; + } + } + } + } + else + { + *out = *in; + ++out; + ++current; + ++in; + } + + } + *out = '\0'; + } + + std::string TextRenderer::StripMaterialTextIcons(const std::string& in) + { + char buffer[1000]{}; // Should be more than enough + StripAllTextIcons(in.data(), buffer, sizeof(buffer)); + return {buffer}; + } + + void TextRenderer::StripAllTextIcons(const char* in, char* out, std::size_t max) + { + if (!in || !out) return; + + --max; + size_t current = 0; + while (*in != 0 && current < max) + { + if (*in == '^' && (in[1] == '\x01' || in[1] == '\x02')) + { + in += 2; + + if (*in) // width + in++; + if (*in) // height + in++; + + if (*in) // material name length + material name characters + { + const auto materialNameLength = *in; + in++; + for (auto i = 0; i < materialNameLength; i++) + { + if (*in) + in++; + } + } + + continue; + } + + if (*in == FONT_ICON_SEPARATOR_CHARACTER) + { + const auto* fontIconEndPos = &in[1]; + FontIconInfo fontIcon{}; + if(IsFontIcon(fontIconEndPos, fontIcon)) + { + in = fontIconEndPos; + continue; + } + } + + *out = *in; + ++out; + ++current; + ++in; + } + + *out = '\0'; + } + + std::string TextRenderer::StripAllTextIcons(const std::string& in) + { + char buffer[1000]{}; // Should be more than enough + StripAllTextIcons(in.data(), buffer, sizeof(buffer)); + return {buffer}; + } + + int TextRenderer::SEH_PrintStrlenWithCursor(const char* string, const Game::field_t* field) + { + if (!string) + { + return 0; + } + + const auto cursorPos = field->cursor; + auto len = 0; + auto lenWithInvisibleTail = 0; + auto count = 0; + const auto* curText = string; + while(*curText) + { + const auto c = Game::SEH_ReadCharFromString(&curText, nullptr); + lenWithInvisibleTail = len; + + if (c == '^' && *curText >= COLOR_FIRST_CHAR && *curText <= COLOR_LAST_CHAR && !(cursorPos > count && cursorPos < count + 2)) + { + curText++; + count++; + } + else if(c != '\r' && c != '\n') + { + len++; + } + + count++; + lenWithInvisibleTail++; + } + + return lenWithInvisibleTail; + } + + __declspec(naked) void TextRenderer::Field_AdjustScroll_PrintLen_Stub() + { + __asm + { + push eax + pushad + + push esi + push [esp + 0x8 + 0x24] + call SEH_PrintStrlenWithCursor + add esp, 0x8 + mov [esp + 0x20], eax + + popad + pop eax + ret + } + } + + void TextRenderer::PatchColorLimit(const char limit) + { + Utils::Hook::Set(0x535629, limit); // DrawText2d + Utils::Hook::Set(0x4C1BE4, limit); // SEH_PrintStrlen + Utils::Hook::Set(0x4863DD, limit); // No idea :P + Utils::Hook::Set(0x486429, limit); // No idea :P + Utils::Hook::Set(0x49A5A8, limit); // No idea :P + Utils::Hook::Set(0x505721, limit); // R_TextWidth + Utils::Hook::Set(0x505801, limit); // No idea :P + Utils::Hook::Set(0x50597F, limit); // No idea :P + Utils::Hook::Set(0x5815DB, limit); // No idea :P + Utils::Hook::Set(0x592ED0, limit); // No idea :P + Utils::Hook::Set(0x5A2E2E, limit); // No idea :P + + Utils::Hook::Set(0x5A2733, static_cast(ColorIndexForChar(limit))); // No idea :P + } + + // Patches team overhead normally + bool TextRenderer::Dvar_GetUnpackedColorByName(const char* name, float* expandedColor) + { + if (r_colorBlind.get()) + { + const auto str = std::string(name); + if (str == "g_TeamColor_EnemyTeam") + { + // Dvar_GetUnpackedColor + const auto* colorblindEnemy = g_ColorBlind_EnemyTeam->current.color; + expandedColor[0] = static_cast(colorblindEnemy[0]) / 255.0f; + expandedColor[1] = static_cast(colorblindEnemy[1]) / 255.0f; + expandedColor[2] = static_cast(colorblindEnemy[2]) / 255.0f; + expandedColor[3] = static_cast(colorblindEnemy[3]) / 255.0f; + return false; + } + + if (str == "g_TeamColor_MyTeam") + { + // Dvar_GetUnpackedColor + const auto* colorblindAlly = g_ColorBlind_MyTeam->current.color; + expandedColor[0] = static_cast(colorblindAlly[0]) / 255.0f; + expandedColor[1] = static_cast(colorblindAlly[1]) / 255.0f; + expandedColor[2] = static_cast(colorblindAlly[2]) / 255.0f; + expandedColor[3] = static_cast(colorblindAlly[3]) / 255.0f; + return false; + } + } + + return true; + } + + __declspec(naked) void TextRenderer::GetUnpackedColorByNameStub() + { + __asm + { + push[esp + 8h] + push[esp + 8h] + call TextRenderer::Dvar_GetUnpackedColorByName + add esp, 8h + + test al, al + jnz continue + + retn + + continue: + push edi + mov edi, [esp + 8h] + push 406535h + retn + } + } + + void TextRenderer::UpdateColorTable() + { + if (cg_newColors.get()) + currentColorTable = &colorTableNew; + else + currentColorTable = &colorTableDefault; + + (*currentColorTable)[TEXT_COLOR_AXIS] = *reinterpret_cast(0x66E5F70); + (*currentColorTable)[TEXT_COLOR_ALLIES] = *reinterpret_cast(0x66E5F74); + (*currentColorTable)[TEXT_COLOR_RAINBOW] = HsvToRgb({ static_cast((Game::Sys_Milliseconds() / 200) % 256), 255,255 }); + (*currentColorTable)[TEXT_COLOR_SERVER] = sv_customTextColor->current.unsignedInt; + } + + void TextRenderer::InitFontIconStrings() + { + stringHintAutoComplete.Format("TAB"); + stringHintModifier.Format(Utils::String::VA("%c", FONT_ICON_MODIFIER_SEPARATOR_CHARACTER)); + stringListHeader.Cache(); + stringListFlipHorizontal.Format(Utils::String::VA("%c", FONT_ICON_MODIFIER_FLIP_HORIZONTALLY)); + stringListFlipVertical.Format(Utils::String::VA("%c", FONT_ICON_MODIFIER_FLIP_VERTICALLY)); + stringListBig.Format(Utils::String::VA("%c", FONT_ICON_MODIFIER_BIG)); + } + + void TextRenderer::InitFontIcons() + { + InitFontIconStrings(); + + fontIconList.clear(); + fontIconLookup.clear(); + + const auto fontIconTable = Game::DB_FindXAssetHeader(Game::ASSET_TYPE_STRINGTABLE, "mp/fonticons.csv").stringTable; + + if (fontIconTable->columnCount < 2 || fontIconTable->rowCount <= 0) + { + Logger::Error(Game::ERR_FATAL, "\x15" "Failed to load mp/fonticons.csv"); + return; + } + + fontIconList.reserve(fontIconTable->rowCount); + for (auto rowIndex = 0; rowIndex < fontIconTable->rowCount; rowIndex++) + { + const auto* columns = &fontIconTable->values[rowIndex * fontIconTable->columnCount]; + + if(columns[0].string == nullptr || columns[1].string == nullptr) + continue; + + if (columns[0].string[0] == '\0' || columns[1].string[1] == '\0') + continue; + + if (columns[0].string[0] == '#') + continue; + + FontIconTableEntry entry + { + columns[0].string, + columns[1].string, + nullptr + }; + + fontIconList.emplace_back(entry); + fontIconLookup.emplace(std::make_pair(entry.iconName, entry)); + } + + std::sort(fontIconList.begin(), fontIconList.end(), [](const FontIconTableEntry& a, const FontIconTableEntry& b) + { + return a.iconName < b.iconName; + }); + } + + void TextRenderer::UI_Init_Hk(const int localClientNum) + { + // Call original method + Utils::Hook::Call(0x4A57D0)(localClientNum); + + InitFontIcons(); + } + + TextRenderer::TextRenderer() + { + currentColorTable = &colorTableDefault; + + cg_newColors = Dvar::Register("cg_newColors", true, Game::DVAR_ARCHIVE, "Use Warfare 2 color code style."); + cg_fontIconAutocomplete = Dvar::Register("cg_fontIconAutocomplete", true, Game::DVAR_ARCHIVE, "Show autocomplete for fonticons when typing."); + cg_fontIconAutocompleteHint = Dvar::Register("cg_fontIconAutocompleteHint", true, Game::DVAR_ARCHIVE, "Show hint text in autocomplete for fonticons."); + sv_customTextColor = Game::Dvar_RegisterColor("sv_customTextColor", 1, 0.7f, 0, 1, Game::DVAR_CODINFO, "Color for the extended color code."); + + // Initialize font icons when initializing UI + Utils::Hook(0x4B5422, UI_Init_Hk, HOOK_CALL).install()->quick(); + + // Replace vanilla text drawing function with a reimplementation with extensions + Utils::Hook(0x535410, DrawText2D, HOOK_JUMP).install()->quick(); + + // Consider material text icons and font icons when calculating text width + Utils::Hook(0x5056C0, R_TextWidth_Hk, HOOK_JUMP).install()->quick(); + + // Patch ColorIndex + Utils::Hook(0x417770, ColorIndex, HOOK_JUMP).install()->quick(); + + // Add a colorblind mode for team colors + r_colorBlind = Dvar::Register("r_colorBlind", false, Game::DVAR_ARCHIVE, "Use color-blindness-friendly colors"); + // A dark red + g_ColorBlind_EnemyTeam = Game::Dvar_RegisterColor("g_ColorBlind_EnemyTeam", 0.659f, 0.088f, 0.145f, 1, Game::DVAR_ARCHIVE, "Enemy team color for colorblind mode"); + // A bright yellow + g_ColorBlind_MyTeam = Game::Dvar_RegisterColor("g_ColorBlind_MyTeam", 1, 0.859f, 0.125f, 1, Game::DVAR_ARCHIVE, "Ally team color for colorblind mode"); + + // Replace team colors with colorblind team colors when colorblind is enabled + Utils::Hook(0x406530, GetUnpackedColorByNameStub, HOOK_JUMP).install()->quick(); + + // Consider the cursor being inside the color escape sequence when getting the print length for a field + Utils::Hook(0x488CBD, Field_AdjustScroll_PrintLen_Stub, HOOK_CALL).install()->quick(); + + // Draw fonticon autocompletion for say field + Utils::Hook(0x4CA1BD, Field_Draw_Say, HOOK_CALL).install()->quick(); + + // Draw fonticon autocompletion for console field + Utils::Hook(0x5A50A5, Con_DrawInput_Hk, HOOK_CALL).install()->quick(); + Utils::Hook(0x5A50BB, Con_DrawInput_Hk, HOOK_CALL).install()->quick(); + + // Handle key inputs for console and chat + Utils::Hook(0x4F685C, Console_Key_Hk, HOOK_CALL).install()->quick(); + Utils::Hook(0x4F6694, Message_Key_Stub, HOOK_CALL).install()->quick(); + Utils::Hook(0x4F684C, Message_Key_Stub, HOOK_CALL).install()->quick(); - PatchColorLimit(COLOR_LAST_CHAR); - } + PatchColorLimit(COLOR_LAST_CHAR); + } } diff --git a/src/Components/Modules/TextRenderer.hpp b/src/Components/Modules/TextRenderer.hpp index 29bfc725..b45c09d8 100644 --- a/src/Components/Modules/TextRenderer.hpp +++ b/src/Components/Modules/TextRenderer.hpp @@ -2,7 +2,7 @@ namespace Components { - enum TextColor + enum TextColor : int { TEXT_COLOR_BLACK = 0, TEXT_COLOR_RED = 1, @@ -42,6 +42,7 @@ namespace Components class TextRenderer : public Component { + public: static constexpr auto STRING_BUFFER_SIZE_BIG = 1024; static constexpr auto STRING_BUFFER_SIZE_SMALL = 128; @@ -88,7 +89,6 @@ namespace Components 1.0f }; - public: static constexpr char FONT_ICON_SEPARATOR_CHARACTER = ':'; static constexpr char FONT_ICON_MODIFIER_SEPARATOR_CHARACTER = '+'; static constexpr char FONT_ICON_MODIFIER_FLIP_HORIZONTALLY = 'h'; @@ -139,7 +139,7 @@ namespace Components class BufferedLocalizedString { public: - BufferedLocalizedString(const char* reference, size_t bufferSize); + BufferedLocalizedString(const char* reference, std::size_t bufferSize); void Cache(); const char* Format(const char* value); const char* GetString() const; @@ -148,7 +148,7 @@ namespace Components private: const char* stringReference; std::unique_ptr stringBuffer; - size_t stringBufferSize; + std::size_t stringBufferSize; int stringWidth[FONT_ICON_ACI_COUNT]; }; diff --git a/src/Components/Modules/Theatre.cpp b/src/Components/Modules/Theatre.cpp index a48a5b07..4d89eb19 100644 --- a/src/Components/Modules/Theatre.cpp +++ b/src/Components/Modules/Theatre.cpp @@ -1,4 +1,5 @@ #include + #include "Theatre.hpp" #include "UIFeeder.hpp" @@ -8,10 +9,25 @@ namespace Components unsigned int Theatre::CurrentSelection; std::vector Theatre::Demos; + Dvar::Var Theatre::CLAutoRecord; + Dvar::Var Theatre::CLDemosKeep; + char Theatre::BaselineSnapshot[131072] = {0}; int Theatre::BaselineSnapshotMsgLen; int Theatre::BaselineSnapshotMsgOff; + nlohmann::json Theatre::DemoInfo::to_json() const + { + return nlohmann::json + { + { "mapname", mapname }, + { "gametype", gametype }, + { "author", author }, + { "length", length }, + { "timestamp", std::to_string(timeStamp) }, + }; + } + void Theatre::GamestateWriteStub(Game::msg_t* msg, char byte) { Game::MSG_WriteLong(msg, 0); @@ -20,8 +36,8 @@ namespace Components void Theatre::RecordGamestateStub() { - const auto sequence = (*Game::serverMessageSequence - 1); - Game::FS_WriteToDemo(&sequence, 4, *Game::demoFile); + const auto sequence = (Game::clientConnections->serverMessageSequence - 1); + Game::FS_WriteToDemo(&sequence, 4, Game::clientConnections->demofile); } void Theatre::StoreBaseline(PBYTE snapshotMsg) @@ -54,7 +70,7 @@ namespace Components Game::msg_t buf; - Game::MSG_Init(&buf, bufData, 131072); + Game::MSG_Init(&buf, bufData, sizeof(bufData)); Game::MSG_WriteData(&buf, &BaselineSnapshot[BaselineSnapshotMsgOff], BaselineSnapshotMsgLen - BaselineSnapshotMsgOff); Game::MSG_WriteByte(&buf, 6); @@ -62,12 +78,12 @@ namespace Components const auto fileCompressedSize = compressedSize + 4; int byte8 = 8; - char byte0 = 0; + unsigned char byte0 = 0; - Game::FS_WriteToDemo(&byte0, 1, *Game::demoFile); - Game::FS_WriteToDemo(Game::serverMessageSequence, 4, *Game::demoFile); - Game::FS_WriteToDemo(&fileCompressedSize, 4, *Game::demoFile); - Game::FS_WriteToDemo(&byte8, 4, *Game::demoFile); + Game::FS_WriteToDemo(&byte0, sizeof(unsigned char), Game::clientConnections->demofile); + Game::FS_WriteToDemo(&Game::clientConnections->serverMessageSequence, sizeof(int), Game::clientConnections->demofile); + Game::FS_WriteToDemo(&fileCompressedSize, sizeof(int), Game::clientConnections->demofile); + Game::FS_WriteToDemo(&byte8, sizeof(int), Game::clientConnections->demofile); for (auto i = 0; i < compressedSize; i += 1024) { @@ -79,7 +95,7 @@ namespace Components break; } - Game::FS_WriteToDemo(&cmpData[i], size, *Game::demoFile); + Game::FS_WriteToDemo(&cmpData[i], size, Game::clientConnections->demofile); } } @@ -105,7 +121,7 @@ namespace Components { __asm { - mov eax, Game::demoPlaying + mov eax, 0xA5EA0C // clientConnections.demoplaying mov eax, [eax] test al, al jz continue @@ -123,7 +139,7 @@ namespace Components { __asm { - mov eax, Game::demoPlaying + mov eax, 0xA5EA0C // clientConnections.demoplaying mov eax, [eax] test al, al jz continue @@ -142,7 +158,7 @@ namespace Components { __asm { - mov eax, Game::demoPlaying + mov eax, 0xA5EA0C // clientConnections.demoplaying mov eax, [eax] test al, al jz continue @@ -159,6 +175,36 @@ namespace Components } } + void Theatre::CG_CompassDrawPlayerMapLocationSelector_Stub(const int localClientNum, Game::CompassType compassType, const Game::rectDef_s* parentRect, const Game::rectDef_s* rect, Game::Material* material, float* color) + { + if (!Game::clientConnections->demoplaying) + { + Utils::Hook::Call(0x45BD60)(localClientNum, compassType, parentRect, rect, material, color); + } + } + + void Theatre::CL_WriteDemoClientArchive_Hk(void(*write)(const void* buffer, int len, int localClientNum), const Game::playerState_s* ps, const float* viewangles, [[maybe_unused]] const float* selectedLocation, [[maybe_unused]] const float selectedLocationAngle, int localClientNum, int index) + { + assert(write); + assert(ps); + + const unsigned char msgType = 1; + (write)(&msgType, sizeof(unsigned char), localClientNum); + + (write)(&index, sizeof(int), localClientNum); + + (write)(ps->origin, sizeof(float[3]), localClientNum); + (write)(ps->velocity, sizeof(float[3]), localClientNum); + (write)(&ps->movementDir, sizeof(int), localClientNum); + (write)(&ps->bobCycle, sizeof(int), localClientNum); + (write)(ps, sizeof(Game::playerState_s*), localClientNum); + (write)(viewangles, sizeof(float[3]), localClientNum); + + // Disable locationSelectionInfo + const auto locationSelectionInfo = 0; + (write)(&locationSelectionInfo, sizeof(int), localClientNum); + } + void Theatre::RecordStub(int channel, char* message, char* file) { Game::Com_Printf(channel, message, file); @@ -292,14 +338,14 @@ namespace Components } } - uint32_t Theatre::InitCGameStub() + int Theatre::CL_FirstSnapshot_Stub() { - if (Dvar::Var("cl_autoRecord").get() && !*Game::demoPlaying) + if (CLAutoRecord.get() && !Game::clientConnections->demoplaying) { std::vector files; auto demos = FileSystem::GetFileList("demos/", "dm_13"); - for (auto demo : demos) + for (auto& demo : demos) { if (Utils::String::StartsWith(demo, "auto_")) { @@ -307,7 +353,7 @@ namespace Components } } - auto numDel = static_cast(files.size()) - Dvar::Var("cl_demosKeep").get(); + auto numDel = static_cast(files.size()) - CLDemosKeep.get(); for (auto i = 0; i < numDel; ++i) { @@ -319,18 +365,18 @@ namespace Components Command::Execute(Utils::String::VA("record auto_%lld", std::time(nullptr)), true); } - return Utils::Hook::Call(0x42BBB0)(); + return Utils::Hook::Call(0x42BBB0)(); // DB_GetLoadedFlags } - void Theatre::MapChangeStub() + void Theatre::SV_SpawnServer_Stub() { StopRecording(); - Utils::Hook::Call(0x464A60)(); + Utils::Hook::Call(0x464A60)(); // Com_SyncThreads } void Theatre::StopRecording() { - if (*Game::demoRecording) + if (Game::clientConnections->demorecording) { Command::Execute("stoprecord", true); } @@ -338,8 +384,13 @@ namespace Components Theatre::Theatre() { - Dvar::Register("cl_autoRecord", true, Game::DVAR_ARCHIVE, "Automatically record games."); - Dvar::Register("cl_demosKeep", 30, 1, 999, Game::DVAR_ARCHIVE, "How many demos to keep with autorecord."); + AssertOffset(Game::clientConnection_t, demorecording, 0x40190); + AssertOffset(Game::clientConnection_t, demoplaying, 0x40194); + AssertOffset(Game::clientConnection_t, demofile, 0x401A4); + AssertOffset(Game::clientConnection_t, serverMessageSequence, 0x2013C); + + CLAutoRecord = Dvar::Register("cl_autoRecord", true, Game::DVAR_ARCHIVE, "Automatically record games"); + CLDemosKeep = Dvar::Register("cl_demosKeep", 30, 1, 999, Game::DVAR_ARCHIVE, "How many demos to keep with autorecord"); Utils::Hook(0x5A8370, GamestateWriteStub, HOOK_CALL).install()->quick(); Utils::Hook(0x5A85D2, RecordGamestateStub, HOOK_CALL).install()->quick(); @@ -349,13 +400,17 @@ namespace Components Utils::Hook(0x50320E, AdjustTimeDeltaStub, HOOK_CALL).install()->quick(); Utils::Hook(0x5A8E03, ServerTimedOutStub, HOOK_JUMP).install()->quick(); + // Fix issue with locationSelectionInfo by disabling it + Utils::Hook(0x5AC20F, CL_WriteDemoClientArchive_Hk, HOOK_CALL).install()->quick(); + Utils::Hook(0x4964A6, CG_CompassDrawPlayerMapLocationSelector_Stub, HOOK_CALL).install()->quick(); + // Hook commands to enforce metadata generation Utils::Hook(0x5A82AE, RecordStub, HOOK_CALL).install()->quick(); Utils::Hook(0x5A8156, StopRecordStub, HOOK_CALL).install()->quick(); // Autorecording - Utils::Hook(0x5A1D6A, InitCGameStub, HOOK_CALL).install()->quick(); - Utils::Hook(0x4A712A, MapChangeStub, HOOK_CALL).install()->quick(); + Utils::Hook(0x5A1D6A, CL_FirstSnapshot_Stub, HOOK_CALL).install()->quick(); + Utils::Hook(0x4A712A, SV_SpawnServer_Stub, HOOK_CALL).install()->quick(); // UIScripts UIScript::Add("loadDemos", LoadDemos); @@ -365,11 +420,11 @@ namespace Components // Feeder UIFeeder::Add(10.0f, GetDemoCount, GetDemoText, SelectDemo); - // set the configstrings stuff to load the default (empty) string table; this should allow demo recording on all gametypes/maps + // Set the configstrings stuff to load the default (empty) string table; this should allow demo recording on all gametypes/maps if (!Dedicated::IsEnabled()) Utils::Hook::Set(0x47440B, "mp/defaultStringTable.csv"); // Change font size - Utils::Hook::Set(0x5AC854, 2); - Utils::Hook::Set(0x5AC85A, 2); + Utils::Hook::Set(0x5AC854, 2); + Utils::Hook::Set(0x5AC85A, 2); } } diff --git a/src/Components/Modules/Theatre.hpp b/src/Components/Modules/Theatre.hpp index 0a880c68..217240b0 100644 --- a/src/Components/Modules/Theatre.hpp +++ b/src/Components/Modules/Theatre.hpp @@ -20,23 +20,16 @@ namespace Components int length; std::time_t timeStamp; - nlohmann::json to_json() const - { - return nlohmann::json - { - { "mapname", mapname }, - { "gametype", gametype }, - { "author", author }, - { "length", length }, - { "timestamp", Utils::String::VA("%lld", timeStamp) } //Ugly, but prevents information loss - }; - } + [[nodiscard]] nlohmann::json to_json() const; }; static DemoInfo CurrentInfo; static unsigned int CurrentSelection; static std::vector Demos; + static Dvar::Var CLAutoRecord; + static Dvar::Var CLDemosKeep; + static char BaselineSnapshot[131072]; static int BaselineSnapshotMsgLen; static int BaselineSnapshotMsgOff; @@ -60,8 +53,11 @@ namespace Components static void ServerTimedOutStub(); static void UISetActiveMenuStub(); - static uint32_t InitCGameStub(); - static void MapChangeStub(); + static int CL_FirstSnapshot_Stub(); + static void SV_SpawnServer_Stub(); + + static void CG_CompassDrawPlayerMapLocationSelector_Stub(int localClientNum, Game::CompassType compassType, const Game::rectDef_s* parentRect, const Game::rectDef_s* rect, Game::Material* material, float* color); + static void CL_WriteDemoClientArchive_Hk(void(*write)(const void* buffer, int len, int localClientNum), const Game::playerState_s* ps, const float* viewangles, const float* selectedLocation, float selectedLocationAngle, int localClientNum, int index); static void RecordStub(int channel, char* message, char* file); static void StopRecordStub(int channel, char* message); diff --git a/src/Components/Modules/Threading.cpp b/src/Components/Modules/Threading.cpp index 0f77dea2..b8d2347e 100644 --- a/src/Components/Modules/Threading.cpp +++ b/src/Components/Modules/Threading.cpp @@ -5,38 +5,37 @@ namespace Components { namespace FrameTime { - void NetSleep(int mSec) + void NetSleep(int ms) { - if (mSec < 0) mSec = 0; + if (ms < 0) ms = 0; fd_set fdr; FD_ZERO(&fdr); - auto highestFd = INVALID_SOCKET; + auto maxfd = INVALID_SOCKET; if (*Game::ip_socket != INVALID_SOCKET) { FD_SET(*Game::ip_socket, &fdr); - highestFd = *Game::ip_socket; + maxfd = *Game::ip_socket; } - if (highestFd == INVALID_SOCKET) + if (maxfd == INVALID_SOCKET) { - // windows ain't happy when select is called without valid FDs - std::this_thread::sleep_for(std::chrono::milliseconds(mSec)); + // On Windows, select fails if no sockets + Game::Sys_Sleep(ms); return; } - timeval timeout; - timeout.tv_sec = mSec / 1000; - timeout.tv_usec = (mSec % 1000) * 1000; + timeval tv = { ms / 1000, (ms % 1000) * 1000 }; + const auto result = ::select(maxfd + 1, &fdr, nullptr, nullptr, &tv); - const auto retVal = ::select(highestFd + 1, &fdr, nullptr, nullptr, &timeout); - - if (retVal == SOCKET_ERROR) + if (result == SOCKET_ERROR) { Logger::Warning(Game::CON_CHANNEL_SYSTEM, "WinAPI: select failed: {}\n", Game::NET_ErrorString()); + return; } - else if (retVal > 0) + + if (result > 0) { if (Dedicated::IsRunning()) { diff --git a/src/Components/Modules/UIFeeder.cpp b/src/Components/Modules/UIFeeder.cpp index 028e82f7..526f745a 100644 --- a/src/Components/Modules/UIFeeder.cpp +++ b/src/Components/Modules/UIFeeder.cpp @@ -6,16 +6,20 @@ namespace Components UIFeeder::Container UIFeeder::Current; std::unordered_map UIFeeder::Feeders; - void UIFeeder::Add(float feeder, UIFeeder::GetItemCount_t itemCountCb, UIFeeder::GetItemText_t itemTextCb, UIFeeder::Select_t selectCb) + Dvar::Var UIFeeder::UIMapLong; + Dvar::Var UIFeeder::UIMapName; + Dvar::Var UIFeeder::UIMapDesc; + + void UIFeeder::Add(float feeder, GetItemCount_t itemCountCb, GetItemText_t itemTextCb, Select_t selectCb) { - UIFeeder::Feeders[feeder] = { itemCountCb, itemTextCb, selectCb }; + Feeders[feeder] = { itemCountCb, itemTextCb, selectCb }; } const char* UIFeeder::GetItemText() { - if (UIFeeder::Feeders.contains(UIFeeder::Current.feeder)) + if (Feeders.contains(Current.feeder)) { - return UIFeeder::Feeders[UIFeeder::Current.feeder].getItemText(UIFeeder::Current.index, UIFeeder::Current.column); + return Feeders[Current.feeder].getItemText(Current.index, Current.column); } return nullptr; @@ -23,9 +27,9 @@ namespace Components unsigned int UIFeeder::GetItemCount() { - if (UIFeeder::Feeders.contains(UIFeeder::Current.feeder)) + if (Feeders.contains(Current.feeder)) { - return UIFeeder::Feeders[UIFeeder::Current.feeder].getItemCount(); + return Feeders[Current.feeder].getItemCount(); } return 0; @@ -33,9 +37,9 @@ namespace Components bool UIFeeder::SetItemSelection() { - if (UIFeeder::Feeders.find(UIFeeder::Current.feeder) != UIFeeder::Feeders.end()) + if (Feeders.contains(Current.feeder)) { - UIFeeder::Feeders[UIFeeder::Current.feeder].select(UIFeeder::Current.index); + Feeders[Current.feeder].select(Current.index); return true; } @@ -44,8 +48,8 @@ namespace Components bool UIFeeder::CheckFeeder() { - if (UIFeeder::Current.feeder == 15.0f) return false; - return (UIFeeder::Feeders.find(UIFeeder::Current.feeder) != UIFeeder::Feeders.end()); + if (Current.feeder == 15.0f) return false; + return Feeders.contains(Current.feeder); } __declspec(naked) void UIFeeder::SetItemSelectionStub() @@ -53,12 +57,12 @@ namespace Components __asm { mov eax, [esp + 08h] - mov UIFeeder::Current.feeder, eax + mov Current.feeder, eax mov eax, [esp + 0Ch] - mov UIFeeder::Current.index, eax + mov Current.index, eax - call UIFeeder::SetItemSelection + call SetItemSelection test al, al jz continue @@ -78,15 +82,15 @@ namespace Components __asm { mov eax, [esp + 0Ch] - mov UIFeeder::Current.feeder, eax + mov Current.feeder, eax mov eax, [esp + 10h] - mov UIFeeder::Current.index, eax + mov Current.index, eax mov eax, [esp + 14h] - mov UIFeeder::Current.column, eax + mov Current.column, eax - call UIFeeder::GetItemText + call GetItemText test eax, eax jz continue @@ -111,9 +115,9 @@ namespace Components __asm { mov eax, [esp + 8h] - mov UIFeeder::Current.feeder, eax + mov Current.feeder, eax - call UIFeeder::GetItemCount + call GetItemCount test eax, eax jz continue @@ -125,23 +129,22 @@ namespace Components fld ds:739FD0h mov eax, 41A0D7h - jmp eax; + jmp eax } } __declspec(naked) void UIFeeder::HandleKeyStub() { - // ReSharper disable once CppEntityNeverUsed static int nextClickTime = 0; __asm { mov ebx, ebp mov eax, [ebp + 12Ch] - mov UIFeeder::Current.feeder, eax + mov Current.feeder, eax push ebx - call UIFeeder::CheckFeeder + call CheckFeeder pop ebx test al, al @@ -180,9 +183,9 @@ namespace Components // Update indices if not mov [ecx], edx - mov UIFeeder::Current.index, edx + mov Current.index, edx - call UIFeeder::SetItemSelection + call SetItemSelection returnSafe: retn @@ -198,9 +201,9 @@ namespace Components __asm { mov eax, [edi + 12Ch] - mov UIFeeder::Current.feeder, eax + mov Current.feeder, eax - call UIFeeder::CheckFeeder + call CheckFeeder test al, al jnz continue @@ -218,9 +221,9 @@ namespace Components __asm { mov eax, [esp + 08h] - mov UIFeeder::Current.feeder, eax + mov Current.feeder, eax - call UIFeeder::CheckFeeder + call CheckFeeder test al, al jnz continue @@ -238,9 +241,9 @@ namespace Components __asm { mov eax, [edi + 12Ch] - mov UIFeeder::Current.feeder, eax + mov Current.feeder, eax - call UIFeeder::CheckFeeder + call CheckFeeder test al, al jnz continue @@ -257,13 +260,13 @@ namespace Components { if (Game::uiContext->openMenuCount > 0) { - Game::menuDef_t* menu = Game::uiContext->menuStack[Game::uiContext->openMenuCount - 1]; + auto* menu = Game::uiContext->menuStack[Game::uiContext->openMenuCount - 1]; if (menu && menu->items) { for (int i = 0; i < menu->itemCount; ++i) { - Game::itemDef_s* item = menu->items[i]; + auto* item = menu->items[i]; if (item && item->type == 6 && item->special == feeder) { item->cursorPos[0] = static_cast(index); @@ -276,74 +279,81 @@ namespace Components unsigned int UIFeeder::GetMapCount() { - Game::UI_UpdateArenas(); - Game::UI_SortArenas(); - return *Game::arenaCount; + return Maps::GetCustomMaps().size(); } const char* UIFeeder::GetMapText(unsigned int index, int /*column*/) { - Game::UI_UpdateArenas(); - Game::UI_SortArenas(); - - if (index < static_cast(*Game::arenaCount)) + const auto& maps = Maps::GetCustomMaps(); + if (index < maps.size()) { - return Game::SEH_StringEd_GetString(ArenaLength::NewArenas[reinterpret_cast(0x633E934)[index]].uiName); + return maps.at(index).data(); } +#ifdef DEBUG + if (IsDebuggerPresent()) + { + __debugbreak(); + } +#endif + return ""; } void UIFeeder::SelectMap(unsigned int index) { - Game::UI_UpdateArenas(); - Game::UI_SortArenas(); + const auto& maps = Maps::GetCustomMaps(); - if (index < static_cast(*Game::arenaCount)) + if (index < maps.size()) { - index = reinterpret_cast(0x633E934)[index]; - const char* mapname = ArenaLength::NewArenas[index].mapName; - const char* longname = ArenaLength::NewArenas[index].uiName; - const char* description = ArenaLength::NewArenas[index].description; + std::string mapName = maps[index]; - Dvar::Var("ui_map_name").set(mapname); - Dvar::Var("ui_map_long").set(Game::SEH_StringEd_GetString(longname)); - Dvar::Var("ui_map_desc").set(Game::SEH_StringEd_GetString(description)); + std::string longName = mapName; + std::string description = "(Missing arena file!)"; + + const auto& arenaPath = Maps::GetArenaPath(mapName); + + if (Utils::IO::FileExists(arenaPath)) + { + const auto& arena = Maps::ParseCustomMapArena(Utils::IO::ReadFile(arenaPath)); + + if (arena.contains("longname")) + { + longName = arena.at("longname"); + } + + if (arena.contains("map")) + { + mapName = arena.at("map"); + } + + if (arena.contains("description")) + { + description = arena.at("description"); + } + } + + UIMapName.set(Localization::Get(mapName.data())); + UIMapLong.set(Localization::Get(longName.data())); + UIMapDesc.set(Localization::Get(description.data())); } } void UIFeeder::ApplyMap([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) { - const auto mapname = Dvar::Var("ui_map_name").get(); + const auto* mapname = Dvar::Var("ui_map_name").get(); - Dvar::Var("ui_mapname").set(mapname); - Utils::Hook::Call(0x503B50)(mapname.data()); // Party_SetDisplayMapName + if (*mapname) + { + Game::Dvar_SetString(*Game::ui_mapname, mapname); + Utils::Hook::Call(0x503B50)(mapname); // Party_SetDisplayMapName + } } void UIFeeder::ApplyInitialMap([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) { - const auto mapname = Dvar::Var("ui_mapname").get(); - - Game::UI_UpdateArenas(); - Game::UI_SortArenas(); - - for (unsigned int i = 0; i < static_cast(*Game::arenaCount); ++i) - { - if (ArenaLength::NewArenas[i].mapName == mapname) - { - for (unsigned int j = 0; j < static_cast(*Game::arenaCount); ++j) - { - if (reinterpret_cast(0x633E934)[j] == i) - { - UIFeeder::SelectMap(j); - UIFeeder::Select(60.0f, j); - break; - } - } - - break; - } - } + Maps::ScanCustomMaps(); + Select(60.0f, 0); // Will select nothing if there's no map } int UIFeeder::CheckSelection(int feeder) @@ -361,7 +371,7 @@ namespace Components call ecx // __ftol2_sse push eax - call UIFeeder::CheckSelection + call CheckSelection test al, al jz returnSafe @@ -382,12 +392,12 @@ namespace Components { if (Dedicated::IsEnabled()) return; - Scheduler::Once([] + Events::OnDvarInit([] { - Dvar::Register("ui_map_long", "Afghan", Game::DVAR_NONE, ""); - Dvar::Register("ui_map_name", "mp_afghan", Game::DVAR_NONE, ""); - Dvar::Register("ui_map_desc", "", Game::DVAR_NONE, ""); - }, Scheduler::Pipeline::MAIN); + UIMapLong = Dvar::Register("ui_map_long", "", Game::DVAR_NONE, ""); + UIMapName = Dvar::Register("ui_map_name", "", Game::DVAR_NONE, ""); + UIMapDesc = Dvar::Register("ui_map_desc", "", Game::DVAR_NONE, ""); + }); // Get feeder item count Utils::Hook(0x41A0D0, UIFeeder::GetItemCountStub, HOOK_JUMP).install()->quick(); diff --git a/src/Components/Modules/UIFeeder.hpp b/src/Components/Modules/UIFeeder.hpp index 8f597c9d..073c210d 100644 --- a/src/Components/Modules/UIFeeder.hpp +++ b/src/Components/Modules/UIFeeder.hpp @@ -31,6 +31,10 @@ namespace Components static Container Current; + static Dvar::Var UIMapLong; + static Dvar::Var UIMapName; + static Dvar::Var UIMapDesc; + static void GetItemCountStub(); static unsigned int GetItemCount(); diff --git a/src/Components/Modules/Voice.cpp b/src/Components/Modules/Voice.cpp index 10e40a21..296ff4a8 100644 --- a/src/Components/Modules/Voice.cpp +++ b/src/Components/Modules/Voice.cpp @@ -1,4 +1,5 @@ #include +#include "Chat.hpp" #include "Voice.hpp" namespace Components @@ -104,10 +105,12 @@ namespace Components { return false; } + if (ent1->client->sess.cs.team) { return ent1->client->sess.cs.team == ent2->client->sess.cs.team; } + return false; } @@ -381,6 +384,13 @@ namespace Components Events::OnSteamDisconnect(CL_ClearMutedList); Events::OnClientDisconnect(SV_UnmuteClient); + Events::OnClientConnect([](Game::client_t* cl) -> void + { + if (Chat::IsMuted(cl)) + { + SV_MuteClient(cl - Game::svs_clients); + } + }); // Write voice packets to the server instead of other clients Utils::Hook(0x487935, CL_WriteVoicePacket_Hk, HOOK_CALL).install()->quick(); diff --git a/src/Components/Modules/Weapon.cpp b/src/Components/Modules/Weapon.cpp index 01af8241..b3f4ee6d 100644 --- a/src/Components/Modules/Weapon.cpp +++ b/src/Components/Modules/Weapon.cpp @@ -548,21 +548,53 @@ namespace Components } } + void Weapon::PlayerCmd_initialWeaponRaise(Game::scr_entref_t entref) + { + auto* ent = GSC::Script::Scr_GetPlayerEntity(entref); + const auto* weapon = Game::Scr_GetString(0); + const auto index = Game::G_GetWeaponIndexForName(weapon); + + auto* ps = &ent->client->ps; + if (!Game::BG_IsWeaponValid(ps, index)) + { + Game::Scr_Error(Utils::String::VA("invalid InitialWeaponRaise: %s", weapon)); + return; + } + + assert(ps); + + if (!index) + { + return; + } + + auto* equippedWeapon = Game::BG_GetEquippedWeaponState(ps, index); + if (!equippedWeapon) + { + return; + } + + equippedWeapon->usedBefore = false; + Game::Player_SwitchToWeapon(ent); + } + void Weapon::AddScriptMethods() { - Script::AddMethod("DisableWeaponPickup", [](Game::scr_entref_t entref) + GSC::Script::AddMethod("DisableWeaponPickup", [](Game::scr_entref_t entref) { - const auto* ent = Game::GetPlayerEntity(entref); + const auto* ent = GSC::Script::Scr_GetPlayerEntity(entref); ent->client->ps.weapCommon.weapFlags |= Game::PWF_DISABLE_WEAPON_PICKUP; }); - Script::AddMethod("EnableWeaponPickup", [](Game::scr_entref_t entref) + GSC::Script::AddMethod("EnableWeaponPickup", [](Game::scr_entref_t entref) { - const auto* ent = Game::GetPlayerEntity(entref); + const auto* ent = GSC::Script::Scr_GetPlayerEntity(entref); ent->client->ps.weapCommon.weapFlags &= ~Game::PWF_DISABLE_WEAPON_PICKUP; }); + + GSC::Script::AddMethod("InitialWeaponRaise", PlayerCmd_initialWeaponRaise); } Weapon::Weapon() diff --git a/src/Components/Modules/Weapon.hpp b/src/Components/Modules/Weapon.hpp index 7e5a235a..acfe3d00 100644 --- a/src/Components/Modules/Weapon.hpp +++ b/src/Components/Modules/Weapon.hpp @@ -34,6 +34,9 @@ namespace Components static void JavelinResetHook_Stub(); static void WeaponEntCanBeGrabbed_Stub(); + + static void PlayerCmd_initialWeaponRaise(Game::scr_entref_t entref); + static void AddScriptMethods(); }; } diff --git a/src/Components/Modules/Window.cpp b/src/Components/Modules/Window.cpp index f044ea08..5ed7e497 100644 --- a/src/Components/Modules/Window.cpp +++ b/src/Components/Modules/Window.cpp @@ -1,5 +1,7 @@ #include +#include "FastFiles.hpp" + namespace Components { Dvar::Var Window::NoBorder; diff --git a/src/Components/Modules/ZoneBuilder.cpp b/src/Components/Modules/ZoneBuilder.cpp index 4d10a89d..c1b7a133 100644 --- a/src/Components/Modules/ZoneBuilder.cpp +++ b/src/Components/Modules/ZoneBuilder.cpp @@ -1,5 +1,8 @@ #include +#include + #include "Console.hpp" +#include "FastFiles.hpp" #include @@ -16,8 +19,6 @@ namespace Components volatile bool ZoneBuilder::CommandThreadTerminate = false; std::thread ZoneBuilder::CommandThread; - Dvar::Var ZoneBuilder::PreferDiskAssetsDvar; - ZoneBuilder::Zone::Zone(const std::string& name) : indexStart(0), externalSize(0), // Reserve 100MB by default. // That's totally fine, as the dedi doesn't load images and therefore doesn't need much memory. @@ -25,7 +26,11 @@ namespace Components // Side note: if you need a fastfile larger than 100MB, you're doing it wrong- // Well, decompressed maps can get way larger than 100MB, so let's increase that. buffer(0xC800000), - zoneName(name), dataMap("zone_source/" + name + ".csv"), branding{nullptr}, assetDepth(0) + zoneName(name), + dataMap("zone_source/" + name + ".csv"), + branding{nullptr}, + assetDepth(0), + iw4ofApi(getIW4OfApiParams()) { } @@ -95,6 +100,11 @@ namespace Components return &this->memAllocator; } + iw4of::api* ZoneBuilder::Zone::getIW4OfApi() + { + return &iw4ofApi; + } + void ZoneBuilder::Zone::Zone::build() { if (!this->dataMap.isValid()) @@ -362,7 +372,6 @@ namespace Components Logger::Error(Game::ERR_FATAL, "Missing required asset '{}' ({}). Export failed!", name, Game::DB_GetXAssetTypeName(type)); } - Logger::Debug("Saving require ({}): {}", Game::DB_GetXAssetTypeName(type), Game::DB_GetXAssetNameHandlers[type](&header)); // we alias the next 4 (aligned) bytes of the stream b/c DB_InsertPointer gives us a nice pointer to use as the alias // otherwise it would be a fuckfest trying to figure out where the alias is in the stream @@ -498,7 +507,6 @@ namespace Components this->buffer.pushBlock(Game::XFILE_BLOCK_TEMP); this->buffer.align(Utils::Stream::ALIGN_4); - Logger::Debug("Saving ({}): {}", Game::DB_GetXAssetTypeName(asset.type), Game::DB_GetXAssetNameHandlers[asset.type](&asset.header)); this->store(asset.header); AssetHandler::ZoneSave(asset, this); @@ -525,18 +533,20 @@ namespace Components // Add branding asset void ZoneBuilder::Zone::addBranding() { - constexpr auto* data = "Built using the IW4x Zone:B:uilder Version 4"; - auto dataLen = std::strlen(data); // + 1 is added by the save code + const auto now = std::chrono::system_clock::now(); + + auto zoneBranding = std::format("Built using the IW4x ZoneBuilder! {:%d-%m-%Y %H:%M:%OS}", now); + auto brandingLen = zoneBranding.size(); // + 1 is added by the save code - this->branding = {this->zoneName.data(), 0, static_cast(dataLen), data}; + this->branding = {this->zoneName.data(), 0, static_cast(brandingLen), getAllocator()->duplicateString(zoneBranding)}; - if (this->findAsset(Game::XAssetType::ASSET_TYPE_RAWFILE, this->branding.name) != -1) + if (this->findAsset(Game::ASSET_TYPE_RAWFILE, this->branding.name) != -1) { Logger::Error(Game::ERR_FATAL, "Unable to add branding. Asset '{}' already exists!", this->branding.name); } Game::XAssetHeader header = { &this->branding }; - Game::XAsset brandingAsset = { Game::XAssetType::ASSET_TYPE_RAWFILE, header }; + Game::XAsset brandingAsset = { Game::ASSET_TYPE_RAWFILE, header }; this->loadedAssets.push_back(brandingAsset); } @@ -747,6 +757,58 @@ namespace Components return header; } + iw4of::params_t ZoneBuilder::Zone::getIW4OfApiParams() + { + iw4of::params_t params{}; + + params.write_only_once = true; + + params.find_other_asset = [this](int type, const std::string& name) -> void* + { + return AssetHandler::FindAssetForZone(static_cast(type), name, this).data; + }; + + params.fs_read_file = [](const std::string& filename) -> std::string + { + auto file = FileSystem::File(filename); + if (file.exists()) + { + return file.getBuffer(); + } + + return {}; + }; + + params.store_in_string_table = [](const std::string& text) -> unsigned int + { + return Game::SL_GetString(text.data(), 0); + }; + + params.print = [](iw4of::params_t::print_type t, const std::string& message) -> void + { + switch (t) + { + case iw4of::params_t::P_ERR: + Logger::Error(Game::ERR_FATAL, "{}", message); + break; + case iw4of::params_t::P_WARN: + Logger::Print("{}", message); + break; + } + }; + + if (*Game::fs_basepath && *Game::fs_gameDirVar) + { + params.work_directory = std::format("{}/{}", (*Game::fs_basepath)->current.string, (*Game::fs_gameDirVar)->current.string); + } + else + { + Logger::Error(Game::ERR_FATAL, "Missing FS Game directory or basepath directory!"); + } + + return params; + } + int ZoneBuilder::StoreTexture(Game::GfxImageLoadDef **loadDef, Game::GfxImage *image) { size_t size = 16 + (*loadDef)->resourceSize; @@ -1096,18 +1158,6 @@ namespace Components Utils::Hook::Set(0x5BC759, g_copyInfo_new); Utils::Hook::Set(0x5BB9AD, newLimit); // limit check - // this one lets us keep loading zones and it will ignore assets when the pool is filled - /* - AssetHandler::OnLoad([](Game::XAssetType type, Game::XAssetHeader, const std::string&, bool* restrict) - { - //if (*static_cast(Game::DB_XAssetPool[type].data) == 0) - if (Game::g_poolSize[type] == 0) - { - *restrict = true; - } - }); - */ - // hunk size (was 300 MiB) Utils::Hook::Set(0x64A029, 0x38400000); // 900 MiB Utils::Hook::Set(0x64A057, 0x38400000); @@ -1212,7 +1262,7 @@ namespace Components Command::Add("buildall", []([[maybe_unused]] Command::Params* params) { - auto path = std::format("{}\\zone_source", Dvar::Var("fs_basepath").get()); + auto path = std::format("{}\\zone_source", (*Game::fs_basepath)->current.string); auto zoneSources = FileSystem::GetSysFileList(path, "csv", false); for (auto source : zoneSources) @@ -1284,9 +1334,11 @@ namespace Components std::string csvStr; - auto fileList = Utils::IO::ListFiles(Utils::String::VA("zone/%s", Game::Win_GetLanguage())); - for (auto zone : fileList) + const auto dir = std::format("zone/{}", Game::Win_GetLanguage()); + auto fileList = Utils::IO::ListFiles(dir, false); + for (const auto& entry : fileList) { + auto zone = entry.path().string(); Utils::String::Replace(zone, Utils::String::VA("zone/%s/", Game::Win_GetLanguage()), ""); Utils::String::Replace(zone, ".ff", ""); @@ -1320,7 +1372,7 @@ namespace Components while (!Game::Sys_IsDatabaseReady()) std::this_thread::sleep_for(100ms); // wait till its fully loaded - if (curTechsets_list.size() == 0) + if (curTechsets_list.empty()) { Logger::Print("Skipping empty zone {}\n", zone); // unload zone @@ -1347,7 +1399,7 @@ namespace Components } // save csv - Utils::IO::WriteFile("zone_source/techsets/" + zone + "_techsets.csv", csvStr.data()); + Utils::IO::WriteFile("zone_source/techsets/" + zone + "_techsets.csv", csvStr); // build the techset zone std::string zoneName = "techsets/" + zone + "_techsets"; @@ -1377,11 +1429,13 @@ namespace Components Utils::Hook::Set(0x649E740, "techsets"); // load generated techset fastfiles - auto list = Utils::IO::ListFiles("zone/techsets"); + auto list = Utils::IO::ListFiles("zone/techsets", false); int i = 0; int subCount = 0; - for (auto it : list) + for (const auto& entry : list) { + auto it = entry.path().string(); + Utils::String::Replace(it, "zone/techsets/", ""); Utils::String::Replace(it, ".ff", ""); @@ -1422,7 +1476,7 @@ namespace Components std::string tempZoneFile = Utils::String::VA("zone_source/techsets/techsets%d.csv", subCount); std::string tempZone = Utils::String::VA("techsets/techsets%d", subCount); - Utils::IO::WriteFile(tempZoneFile, csvStr.data()); + Utils::IO::WriteFile(tempZoneFile, csvStr); Logger::Print("Building zone '{}'...\n", tempZone); Zone(tempZone).build(); @@ -1467,7 +1521,7 @@ namespace Components std::string tempZoneFile = Utils::String::VA("zone_source/techsets/techsets%d.csv", subCount); std::string tempZone = Utils::String::VA("techsets/techsets%d", subCount); - Utils::IO::WriteFile(tempZoneFile, csvStr.data()); + Utils::IO::WriteFile(tempZoneFile, csvStr); Logger::Print("Building zone '{}'...\n", tempZone); Zone(tempZone).build(); @@ -1504,9 +1558,9 @@ namespace Components // create csv with the techsets in it csvStr.clear(); - for (auto tech : curTechsets_list) + for (const auto& tech : curTechsets_list) { - std::string mat = ZoneBuilder::FindMaterialByTechnique(tech); + auto mat = ZoneBuilder::FindMaterialByTechnique(tech); if (mat.length() == 0) { csvStr.append("techset," + tech + "\n"); @@ -1517,7 +1571,7 @@ namespace Components } } - Utils::IO::WriteFile("zone_source/techsets/techsets.csv", csvStr.data()); + Utils::IO::WriteFile("zone_source/techsets/techsets.csv", csvStr); // set language back Utils::Hook::Set(0x649E740, language); @@ -1578,7 +1632,7 @@ namespace Components { if (params->size() < 2) return; - auto path = std::format("{}\\mods\\{}\\images", Dvar::Var("fs_basepath").get(), params->get(1)); + auto path = std::format("{}\\mods\\{}\\images", (*Game::fs_basepath)->current.string, params->get(1)); auto images = FileSystem::GetSysFileList(path, "iwi", false); for (auto i = images.begin(); i != images.end();) @@ -1598,9 +1652,6 @@ namespace Components Logger::Print("{}\n", nlohmann::json(images).dump()); Logger::Print("------------------- END IWI DUMP -------------------\n"); }); - - // True by default, but can be put to zero for backward compatibility if needed - ZoneBuilder::PreferDiskAssetsDvar = Dvar::Register("zb_prefer_disk_assets", true, Game::DVAR_NONE, "Should zonebuilder prefer in-memory assets (requirements) or disk assets, when both are present?"); } } diff --git a/src/Components/Modules/ZoneBuilder.hpp b/src/Components/Modules/ZoneBuilder.hpp index f61b140f..2b5fe4de 100644 --- a/src/Components/Modules/ZoneBuilder.hpp +++ b/src/Components/Modules/ZoneBuilder.hpp @@ -1,5 +1,7 @@ #pragma once +#include + #define XFILE_MAGIC_UNSIGNED 0x3030317566665749 #define XFILE_VERSION 276 @@ -38,6 +40,7 @@ namespace Components Utils::Stream* getBuffer(); Utils::Memory::Allocator* getAllocator(); + iw4of::api* getIW4OfApi(); bool hasPointer(const void* pointer); void storePointer(const void* pointer); @@ -87,11 +90,14 @@ namespace Components void addBranding(); + iw4of::params_t getIW4OfApiParams(); + uint32_t safeGetPointer(const void* pointer); int indexStart; unsigned int externalSize; Utils::Stream buffer; + iw4of::api iw4ofApi; std::string zoneName; Utils::CSV dataMap; @@ -131,7 +137,6 @@ namespace Components static std::vector> EndAssetTrace(); static Game::XAssetHeader GetEmptyAssetIfCommon(Game::XAssetType type, const std::string& name, Zone* builder); - static Dvar::Var PreferDiskAssetsDvar; private: static int StoreTexture(Game::GfxImageLoadDef **loadDef, Game::GfxImage *image); diff --git a/src/Components/Modules/Zones.cpp b/src/Components/Modules/Zones.cpp index 6efd7be6..2206df39 100644 --- a/src/Components/Modules/Zones.cpp +++ b/src/Components/Modules/Zones.cpp @@ -1,5 +1,9 @@ #include +#include + +#include "FastFiles.hpp" + #pragma optimize( "", off ) namespace Components { @@ -43,7 +47,7 @@ namespace Components Game::XAssetType currentAssetType = Game::XAssetType::ASSET_TYPE_INVALID; Game::XAssetType previousAssetType = Game::XAssetType::ASSET_TYPE_INVALID; - + bool Zones::LoadFxEffectDef(bool atStreamStart, char* buffer, int size) { int count = 0; @@ -120,7 +124,7 @@ namespace Components } bool result = Game::Load_Stream(atStreamStart, xmodel, size); - + if (Zones::Version() >= VERSION_ALPHA2) { Game::XModel model[2]; // Allocate 2 models, as we exceed the buffer @@ -321,7 +325,7 @@ namespace Components { auto stringCount = (Zones::Version() >= 460) ? 62 : 52; auto arraySize = stringCount * 4; - + // 236 *Game::varXString = reinterpret_cast(varWeaponCompleteDef + 124); Game::Load_XStringArray(false, stringCount); @@ -356,31 +360,31 @@ namespace Components { for (int i = 0; i < 16; i++) { - *Game::varFxEffectDefHandle = reinterpret_cast(varWeaponCompleteDef + 1028 + (i * 4)); + *Game::varFxEffectDefHandle = reinterpret_cast(varWeaponCompleteDef + 1028 + (i * 4)); Game::Load_FxEffectDefHandle(false); } for (int i = 0; i < 16; i++) { - *Game::varFxEffectDefHandle = reinterpret_cast(varWeaponCompleteDef + 1124 + (i * 4)); + *Game::varFxEffectDefHandle = reinterpret_cast(varWeaponCompleteDef + 1124 + (i * 4)); Game::Load_FxEffectDefHandle(false); } for (int i = 0; i < 16; i++) { - *Game::varFxEffectDefHandle = reinterpret_cast(varWeaponCompleteDef + 1188 + (i * 4)); + *Game::varFxEffectDefHandle = reinterpret_cast(varWeaponCompleteDef + 1188 + (i * 4)); Game::Load_FxEffectDefHandle(false); } for (int i = 0; i < 16; i++) { - *Game::varFxEffectDefHandle = reinterpret_cast(varWeaponCompleteDef + 1316 + (i * 4)); + *Game::varFxEffectDefHandle = reinterpret_cast(varWeaponCompleteDef + 1316 + (i * 4)); Game::Load_FxEffectDefHandle(false); } for (int i = 0; i < 5; i++) { - *Game::varFxEffectDefHandle = reinterpret_cast(varWeaponCompleteDef + 1444 + (i * 4)); + *Game::varFxEffectDefHandle = reinterpret_cast(varWeaponCompleteDef + 1444 + (i * 4)); Game::Load_FxEffectDefHandle(false); } } @@ -417,7 +421,7 @@ namespace Components { auto offset = (Zones::Version() >= 460) ? 1476 : 916; auto count = (Zones::Version() >= 461) ? 58 : (Zones::Version() >= 460) ? 57 : 52; - + // 53 soundalias name references; up to and including 1124 for (int i = 0; i < count; ++i, offset += 4) { @@ -453,7 +457,7 @@ namespace Components if (*reinterpret_cast(varWeaponCompleteDef + 1708) == -1) { *reinterpret_cast(varWeaponCompleteDef + 1708) = Game::DB_AllocStreamPos(3); - *Game::varsnd_alias_list_name = *reinterpret_cast(varWeaponCompleteDef + 1708); + *Game::varsnd_alias_list_name = *reinterpret_cast(varWeaponCompleteDef + 1708); Game::Load_snd_alias_list_nameArray(true, 31); } @@ -468,7 +472,7 @@ namespace Components if (*reinterpret_cast(varWeaponCompleteDef + 1712) == -1) { *reinterpret_cast(varWeaponCompleteDef + 1712) = Game::DB_AllocStreamPos(3); - *Game::varsnd_alias_list_name = *reinterpret_cast(varWeaponCompleteDef + 1712); + *Game::varsnd_alias_list_name = *reinterpret_cast(varWeaponCompleteDef + 1712); Game::Load_snd_alias_list_nameArray(true, 31); } @@ -548,7 +552,7 @@ namespace Components // 1192 for (int offset = 1716; offset <= 1728; offset += 4) { - *Game::varFxEffectDefHandle = reinterpret_cast(varWeaponCompleteDef + offset); + *Game::varFxEffectDefHandle = reinterpret_cast(varWeaponCompleteDef + offset); Game::Load_FxEffectDefHandle(false); } } @@ -577,7 +581,7 @@ namespace Components static int matOffsets1[] = { 1732, 1736, 1952, 1956, 1960, 1964, 1968, 1972, 1980, 1988, 2000, 2004, 2008, 2012 }; for (int i = 0; i < ARRAYSIZE(matOffsets1); ++i) { - *Game::varMaterialHandle = reinterpret_cast(varWeaponCompleteDef + matOffsets1[i]); + *Game::varMaterialHandle = reinterpret_cast(varWeaponCompleteDef + matOffsets1[i]); Game::Load_MaterialHandle(false); } } @@ -639,7 +643,7 @@ namespace Components { for (int offset = 2332; offset <= 2344; offset += 4) { - *Game::varMaterialHandle = reinterpret_cast(varWeaponCompleteDef + offset); + *Game::varMaterialHandle = reinterpret_cast(varWeaponCompleteDef + offset); Game::Load_MaterialHandle(false); } } @@ -662,10 +666,10 @@ namespace Components if (Zones::Version() >= 460) { - *Game::varPhysCollmapPtr = reinterpret_cast(varWeaponCompleteDef + 2544); + *Game::varPhysCollmapPtr = reinterpret_cast(varWeaponCompleteDef + 2544); Game::Load_PhysCollmapPtr(false); - *Game::varPhysPresetPtr = reinterpret_cast(varWeaponCompleteDef + 2548); + *Game::varPhysPresetPtr = reinterpret_cast(varWeaponCompleteDef + 2548); Game::Load_PhysPresetPtr(false); } else if (Zones::ZoneVersion >= 359) @@ -684,7 +688,7 @@ namespace Components if (Zones::Version() >= 460) { - *Game::varXModelPtr = reinterpret_cast(varWeaponCompleteDef + 2656); + *Game::varXModelPtr = reinterpret_cast(varWeaponCompleteDef + 2656); Game::Load_XModelPtr(false); } else if (Zones::ZoneVersion >= 359) @@ -700,10 +704,10 @@ namespace Components if (Zones::Version() >= 460) { - *Game::varFxEffectDefHandle = reinterpret_cast(varWeaponCompleteDef + 2664); + *Game::varFxEffectDefHandle = reinterpret_cast(varWeaponCompleteDef + 2664); Game::Load_FxEffectDefHandle(false); - *Game::varFxEffectDefHandle = reinterpret_cast(varWeaponCompleteDef + 2668); + *Game::varFxEffectDefHandle = reinterpret_cast(varWeaponCompleteDef + 2668); Game::Load_FxEffectDefHandle(false); } else if (Zones::ZoneVersion >= 359) @@ -725,10 +729,10 @@ namespace Components if (Zones::Version() >= 460) { - *Game::varsnd_alias_list_name = reinterpret_cast(varWeaponCompleteDef + 2672); + *Game::varsnd_alias_list_name = reinterpret_cast(varWeaponCompleteDef + 2672); Game::Load_SndAliasCustom(*Game::varsnd_alias_list_name); - *Game::varsnd_alias_list_name = reinterpret_cast(varWeaponCompleteDef + 2676); + *Game::varsnd_alias_list_name = reinterpret_cast(varWeaponCompleteDef + 2676); Game::Load_SndAliasCustom(*Game::varsnd_alias_list_name); } else if (Zones::ZoneVersion >= 359) @@ -750,13 +754,13 @@ namespace Components if (Zones::Version() >= 460) { - *Game::varFxEffectDefHandle = reinterpret_cast(varWeaponCompleteDef + 2952); + *Game::varFxEffectDefHandle = reinterpret_cast(varWeaponCompleteDef + 2952); Game::Load_FxEffectDefHandle(false); - *Game::varFxEffectDefHandle = reinterpret_cast(varWeaponCompleteDef + 2956); + *Game::varFxEffectDefHandle = reinterpret_cast(varWeaponCompleteDef + 2956); Game::Load_FxEffectDefHandle(false); - *Game::varFxEffectDefHandle = reinterpret_cast(varWeaponCompleteDef + 2984); + *Game::varFxEffectDefHandle = reinterpret_cast(varWeaponCompleteDef + 2984); Game::Load_FxEffectDefHandle(false); } else if (Zones::ZoneVersion >= 359) @@ -784,24 +788,24 @@ namespace Components if (Zones::Version() >= 460) { - *Game::varsnd_alias_list_name = reinterpret_cast(varWeaponCompleteDef + 2988); + *Game::varsnd_alias_list_name = reinterpret_cast(varWeaponCompleteDef + 2988); Game::Load_SndAliasCustom(*Game::varsnd_alias_list_name); - *Game::varFxEffectDefHandle = reinterpret_cast(varWeaponCompleteDef + 2992); + *Game::varFxEffectDefHandle = reinterpret_cast(varWeaponCompleteDef + 2992); Game::Load_FxEffectDefHandle(false); - *Game::varFxEffectDefHandle = reinterpret_cast(varWeaponCompleteDef + 2996); + *Game::varFxEffectDefHandle = reinterpret_cast(varWeaponCompleteDef + 2996); Game::Load_FxEffectDefHandle(false); - *Game::varFxEffectDefHandle = reinterpret_cast(varWeaponCompleteDef + 3000); + *Game::varFxEffectDefHandle = reinterpret_cast(varWeaponCompleteDef + 3000); Game::Load_FxEffectDefHandle(false); - *Game::varFxEffectDefHandle = reinterpret_cast(varWeaponCompleteDef + 3004); + *Game::varFxEffectDefHandle = reinterpret_cast(varWeaponCompleteDef + 3004); Game::Load_FxEffectDefHandle(false); - *Game::varFxEffectDefHandle = reinterpret_cast(varWeaponCompleteDef + 3008); + *Game::varFxEffectDefHandle = reinterpret_cast(varWeaponCompleteDef + 3008); Game::Load_FxEffectDefHandle(false); - + *Game::varXString = reinterpret_cast(varWeaponCompleteDef + 3196); Game::Load_XString(false); } @@ -949,37 +953,37 @@ namespace Components if (Zones::Version() >= 460) { - *Game::varTracerDefPtr = reinterpret_cast(varWeaponCompleteDef + 3492 + offsetShift); + *Game::varTracerDefPtr = reinterpret_cast(varWeaponCompleteDef + 3492 + offsetShift); Game::Load_TracerDefPtr(false); - *Game::varTracerDefPtr = reinterpret_cast(varWeaponCompleteDef + 3496 + offsetShift); + *Game::varTracerDefPtr = reinterpret_cast(varWeaponCompleteDef + 3496 + offsetShift); Game::Load_TracerDefPtr(false); - *Game::varTracerDefPtr = reinterpret_cast(varWeaponCompleteDef + 3500 + offsetShift); + *Game::varTracerDefPtr = reinterpret_cast(varWeaponCompleteDef + 3500 + offsetShift); Game::Load_TracerDefPtr(false); - - *Game::varsnd_alias_list_name = reinterpret_cast(varWeaponCompleteDef + 3528 + offsetShift); + + *Game::varsnd_alias_list_name = reinterpret_cast(varWeaponCompleteDef + 3528 + offsetShift); Game::Load_SndAliasCustom(*Game::varsnd_alias_list_name); // 2848 - *Game::varFxEffectDefHandle = reinterpret_cast(varWeaponCompleteDef + 3532 + offsetShift); + *Game::varFxEffectDefHandle = reinterpret_cast(varWeaponCompleteDef + 3532 + offsetShift); Game::Load_FxEffectDefHandle(false); *Game::varXString = reinterpret_cast(varWeaponCompleteDef + 3536 + offsetShift); Game::Load_XString(false); - *Game::varsnd_alias_list_name = reinterpret_cast(varWeaponCompleteDef + 3552 + offsetShift); + *Game::varsnd_alias_list_name = reinterpret_cast(varWeaponCompleteDef + 3552 + offsetShift); Game::Load_SndAliasCustom(*Game::varsnd_alias_list_name); - *Game::varsnd_alias_list_name = reinterpret_cast(varWeaponCompleteDef + 3556 + offsetShift); + *Game::varsnd_alias_list_name = reinterpret_cast(varWeaponCompleteDef + 3556 + offsetShift); Game::Load_snd_alias_list_nameArray(false, 4); - *Game::varsnd_alias_list_name = reinterpret_cast(varWeaponCompleteDef + 3572 + offsetShift); + *Game::varsnd_alias_list_name = reinterpret_cast(varWeaponCompleteDef + 3572 + offsetShift); Game::Load_snd_alias_list_nameArray(false, 4); - *Game::varsnd_alias_list_name = reinterpret_cast(varWeaponCompleteDef + 3588 + offsetShift); + *Game::varsnd_alias_list_name = reinterpret_cast(varWeaponCompleteDef + 3588 + offsetShift); Game::Load_SndAliasCustom(*Game::varsnd_alias_list_name); - *Game::varsnd_alias_list_name = reinterpret_cast(varWeaponCompleteDef + 3592 + offsetShift); + *Game::varsnd_alias_list_name = reinterpret_cast(varWeaponCompleteDef + 3592 + offsetShift); Game::Load_SndAliasCustom(*Game::varsnd_alias_list_name); } else if (Zones::ZoneVersion >= 359) @@ -1045,7 +1049,7 @@ namespace Components { for (int i = 0, offset = 3660 + offsetShift; i < 6; ++i, offset += 4) { - *Game::varsnd_alias_list_name = reinterpret_cast(varWeaponCompleteDef + offset); + *Game::varsnd_alias_list_name = reinterpret_cast(varWeaponCompleteDef + offset); Game::Load_SndAliasCustom(*Game::varsnd_alias_list_name); } } @@ -1087,16 +1091,16 @@ namespace Components *Game::varXString = reinterpret_cast(varWeaponCompleteDef + 3732 + offsetShift); Game::Load_XString(false); - *Game::varMaterialHandle = reinterpret_cast(varWeaponCompleteDef + 3740 + offsetShift); + *Game::varMaterialHandle = reinterpret_cast(varWeaponCompleteDef + 3740 + offsetShift); Game::Load_MaterialHandle(false); - *Game::varMaterialHandle = reinterpret_cast(varWeaponCompleteDef + 3744 + offsetShift); + *Game::varMaterialHandle = reinterpret_cast(varWeaponCompleteDef + 3744 + offsetShift); Game::Load_MaterialHandle(false); - *Game::varMaterialHandle = reinterpret_cast(varWeaponCompleteDef + 3748 + offsetShift); + *Game::varMaterialHandle = reinterpret_cast(varWeaponCompleteDef + 3748 + offsetShift); Game::Load_MaterialHandle(false); - *Game::varMaterialHandle = reinterpret_cast(varWeaponCompleteDef + 3752 + offsetShift); + *Game::varMaterialHandle = reinterpret_cast(varWeaponCompleteDef + 3752 + offsetShift); Game::Load_MaterialHandle(false); } else if (Zones::ZoneVersion >= 359) @@ -1437,11 +1441,11 @@ namespace Components Game::Load_XString(false); *reinterpret_cast(varWeaponAttach + 8) = Game::DB_AllocStreamPos(3); - Zones::LoadWeaponAttachStuff(*reinterpret_cast(varWeaponAttach + 8), *reinterpret_cast(varWeaponAttach + 4)); + Zones::LoadWeaponAttachStuff(*reinterpret_cast(varWeaponAttach + 8), *reinterpret_cast(varWeaponAttach + 4)); Game::DB_PopStreamPos(); } - + } bool Zones::LoadMaterialShaderArgumentArray(bool atStreamStart, Game::MaterialShaderArgument* argument, int size) @@ -1451,7 +1455,7 @@ namespace Components Game::MaterialPass* curPass = *Game::varMaterialPass; int count = curPass->perPrimArgCount + curPass->perObjArgCount + curPass->stableArgCount; - + for (int i = 0; i < count && (Zones::ZoneVersion >= VERSION_ALPHA2); ++i) { Game::MaterialShaderArgument* arg = &argument[i]; @@ -1540,13 +1544,13 @@ namespace Components } else if (Zones::Version() == 461) { - static std::unordered_map mapped_constants = + static std::unordered_map mapped_constants = { // mp_raid { 33, 31 }, { 34, 32 }, { 36, 34 }, - { 39, 37 }, + { 39, 37 }, { 40, 38 }, { 42, 40 }, { 43, 41 }, @@ -1578,7 +1582,7 @@ namespace Components // dont know if this applies to 460 too, but I dont have 460 files to test if (!strncmp(techsetName, "wc_unlit_add", 12) || - !strncmp(techsetName, "wc_unlit_multiply", 17) ) + !strncmp(techsetName, "wc_unlit_multiply", 17)) { // fixes glass and water arg->u.codeConst.index = 116; @@ -1649,10 +1653,10 @@ namespace Components { if (arg->u.codeConst.index == 257) { - if (FastFiles::Current() != "mp_conflict" && FastFiles::Current() != "mp_derail_sh" && FastFiles::Current() != "mp_overwatch_sh" && + if (FastFiles::Current() != "mp_conflict" && FastFiles::Current() != "mp_derail_sh" && FastFiles::Current() != "mp_overwatch_sh" && FastFiles::Current() != "mp_con_spring" && FastFiles::Current() != "mp_resistance_sh" && FastFiles::Current() != "mp_lookout_sh") { - const auto varMaterialTechniqueSet = *reinterpret_cast(0x112AE8C); + const auto varMaterialTechniqueSet = *reinterpret_cast(0x112AE8C); if (varMaterialTechniqueSet->name && !strncmp(varMaterialTechniqueSet->name, "mc_", 3)) { // fixes trees @@ -1785,25 +1789,26 @@ namespace Components return result; } - void Zones::FixImageCategory(Game::GfxImage* image) { + void Zones::FixImageCategory(Game::GfxImage* image) + { // CODO makes use of additional enumerator values (9, 10, 11) that don't exist in IW4 // We have to translate them. 9 is for Reflection probes, 11 is for Compass, 10 is for Lightmap switch (image->category) { - case 9: - image->category = Game::ImageCategory::IMG_CATEGORY_AUTO_GENERATED; - break; - case 10: - image->category = Game::ImageCategory::IMG_CATEGORY_LIGHTMAP; - break; - case 11: - image->category = Game::ImageCategory::IMG_CATEGORY_LOAD_FROM_FILE; - break; + case 9: + image->category = Game::ImageCategory::IMG_CATEGORY_AUTO_GENERATED; + break; + case 10: + image->category = Game::ImageCategory::IMG_CATEGORY_LIGHTMAP; + break; + case 11: + image->category = Game::ImageCategory::IMG_CATEGORY_LOAD_FROM_FILE; + break; } - if (image->category > 7 || image->category < 0) { - + if (image->category > 7 || image->category < 0) + { #ifdef DEBUG if (IsDebuggerPresent()) __debugbreak(); #endif @@ -1833,15 +1838,15 @@ namespace Components if (Zones::Version() >= 423) { // don't read assets that are unused by codol, for some retarded reason their header is written in the FF anyway - if (*reinterpret_cast(buffer + (i * 8)) == Game::XAssetType::ASSET_TYPE_CLIPMAP_SP || - *reinterpret_cast(buffer + (i * 8)) == Game::XAssetType::ASSET_TYPE_GAMEWORLD_SP || + if (*reinterpret_cast(buffer + (i * 8)) == Game::XAssetType::ASSET_TYPE_CLIPMAP_SP || + *reinterpret_cast(buffer + (i * 8)) == Game::XAssetType::ASSET_TYPE_GAMEWORLD_SP || *reinterpret_cast(buffer + (i * 8)) == Game::XAssetType::ASSET_TYPE_GAMEWORLD_MP) { *reinterpret_cast(buffer + (i * 8)) = Game::XAssetType::ASSET_TYPE_UI_MAP; *reinterpret_cast(buffer + (i * 8) + 4) = nullptr; } } - + AssetHandler::Relocate(buffer + (i * 16), buffer + (i * 8) + 0, 4); AssetHandler::Relocate(buffer + (i * 16) + 8, buffer + (i * 8) + 4, 4); } @@ -1856,7 +1861,7 @@ namespace Components if (Zones::ZoneVersion >= 359) size += 4; // 446 amd above adds an additional technique if (Zones::ZoneVersion >= 446) size += 4; - + bool result = Game::Load_Stream(atStreamStart, buffer, size); if (Zones::ZoneVersion >= 359) @@ -1884,8 +1889,8 @@ namespace Components if (Zones::Version() >= 446) { - auto lastTechnique = **reinterpret_cast(0x112AEDC); - auto varMaterialTechniqueSet = **reinterpret_cast(0x112B070); + auto lastTechnique = **reinterpret_cast(0x112AEDC); + auto varMaterialTechniqueSet = **reinterpret_cast(0x112B070); // patch last technique to match iw4 varMaterialTechniqueSet->techniques[47] = lastTechnique; @@ -1916,7 +1921,7 @@ namespace Components std::memmove(buffer + 0x50, codol_material + 0x58, 0x10); std::memset(buffer + 0x48 + 5, 0, 3); - + // relocate pointers AssetHandler::Relocate(buffer + 10, buffer, 4); AssetHandler::Relocate(buffer + 0x1B, buffer + 4, 4); @@ -1929,12 +1934,12 @@ namespace Components reinterpret_cast(buffer)->stateBitsEntry[47] = codol_material[0x50]; } else if (Zones::ZoneVersion >= 359) - { + { struct material339_s { char drawSurfBegin[8]; // 4 //int surfaceTypeBits; - const char *name; + const char* name; char drawSurf[6]; union @@ -1975,11 +1980,6 @@ namespace Components //material->drawSurf[9] = material359.drawSurf[1]; //material->drawSurf[10] = material359.drawSurf[2]; //material->drawSurf[11] = material359.drawSurf[3]; - - if (material359.sGameFlags & 0x100) - { - //OutputDebugStringA(""); - } } return result; @@ -1988,7 +1988,7 @@ namespace Components int gfxLightMapExtraCount = 0; int* gfxLightMapExtraPtr1 = nullptr; int* gfxLightMapExtraPtr2 = nullptr; - + bool Zones::LoadGfxWorld(bool atStreamStart, char* buffer, int size) { gfxLightMapExtraPtr1 = nullptr; @@ -1997,7 +1997,7 @@ namespace Components if (Zones::Version() >= 460) size += 984; else if (Zones::Version() >= 423) size += 980; else if (Zones::Version() >= 359) size += 968; - + bool result = Game::Load_Stream(atStreamStart, buffer, size); // fix 4 byte difference in FF ver 460+ this turns 460+ into 423 @@ -2006,7 +2006,7 @@ namespace Components std::memmove(buffer + 0x34, buffer + 0x38, size - 0x38); AssetHandler::Relocate(buffer + 0x38, buffer + 0x34, size - 0x38); } - + // fix 12 byte difference in FF ver 423+, this turns 423+ into 359 if (Zones::Version() >= 423) { @@ -2019,7 +2019,7 @@ namespace Components std::memmove(buffer + 0x50 + 0x10, buffer + 0x50 + 0x1C, size - 0x6C); AssetHandler::Relocate(buffer + 0x50 + 0x1C, buffer + 0x50 + 0x10, size - 0x6C); } - + if (Zones::Version() >= 359) { int sunDiff = 8; // Stuff that is part of the sunflare we would overwrite @@ -2124,7 +2124,7 @@ namespace Components { return 19; } - + if (Zones::Version() >= VERSION_ALPHA2) { return 16; @@ -2258,7 +2258,7 @@ namespace Components retn } } - + __declspec(naked) void Zones::GetCurrentAssetTypeStub() { __asm pushad; @@ -2270,7 +2270,7 @@ namespace Components // get asset type mov eax, ds:0x112aa54 mov eax, [eax]; - + // log asset type // mov previousAssetType, currentAssetType; mov currentAssetType, eax; @@ -2295,10 +2295,10 @@ namespace Components if (Zones::Version() >= 446) { for (auto i = 0; i < count; i++) - { + { std::memcpy(buffer + (48 * i) + 0, buffer + (64 * i) + 0, 24); std::memcpy(buffer + (48 * i) + 24, buffer + (64 * i) + 32, 24); - + AssetHandler::Relocate(buffer + (64 * i) + 0, buffer + (48 * i) + 0, 24); AssetHandler::Relocate(buffer + (64 * i) + 32, buffer + (48 * i) + 24, 24); } @@ -2309,14 +2309,14 @@ namespace Components int currentGfxSurfaceIndex = 0; std::vector> gfxSurfaceExtraData; - + int Zones::LoadGfxXSurfaceArray(bool atStreamStart, char* buffer, int size) { currentGfxSurfaceIndex = 0; gfxSurfaceExtraData.clear(); int count = 0; - + if (Zones::Version() >= 423) { size /= 40; @@ -2370,7 +2370,7 @@ namespace Components int Zones::LoadGfxReflectionProbes(bool atStreamStart, char* buffer, int size) { int count = 0; - + if (Zones::Version() >= 446) { size /= 12; @@ -2386,7 +2386,7 @@ namespace Components { auto garbage = *reinterpret_cast(buffer + (20 * i) + 12); auto garbage_count = *reinterpret_cast(buffer + (20 * i) + 16); - + if (garbage != nullptr) { garbage = Game::DB_AllocStreamPos(3); @@ -2436,7 +2436,7 @@ namespace Components __declspec(naked) void Zones::LoadXModelColSurfPtr() { static auto DB_ConvertOffsetToPointer_Address = 0x4A82B0; - + __asm { cmp dword ptr[eax], 0; @@ -2536,7 +2536,7 @@ namespace Components static Game::MapEnts codolMapEnts; static Game::MapEnts* codolMapEntsPtr; - + int Zones::LoadMapEnts(bool atStreamStart, Game::MapEnts* buffer, int size) { if (Zones::Version() >= 446) @@ -2557,16 +2557,16 @@ namespace Components return Game::Load_Stream(atStreamStart, buffer, size); } - + ClipInfo* varClipInfoPtr; void Zones::Load_ClipInfo(bool atStreamStart) { AssertSize(ClipInfo, 64); AssertSize(Game::cplane_s, 20); AssertSize(Game::Bounds, 24); - + Game::Load_Stream(atStreamStart, varClipInfoPtr, sizeof ClipInfo); - + if (varClipInfoPtr->cPlanes) { if (varClipInfoPtr->cPlanes == reinterpret_cast(0xFFFFFFFF)) @@ -2624,7 +2624,7 @@ namespace Components if (varClipInfoPtr->cLeafBrushNodes) { if (varClipInfoPtr->cLeafBrushNodes == reinterpret_cast(0xFFFFFFFF)) - { + { varClipInfoPtr->cLeafBrushNodes = reinterpret_cast(Game::DB_AllocStreamPos(3)); *reinterpret_cast(0x112B130) = varClipInfoPtr->cLeafBrushNodes; Utils::Hook::Call(0x4C29D0)(true, varClipInfoPtr->numCLeafBrushNodes); @@ -2693,13 +2693,13 @@ namespace Components } } } - + int Zones::LoadClipMap(bool atStreamStart) { if (Zones::Version() >= 446) - { + { AssertOffset(codolClipMap_t, pInfo, 72); - + AssertSize(Game::cStaticModel_s, 76); AssertOffset(codolClipMap_t, numStaticModels, 76); AssertOffset(codolClipMap_t, staticModelList, 80); @@ -2708,7 +2708,7 @@ namespace Components Game::Load_Stream(atStreamStart, varClipMap, 256); Game::DB_PushStreamPos(3); - + *Game::varXString = &varClipMap->name; Game::Load_XString(false); @@ -2716,12 +2716,12 @@ namespace Components Load_ClipInfo(false); Game::DB_PushStreamPos(0); - + varClipInfoPtr = varClipMap->pInfo; ClipInfo** assetPointer = nullptr; if (varClipMap->pInfo) { - if (varClipMap->pInfo == reinterpret_cast(0xFFFFFFFF) || + if (varClipMap->pInfo == reinterpret_cast(0xFFFFFFFF) || varClipMap->pInfo == reinterpret_cast(0xFFFFFFFE)) { const auto needsToAllocPointer = varClipMap->pInfo == reinterpret_cast(0xFFFFFFFE); @@ -2843,7 +2843,7 @@ namespace Components } } } - + Game::DB_PopStreamPos(); } @@ -2888,7 +2888,7 @@ namespace Components AssertOffset(codolClipMap_t, dynEntCount[0], 196); AssertOffset(codolClipMap_t, dynEntCount[1], 198); - + // dynamic entity shit for (int i = 0; i < 2; i++) { @@ -2901,7 +2901,7 @@ namespace Components } Game::DB_PushStreamPos(2); - + for (int i = 0; i < 2; i++) { if (varClipMap->dynEntPoseList[i]) @@ -2928,7 +2928,7 @@ namespace Components } Game::DB_PopStreamPos(); - + Game::DB_PopStreamPos(); auto codolMap = new codolClipMap_t; @@ -2936,7 +2936,7 @@ namespace Components auto cancerMap = reinterpret_cast(varClipMap); auto iw4Map = reinterpret_cast(varClipMap); - + memcpy(&iw4Map->planeCount, &codolMap->info.numCPlanes, 8); memcpy(&iw4Map->numStaticModels, &codolMap->numStaticModels, 8); memcpy(&iw4Map->numMaterials, &codolMap->info.numMaterials, 24); @@ -2955,9 +2955,9 @@ namespace Components AssetHandler::Relocate(&cancerMap->numVerts, &iw4Map->vertCount, 52); AssetHandler::Relocate(&cancerMap->info.numBrushes, &iw4Map->numBrushes, 16); AssetHandler::Relocate(&cancerMap->smodelNodeCount, &iw4Map->smodelNodeCount, 48); - + delete codolMap; - + return 1; } else @@ -3028,17 +3028,18 @@ namespace Components std::unordered_map Zones::fileDataMap; std::mutex Zones::fileDataMutex; - + __declspec(naked) int Zones::FS_FOpenFileReadForThreadOriginal(const char*, int*, int) { __asm { - sub esp, 0x33C; - - push 0x643276; - retn; + sub esp, 0x33C + + push 0x643276 + ret } } + int Zones::FS_FOpenFileReadForThreadHook(const char* file, int* filePointer, int thread) { const auto retval = FS_FOpenFileReadForThreadOriginal(file, filePointer, thread); @@ -3052,15 +3053,15 @@ namespace Components // check if file should be skipped auto skipFile = false; - if (strlen(file) > 5 && ((strncmp(&file[strlen(file) - 4], ".iwi", 4) != 0))) + if (std::strlen(file) > 5 && ((std::strncmp(&file[strlen(file) - 4], ".iwi", 4) != 0))) { skipFile = true; } - else if (readSize >= 3 && (!memcmp(&fileBuffer[0], "IWi", 3))) + else if (readSize >= 3 && (std::memcmp(&fileBuffer[0], "IWi", 3) == 0)) { skipFile = true; } - + // if the header seems encrypted... if (fileBuffer.size() > 4 && readSize == retval && !skipFile) { @@ -3084,23 +3085,23 @@ namespace Components register_cipher(&aes_desc); auto aes = find_cipher("aes"); - + // attempt to decrypt the IWI symmetric_CTR ctr_state; - memset(&ctr_state, 0, sizeof(symmetric_CTR)); - + ZeroMemory(&ctr_state, sizeof(symmetric_CTR)); + // decryption keys std::uint8_t aesKey[24] = { 0x15, 0x9a, 0x03, 0x25, 0xe0, 0x75, 0x2e, 0x80, 0xc6, 0xc0, 0x94, 0x2a, 0x50, 0x5c, 0x1c, 0x68, 0x8c, 0x17, 0xef, 0x53, 0x99, 0xf8, 0x68, 0x3c }; std::uint32_t aesIV[4] = { 0x1010101, 0x1010101, 0x1010101, 0x1010101 }; auto strippedFileName = std::filesystem::path(file).filename().string(); auto nonce = HashCRC32StringInt(strippedFileName, strippedFileName.size()); - + std::uint8_t iv[16]; - memset(iv, 0, sizeof iv); - memcpy(iv, &nonce, 4); - memcpy(iv + 4, &unpackedSize, 4); - + std::memset(iv, 0, sizeof iv); + std::memcpy(iv, &nonce, 4); + std::memcpy(iv + 4, &unpackedSize, 4); + ctr_start(aes, reinterpret_cast(&aesIV[0]), &aesKey[0], sizeof aesKey, 0, CTR_COUNTER_BIG_ENDIAN, &ctr_state); // decrypt image @@ -3110,15 +3111,15 @@ namespace Components auto left = (packedSize - readDataSize); auto blockSize = (left > 0x8000) ? 0x8000 : left; - memcpy(iv + 8, &readDataSize, 4); - memcpy(iv + 12, &blockSize, 4); + std::memcpy(iv + 8, &readDataSize, 4); + std::memcpy(iv + 12, &blockSize, 4); ctr_setiv(iv, sizeof iv, &ctr_state); ctr_decrypt(reinterpret_cast(&encryptedData[readDataSize]), reinterpret_cast(&decryptedData[readDataSize]), blockSize, &ctr_state); readDataSize += blockSize; } - + ctr_done(&ctr_state); if (static_cast(decryptedData[0]) == 0x78) @@ -3135,7 +3136,7 @@ namespace Components // insert file data if (result == Z_OK) { - std::lock_guard $(fileDataMutex); + std::lock_guard _(fileDataMutex); fileDataMap[*filePointer] = data; return unpackedSize; } @@ -3145,7 +3146,7 @@ namespace Components // un-read data, file is apparently not encrypted Game::FS_Seek(*filePointer, 0, Game::FS_SEEK_SET); } - + return retval; } @@ -3153,50 +3154,51 @@ namespace Components { __asm { - push ecx; - mov eax, [esp + 0x10]; + push ecx + mov eax, [esp + 0x10] - push 0x4A04C5; - retn; + push 0x4A04C5 + ret } } + int Zones::FS_ReadHook(void* buffer, size_t size, int filePointer) { - std::lock_guard $(fileDataMutex); - - auto itr = fileDataMap.find(filePointer); - if (itr != fileDataMap.end()) + std::lock_guard _(fileDataMutex); + + if (auto itr = fileDataMap.find(filePointer); itr != fileDataMap.end()) { if (!itr->second.fileContents.empty()) { const auto readSize = std::min(size, itr->second.fileContents.size() - itr->second.readPos); - memcpy(buffer, &itr->second.fileContents[itr->second.readPos], readSize); + std::memcpy(buffer, &itr->second.fileContents[itr->second.readPos], readSize); itr->second.readPos += readSize; - return readSize; + return static_cast(readSize); } } return FS_ReadOriginal(buffer, size, filePointer); } + __declspec(naked) void Zones::FS_FCloseFileOriginal(int) { __asm { - mov eax, [esp + 4]; - push esi; + mov eax, [esp + 4] + push esi - push 0x462005; - retn; + push 0x462005 + ret } } + void Zones::FS_FCloseFileHook(int filePointer) { - std::lock_guard $(fileDataMutex); - + std::lock_guard _(fileDataMutex); + FS_FCloseFileOriginal(filePointer); - - const auto itr = fileDataMap.find(filePointer); - if (itr != fileDataMap.end()) + + if (const auto itr = fileDataMap.find(filePointer); itr != fileDataMap.end()) { fileDataMap.erase(itr); } @@ -3205,19 +3207,18 @@ namespace Components { __asm { - push esi; - mov esi, [esp + 8]; + push esi + mov esi, [esp + 8] - push 0x4A63D5; - retn; + push 0x4A63D5 + ret } } std::uint32_t Zones::FS_SeekHook(int fileHandle, int seekPosition, int seekOrigin) { - std::lock_guard $(fileDataMutex); + std::lock_guard _(fileDataMutex); - const auto itr = fileDataMap.find(fileHandle); - if (itr != fileDataMap.end()) + if (const auto itr = fileDataMap.find(fileHandle); itr != fileDataMap.end()) { if (seekOrigin == Game::FS_SEEK_SET) { @@ -3234,10 +3235,8 @@ namespace Components return itr->second.readPos; } - else - { - return FS_SeekOriginal(fileHandle, seekPosition, seekOrigin); - } + + return FS_SeekOriginal(fileHandle, seekPosition, seekOrigin); } __declspec(naked) void Zones::LoadMapTriggersModelPointer() @@ -3339,7 +3338,7 @@ namespace Components void Zones::LoadFxWorldAsset(Game::FxWorld** asset) { - Utils::Hook::Call(0x4857F0)(asset); + Utils::Hook::Call(0x4857F0)(asset); if (Zones::Version() >= 423 && asset && *asset) { @@ -3353,7 +3352,7 @@ namespace Components memset(&glassMap, 0, sizeof Game::GameWorldMp); memset(&glassData, 0, sizeof Game::G_GlassData); glassPieces.clear(); - + // generate glassPieces array const auto pieceCount = (*asset)->glassSys.initPieceCount; if (pieceCount > 0) @@ -3401,7 +3400,7 @@ namespace Components } } - return Utils::Hook::Call(0x47A690)(asset); + return Utils::Hook::Call(0x47A690)(asset); } // patch max file amount returned by Sys_ListFiles @@ -3438,7 +3437,7 @@ namespace Components } void Zones::LoadTracerDef(bool atStreamStart, Game::TracerDef* tracer, int size) - { + { if (Zones::Version() >= 460) { size = 116; @@ -3446,7 +3445,7 @@ namespace Components Game::Load_Stream(atStreamStart, tracer, size); *Game::varFxEffectDefHandle = nullptr; - + if (Zones::Version() >= 460) { *Game::varFxEffectDefHandle = reinterpret_cast(tracer + 8); @@ -3462,10 +3461,10 @@ namespace Components { Game::Load_FxEffectDefHandle(false); } - + Game::DB_PopStreamPos(); } - + char* Zones::ParseShellShock_Stub(const char** data_p) { auto token = Game::Com_Parse(data_p); @@ -3481,71 +3480,74 @@ namespace Components { Zones::ZoneVersion = 0; - Command::Add("decryptImages", [](Command::Params*) + if (ZoneBuilder::IsEnabled()) { - auto images = FileSystem::GetSysFileList("iw4x/images", "iwi"); - Logger::Print("decrypting {} images...\n", images.size()); - - for (auto& image : images) + Command::Add("decryptImages", [](Command::Params*) { - char* buffer = nullptr; - auto fileLength = Game::FS_ReadFile(Utils::String::VA("images/%s", image.data()), &buffer); + auto images = FileSystem::GetSysFileList("iw4x/images", "iwi"); + Logger::Print("decrypting {} images...\n", images.size()); - if (fileLength && buffer) + for (auto& image : images) { - if (!std::filesystem::exists("raw/images")) - { - std::filesystem::create_directories("raw/images"); - } + char* buffer = nullptr; + auto fileLength = Game::FS_ReadFile(Utils::String::Format("images/{}", image), &buffer); - if (!std::filesystem::exists(Utils::String::VA("raw/images/%s", image.data()))) + if (fileLength && buffer) { - const auto fp = fopen(Utils::String::VA("raw/images/%s", image.data()), "wb"); - if (fp) + if (!std::filesystem::exists("raw/images")) { - fwrite(buffer, fileLength, 1, fp); - fclose(fp); + std::filesystem::create_directories("raw/images"); } + + if (!std::filesystem::exists(Utils::String::Format("raw/images/{}", image))) + { + const auto fp = fopen(Utils::String::Format("raw/images/{}", image), "wb"); + if (fp) + { + fwrite(buffer, fileLength, 1, fp); + fclose(fp); + } + } + + Game::FS_FreeFile(buffer); } - - Game::FS_FreeFile(buffer); } - } - Logger::Print("decrypted {} images!\n", images.size()); - }); + Logger::Print("decrypted {} images!\n", images.size()); + }); - Command::Add("decryptSounds", []([[maybe_unused]] Command::Params* params) - { - auto sounds = FileSystem::GetSysFileList("iw4x/sound", "iwi"); - Logger::Print("decrypting {} sounds...\n", sounds.size()); - - for (auto& sound : sounds) + Command::Add("decryptSounds", []([[maybe_unused]] Command::Params* params) { - char* buffer = nullptr; - auto len = Game::FS_ReadFile(Utils::String::Format("sound/{}", sound), &buffer); + auto sounds = FileSystem::GetSysFileList("iw4x/sound", "iwi"); + Logger::Print("decrypting {} sounds...\n", sounds.size()); - if (len && buffer) + for (auto& sound : sounds) { - auto path = std::filesystem::path(sound.data()); - std::filesystem::create_directories("raw/sound" / path.parent_path()); + char* buffer = nullptr; + auto len = Game::FS_ReadFile(Utils::String::Format("sound/{}", sound), &buffer); - if (!std::filesystem::exists(std::format("raw/sound/{}", sound))) + if (len && buffer) { - FILE* fp; - if (!fopen_s(&fp, Utils::String::Format("raw/sound/{}", sound), "wb") && fp) + auto path = std::filesystem::path(sound.data()); + std::filesystem::create_directories("raw/sound" / path.parent_path()); + + if (!std::filesystem::exists(std::format("raw/sound/{}", sound))) { - fwrite(buffer, len, 1, fp); - fclose(fp); + FILE* fp; + if (!fopen_s(&fp, Utils::String::Format("raw/sound/{}", sound), "wb") && fp) + { + fwrite(buffer, len, 1, fp); + fclose(fp); + } } + + Game::FS_FreeFile(buffer); } - - Game::FS_FreeFile(buffer); } - } - Logger::Print("decrypted {} sounds!\n", sounds.size()); - }); + Logger::Print("decrypted {} sounds!\n", sounds.size()); + }); + } // patch max filecount Sys_ListFiles can return Utils::Hook::Set(0x45A66B, (maxFileCount + fileCountMultiplier) * 4); @@ -3554,7 +3556,7 @@ namespace Components Utils::Hook::Set(0x45A8CE, maxFileCount); Utils::Hook(0x45A806, RelocateFileCount, HOOK_CALL).install()->quick(); Utils::Hook(0x45A6A0, RelocateFileCount, HOOK_CALL).install()->quick(); - + #ifndef DEBUG // Ignore missing soundaliases for now // TODO: Include them in the dependency zone! @@ -3568,11 +3570,14 @@ namespace Components Utils::Hook::Set(0x418B31, 0x72); // encrypted images hooks - Utils::Hook(0x462000, Zones::FS_FCloseFileHook, HOOK_JUMP).install()->quick(); - Utils::Hook(0x4A04C0, Zones::FS_ReadHook, HOOK_JUMP).install()->quick(); - Utils::Hook(0x643270, Zones::FS_FOpenFileReadForThreadHook, HOOK_JUMP).install()->quick(); - Utils::Hook(0x4A63D0, Zones::FS_SeekHook, HOOK_JUMP).install()->quick(); - + if (ZoneBuilder::IsEnabled()) + { + Utils::Hook(0x462000, Zones::FS_FCloseFileHook, HOOK_JUMP).install()->quick(); + Utils::Hook(0x4A04C0, Zones::FS_ReadHook, HOOK_JUMP).install()->quick(); + Utils::Hook(0x643270, Zones::FS_FOpenFileReadForThreadHook, HOOK_JUMP).install()->quick(); + Utils::Hook(0x4A63D0, Zones::FS_SeekHook, HOOK_JUMP).install()->quick(); + } + // asset hooks Utils::Hook(0x47146D, Zones::LoadTracerDef, HOOK_CALL).install()->quick(); Utils::Hook(0x4714A3, Zones::LoadTracerDefFxEffect, HOOK_JUMP).install()->quick(); @@ -3626,7 +3631,7 @@ namespace Components Utils::Hook(0x4B4EA1, Zones::ParseShellShock_Stub, HOOK_CALL).install()->quick(); Utils::Hook(0x4B4F0C, Zones::ParseShellShock_Stub, HOOK_CALL).install()->quick(); - + Utils::Hook(0x4F4D3B, [] { if (Zones::ZoneVersion >= VERSION_ALPHA3) @@ -3641,7 +3646,7 @@ namespace Components }, HOOK_CALL).install()->quick(); // Change stream for images - Utils::Hook(0x4D3225, [] () + Utils::Hook(0x4D3225, []() { Game::DB_PushStreamPos((Zones::ZoneVersion >= 332) ? 3 : 0); }, HOOK_CALL).install()->quick(); @@ -3653,7 +3658,7 @@ namespace Components // Easy dirty disk debugging Utils::Hook::Set(0x4CF7F0, 0xC3CC); // disable _invoke_watson to allow debugging - Utils::Hook::Set(0x6B9602,0xCCCC); + Utils::Hook::Set(0x6B9602, 0xCCCC); #endif } } diff --git a/src/Components/Modules/Zones.hpp b/src/Components/Modules/Zones.hpp index 5c4899b0..97154438 100644 --- a/src/Components/Modules/Zones.hpp +++ b/src/Components/Modules/Zones.hpp @@ -21,15 +21,17 @@ namespace Components static void SetVersion(int version); - static int Version() { return Zones::ZoneVersion; } + static int Version() { return ZoneVersion; } private: - static int ZoneVersion; static int FxEffectIndex; static char* FxEffectStrings[64]; + static std::unordered_map fileDataMap; + static std::mutex fileDataMutex; + static bool CheckGameMapSp(int type); static void GameMapSpPatchStub(); @@ -82,8 +84,6 @@ namespace Components static void Load_ClipInfo(bool atStreamStart); static int LoadClipMap(bool atStreamStart); static uint32_t HashCRC32StringInt(const std::string& Value, uint32_t Initial); - static std::unordered_map fileDataMap; - static std::mutex fileDataMutex; static int FS_FOpenFileReadForThreadOriginal(const char*, int*, int); static int FS_FOpenFileReadForThreadHook(const char* file, int* filePointer, int thread); static int FS_ReadOriginal(void*, size_t, int); diff --git a/src/DllMain.cpp b/src/DllMain.cpp index eadda265..35281e3d 100644 --- a/src/DllMain.cpp +++ b/src/DllMain.cpp @@ -26,7 +26,6 @@ namespace Main void Uninitialize() { Components::Loader::Uninitialize(); - google::protobuf::ShutdownProtobufLibrary(); } __declspec(naked) void EntryPoint() @@ -57,7 +56,7 @@ BOOL APIENTRY DllMain(HINSTANCE /*hinstDLL*/, DWORD fdwReason, LPVOID /*lpvReser #ifndef DEBUG_BINARY_CHECK const auto* binary = reinterpret_cast(0x6F9358); - if (binary == nullptr || std::strcmp(binary, BASEGAME_NAME) != 0) + if (!binary || std::memcmp(binary, BASEGAME_NAME, 14) != 0) #endif { MessageBoxA(nullptr, diff --git a/src/Game/BothGames.cpp b/src/Game/BothGames.cpp index 723645e9..e3986666 100644 --- a/src/Game/BothGames.cpp +++ b/src/Game/BothGames.cpp @@ -8,4 +8,7 @@ namespace Game BG_LoadWeaponCompleteDefInternal_t BG_LoadWeaponCompleteDefInternal = BG_LoadWeaponCompleteDefInternal_t(0x4B5F10); BG_GetWeaponDef_t BG_GetWeaponDef = BG_GetWeaponDef_t(0x440EB0); BG_GetEntityTypeName_t BG_GetEntityTypeName = BG_GetEntityTypeName_t(0x43A0E0); + BG_IsWeaponValid_t BG_IsWeaponValid = BG_IsWeaponValid_t(0x415BA0); + BG_GetEquippedWeaponIndex_t BG_GetEquippedWeaponIndex = BG_GetEquippedWeaponIndex_t(0x4D8BA0); + BG_GetEquippedWeaponState_t BG_GetEquippedWeaponState = BG_GetEquippedWeaponState_t(0x4E79E0); } diff --git a/src/Game/BothGames.hpp b/src/Game/BothGames.hpp index 3f65a63b..946979b5 100644 --- a/src/Game/BothGames.hpp +++ b/src/Game/BothGames.hpp @@ -19,4 +19,13 @@ namespace Game typedef const char*(*BG_GetEntityTypeName_t)(int eType); extern BG_GetEntityTypeName_t BG_GetEntityTypeName; + + typedef bool(*BG_IsWeaponValid_t)(const playerState_s* ps, unsigned int weaponIndex); + extern BG_IsWeaponValid_t BG_IsWeaponValid; + + typedef int(*BG_GetEquippedWeaponIndex_t)(const playerState_s* ps, unsigned int weaponIndex); + extern BG_GetEquippedWeaponIndex_t BG_GetEquippedWeaponIndex; + + typedef PlayerEquippedWeaponState*(*BG_GetEquippedWeaponState_t)(playerState_s* ps, unsigned int weaponIndex); + extern BG_GetEquippedWeaponState_t BG_GetEquippedWeaponState; } diff --git a/src/Game/Client.cpp b/src/Game/Client.cpp index 2a690e29..77a8e6cf 100644 --- a/src/Game/Client.cpp +++ b/src/Game/Client.cpp @@ -27,6 +27,8 @@ namespace Game CL_WriteDemoMessage_t CL_WriteDemoMessage = CL_WriteDemoMessage_t(0x4707C0); CL_AddDebugStarWithText_t CL_AddDebugStarWithText = CL_AddDebugStarWithText_t(0x4D03C0); + Key_ClearStates_t Key_ClearStates = Key_ClearStates_t(0x5A7F00); + float* cl_angles = reinterpret_cast(0xB2F8D0); clientConnection_t* clientConnections = reinterpret_cast(0xA1E878); @@ -87,4 +89,33 @@ namespace Game CL_AddDebugStarWithText(point, color, MY_NULLTEXTCOLOR, nullptr, 1.0f, duration, fromServer); } + + void CL_MouseMove(const int localClientNum, Game::usercmd_s* cmd, const float frametime_base) + { + static const DWORD CL_MouseMove_t = 0x5A6240; + + __asm + { + pushad + mov ebx, cmd + mov eax, localClientNum + push frametime_base + call CL_MouseMove_t + add esp, 0x4 + popad + } + } + + void AdjustViewanglesForKeyboard(const int localClientNum) + { + static const DWORD AdjustViewanglesForKeyboard_t = 0x5A5D80; + + __asm + { + pushad + mov eax, localClientNum + call AdjustViewanglesForKeyboard_t + popad + } + } } diff --git a/src/Game/Client.hpp b/src/Game/Client.hpp index 13c96884..1b31d72a 100644 --- a/src/Game/Client.hpp +++ b/src/Game/Client.hpp @@ -74,6 +74,9 @@ namespace Game typedef void(*CL_AddDebugStarWithText_t)(const float* point, const float* starColor, const float* textColor, const char* string, float fontsize, int duration, int fromServer); extern CL_AddDebugStarWithText_t CL_AddDebugStarWithText; + typedef void(*Key_ClearStates_t)(int localClientNum); + extern Key_ClearStates_t Key_ClearStates; + extern float* cl_angles; extern clientConnection_t* clientConnections; @@ -86,12 +89,16 @@ namespace Game extern voiceCommunication_t* cl_voiceCommunication; - extern int CL_GetMaxXP(); - extern clientConnection_t* CL_GetLocalClientConnection(int localClientNum); - extern connstate_t CL_GetLocalClientConnectionState(int localClientNum); - extern voiceCommunication_t* CL_GetLocalClientVoiceCommunication(int localClientNum); - extern clientUIActive_t* CL_GetLocalClientUIGlobals(int localClientNum); - extern clientActive_t* CL_GetLocalClientGlobals(int localClientNum); + extern [[nodiscard]] int CL_GetMaxXP(); + extern [[nodiscard]] clientConnection_t* CL_GetLocalClientConnection(int localClientNum); + extern [[nodiscard]] connstate_t CL_GetLocalClientConnectionState(int localClientNum); + extern [[nodiscard]] voiceCommunication_t* CL_GetLocalClientVoiceCommunication(int localClientNum); + extern [[nodiscard]] clientUIActive_t* CL_GetLocalClientUIGlobals(int localClientNum); + extern [[nodiscard]] clientActive_t* CL_GetLocalClientGlobals(int localClientNum); extern void CL_AddDebugStar(const float* point, const float* color, int duration, int fromServer); + + extern void CL_MouseMove(int localClientNum, Game::usercmd_s* cmd, float frametime_base); + + extern void AdjustViewanglesForKeyboard(int localClientNum); } diff --git a/src/Game/Database.cpp b/src/Game/Database.cpp index 71e2926b..39c188b2 100644 --- a/src/Game/Database.cpp +++ b/src/Game/Database.cpp @@ -48,6 +48,9 @@ namespace Game const char* DB_GetXAssetName(XAsset* asset) { if (!asset) return ""; + + assert(asset->header.data); + return DB_GetXAssetNameHandlers[asset->type](&asset->header); } diff --git a/src/Game/Dvars.cpp b/src/Game/Dvars.cpp index be172a8a..cae637d4 100644 --- a/src/Game/Dvars.cpp +++ b/src/Game/Dvars.cpp @@ -29,18 +29,25 @@ namespace Game Dvar_SetBool_t Dvar_SetBool = Dvar_SetBool_t(0x4A9510); Dvar_SetBoolByName_t Dvar_SetBoolByName = Dvar_SetBoolByName_t(0x45C4D0); Dvar_SetFloat_t Dvar_SetFloat = Dvar_SetFloat_t(0x40BB20); + Dvar_SetFloatByName_t Dvar_SetFloatByName = Dvar_SetFloatByName_t(0x466320); Dvar_SetInt_t Dvar_SetInt = Dvar_SetInt_t(0x421DA0); const dvar_t** com_developer = reinterpret_cast(0x1AD78E8); const dvar_t** com_developer_script = reinterpret_cast(0x1AD8F10); const dvar_t** com_timescale = reinterpret_cast(0x1AD7920); + const dvar_t** com_maxFrameTime = reinterpret_cast(0x1AD78F4); const dvar_t** com_sv_running = reinterpret_cast(0x1AD7934); + const dvar_t** com_masterServerName = reinterpret_cast(0x1AD8F48); + const dvar_t** com_masterPort = reinterpret_cast(0x1AD8F30); const dvar_t** dev_timescale = reinterpret_cast(0x1AD8F20); const dvar_t** dvar_cheats = reinterpret_cast(0x63F3348); + const dvar_t** fs_cdpath = reinterpret_cast(0x63D0BB0); + const dvar_t** fs_basepath = reinterpret_cast(0x63D0CD4); const dvar_t** fs_gameDirVar = reinterpret_cast(0x63D0CC0); + const dvar_t** fs_homepath = reinterpret_cast(0x63D4FD8); const dvar_t** sv_hostname = reinterpret_cast(0x2098D98); const dvar_t** sv_gametype = reinterpret_cast(0x2098DD4); @@ -51,6 +58,8 @@ namespace Game const dvar_t** sv_cheats = reinterpret_cast(0x2098DE0); const dvar_t** sv_voiceQuality = reinterpret_cast(0x2098DB0); + const dvar_t** nextmap = reinterpret_cast(0x1AD7924); + const dvar_t** cl_showSend = reinterpret_cast(0xA1E870); const dvar_t** cl_voice = reinterpret_cast(0xB2BB44); const dvar_t** cl_ingame = reinterpret_cast(0xB2BB80); @@ -63,6 +72,11 @@ namespace Game const dvar_t** g_gametype = reinterpret_cast(0x1A45DC8); const dvar_t** g_password = reinterpret_cast(0x18835C0); + const dvar_t** cg_chatHeight = reinterpret_cast(0x7ED398); + const dvar_t** cg_chatTime = reinterpret_cast(0x9F5DE8); + const dvar_t** cg_scoreboardHeight = reinterpret_cast(0x9FD070); + const dvar_t** cg_scoreboardWidth = reinterpret_cast(0x9FD0AC); + const dvar_t** version = reinterpret_cast(0x1AD7930); const dvar_t** viewposNow = reinterpret_cast(0x9FD30C); @@ -77,6 +91,12 @@ namespace Game const dvar_t** loc_warnings = reinterpret_cast(0x62C8700); const dvar_t** loc_warningsAsErrors = reinterpret_cast(0x62C86FC); + const dvar_t** party_minplayers = reinterpret_cast(0x1081BFC); + const dvar_t** party_maxplayers = reinterpret_cast(0x1080998); + + const dvar_t** ip = reinterpret_cast(0x64A1DF8); + const dvar_t** port = reinterpret_cast(0x64A3004); + __declspec(naked) void Dvar_SetVariant(dvar_t*, DvarValue, DvarSetSource) { static DWORD Dvar_SetVariant_t = 0x647400; diff --git a/src/Game/Dvars.hpp b/src/Game/Dvars.hpp index f54eaba7..02cde645 100644 --- a/src/Game/Dvars.hpp +++ b/src/Game/Dvars.hpp @@ -54,6 +54,9 @@ namespace Game typedef void(*Dvar_SetFloat_t)(const dvar_t* dvar, float value); extern Dvar_SetFloat_t Dvar_SetFloat; + typedef void(*Dvar_SetFloatByName_t)(const char* dvarName, float value); + extern Dvar_SetFloatByName_t Dvar_SetFloatByName; + typedef void(*Dvar_SetInt_t)(const dvar_t* dvar, int integer); extern Dvar_SetInt_t Dvar_SetInt; @@ -84,13 +87,19 @@ namespace Game extern const dvar_t** com_developer; extern const dvar_t** com_developer_script; extern const dvar_t** com_timescale; + extern const dvar_t** com_maxFrameTime; extern const dvar_t** com_sv_running; + extern const dvar_t** com_masterServerName; + extern const dvar_t** com_masterPort; extern const dvar_t** dev_timescale; extern const dvar_t** dvar_cheats; + extern const dvar_t** fs_cdpath; + extern const dvar_t** fs_basepath; extern const dvar_t** fs_gameDirVar; + extern const dvar_t** fs_homepath; extern const dvar_t** sv_hostname; extern const dvar_t** sv_gametype; @@ -101,6 +110,8 @@ namespace Game extern const dvar_t** sv_cheats; extern const dvar_t** sv_voiceQuality; + extern const dvar_t** nextmap; + extern const dvar_t** cl_showSend; extern const dvar_t** cl_voice; extern const dvar_t** cl_ingame; @@ -113,6 +124,11 @@ namespace Game extern const dvar_t** g_gametype; extern const dvar_t** g_password; + extern const dvar_t** cg_chatHeight; + extern const dvar_t** cg_chatTime; + extern const dvar_t** cg_scoreboardHeight; + extern const dvar_t** cg_scoreboardWidth; + extern const dvar_t** version; extern const dvar_t** viewposNow; @@ -127,6 +143,12 @@ namespace Game extern const dvar_t** loc_warnings; extern const dvar_t** loc_warningsAsErrors; + extern const dvar_t** party_minplayers; + extern const dvar_t** party_maxplayers; + + extern const dvar_t** ip; + extern const dvar_t** port; + extern void Dvar_SetVariant(dvar_t* var, DvarValue value, DvarSetSource source); extern void Dvar_SetFromStringFromSource(const dvar_t* dvar, const char* string, DvarSetSource source); } diff --git a/src/Game/Engine/Hunk.cpp b/src/Game/Engine/Hunk.cpp new file mode 100644 index 00000000..57b27576 --- /dev/null +++ b/src/Game/Engine/Hunk.cpp @@ -0,0 +1,73 @@ +#include +#include "Hunk.hpp" + +namespace Game::Engine +{ + HunkUser* g_debugUser; + + HunkUser* Hunk_UserCreate(int maxSize, const char* name, bool fixed, int type) + { + assert((!(maxSize % (64 * 1024)))); + + auto* user = static_cast(Z_VirtualReserve(maxSize)); + Z_VirtualCommit(user, sizeof(HunkUser) - 4); + + user->end = reinterpret_cast(user->buf) + maxSize - (sizeof(HunkUser) - 4); + user->pos = reinterpret_cast(user->buf); + + assert((!(user->pos & 31))); + + user->maxSize = maxSize; + user->name = name; + user->current = user; + user->fixed = fixed; + user->type = type; + + assert(!user->next); + + return user; + } + + void Hunk_UserDestroy(HunkUser* user) + { + auto* current = user->next; + while (current) + { + auto* next = current->next; + Z_VirtualFree(current); + current = next; + } + + Z_VirtualFree(user); + } + + void Hunk_InitDebugMemory() + { + assert(Sys_IsMainThread()); + assert(!g_debugUser); + g_debugUser = Hunk_UserCreate(0x1000000, "Hunk_InitDebugMemory", false, 0); + } + + void Hunk_ShutdownDebugMemory() + { + assert(Sys_IsMainThread()); + assert(g_debugUser); + Hunk_UserDestroy(g_debugUser); + g_debugUser = nullptr; + } + + void* Hunk_AllocDebugMem(int size) + { + assert(Sys_IsMainThread()); + assert(g_debugUser); + return Hunk_UserAlloc(g_debugUser, size, 4); + } + + void Hunk_FreeDebugMem([[maybe_unused]] void* ptr) + { + assert(Sys_IsMainThread()); + assert(g_debugUser); + + // Let's hope it gets cleared by Hunk_ShutdownDebugMemory + } +} diff --git a/src/Game/Engine/Hunk.hpp b/src/Game/Engine/Hunk.hpp new file mode 100644 index 00000000..cbad0f9b --- /dev/null +++ b/src/Game/Engine/Hunk.hpp @@ -0,0 +1,17 @@ +#pragma once + +#define FIXED_HUNK_USER_COUNT 1 +#define VIRTUAL_HUNK_USER_MAX 128 + +namespace Game::Engine +{ + extern HunkUser* g_debugUser; + + extern HunkUser* Hunk_UserCreate(int maxSize, const char* name, bool fixed, int type); + extern void Hunk_UserDestroy(HunkUser* user); + + extern void Hunk_InitDebugMemory(); + extern void Hunk_ShutdownDebugMemory(); + extern void* Hunk_AllocDebugMem(int size); + extern void Hunk_FreeDebugMem(void* ptr); +} diff --git a/src/Game/FileSystem.cpp b/src/Game/FileSystem.cpp index eb51f368..4486f405 100644 --- a/src/Game/FileSystem.cpp +++ b/src/Game/FileSystem.cpp @@ -29,7 +29,7 @@ namespace Game FS_Delete_t FS_Delete = FS_Delete_t(0x48A5B0); FS_BuildOSPath_t FS_BuildOSPath = FS_BuildOSPath_t(0x4702C0); - searchpath_t** fs_searchpaths = reinterpret_cast(0x63D96E0); + searchpath_s** fs_searchpaths = reinterpret_cast(0x63D96E0); int FS_FOpenFileReadCurrentThread(const char* filename, int* file) { diff --git a/src/Game/FileSystem.hpp b/src/Game/FileSystem.hpp index a89f887f..41a0029e 100644 --- a/src/Game/FileSystem.hpp +++ b/src/Game/FileSystem.hpp @@ -80,7 +80,7 @@ namespace Game typedef void(*FS_BuildOSPath_t)(const char* base, const char* game, const char* qpath, char* ospath); extern FS_BuildOSPath_t FS_BuildOSPath; - extern searchpath_t** fs_searchpaths; + extern searchpath_s** fs_searchpaths; extern int FS_FOpenFileReadCurrentThread(const char* filename, int* file); diff --git a/src/Game/Functions.cpp b/src/Game/Functions.cpp index ed6f44c8..5cc612ed 100644 --- a/src/Game/Functions.cpp +++ b/src/Game/Functions.cpp @@ -43,8 +43,6 @@ namespace Game Svcmd_EntityList_f_t Svcmd_EntityList_f = Svcmd_EntityList_f_t(0x4B6A70); - GScr_LoadGameTypeScript_t GScr_LoadGameTypeScript = GScr_LoadGameTypeScript_t(0x4ED9A0); - Image_LoadFromFileWithReader_t Image_LoadFromFileWithReader = Image_LoadFromFileWithReader_t(0x53ABF0); Image_Release_t Image_Release = Image_Release_t(0x51F010); @@ -124,16 +122,13 @@ namespace Game Live_GetPrestige_t Live_GetPrestige = Live_GetPrestige_t(0x430F90); Live_GetXp_t Live_GetXp = Live_GetXp_t(0x404C60); Live_GetLocalClientName_t Live_GetLocalClientName = Live_GetLocalClientName_t(0x441FC0); + Live_IsSystemUiActive_t Live_IsSystemUiActive = Live_IsSystemUiActive_t(0x4F5CB0); LiveStorage_GetStat_t LiveStorage_GetStat = LiveStorage_GetStat_t(0x471F60); LiveStorage_SetStat_t LiveStorage_SetStat = LiveStorage_SetStat_t(0x4CC5D0); Scr_AddSourceBuffer_t Scr_AddSourceBuffer = Scr_AddSourceBuffer_t(0x61ABC0); - PC_ReadToken_t PC_ReadToken = PC_ReadToken_t(0x4ACCD0); - PC_ReadTokenHandle_t PC_ReadTokenHandle = PC_ReadTokenHandle_t(0x4D2060); - PC_SourceError_t PC_SourceError = PC_SourceError_t(0x467A00); - Party_GetMaxPlayers_t Party_GetMaxPlayers = Party_GetMaxPlayers_t(0x4F5D60); PartyHost_CountMembers_t PartyHost_CountMembers = PartyHost_CountMembers_t(0x497330); PartyHost_GetMemberAddressBySlot_t PartyHost_GetMemberAddressBySlot = PartyHost_GetMemberAddressBySlot_t(0x44E100); @@ -171,10 +166,6 @@ namespace Game Steam_JoinLobby_t Steam_JoinLobby = Steam_JoinLobby_t(0x49CF70); - StringTable_Lookup_t StringTable_Lookup = StringTable_Lookup_t(0x42F0E0); - StringTable_GetColumnValueForRow_t StringTable_GetColumnValueForRow = StringTable_GetColumnValueForRow_t(0x4F2C80); - StringTable_HashString_t StringTable_HashString = StringTable_HashString_t(0x475EB0); - TeleportPlayer_t TeleportPlayer = TeleportPlayer_t(0x496850); UI_AddMenuList_t UI_AddMenuList = UI_AddMenuList_t(0x4533C0); @@ -194,6 +185,7 @@ namespace Game UI_SafeTranslateString_t UI_SafeTranslateString = UI_SafeTranslateString_t(0x4F1700); UI_ReplaceConversions_t UI_ReplaceConversions = UI_ReplaceConversions_t(0x4E9740); UI_ParseInfos_t UI_ParseInfos = UI_ParseInfos_t(0x4027A0); + UI_GetMapDisplayName_t UI_GetMapDisplayName = UI_GetMapDisplayName_t(0x420700); Win_GetLanguage_t Win_GetLanguage = Win_GetLanguage_t(0x45CBA0); @@ -221,13 +213,6 @@ namespace Game Weapon_RocketLauncher_Fire_t Weapon_RocketLauncher_Fire = Weapon_RocketLauncher_Fire_t(0x424680); Bullet_Fire_t Bullet_Fire = Bullet_Fire_t(0x4402C0); - Jump_ClearState_t Jump_ClearState = Jump_ClearState_t(0x04B3890); - PM_playerTrace_t PM_playerTrace = PM_playerTrace_t(0x458980); - PM_Trace_t PM_Trace = PM_Trace_t(0x441F60); - PM_GetEffectiveStance_t PM_GetEffectiveStance = PM_GetEffectiveStance_t(0x412540); - PM_UpdateLean_t PM_UpdateLean = PM_UpdateLean_t(0x43DED0); - PM_IsSprinting_t PM_IsSprinting = PM_IsSprinting_t(0x4B3830); - IN_RecenterMouse_t IN_RecenterMouse = IN_RecenterMouse_t(0x463D80); IN_MouseMove_t IN_MouseMove = IN_MouseMove_t(0x64C490); @@ -247,9 +232,6 @@ namespace Game Vec2Normalize_t Vec2Normalize = Vec2Normalize_t(0x416F70); Vec2NormalizeFast_t Vec2NormalizeFast = Vec2NormalizeFast_t(0x5FC830); - Z_VirtualAlloc_t Z_VirtualAlloc = Z_VirtualAlloc_t(0x4CFBA0); - Z_Malloc_t Z_Malloc = Z_Malloc_t(0x4F3680); - I_strncpyz_t I_strncpyz = I_strncpyz_t(0x4D6F80); I_CleanStr_t I_CleanStr = I_CleanStr_t(0x4AD470); @@ -262,14 +244,22 @@ namespace Game LargeLocalBeginRight_t LargeLocalBeginRight = LargeLocalBeginRight_t(0x644140); LargeLocalReset_t LargeLocalReset = LargeLocalReset_t(0x430630); + StructuredDataDef_GetAsset_t StructuredDataDef_GetAsset = StructuredDataDef_GetAsset_t(0x4D5C50); + + StringTable_Lookup_t StringTable_Lookup = StringTable_Lookup_t(0x42F0E0); + StringTable_HashString_t StringTable_HashString = StringTable_HashString_t(0x475EB0); + StringTable_GetAsset_FastFile_t StringTable_GetAsset_FastFile = StringTable_GetAsset_FastFile_t(0x41A0B0); + StringTable_LookupRowNumForValue_t StringTable_LookupRowNumForValue = StringTable_LookupRowNumForValue_t(0x4AC180); + StringTable_GetColumnValueForRow_t StringTable_GetColumnValueForRow = StringTable_GetColumnValueForRow_t(0x4F2C80); + longjmp_internal_t longjmp_internal = longjmp_internal_t(0x6B8898); CmdArgs* cmd_args = reinterpret_cast(0x1AAC5D0); CmdArgs* sv_cmd_args = reinterpret_cast(0x1ACF8A0); - cmd_function_t** cmd_functions = reinterpret_cast(0x1AAC658); + cmd_function_s** cmd_functions = reinterpret_cast(0x1AAC658); - source_t** sourceFiles = reinterpret_cast(0x7C4A98); + source_s** sourceFiles = reinterpret_cast(0x7C4A98); float* cgameFOVSensitivityScale = reinterpret_cast(0xB2F884); @@ -290,11 +280,6 @@ namespace Game int* numIP = reinterpret_cast(0x64A1E68); netIP_t* localIP = reinterpret_cast(0x64A1E28); - int* demoFile = reinterpret_cast(0xA5EA1C); - int* demoPlaying = reinterpret_cast(0xA5EA0C); - int* demoRecording = reinterpret_cast(0xA5EA08); - int* serverMessageSequence = reinterpret_cast(0xA3E9B4); - netadr_t* connectedHost = reinterpret_cast(0xA1E888); SOCKET* ip_socket = reinterpret_cast(0x64A3008); @@ -400,6 +385,7 @@ namespace Game int* ui_arenaBufPos = reinterpret_cast(0x62D278C); punctuation_s* default_punctuations = reinterpret_cast(0x797F80); + int* numtokens = reinterpret_cast(0x7C4BA0); bool* s_havePlaylists = reinterpret_cast(0x1AD3680); @@ -791,7 +777,6 @@ namespace Game I_strncpyz_s(dest, destsize, src, destsize); } -#pragma optimize("", off) __declspec(naked) float UI_GetScoreboardLeft(void* /*a1*/) { __asm @@ -1203,5 +1188,16 @@ namespace Game return answer; } -#pragma optimize("", on) + void Player_SwitchToWeapon(gentity_s* player) + { + static DWORD Player_SwitchToWeapon_t = 0x5D97B0; + + __asm + { + pushad + mov ebx, player + call Player_SwitchToWeapon_t + popad + } + } } diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index 45ed25bf..53f65385 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -51,10 +51,10 @@ namespace Game typedef void(*CG_SetupWeaponDef_t)(int localClientNum, unsigned int weapIndex); extern CG_SetupWeaponDef_t CG_SetupWeaponDef; - typedef void(*Cmd_AddCommand_t)(const char* cmdName, void(*function), cmd_function_t* allocedCmd, int isKey); + typedef void(*Cmd_AddCommand_t)(const char* cmdName, void(*function), cmd_function_s* allocedCmd, int isKey); extern Cmd_AddCommand_t Cmd_AddCommand; - typedef void(*Cmd_AddServerCommand_t)(const char* name, void(*callback), cmd_function_t* data); + typedef void(*Cmd_AddServerCommand_t)(const char* name, void(*callback), cmd_function_s* data); extern Cmd_AddServerCommand_t Cmd_AddServerCommand; typedef void(*Cmd_ExecuteSingleCommand_t)(int localClientNum, int controllerIndex, const char* cmd); @@ -84,9 +84,6 @@ namespace Game typedef void(*Svcmd_EntityList_f_t)(); extern Svcmd_EntityList_f_t Svcmd_EntityList_f; - typedef void(*GScr_LoadGameTypeScript_t)(); - extern GScr_LoadGameTypeScript_t GScr_LoadGameTypeScript; - typedef int(*Reader_t)(char const*, int *); typedef bool(*Image_LoadFromFileWithReader_t)(GfxImage* image, Reader_t reader); @@ -205,6 +202,9 @@ namespace Game typedef int(*UI_ParseInfos_t)(const char* buf, int max, char** infos); extern UI_ParseInfos_t UI_ParseInfos; + + typedef const char*(*UI_GetMapDisplayName_t)(const char* pszMap); + extern UI_GetMapDisplayName_t UI_GetMapDisplayName; typedef void(*MSG_Init_t)(msg_t* buf, unsigned char* data, int length); extern MSG_Init_t MSG_Init; @@ -272,7 +272,7 @@ namespace Game typedef bool(*NET_CompareAdr_t)(netadr_t a, netadr_t b); extern NET_CompareAdr_t NET_CompareAdr; - typedef void(*NET_DeferPacketToClient_t)(netadr_t *, msg_t *); + typedef void(*NET_DeferPacketToClient_t)(netadr_t*, msg_t*); extern NET_DeferPacketToClient_t NET_DeferPacketToClient; typedef const char* (*NET_ErrorString_t)(); @@ -284,19 +284,19 @@ namespace Game typedef bool(*NET_IsLocalAddress_t)(netadr_t adr); extern NET_IsLocalAddress_t NET_IsLocalAddress; - typedef int(*NET_StringToAdr_t)(const char *s, netadr_t *a); + typedef int(*NET_StringToAdr_t)(const char* s, netadr_t* a); extern NET_StringToAdr_t NET_StringToAdr; - typedef void(*NET_OutOfBandPrint_t)(netsrc_t sock, netadr_t adr, const char *data); + typedef void(*NET_OutOfBandPrint_t)(netsrc_t sock, netadr_t adr, const char* data); extern NET_OutOfBandPrint_t NET_OutOfBandPrint; - typedef void(*NET_OutOfBandData_t)(netsrc_t sock, netadr_t adr, const char *format, int len); + typedef void(*NET_OutOfBandData_t)(netsrc_t sock, netadr_t adr, const char* format, int len); extern NET_OutOfBandData_t NET_OutOfBandData; typedef int(*NET_OutOfBandVoiceData_t)(netsrc_t sock, netadr_t adr, unsigned char* format, int len, bool voiceData); extern NET_OutOfBandVoiceData_t NET_OutOfBandVoiceData; - typedef void(*Live_MPAcceptInvite_t)(_XSESSION_INFO *hostInfo, const int controllerIndex, bool fromGameInvite); + typedef void(*Live_MPAcceptInvite_t)(_XSESSION_INFO *hostInfo, int controllerIndex, bool fromGameInvite); extern Live_MPAcceptInvite_t Live_MPAcceptInvite; typedef int(*Live_GetMapIndex_t)(const char* mapname); @@ -311,6 +311,9 @@ namespace Game typedef const char*(*Live_GetLocalClientName_t)(int controllerIndex); extern Live_GetLocalClientName_t Live_GetLocalClientName; + typedef bool(*Live_IsSystemUiActive_t)(); + extern Live_IsSystemUiActive_t Live_IsSystemUiActive; + typedef int(*LiveStorage_GetStat_t)(int controllerIndex, int index); extern LiveStorage_GetStat_t LiveStorage_GetStat; @@ -320,15 +323,6 @@ namespace Game typedef char*(*Scr_AddSourceBuffer_t)(const char* filename, const char* extFilename, const char* codePos, bool archive); extern Scr_AddSourceBuffer_t Scr_AddSourceBuffer; - typedef int(*PC_ReadToken_t)(source_t*, token_t*); - extern PC_ReadToken_t PC_ReadToken; - - typedef int(*PC_ReadTokenHandle_t)(int handle, pc_token_s *pc_token); - extern PC_ReadTokenHandle_t PC_ReadTokenHandle; - - typedef void(*PC_SourceError_t)(int, const char*, ...); - extern PC_SourceError_t PC_SourceError; - typedef int(*Party_GetMaxPlayers_t)(PartyData* party); extern Party_GetMaxPlayers_t Party_GetMaxPlayers; @@ -413,15 +407,6 @@ namespace Game typedef void(*Steam_JoinLobby_t)(SteamID, char); extern Steam_JoinLobby_t Steam_JoinLobby; - typedef const char*(*StringTable_Lookup_t)(const StringTable *table, const int comparisonColumn, const char *value, const int valueColumn); - extern StringTable_Lookup_t StringTable_Lookup; - - typedef const char* (*StringTable_GetColumnValueForRow_t)(const StringTable* table, int, int column); - extern StringTable_GetColumnValueForRow_t StringTable_GetColumnValueForRow; - - typedef int(*StringTable_HashString_t)(const char* string); - extern StringTable_HashString_t StringTable_HashString; - typedef void(*TeleportPlayer_t)(gentity_t* entity, float* pos, float* orientation); extern TeleportPlayer_t TeleportPlayer; @@ -515,24 +500,6 @@ namespace Game typedef int(*Bullet_Fire_t)(gentity_s* attacker, float spread, weaponParms* wp, gentity_s* weaponEnt, PlayerHandIndex hand, int gameTime); extern Bullet_Fire_t Bullet_Fire; - typedef void(*Jump_ClearState_t)(playerState_s* ps); - extern Jump_ClearState_t Jump_ClearState; - - typedef void(*PM_playerTrace_t)(pmove_s* pm, trace_t* results, const float* start, const float* end, const Bounds* bounds, int passEntityNum, int contentMask); - extern PM_playerTrace_t PM_playerTrace; - - typedef void(*PM_Trace_t)(pmove_s* pm, trace_t* results, const float* start, const float* end, const Bounds* bounds, int passEntityNum, int contentMask); - extern PM_Trace_t PM_Trace; - - typedef EffectiveStance(*PM_GetEffectiveStance_t)(const playerState_s* ps); - extern PM_GetEffectiveStance_t PM_GetEffectiveStance; - - typedef void(*PM_UpdateLean_t)(playerState_s* ps, float msec, usercmd_s* cmd, void(*capsuleTrace)(trace_t*, const float*, const float*, const Bounds*, int, int)); - extern PM_UpdateLean_t PM_UpdateLean; - - typedef bool(*PM_IsSprinting_t)(const playerState_s* ps); - extern PM_IsSprinting_t PM_IsSprinting; - typedef void(*IN_RecenterMouse_t)(); extern IN_RecenterMouse_t IN_RecenterMouse; @@ -569,12 +536,6 @@ namespace Game typedef void(*Vec2NormalizeFast_t)(float* v); extern Vec2NormalizeFast_t Vec2NormalizeFast; - typedef void*(*Z_VirtualAlloc_t)(int size); - extern Z_VirtualAlloc_t Z_VirtualAlloc; - - typedef void*(*Z_Malloc_t)(int size); - extern Z_Malloc_t Z_Malloc; - typedef void(*I_strncpyz_t)(char* dest, const char* src, int destsize); extern I_strncpyz_t I_strncpyz; @@ -599,6 +560,24 @@ namespace Game typedef void(*LargeLocalReset_t)(); extern LargeLocalReset_t LargeLocalReset; + typedef StructuredDataDef*(*StructuredDataDef_GetAsset_t)(const char* filename, unsigned int maxSize); + extern StructuredDataDef_GetAsset_t StructuredDataDef_GetAsset; + + typedef void(*StringTable_GetAsset_FastFile_t)(const char* filename, const StringTable** tablePtr); + extern StringTable_GetAsset_FastFile_t StringTable_GetAsset_FastFile; + + typedef const char*(*StringTable_Lookup_t)(const StringTable* table, const int comparisonColumn, const char* value, const int valueColumn); + extern StringTable_Lookup_t StringTable_Lookup; + + typedef int(*StringTable_HashString_t)(const char* string); + extern StringTable_HashString_t StringTable_HashString; + + typedef int(*StringTable_LookupRowNumForValue_t)(const StringTable* table, int comparisonColumn, const char* value); + extern StringTable_LookupRowNumForValue_t StringTable_LookupRowNumForValue; + + typedef const char*(*StringTable_GetColumnValueForRow_t)(const StringTable*, int row, int column); + extern StringTable_GetColumnValueForRow_t StringTable_GetColumnValueForRow; + typedef void(*longjmp_internal_t)(jmp_buf env, int status); extern longjmp_internal_t longjmp_internal; @@ -606,15 +585,17 @@ namespace Game constexpr std::size_t MAX_LOCAL_CLIENTS = 1; constexpr std::size_t MAX_CLIENTS = 18; + constexpr auto MAX_CMD_BUFFER = 0x10000; + constexpr auto MAX_CMD_LINE = 0x1000; constexpr auto CMD_MAX_NESTING = 8; extern CmdArgs* cmd_args; extern CmdArgs* sv_cmd_args; - extern cmd_function_t** cmd_functions; + extern cmd_function_s** cmd_functions; extern float* cgameFOVSensitivityScale; - extern source_t** sourceFiles; + extern source_s** sourceFiles; extern UiContext* uiContext; @@ -633,11 +614,6 @@ namespace Game extern int* numIP; extern netIP_t* localIP; - extern int* demoFile; - extern int* demoPlaying; - extern int* demoRecording; - extern int* serverMessageSequence; - extern netadr_t* connectedHost; extern SOCKET* ip_socket; @@ -747,6 +723,7 @@ namespace Game extern int* ui_arenaBufPos; extern punctuation_s* default_punctuations; + extern int* numtokens; extern bool* s_havePlaylists; @@ -810,4 +787,6 @@ namespace Game void I_strncpyz_s(char* dest, std::size_t destsize, const char* src, std::size_t count); void I_strcpy(char* dest, std::size_t destsize, const char* src); + + void Player_SwitchToWeapon(gentity_s* player); } diff --git a/src/Game/Game.hpp b/src/Game/Game.hpp index 458d6b20..6974a034 100644 --- a/src/Game/Game.hpp +++ b/src/Game/Game.hpp @@ -7,9 +7,12 @@ #include "FileSystem.hpp" #include "Functions.hpp" #include "Dvars.hpp" +#include "PlayerMovement.hpp" +#include "PreProcessor.hpp" #include "Script.hpp" #include "Server.hpp" #include "System.hpp" +#include "Zone.hpp" namespace Game { diff --git a/src/Game/PlayerMovement.cpp b/src/Game/PlayerMovement.cpp new file mode 100644 index 00000000..b19a3825 --- /dev/null +++ b/src/Game/PlayerMovement.cpp @@ -0,0 +1,11 @@ +#include + +namespace Game +{ + Jump_ClearState_t Jump_ClearState = Jump_ClearState_t(0x04B3890); + PM_playerTrace_t PM_playerTrace = PM_playerTrace_t(0x458980); + PM_Trace_t PM_Trace = PM_Trace_t(0x441F60); + PM_GetEffectiveStance_t PM_GetEffectiveStance = PM_GetEffectiveStance_t(0x412540); + PM_UpdateLean_t PM_UpdateLean = PM_UpdateLean_t(0x43DED0); + PM_IsSprinting_t PM_IsSprinting = PM_IsSprinting_t(0x4B3830); +} diff --git a/src/Game/PlayerMovement.hpp b/src/Game/PlayerMovement.hpp new file mode 100644 index 00000000..3efafee8 --- /dev/null +++ b/src/Game/PlayerMovement.hpp @@ -0,0 +1,22 @@ +#pragma once + +namespace Game +{ + typedef void(*Jump_ClearState_t)(playerState_s* ps); + extern Jump_ClearState_t Jump_ClearState; + + typedef void(*PM_playerTrace_t)(pmove_s* pm, trace_t* results, const float* start, const float* end, const Bounds* bounds, int passEntityNum, int contentMask); + extern PM_playerTrace_t PM_playerTrace; + + typedef void(*PM_Trace_t)(pmove_s* pm, trace_t* results, const float* start, const float* end, const Bounds* bounds, int passEntityNum, int contentMask); + extern PM_Trace_t PM_Trace; + + typedef EffectiveStance(*PM_GetEffectiveStance_t)(const playerState_s* ps); + extern PM_GetEffectiveStance_t PM_GetEffectiveStance; + + typedef void(*PM_UpdateLean_t)(playerState_s* ps, float msec, usercmd_s* cmd, void(*capsuleTrace)(trace_t*, const float*, const float*, const Bounds*, int, int)); + extern PM_UpdateLean_t PM_UpdateLean; + + typedef bool(*PM_IsSprinting_t)(const playerState_s* ps); + extern PM_IsSprinting_t PM_IsSprinting; +} diff --git a/src/Game/PreProcessor.cpp b/src/Game/PreProcessor.cpp new file mode 100644 index 00000000..2f3c9e86 --- /dev/null +++ b/src/Game/PreProcessor.cpp @@ -0,0 +1,86 @@ +#include + +namespace Game +{ + PC_FreeDefine_t PC_FreeDefine = PC_FreeDefine_t(0x4E0D60); + PC_FindHashedDefine_t PC_FindHashedDefine = PC_FindHashedDefine_t(0x421E00); + PC_ReadToken_t PC_ReadToken = PC_ReadToken_t(0x4ACCD0); + PC_ReadTokenHandle_t PC_ReadTokenHandle = PC_ReadTokenHandle_t(0x4D2060); + PC_ReadSourceToken_t PC_ReadSourceToken = PC_ReadSourceToken_t(0x4B16F0); + PC_UnreadSourceToken_t PC_UnreadSourceToken = PC_UnreadSourceToken_t(0x47CD00); + PC_SourceError_t PC_SourceError = PC_SourceError_t(0x467A00); + + SourceError_t SourceError = SourceError_t(0x44C6C0); + + PC_Directive_if_def_t PC_Directive_if_def = PC_Directive_if_def_t(0x490A70); + + PC_Directive_if_t PC_Directive_if = PC_Directive_if_t(0x486220); + PC_Directive_ifdef_t PC_Directive_ifdef = PC_Directive_ifdef_t(0x4F4ED0); + PC_Directive_ifndef_t PC_Directive_ifndef = PC_Directive_ifndef_t(0x42EF10); + PC_Directive_elif_t PC_Directive_elif = PC_Directive_elif_t(0x41AAB0); + PC_Directive_else_t PC_Directive_else = PC_Directive_else_t(0x4B55B0); + PC_Directive_endif_t PC_Directive_endif = PC_Directive_endif_t(0x491920); + PC_Directive_include_t PC_Directive_include = PC_Directive_include_t(0x495310); + PC_Directive_define_t PC_Directive_define = PC_Directive_define_t(0x42E460); + PC_Directive_undef_t PC_Directive_undef = PC_Directive_undef_t(0x4E1820); + PC_Directive_line_t PC_Directive_line = PC_Directive_line_t(0x4FD8A0); + PC_Directive_error_t PC_Directive_error = PC_Directive_error_t(0x494AA0); + PC_Directive_pragma_t PC_Directive_pragma = PC_Directive_pragma_t(0x42C160); + PC_Directive_eval_t PC_Directive_eval = PC_Directive_eval_t(0x57DB20); + PC_Directive_evalfloat_t PC_Directive_evalfloat = PC_Directive_evalfloat_t(0x4BC2A0); + + __declspec(naked) int PC_ReadLine([[maybe_unused]] source_s* source, [[maybe_unused]] token_s* token, [[maybe_unused]] bool expandDefines) + { + static const DWORD PC_ReadLine_t = 0x57D830; + + __asm + { + push eax + pushad + + mov ebx, [esp + 0x24 + 0x4] // source + + push [esp + 0x24 + 0xC] // expandDefines + push [esp + 0x24 + 0xC] // token + call PC_ReadLine_t + add esp, 0x8 + + mov [esp + 0x20], eax + popad + pop eax + + ret + } + } + + void PC_PushIndent(source_s* source, int type_, int skip) + { + static const DWORD PC_PushIndent_t = 0x57D740; + + __asm + { + pushad + mov edi, skip + mov esi, source + push type_ + call PC_PushIndent_t + add esp, 0x4 + popad + } + } + + void PC_PopIndent(source_s* source, int* type_, int* skip) + { + static const DWORD PC_PopIndent_t = 0x57D780; + + __asm + { + pushad + mov edx, skip + mov eax, type_ + mov ecx, source + call PC_PopIndent_t + popad + } + } +} diff --git a/src/Game/PreProcessor.hpp b/src/Game/PreProcessor.hpp new file mode 100644 index 00000000..54f803bf --- /dev/null +++ b/src/Game/PreProcessor.hpp @@ -0,0 +1,77 @@ +#pragma once + +namespace Game +{ + typedef void(*PC_FreeDefine_t)(define_s* define); + extern PC_FreeDefine_t PC_FreeDefine; + + typedef define_s*(*PC_FindHashedDefine_t)(define_s** definehash, const char* name); + extern PC_FindHashedDefine_t PC_FindHashedDefine; + + typedef int(*PC_ReadToken_t)(source_s*, token_s*); + extern PC_ReadToken_t PC_ReadToken; + + typedef int(*PC_ReadTokenHandle_t)(int handle, pc_token_s* pc_token); + extern PC_ReadTokenHandle_t PC_ReadTokenHandle; + + typedef int(*PC_ReadSourceToken_t)(source_s* source, token_s* token); + extern PC_ReadSourceToken_t PC_ReadSourceToken; + + typedef int(*PC_UnreadSourceToken_t)(source_s* source, token_s* token); + extern PC_UnreadSourceToken_t PC_UnreadSourceToken; + + typedef void(*PC_SourceError_t)(int, const char*, ...); + extern PC_SourceError_t PC_SourceError; + + typedef void(*SourceError_t)(source_s* source, const char* str, ...); + extern SourceError_t SourceError; + + typedef int(*PC_Directive_if_def_t)(source_s* source, int type); + extern PC_Directive_if_def_t PC_Directive_if_def; + + typedef int(*PC_Directive_if_t)(source_s* source); + extern PC_Directive_if_t PC_Directive_if; + + typedef int(*PC_Directive_ifdef_t)(source_s* source); + extern PC_Directive_ifdef_t PC_Directive_ifdef; + + typedef int(*PC_Directive_ifndef_t)(source_s* source); + extern PC_Directive_ifndef_t PC_Directive_ifndef; + + typedef int(*PC_Directive_elif_t)(source_s* source); + extern PC_Directive_elif_t PC_Directive_elif; + + typedef int(*PC_Directive_else_t)(source_s* source); + extern PC_Directive_else_t PC_Directive_else; + + typedef int(*PC_Directive_endif_t)(source_s* source); + extern PC_Directive_endif_t PC_Directive_endif; + + typedef int(*PC_Directive_include_t)(source_s* source); + extern PC_Directive_include_t PC_Directive_include; + + typedef int(*PC_Directive_define_t)(source_s* source); + extern PC_Directive_define_t PC_Directive_define; + + typedef int(*PC_Directive_undef_t)(source_s* source); + extern PC_Directive_undef_t PC_Directive_undef; + + typedef int(*PC_Directive_line_t)(source_s* source); + extern PC_Directive_line_t PC_Directive_line; + + typedef int(*PC_Directive_error_t)(source_s* source); + extern PC_Directive_error_t PC_Directive_error; + + typedef int(*PC_Directive_pragma_t)(source_s* source); + extern PC_Directive_pragma_t PC_Directive_pragma; + + typedef int(*PC_Directive_eval_t)(source_s* source); + extern PC_Directive_eval_t PC_Directive_eval; + + typedef int(*PC_Directive_evalfloat_t)(source_s* source); + extern PC_Directive_evalfloat_t PC_Directive_evalfloat; + + extern int PC_ReadLine(source_s* source, token_s* token, bool expandDefines); + extern void PC_PushIndent(source_s* source, int type, int skip); + extern void PC_PopIndent(source_s* source, int* type, int* skip); +} diff --git a/src/Game/Script.cpp b/src/Game/Script.cpp index 4766e89b..1f1a62d0 100644 --- a/src/Game/Script.cpp +++ b/src/Game/Script.cpp @@ -4,8 +4,15 @@ namespace Game { AddRefToObject_t AddRefToObject = AddRefToObject_t(0x61C360); RemoveRefToObject_t RemoveRefToObject = RemoveRefToObject_t(0x437190); + RemoveRefToValue_t RemoveRefToValue = RemoveRefToValue_t(0x48E170); AllocObject_t AllocObject = AllocObject_t(0x434320); AddRefToValue_t AddRefToValue = AddRefToValue_t(0x482740); + FindVariable_t FindVariable = FindVariable_t(0x4AB650); + GetVariable_t GetVariable = GetVariable_t(0x419970); + RemoveVariable_t RemoveVariable = RemoveVariable_t(0x480B40); + FindObject_t FindObject = FindObject_t(0x4CF2F0); + GetObject_t GetObject = GetObject_t(0x48B9D0); + GetNewVariable_t GetNewVariable = GetNewVariable_t(0x4CC520); AllocThread_t AllocThread = AllocThread_t(0x4F78C0); VM_Execute_0_t VM_Execute_0 = VM_Execute_0_t(0x6222A0); @@ -16,7 +23,9 @@ namespace Game Scr_StartupGameType_t Scr_StartupGameType = Scr_StartupGameType_t(0x438720); Scr_LoadScript_t Scr_LoadScript = Scr_LoadScript_t(0x45D940); + Scr_ReadFile_FastFile_t Scr_ReadFile_FastFile = Scr_ReadFile_FastFile_t(0x61AAB0); Scr_GetFunctionHandle_t Scr_GetFunctionHandle = Scr_GetFunctionHandle_t(0x4234F0); + Scr_CreateCanonicalFilename_t Scr_CreateCanonicalFilename = Scr_CreateCanonicalFilename_t(0x4A0220); Scr_GetString_t Scr_GetString = Scr_GetString_t(0x425900); Scr_GetConstString_t Scr_GetConstString = Scr_GetConstString_t(0x494830); @@ -41,6 +50,7 @@ namespace Game Scr_Notify_t Scr_Notify = Scr_Notify_t(0x4A4750); Scr_NotifyLevel_t Scr_NotifyLevel = Scr_NotifyLevel_t(0x4D9C30); + Scr_ErrorInternal_t Scr_ErrorInternal = Scr_ErrorInternal_t(0x61DB10); Scr_Error_t Scr_Error = Scr_Error_t(0x61E8B0); Scr_ObjectError_t Scr_ObjectError = Scr_ObjectError_t(0x42EF40); Scr_ParamError_t Scr_ParamError = Scr_ParamError_t(0x4FBC70); @@ -60,6 +70,8 @@ namespace Game Scr_FreeHudElemConstStrings_t Scr_FreeHudElemConstStrings = Scr_FreeHudElemConstStrings_t(0x5E1120); + GScr_LoadGameTypeScript_t GScr_LoadGameTypeScript = GScr_LoadGameTypeScript_t(0x4ED9A0); + GetEntity_t GetEntity = GetEntity_t(0x4BC270); GetPlayerEntity_t GetPlayerEntity = GetPlayerEntity_t(0x49C4A0); @@ -75,10 +87,18 @@ namespace Game SL_AddRefToString_t SL_AddRefToString = SL_AddRefToString_t(0x4D9B00); SL_RemoveRefToString_t SL_RemoveRefToString = SL_RemoveRefToString_t(0x47CD70); + ScriptParse_t ScriptParse = ScriptParse_t(0x48A4F0); + ScriptCompile_t ScriptCompile = ScriptCompile_t(0x426B80); + scr_const_t* scr_const = reinterpret_cast(0x1AA2E00); scrVmPub_t* scrVmPub = reinterpret_cast(0x2040CF0); scrVarPub_t* scrVarPub = reinterpret_cast(0x201A408); + scrCompilePub_t* scrCompilePub = reinterpret_cast(0x1CDEEC0); + scrAnimPub_t* scrAnimPub = reinterpret_cast(0x1CDEAA0); + + char* g_EndPos = reinterpret_cast(0x2045498); + bool* g_loadedImpureScript = reinterpret_cast(0x1DC2208); game_hudelem_s* g_hudelems = reinterpret_cast(0x18565A8); diff --git a/src/Game/Script.hpp b/src/Game/Script.hpp index 7c998c50..ae3e633e 100644 --- a/src/Game/Script.hpp +++ b/src/Game/Script.hpp @@ -8,12 +8,33 @@ namespace Game typedef void(*RemoveRefToObject_t)(unsigned int id); extern RemoveRefToObject_t RemoveRefToObject; + typedef void(*RemoveRefToValue_t)(int type, VariableUnion u); + extern RemoveRefToValue_t RemoveRefToValue; + typedef unsigned int(*AllocObject_t)(); extern AllocObject_t AllocObject; typedef void(*AddRefToValue_t)(int type, VariableUnion u); extern AddRefToValue_t AddRefToValue; + typedef unsigned int(*FindVariable_t)(unsigned int parentId, unsigned int unsignedValue); + extern FindVariable_t FindVariable; + + typedef unsigned int(*GetVariable_t)(unsigned int parentId, unsigned int unsignedValue); + extern GetVariable_t GetVariable; + + typedef void(*RemoveVariable_t)(unsigned int parentId, unsigned int unsignedValue); + extern RemoveVariable_t RemoveVariable; + + typedef unsigned int(*FindObject_t)(unsigned int parentId, unsigned int id); + extern FindObject_t FindObject; + + typedef unsigned int(*GetObject_t)(unsigned int parentId, unsigned int id); + extern GetObject_t GetObject; + + typedef unsigned int(*GetNewVariable_t)(unsigned int parentId, unsigned int unsignedValue); + extern GetNewVariable_t GetNewVariable; + typedef unsigned int(*AllocThread_t)(unsigned int self); extern AllocThread_t AllocThread; @@ -44,7 +65,7 @@ namespace Game typedef void(*Scr_ShutdownAllocNode_t)(); extern Scr_ShutdownAllocNode_t Scr_ShutdownAllocNode; - typedef char* (*Scr_GetGameTypeNameForScript_t)(const char* pszGameTypeScript); + typedef char*(*Scr_GetGameTypeNameForScript_t)(const char* pszGameTypeScript); extern Scr_GetGameTypeNameForScript_t Scr_GetGameTypeNameForScript; typedef int(*Scr_IsValidGameType_t)(const char* pszGameType); @@ -59,6 +80,15 @@ namespace Game typedef int(*Scr_LoadScript_t)(const char*); extern Scr_LoadScript_t Scr_LoadScript; + typedef char*(*Scr_ReadFile_FastFile_t)(const char* filename, const char* extFilename, const char* codePos, const char* archive); + extern Scr_ReadFile_FastFile_t Scr_ReadFile_FastFile; + + typedef int(*Scr_GetFunctionHandle_t)(const char* filename, const char* name); + extern Scr_GetFunctionHandle_t Scr_GetFunctionHandle; + + typedef unsigned int(*Scr_CreateCanonicalFilename_t)(const char* filename); + extern Scr_CreateCanonicalFilename_t Scr_CreateCanonicalFilename; + typedef const char*(*Scr_GetString_t)(unsigned int index); extern Scr_GetString_t Scr_GetString; @@ -86,9 +116,6 @@ namespace Game typedef unsigned int(*Scr_GetEntityId_t)(int entnum, unsigned int classnum); extern Scr_GetEntityId_t Scr_GetEntityId; - typedef int(*Scr_GetFunctionHandle_t)(const char* filename, const char* name); - extern Scr_GetFunctionHandle_t Scr_GetFunctionHandle; - typedef int(*Scr_ExecThread_t)(int handle, unsigned int paramcount); extern Scr_ExecThread_t Scr_ExecThread; @@ -119,6 +146,9 @@ namespace Game typedef int(*Scr_GetPointerType_t)(unsigned int index); extern Scr_GetPointerType_t Scr_GetPointerType; + typedef void(*Scr_ErrorInternal_t)(); + extern Scr_ErrorInternal_t Scr_ErrorInternal; + typedef void(*Scr_Error_t)(const char* error); extern Scr_Error_t Scr_Error; @@ -149,6 +179,9 @@ namespace Game typedef void(*Scr_FreeHudElemConstStrings_t)(game_hudelem_s* hud); extern Scr_FreeHudElemConstStrings_t Scr_FreeHudElemConstStrings; + typedef void(*GScr_LoadGameTypeScript_t)(); + extern GScr_LoadGameTypeScript_t GScr_LoadGameTypeScript; + typedef gentity_s*(*GetPlayerEntity_t)(scr_entref_t entref); extern GetPlayerEntity_t GetPlayerEntity; @@ -176,6 +209,18 @@ namespace Game typedef void(*SL_RemoveRefToString_t)(unsigned int stringValue); extern SL_RemoveRefToString_t SL_RemoveRefToString; + typedef void(*ScriptParse_t)(sval_u* parseData, unsigned char user); + extern ScriptParse_t ScriptParse; + + typedef void(*ScriptCompile_t)(sval_u* val, unsigned int filePosId, unsigned int fileCountId, unsigned int scriptId, PrecacheEntry* entries, int entriesCount); + extern ScriptCompile_t ScriptCompile; + + constexpr auto MAX_OPCODE_LOOKUP_SIZE = 0x1000000; + constexpr auto MAX_SOURCEPOS_LOOKUP_SIZE = 0x800000; + constexpr auto MAX_SOURCEBUF_LOOKUP_SIZE = 0x40000; + + constexpr auto LOCAL_VAR_STACK_SIZE = 64; + extern void IncInParam(); extern void Scr_AddBool(int value); @@ -188,6 +233,11 @@ namespace Game extern scrVmPub_t* scrVmPub; extern scrVarPub_t* scrVarPub; + extern scrCompilePub_t* scrCompilePub; + extern scrAnimPub_t* scrAnimPub; + + extern char* g_EndPos; + extern bool* g_loadedImpureScript; extern game_hudelem_s* g_hudelems; } diff --git a/src/Game/Server.cpp b/src/Game/Server.cpp index c64f5811..448b5c00 100644 --- a/src/Game/Server.cpp +++ b/src/Game/Server.cpp @@ -20,6 +20,8 @@ namespace Game SV_GetPlayerByNum_t SV_GetPlayerByNum = SV_GetPlayerByNum_t(0x624390); SV_FindClientByAddress_t SV_FindClientByAddress = SV_FindClientByAddress_t(0x44F450); SV_WaitServer_t SV_WaitServer = SV_WaitServer_t(0x4256F0); + SV_GetClientPersistentDataBuffer_t SV_GetClientPersistentDataBuffer = SV_GetClientPersistentDataBuffer_t(0x4014C0); + SV_GetClientPersistentDataModifiedFlags_t SV_GetClientPersistentDataModifiedFlags = SV_GetClientPersistentDataModifiedFlags_t(0x4F4AC0); int* svs_time = reinterpret_cast(0x31D9384); int* sv_timeResidual = reinterpret_cast(0x2089E14); diff --git a/src/Game/Server.hpp b/src/Game/Server.hpp index 8fd62bba..b507c417 100644 --- a/src/Game/Server.hpp +++ b/src/Game/Server.hpp @@ -56,6 +56,12 @@ namespace Game typedef void(*SV_WaitServer_t)(); extern SV_WaitServer_t SV_WaitServer; + typedef char*(*SV_GetClientPersistentDataBuffer_t)(int clientNum); + extern SV_GetClientPersistentDataBuffer_t SV_GetClientPersistentDataBuffer; + + typedef char*(*SV_GetClientPersistentDataModifiedFlags_t)(int clientNum); + extern SV_GetClientPersistentDataModifiedFlags_t SV_GetClientPersistentDataModifiedFlags; + constexpr auto MAX_STATPACKETS = 7; extern int* svs_time; diff --git a/src/Game/Structs.hpp b/src/Game/Structs.hpp index f27366c1..c42eb789 100644 --- a/src/Game/Structs.hpp +++ b/src/Game/Structs.hpp @@ -4,6 +4,9 @@ #define NUM_CUSTOM_CLASSES 15 #define FX_ELEM_FIELD_COUNT 90 +#define MAX_QPATH 64 +#define MAX_OSPATH 256 + // This allows us to compile our structures in IDA, for easier reversing :3 #ifndef __cplusplus #define IDA @@ -614,7 +617,7 @@ namespace Game CLASS_NUM_COUNT = 0x6, }; - typedef enum + enum hitLocation_t { HITLOC_NONE, HITLOC_HELMET, @@ -637,7 +640,7 @@ namespace Game HITLOC_GUN, HITLOC_SHIELD, HITLOC_NUM - } hitLocation_t; + }; enum svscmd_type { @@ -664,7 +667,7 @@ namespace Game static_assert(sizeof(CmdArgs) == 0x84); - typedef struct cmd_function_s + struct cmd_function_s { cmd_function_s* next; const char* name; @@ -672,7 +675,7 @@ namespace Game const char* autoCompleteExt; void(__cdecl* function)(); int flags; - } cmd_function_t; + }; #pragma pack(push, 4) struct kbutton_t @@ -879,8 +882,8 @@ namespace Game { float normal[3]; float dist; - char type; - char pad[3]; + unsigned char type; + unsigned char pad[3]; }; struct cbrushside_t @@ -893,13 +896,13 @@ namespace Game struct cbrush_t { - unsigned __int16 numsides; - unsigned __int16 glassPieceIndex; + unsigned short numsides; + unsigned short glassPieceIndex; cbrushside_t* sides; - char* baseAdjacentSide; - __int16 axialMaterialNum[2][3]; - char firstAdjacentSideOffsets[2][3]; - char edgeCount[2][3]; + unsigned char* baseAdjacentSide; + unsigned short axialMaterialNum[2][3]; + unsigned char firstAdjacentSideOffsets[2][3]; + unsigned char edgeCount[2][3]; }; struct BrushWrapper @@ -1965,7 +1968,6 @@ namespace Game CMD_BUTTON_THROW = 1 << 19, }; -#pragma pack(push, 4) struct usercmd_s { int serverTime; @@ -1982,7 +1984,8 @@ namespace Game char selectedLocAngle; char remoteControlAngles[2]; }; -#pragma pack(pop) + + static_assert(sizeof(usercmd_s) == 0x28); enum trType_t { @@ -2096,7 +2099,7 @@ namespace Game int extrapolatedSnapshot; int newSnapshots; int serverId; - char mapname[64]; + char mapname[MAX_QPATH]; int parseEntitiesIndex; int parseClientsIndex; int mouseDx[2]; @@ -2494,6 +2497,123 @@ namespace Game SAT_COUNT = 0x4, }; + struct snd_volume_info_t + { + float volume; + float goalvolume; + float goalrate; + }; + + struct snd_channelvolgroup + { + snd_volume_info_t channelvol[64]; + bool active; + }; + + struct snd_background_info_t + { + float goalvolume; + float goalrate; + }; + + struct snd_enveffect + { + int roomtype; + float drylevel; + float drygoal; + float dryrate; + float wetlevel; + float wetgoal; + float wetrate; + bool active; + }; + + struct orientation_t + { + float origin[3]; + float axis[3][3]; + }; + + struct snd_listener + { + orientation_t orient; + float velocity; + int clientNum; + bool active; + }; + + struct snd_amplifier + { + snd_listener* listener; + int minRadius; + int maxRadius; + float falloffExp; + float minVol; + float maxVol; + }; + + struct snd_entchannel_info_t + { + char name[64]; + int priority; + bool is3d; + bool isRestricted; + bool isPausable; + int maxVoices; + int voiceCount; + }; + + struct snd_entchan_overrides_t + { + unsigned int isPausable[2]; + float timescaleLerp[64]; + }; + + enum SndFileLoadingState + { + SFLS_UNLOADED = 0x0, + SFLS_LOADING = 0x1, + SFLS_LOADED = 0x2, + }; + + struct SndFileSpecificChannelInfo + { + SndFileLoadingState loadingState; + int srcChannelCount; + int baserate; + }; + + union SndEntHandle + { + struct + { + unsigned int entIndex; + } field; + int handle; + }; + + enum SndLengthId + { + SndLengthNotify_Subtitle = 0x0, + SndLengthNotify_EntityCustom = 0x1, + SndLengthNotifyCount = 0x2, + }; + + struct sndLengthNotifyInfo + { + SndLengthId id[4]; + void* data[4]; + int count; + }; + + enum snd_alias_system_t + { + SASYS_UI = 0x0, + SASYS_CGAME = 0x1, + SASYS_GAME = 0x2, + SASYS_COUNT = 0x3, + }; + struct SoundFile { char type; @@ -2501,19 +2621,6 @@ namespace Game SoundFileRef u; }; - union $C8D87EB0090687D323381DFB7A82089C - { - float slavePercentage; - float masterPercentage; - }; - - struct SndCurve - { - const char* filename; - unsigned __int16 knotCount; - float knots[16][2]; - }; - struct MSSSpeakerLevels { int speaker; @@ -2534,7 +2641,34 @@ namespace Game MSSChannelMap channelMaps[2][2]; }; - const struct snd_alias_t + union SoundAliasFlags + { +#pragma warning(push) +#pragma warning(disable: 4201) + struct + { + unsigned int looping : 1; // & 1 / 0x1 / 0000 0000 0000 0001 + unsigned int isMaster : 1; // & 2 / 0x2 / 0000 0000 0000 0010 + unsigned int isSlave : 1; // & 4 / 0x4 / 0000 0000 0000 0100 + unsigned int fullDryLevel : 1; // & 8 / 0x8 / 0000 0000 0000 1000 + unsigned int noWetLevel : 1; // & 16 / 0x10 / 0000 0000 0001 0000 + unsigned int unknown : 1; // & 32 / 0x20 / 0000 0000 0010 0000 + unsigned int unk_is3D : 1; // & 64 / 0x40 / 0000 0000 0100 0000 // CONFIRMED IW4 IW5 + unsigned int type : 2; // & 384 / 0x180 / 0000 0001 1000 0000 // CONFIRMED IW4 IW5 + unsigned int channel : 6; // & 32256 / 0x7E00 / 0111 1110 0000 0000 // CONFIRMED IW4 IW5 + }; +#pragma warning(pop) + unsigned int intValue; + }; + + struct SndCurve + { + const char* filename; + unsigned __int16 knotCount; + float knots[16][2]; + }; + + struct snd_alias_t { const char* aliasName; const char* subtitle; @@ -2550,8 +2684,12 @@ namespace Game float distMin; float distMax; float velocityMin; - int flags; - $C8D87EB0090687D323381DFB7A82089C ___u15; + SoundAliasFlags flags; + union + { + float slavePercentage; + float masterPercentage; + } ___u15; float probability; float lfePercentage; float centerPercentage; @@ -2563,6 +2701,71 @@ namespace Game SpeakerMap* speakerMap; }; + struct snd_channel_info_t + { + SndFileSpecificChannelInfo soundFileInfo; + SndEntHandle sndEnt; + int entchannel; + int startDelay; + int looptime; + int totalMsec; + int playbackId; + sndLengthNotifyInfo lengthNotifyInfo; + float basevolume; + float pitch; + snd_alias_t* alias0; + snd_alias_t* alias1; + int saveIndex0; + int saveIndex1; + float lerp; + float org[3]; + float offset[3]; + bool paused; + bool master; + float timescaleLerp; + snd_alias_system_t system; + }; + + struct snd_local_t + { + bool Initialized2d; + bool Initialized3d; + bool paused; + int playbackIdCounter; + unsigned int playback_rate; + int playback_channels; + float timescale; + int pausetime; + int cpu; + struct + { + char buffer[16384]; + volatile int size; + bool compress; + } restore; + float volume; + snd_volume_info_t mastervol; + snd_channelvolgroup channelVolGroups[4]; + snd_channelvolgroup* channelvol; + snd_background_info_t background[4]; + int ambient_track; + float slaveLerp; + float masterPercentage; + snd_enveffect envEffects[5]; + snd_enveffect* effect; + snd_listener listeners[2]; + int time; + int looptime; + snd_amplifier amplifier; + snd_entchannel_info_t entchaninfo[64]; + snd_entchan_overrides_t entchanOverrides; + int entchannel_count; + snd_channel_info_t chaninfo[52]; + int max_2D_channels; + int max_3D_channels; + int max_stream_channels; + }; + struct Poly { float(*pts)[3]; @@ -2634,7 +2837,7 @@ namespace Game struct cLeafBrushNode_s { - char axis; + unsigned char axis; __int16 leafBrushCount; int contents; cLeafBrushNodeData_t data; @@ -2651,9 +2854,9 @@ namespace Game struct CollisionPartition { - char triCount; - char borderCount; - char firstVertSegment; + unsigned char triCount; + unsigned char borderCount; + unsigned char firstVertSegment; int firstTri; CollisionBorder* borders; }; @@ -2990,7 +3193,7 @@ namespace Game { const char* name; int isInUse; - int planeCount; + unsigned int planeCount; cplane_s* planes; unsigned int numStaticModels; cStaticModel_s* staticModelList; @@ -2999,7 +3202,7 @@ namespace Game unsigned int numBrushSides; cbrushside_t* brushsides; unsigned int numBrushEdges; - char* brushEdges; + unsigned char* brushEdges; unsigned int numNodes; cNode_t* nodes; unsigned int numLeafs; @@ -3011,15 +3214,15 @@ namespace Game unsigned int numLeafSurfaces; unsigned int* leafsurfaces; unsigned int vertCount; - float(*verts)[3]; - int triCount; + vec3_t* verts; + unsigned int triCount; unsigned __int16* triIndices; - char* triEdgeIsWalkable; - int borderCount; + unsigned char* triEdgeIsWalkable; + unsigned int borderCount; CollisionBorder* borders; int partitionCount; CollisionPartition* partitions; - int aabbTreeCount; + unsigned int aabbTreeCount; CollisionAabbTree* aabbTrees; unsigned int numSubModels; cmodel_t* cmodels; @@ -3391,9 +3594,9 @@ namespace Game float texCoordOrigin[2]; unsigned int supportMask; float areaX2; - char defIndex; - char vertCount; - char fanDataCount; + unsigned char defIndex; + unsigned char vertCount; + unsigned char fanDataCount; char pad[1]; }; @@ -5810,9 +6013,9 @@ namespace Game struct iwd_t { - char iwdFilename[256]; - char iwdBasename[256]; - char iwdGamename[256]; + char iwdFilename[MAX_OSPATH]; + char iwdBasename[MAX_OSPATH]; + char iwdGamename[MAX_OSPATH]; char* handle; int checksum; int pure_checksum; @@ -5851,12 +6054,13 @@ namespace Game char name[256]; }; - typedef struct { - char path[256]; // c:\quake3 - char gamedir[256]; // baseq3 - } directory_t; + struct directory_t + { + char path[MAX_OSPATH]; + char gamedir[MAX_OSPATH]; + }; - typedef struct searchpath_s + struct searchpath_s { searchpath_s* next; iwd_t* iwd; @@ -5865,7 +6069,7 @@ namespace Game int ignore; int ignorePureCheck; int language; - } searchpath_t; + }; struct SafeArea { @@ -5891,9 +6095,35 @@ namespace Game typedef char mapname_t[40]; + struct TraceExtents + { + float midPoint[3]; + float halfDelta[3]; + float halfDeltaAbs[3]; + float invDeltaAbs[3]; + float start[3]; + float end[3]; + }; + + struct TraceCheckCount + { + int global; + int* partitions; + }; + + struct TraceThreadInfo + { + TraceCheckCount checkcount; + }; + + struct CM_WorldTraceCallbacks + { + bool(*isGlassSolid)(unsigned int); + }; + struct traceWork_t { - /*TraceExtents*/int extents; + TraceExtents extents; float delta[3]; float deltaLen; float deltaLenSq; @@ -5908,8 +6138,8 @@ namespace Game float offset[3]; float radiusOffset[3]; float boundingRadius; - /*TraceThreadInfo*/ int threadInfo; - /*CM_WorldTraceCallbacks*/ void* callbacks; + TraceThreadInfo threadInfo; + CM_WorldTraceCallbacks* callbacks; }; struct gameState_t @@ -5919,6 +6149,38 @@ namespace Game int dataCount; }; + struct PrecacheEntry + { + unsigned __int16 filename; + bool include; + unsigned int sourcePos; + }; + + struct XAnimParent + { + unsigned short flags; + unsigned short children; + }; + + struct XAnimEntry + { + unsigned short numAnims; + unsigned short parent; + union + { + XAnimParts* parts; + XAnimParent animParent; + } ___u2; + }; + + struct XAnim_s + { + unsigned int size; + const char* debugName; + const char** debugAnimNames; + XAnimEntry entries[1]; + }; + struct HunkUser { HunkUser* current; @@ -6072,6 +6334,162 @@ namespace Game static_assert(sizeof(scrVarPub_t) == 0x24060); + struct scrCompilePub_t + { + int value_count; + int far_function_count; + unsigned int loadedscripts; + unsigned int scriptsPos; + unsigned int scriptsCount; + unsigned int scriptsDefine; + unsigned int builtinFunc; + unsigned int builtinMeth; + unsigned __int16 canonicalStrings[65536]; + const char* in_ptr; + bool in_ptr_valid; + const char* parseBuf; + bool script_loading; + bool allowedBreakpoint; + int developer_statement; + char* opcodePos; + unsigned int programLen; + int func_table_size; + int func_table[1024]; + }; + + enum + { + SOURCE_TYPE_BREAKPOINT = 0x1, + SOURCE_TYPE_CALL = 0x2, + SOURCE_TYPE_CALL_POINTER = 0x4, + SOURCE_TYPE_THREAD_START = 0x8, + SOURCE_TYPE_BUILTIN_CALL = 0x10, + SOURCE_TYPE_NOTIFY = 0x20, + SOURCE_TYPE_GETFUNCTION = 0x40, + SOURCE_TYPE_WAIT = 0x80, + }; + + struct OpcodeLookup + { + const char* codePos; + unsigned int sourcePosIndex; + unsigned __int16 sourcePosCount; + int profileTime; + int profileBuiltInTime; + int profileUsage; + }; + + static_assert(sizeof(OpcodeLookup) == 24); + + struct SourceLookup + { + unsigned int sourcePos; + int type; + }; + + struct SaveSourceBufferInfo + { + char* buf; + char* sourceBuf; + int len; + }; + + struct scrParserGlob_t + { + OpcodeLookup* opcodeLookup; + unsigned int opcodeLookupMaxSize; + unsigned int opcodeLookupLen; + SourceLookup* sourcePosLookup; + unsigned int sourcePosLookupMaxSize; + unsigned int sourcePosLookupLen; + unsigned int sourceBufferLookupMaxSize; + const char* currentCodePos; + unsigned int currentSourcePosCount; + SaveSourceBufferInfo* saveSourceBufferLookup; + unsigned int saveSourceBufferLookupLen; + int delayedSourceIndex; + int threadStartSourceIndex; + }; + + struct SourceBufferInfo + { + const char* codePos; + char* buf; + const char* sourceBuf; + int len; + int sortedIndex; + bool archive; + int time; + int avgTime; + int maxTime; + float totalTime; + float totalBuiltIn; + }; + + struct scrParserPub_t + { + SourceBufferInfo* sourceBufferLookup; + unsigned int sourceBufferLookupLen; + const char* scriptfilename; + const char* sourceBuf; + }; + + struct scr_animtree_t + { + XAnim_s* anims; + }; + + struct scrAnimPub_t + { + unsigned int animtrees; + unsigned int animtree_node; + unsigned int animTreeNames; + scr_animtree_t xanim_lookup[2][128]; + unsigned int xanim_num[2]; + unsigned int animTreeIndex; + bool animtree_loading; + }; + + struct scr_localVar_t + { + unsigned int name; + unsigned int sourcePos; + }; + + struct scr_block_t + { + int abortLevel; + int localVarsCreateCount; + int localVarsPublicCount; + int localVarsCount; + char localVarsInitBits[8]; + scr_localVar_t localVars[64]; + }; + + union sval_u + { + int type; + unsigned int stringValue; + unsigned int idValue; + float floatValue; + int intValue; + sval_u* node; + unsigned int sourcePosValue; + const char* codePosValue; + const char* debugString; + scr_block_t* block; + }; + + static_assert(sizeof(sval_u) == 0x4); + + struct stype_t + { + sval_u val; + unsigned int pos; + }; + + static_assert(sizeof(stype_t) == 0x8); + struct scr_const_t { scr_string_t _; @@ -6583,7 +7001,7 @@ namespace Game struct FxEditorEffectDef { - char name[64]; + char name[MAX_QPATH]; int elemCount; FxEditorElemDef elems[32]; }; @@ -6720,29 +7138,29 @@ namespace Game DB_ZONE_DEV = 0x40 }; - enum playerFlag + enum { - PLAYER_FLAG_NOCLIP = 1 << 0, - PLAYER_FLAG_UFO = 1 << 1, - PLAYER_FLAG_FROZEN = 1 << 2, + PF_NOCLIP = 1 << 0, + PF_UFO = 1 << 1, + PF_FROZEN = 1 << 2, }; - typedef enum + enum sessionState_t { SESS_STATE_PLAYING = 0x0, SESS_STATE_DEAD = 0x1, SESS_STATE_SPECTATOR = 0x2, SESS_STATE_INTERMISSION = 0x3 - } sessionState_t; + }; - typedef enum + enum clientConnected_t { CON_DISCONNECTED = 0x0, CON_CONNECTING = 0x1, CON_CONNECTED = 0x2 - } clientConnected_t; + }; - typedef enum + enum visionSetMode_t { VISIONSET_NORMAL, VISIONSET_NIGHT, @@ -6750,7 +7168,7 @@ namespace Game VISIONSET_THERMAL, VISIONSET_PAIN, VISIONSETCOUNT - } visionSetMode_t; + }; enum hintType_t { @@ -6959,8 +7377,8 @@ namespace Game const char* name; int ofs; fieldtype_t type; - void(__cdecl* setter)(gentity_s*, int); - void(__cdecl* getter)(gentity_s*, int); + void(*setter)(gentity_s*, int); + void(*getter)(gentity_s*, int); }; struct client_fields_s @@ -6968,12 +7386,12 @@ namespace Game const char* name; int ofs; fieldtype_t type; - void(__cdecl* setter)(gclient_s*, const client_fields_s*); - void(__cdecl* getter)(gclient_s*, const client_fields_s*); + void(*setter)(gclient_s*, const client_fields_s*); + void(*getter)(gclient_s*, const client_fields_s*); }; - typedef void(__cdecl* ScriptCallbackEnt)(gentity_s*, int); - typedef void(__cdecl* ScriptCallbackClient)(gclient_s*, const client_fields_s*); + typedef void(*ScriptCallbackEnt)(gentity_s*, int); + typedef void(*ScriptCallbackClient)(gclient_s*, const client_fields_s*); struct lockonFireParms { @@ -7077,6 +7495,12 @@ namespace Game static_assert(sizeof(client_t) == 0xA6790); + enum CompassType + { + COMPASS_TYPE_PARTIAL = 0x0, + COMPASS_TYPE_FULL = 0x1, + }; + struct clientConnection_t { int qport; @@ -7098,7 +7522,7 @@ namespace Game char serverCommands[128][1024]; bool isServerRestarting; int lastClientArchiveIndex; - char demoName[64]; + char demoName[MAX_QPATH]; int demorecording; int demoplaying; int isTimeDemo; @@ -7151,92 +7575,105 @@ namespace Game CModelSectionHeader sectionHeader[4]; }; - typedef struct punctuation_s + struct punctuation_s { char* p; //punctuation character(s) int n; //punctuation indication punctuation_s* next; //next punctuation - } punctuation_t; + }; #define MAX_TOKEN 1024 #define MAX_TOKENLENGTH 1024 - typedef struct token_s + enum parseSkip_t { - char string[MAX_TOKEN]; //available token - int type; //last read token type - int subtype; //last read token sub type - unsigned long int intvalue; //integer value - long double floatvalue; //floating point value - char* whitespace_p; //start of white space before token - char* endwhitespace_p; //start of white space before token - int line; //line the token was on - int linescrossed; //lines crossed in white space - token_s* next; //next token in chain - } token_t; + SKIP_NO = 0x0, + SKIP_YES = 0x1, + SKIP_ALL_ELIFS = 0x2, + }; - typedef struct script_s + struct token_s { - char filename[64]; //file name of the script - char* buffer; //buffer containing the script - char* script_p; //current pointer in the script - char* end_p; //pointer to the end of the script - char* lastscript_p; //script pointer before reading token - char* whitespace_p; //begin of the white space - char* endwhitespace_p; //end of the white space - int length; //length of the script in bytes - int line; //current line in script - int lastline; //line before reading token - int tokenavailable; //set by UnreadLastToken - int flags; //several script flags - punctuation_t* punctuations; //the punctuations used in the script - punctuation_t** punctuationtable; - token_t token; //available token - struct script_s* next; //next script in a chain - } script_t; + char string[MAX_TOKEN]; // available token + int type; // last read token type + int subtype; // last read token sub type + unsigned long int intvalue; // integer value + long double floatvalue; // floating point value + char* whitespace_p; // start of white space before token + char* endwhitespace_p; // start of white space before token + int line; // line the token was on + int linescrossed; // lines crossed in white space + token_s* next; // next token in chain + }; - typedef struct define_s + struct script_s { - char* name; //define name - int flags; //define flags - int builtin; // > 0 if builtin define - int numparms; //number of define parameters - token_t* parms; //define parameters - token_t* tokens; //macro tokens (possibly containing parm tokens) - struct define_s* next; //next defined macro in a list - struct define_s* hashnext; //next define in the hash chain - } define_t; + char filename[64]; //file name of the script + char* buffer; //buffer containing the script + char* script_p; //current pointer in the script + char* end_p; //pointer to the end of the script + char* lastscript_p; //script pointer before reading token + char* whitespace_p; //begin of the white space + char* endwhitespace_p; //end of the white space + int length; //length of the script in bytes + int line; //current line in script + int lastline; //line before reading token + int tokenavailable; //set by UnreadLastToken + int flags; //several script flags + punctuation_s* punctuations; //the punctuations used in the script + punctuation_s** punctuationtable; + token_s token; //available token + script_s* next; //next script in a chain + }; - typedef struct indent_s + struct define_s { - int type; //indent type - int skip; //true if skipping current indent - script_t* script; //script the indent was in - struct indent_s* next; //next indent on the indent stack - } indent_t; + char* name; //define name + int flags; //define flags + int builtin; // > 0 if builtin define + int numparms; //number of define parameters + token_s* parms; //define parameters + token_s* tokens; //macro tokens (possibly containing parm tokens) + define_s* next; //next defined macro in a list + define_s* hashnext; //next define in the hash chain + }; - typedef struct source_s + struct indent_s { - char filename[64]; //file name of the script - char includepath[64]; //path to include files - punctuation_t* punctuations; //punctuations to use - script_t* scriptstack; //stack with scripts of the source - token_t* tokens; //tokens to read first - define_t* defines; //list with macro definitions - define_t** definehash; //hash chain with defines - indent_t* indentstack; //stack with indents - int skip; // > 0 if skipping conditional code - token_t token; //last read token - } source_t; + int type; //indent type + int skip; //true if skipping current indent + script_s* script; //script the indent was in + indent_s* next; //next indent on the indent stack + }; - typedef struct pc_token_s + struct source_s + { + char filename[MAX_QPATH]; //file name of the script + char includepath[MAX_QPATH]; //path to include files + punctuation_s* punctuations; //punctuations to use + script_s* scriptstack; //stack with scripts of the source + token_s* tokens; //tokens to read first + define_s* defines; //list with macro definitions + define_s** definehash; //hash chain with defines + indent_s* indentstack; //stack with indents + int skip; // > 0 if skipping conditional code + token_s token; //last read token + }; + + struct directive_s + { + const char* name; + int (*func)(source_s* source); + }; + + struct pc_token_s { int type; int subtype; int intvalue; float floatvalue; char string[MAX_TOKENLENGTH]; - } pc_token_t; + }; template struct KeywordHashEntry @@ -7372,6 +7809,13 @@ namespace Game R_RENDERTARGET_NONE = 0xD, }; + struct GfxDrawPrimArgs + { + int vertexCount; + int triCount; + int baseIndex; + }; + struct GfxCmdBufState { char refSamplerState[16]; @@ -8416,33 +8860,7 @@ namespace Game float scale; }; - struct XAnimParent - { - unsigned short flags; - unsigned short children; - }; - - struct XAnimEntry - { - unsigned short numAnims; - unsigned short parent; - - union - { - XAnimParts* parts; - XAnimParent animParent; - }; - }; - - struct XAnim_s - { - unsigned int size; - const char* debugName; - const char** debugAnimNames; - XAnimEntry entries[1]; - }; - - struct __declspec(align(4)) XAnimTree_s + struct XAnimTree_s { XAnim_s* anims; int info_usage; @@ -8624,7 +9042,20 @@ namespace Game CUBEMAPSHOT_COUNT = 0x7, }; - struct __declspec(align(8)) cg_s + struct snapshot_s + { + playerState_s ps; + int snapFlags; + int ping; + int serverTime; + int numEntities; + int numClients; + entityState_s entities[768]; + clientState_s clients[18]; + int serverCommandSequence; + }; + + struct cg_s { playerState_s predictedPlayerState; centity_s predictedPlayerEntity; @@ -8640,9 +9071,10 @@ namespace Game int latestSnapshotNum; int latestSnapshotTime; char pad0[16]; - void* snap; - void* nextSnap; - char _pad1[0x673DC]; + snapshot_s* snap; + snapshot_s* nextSnap; + snapshot_s activeSnapshots[2]; + float frameInterpolation; int frametime; // + 0x6A754 int time; int oldTime; @@ -8659,6 +9091,8 @@ namespace Game }; static_assert(sizeof(cg_s) == 0xFD540); + static_assert(offsetof(cg_s, frametime) == 0x6A754); + static_assert(offsetof(cg_s, selectedLocation) == 0x73DE0); static constexpr auto MAX_GPAD_COUNT = 1; @@ -8958,7 +9392,7 @@ namespace Game struct animation_s { - char name[64]; + char name[MAX_QPATH]; int initialLerp; float moveSpeed; int duration; @@ -10387,17 +10821,6 @@ namespace Game int index; }; - struct TraceCheckCount - { - int global; - int* partitions; - }; - - struct TraceThreadInfo - { - TraceCheckCount checkcount; - }; - struct ProfileAtom { unsigned int value[1]; @@ -10464,6 +10887,130 @@ namespace Game HANDLE handle; }; + struct FxCamera + { + float origin[3]; + volatile int isValid; + float frustum[6][4]; + float axis[3][3]; + unsigned int frustumPlaneCount; + float viewOffset[3]; + bool thermal; + unsigned int pad[2]; + }; + + struct r_double_index_t + { + unsigned __int16 value[2]; + }; + + struct FxSpriteInfo + { + r_double_index_t* indices; + unsigned int indexCount; + Material* material; + const char* name; + }; + + struct FxVisBlocker + { + float origin[3]; + unsigned __int16 radius; + unsigned __int16 visibility; + }; + + struct FxVisState + { + FxVisBlocker blocker[256]; + volatile int blockerCount; + unsigned int pad[3]; + }; + + struct FxElem + { + char defIndex; + char sequence; + char atRestFraction; + char emitResidual; + unsigned __int16 nextElemHandleInEffect; + unsigned __int16 prevElemHandleInEffect; + int msecBegin; + float baseVel[3]; + union + { + int physObjId; + float origin[3]; + } ___u8; + union + { + unsigned __int16 lightingHandle; + unsigned __int16 sparkCloudHandle; + unsigned __int16 sparkFountainHandle; + } u; + }; + + struct FxSystem + { + FxCamera camera; + FxCamera cameraPrev; + FxSpriteInfo sprite; + FxEffect* effects; + FxElem* elems; + void* trails; + void* trailElems; + void* bolts; + void* sparkClouds; + void* sparkFountains; + void* sparkFountainClusters; + unsigned __int16* deferredElems; + volatile int firstFreeElem; + volatile int firstFreeTrailElem; + volatile int firstFreeTrail; + volatile int firstFreeBolt; + volatile int firstFreeSparkCloud; + volatile int firstFreeSparkFountain; + volatile int firstFreeSparkFountainCluster; + volatile int deferredElemCount; + volatile int activeElemCount; + volatile int activeTrailElemCount; + volatile int activeTrailCount; + volatile int activeBoltCount; + volatile int activeSparkCloudCount; + volatile int activeSparkFountainCount; + volatile int activeSparkFountainClusterCount; + volatile int gfxCloudCount; + FxVisState* visState; + FxVisState* visStateBufferRead; + FxVisState* visStateBufferWrite; + volatile int firstActiveEffect; + volatile int firstNewEffect; + volatile int firstFreeEffect; + unsigned __int16 allEffectHandles[1024]; + volatile int activeSpotLightEffectCount; + volatile int activeSpotLightElemCount; + unsigned __int16 activeSpotLightEffectHandle; + unsigned __int16 activeSpotLightElemHandle; + __int16 activeSpotLightBoltDobj; + volatile int iteratorCount; + int msecNow; + volatile int msecDraw; + int frameCount; + bool isInitialized; + bool needsGarbageCollection; + bool isArchiving; + char localClientNum; + unsigned int restartList[32]; + FxEffect** restartEffectsList; + unsigned int restartCount; + unsigned int pad1[14]; + }; + + struct ClientEntSound + { + float origin[3]; + snd_alias_list_t* aliasList; + }; + #pragma endregion #ifndef IDA diff --git a/src/Game/System.cpp b/src/Game/System.cpp index 0068ff51..50f9d3ab 100644 --- a/src/Game/System.cpp +++ b/src/Game/System.cpp @@ -24,6 +24,7 @@ namespace Game Sys_SuspendOtherThreads_t Sys_SuspendOtherThreads = Sys_SuspendOtherThreads_t(0x45A190); Sys_SetValue_t Sys_SetValue = Sys_SetValue_t(0x4B2F50); Sys_CreateFile_t Sys_CreateFile = Sys_CreateFile_t(0x4B2EF0); + Sys_OutOfMemErrorInternal_t Sys_OutOfMemErrorInternal = Sys_OutOfMemErrorInternal_t(0x4B2E60); char(*sys_exitCmdLine)[1024] = reinterpret_cast(0x649FB68); diff --git a/src/Game/System.hpp b/src/Game/System.hpp index 68eb6ee5..70b37c1c 100644 --- a/src/Game/System.hpp +++ b/src/Game/System.hpp @@ -68,6 +68,9 @@ namespace Game typedef Sys_File(*Sys_CreateFile_t)(const char* dir, const char* filename); extern Sys_CreateFile_t Sys_CreateFile; + typedef void(*Sys_OutOfMemErrorInternal_t)(const char* filename, int line); + extern Sys_OutOfMemErrorInternal_t Sys_OutOfMemErrorInternal; + extern char(*sys_exitCmdLine)[1024]; extern RTL_CRITICAL_SECTION* s_criticalSection; @@ -99,3 +102,5 @@ namespace Game } }; } + +#define Sys_OutOfMemError() Game::Sys_OutOfMemErrorInternal(__FILE__, __LINE__); diff --git a/src/Game/Zone.cpp b/src/Game/Zone.cpp new file mode 100644 index 00000000..2bc7155b --- /dev/null +++ b/src/Game/Zone.cpp @@ -0,0 +1,73 @@ +#include + +namespace Game +{ + Z_VirtualAlloc_t Z_VirtualAlloc = Z_VirtualAlloc_t(0x4CFBA0); + Z_Malloc_t Z_Malloc = Z_Malloc_t(0x4F3680); + + Hunk_AllocateTempMemoryHigh_t Hunk_AllocateTempMemoryHigh = Hunk_AllocateTempMemoryHigh_t(0x475B30); + Hunk_UserAlloc_t Hunk_UserAlloc = Hunk_UserAlloc_t(0x45D1C0); + + TempMalloc_t TempMalloc = TempMalloc_t(0x4613A0); + + int Z_TryVirtualCommitInternal(void* ptr, int size) + { + assert((size >= 0)); + + return VirtualAlloc(ptr, size, MEM_COMMIT, PAGE_READWRITE) != nullptr; + } + + void Z_VirtualDecommitInternal(void* ptr, int size) + { + assert((size >= 0)); +#pragma warning(push) +#pragma warning(disable: 6250) + [[maybe_unused]] const auto result = VirtualFree(ptr, size, MEM_DECOMMIT); +#pragma warning(pop) + } + + void Z_VirtualCommitInternal(void* ptr, int size) + { + if (Z_TryVirtualCommitInternal(ptr, size)) + { + return; + } + + Sys_OutOfMemError(); + } + + void Z_VirtualFreeInternal(void* ptr) + { + VirtualFree(ptr, 0, MEM_RELEASE); + } + + void Z_VirtualCommit(void* ptr, int size) + { + assert(ptr); + assert(size); + + Z_VirtualCommitInternal(ptr, size); + } + + void* Z_VirtualReserve(int size) + { + assert((size >= 0)); + + void* buf = VirtualAlloc(nullptr, size, MEM_RESERVE, PAGE_READWRITE); + assert(buf); + return buf; + } + + void Z_VirtualDecommit(void* ptr, int size) + { + assert(ptr); + assert(size); + + Z_VirtualDecommitInternal(ptr, size); + } + + void Z_VirtualFree(void* ptr) + { + Z_VirtualFreeInternal(ptr); + } +} diff --git a/src/Game/Zone.hpp b/src/Game/Zone.hpp new file mode 100644 index 00000000..914a1a03 --- /dev/null +++ b/src/Game/Zone.hpp @@ -0,0 +1,26 @@ +#pragma once + +namespace Game +{ + typedef void*(*Z_VirtualAlloc_t)(int size); + extern Z_VirtualAlloc_t Z_VirtualAlloc; + + typedef void*(*Z_Malloc_t)(int size); + extern Z_Malloc_t Z_Malloc; + + typedef void*(*Hunk_AllocateTempMemoryHigh_t)(int size); + extern Hunk_AllocateTempMemoryHigh_t Hunk_AllocateTempMemoryHigh; + + typedef void*(*Hunk_UserAlloc_t)(HunkUser* user, int size, int alignment); + extern Hunk_UserAlloc_t Hunk_UserAlloc; + + typedef char*(*TempMalloc_t)(int len); + extern TempMalloc_t TempMalloc; + + constexpr auto PAGE_SIZE = 4096; + + extern [[nodiscard]] void* Z_VirtualReserve(int size); + extern void Z_VirtualCommit(void* ptr, int size); + extern void Z_VirtualDecommit(void* ptr, int size); + extern void Z_VirtualFree(void* ptr); +} diff --git a/src/STDInclude.hpp b/src/STDInclude.hpp index 776d1211..7bd49baa 100644 --- a/src/STDInclude.hpp +++ b/src/STDInclude.hpp @@ -16,22 +16,20 @@ #include #include #include -#include +#include #include #include #include #include #include -#pragma warning(push) -#pragma warning(disable: 4091) -#pragma warning(disable: 4244) #include #include #include #include #include +#include #include #include #include @@ -46,9 +44,10 @@ #include #include #include +#include +#include #include - -#pragma warning(pop) +#include #include #pragma comment(lib, "D3dx9.lib") @@ -61,27 +60,9 @@ // Ignore the warnings #pragma warning(push) -#pragma warning(disable: 4005) -#pragma warning(disable: 4091) #pragma warning(disable: 4100) -#pragma warning(disable: 4244) -#pragma warning(disable: 4389) -#pragma warning(disable: 4702) -#pragma warning(disable: 4800) -#pragma warning(disable: 5054) -#pragma warning(disable: 6001) -#pragma warning(disable: 6011) -#pragma warning(disable: 6031) -#pragma warning(disable: 6255) -#pragma warning(disable: 6258) -#pragma warning(disable: 6386) -#pragma warning(disable: 6387) #pragma warning(disable: 26812) -#include -#include -#include - // Enable additional literals using namespace std::literals; @@ -93,11 +74,9 @@ using namespace std::literals; #undef min #endif -// Needs to be included after the nominmax above ^ -#ifdef snprintf - #undef snprintf +#ifdef GetObject + #undef GetObject #endif -#include #define AssertSize(x, size) \ static_assert(sizeof(x) == (size), \ @@ -111,14 +90,9 @@ using namespace std::literals; #define AssertUnreachable assert(0 && "unreachable") -// Protobuf -#include "proto/session.pb.h" -#include "proto/party.pb.h" -#include "proto/auth.pb.h" -#include "proto/node.pb.h" -#include "proto/rcon.pb.h" -#include "proto/ipc.pb.h" -#include "proto/friends.pb.h" +#include +#include +#include #pragma warning(pop) @@ -126,15 +100,12 @@ using namespace std::literals; #include "Utils/Cache.hpp" #include "Utils/Chain.hpp" -#include "Utils/Compression.hpp" #include "Utils/Concurrency.hpp" #include "Utils/Cryptography.hpp" #include "Utils/CSV.hpp" #include "Utils/Entities.hpp" #include "Utils/Hooking.hpp" -#include "Utils/InfoString.hpp" #include "Utils/IO.hpp" -#include "Utils/Json.hpp" #include "Utils/Library.hpp" #include "Utils/Maths.hpp" #include "Utils/NamedMutex.hpp" @@ -142,7 +113,6 @@ using namespace std::literals; #include "Utils/Thread.hpp" #include "Utils/Time.hpp" #include "Utils/Utils.hpp" -#include "Utils/WebIO.hpp" #include "Steam/Steam.hpp" // Some definitions are used in functions and structs diff --git a/src/Steam/Interfaces/SteamMatchmaking.cpp b/src/Steam/Interfaces/SteamMatchmaking.cpp index 72e56f94..a5cf4fba 100644 --- a/src/Steam/Interfaces/SteamMatchmaking.cpp +++ b/src/Steam/Interfaces/SteamMatchmaking.cpp @@ -1,4 +1,5 @@ #include +#include STEAM_IGNORE_WARNINGS_START diff --git a/src/Steam/Interfaces/SteamRemoteStorage.cpp b/src/Steam/Interfaces/SteamRemoteStorage.cpp index 2cbe6620..0974512b 100644 --- a/src/Steam/Interfaces/SteamRemoteStorage.cpp +++ b/src/Steam/Interfaces/SteamRemoteStorage.cpp @@ -16,7 +16,9 @@ namespace Steam int RemoteStorage::FileRead(const char *pchFile, void *pvData, int cubDataToRead) { +#ifdef _DEBUG OutputDebugStringA(pchFile); +#endif return 0; } diff --git a/src/Steam/Interfaces/SteamUser.cpp b/src/Steam/Interfaces/SteamUser.cpp index a61a2e2f..26f8ce43 100644 --- a/src/Steam/Interfaces/SteamUser.cpp +++ b/src/Steam/Interfaces/SteamUser.cpp @@ -22,11 +22,11 @@ namespace Steam if (!idBits) { - if (Components::Dedicated::IsEnabled() || Components::ZoneBuilder::IsEnabled()) // Dedi guid + if (Components::ZoneBuilder::IsEnabled()) { idBits = *reinterpret_cast(const_cast("DEDICATE")); } - else if (Components::Singleton::IsFirstInstance()) // ECDSA guid + else if (Components::Singleton::IsFirstInstance() && !Components::Dedicated::IsEnabled()) // ECDSA guid { idBits = Components::Auth::GetKeyHash(); } diff --git a/src/Steam/Proxy.hpp b/src/Steam/Proxy.hpp index 27c0cf30..cec5a8fc 100644 --- a/src/Steam/Proxy.hpp +++ b/src/Steam/Proxy.hpp @@ -156,24 +156,30 @@ namespace Steam template T invoke(const std::string& methodName, Args... args) { - if(!this->interfacePtr) + if (!this->interfacePtr) { - OutputDebugStringA(::Utils::String::VA("Steam interface pointer is invalid (%s)!\n", methodName.data())); +#ifdef _DEBUG + OutputDebugStringA(::Utils::String::Format("Steam interface pointer is invalid '{}'!\n", methodName)); +#endif return T(); } auto method = this->getMethod(methodName); if (!method.first) { - OutputDebugStringA(::Utils::String::VA("Steam interface method %s not found!\n", methodName.data())); +#ifdef _DEBUG + OutputDebugStringA(::Utils::String::Format("Steam interface method '{}' not found!\n", methodName)); +#endif return T(); } std::size_t argc = method.second; constexpr std::size_t passedArgc = Interface::AddSizes::value; - if(passedArgc != argc) + if (passedArgc != argc) { - OutputDebugStringA(::Utils::String::VA("Steam interface arguments for method %s do not match (expected %d bytes, but got %d bytes)!\n", methodName.data(), argc, passedArgc)); +#ifdef _DEBUG + OutputDebugStringA(::Utils::String::Format("Steam interface arguments for method '{}' do not match (expected {} bytes, but got {} bytes)!\n", methodName, argc, passedArgc)); +#endif return T(); } diff --git a/src/Steam/Steam.cpp b/src/Steam/Steam.cpp index 9e206af9..37ec9361 100644 --- a/src/Steam/Steam.cpp +++ b/src/Steam/Steam.cpp @@ -123,7 +123,9 @@ namespace Steam if (!Proxy::Inititalize()) { +#ifdef _DEBUG OutputDebugStringA("Steam proxy not initialized properly"); +#endif Components::StartupMessages::AddMessage("Warning:\nUnable to connect to Steam. Steam features will be unavailable"); } else diff --git a/src/Utils/Cache.cpp b/src/Utils/Cache.cpp index 99c79d0d..b3240757 100644 --- a/src/Utils/Cache.cpp +++ b/src/Utils/Cache.cpp @@ -1,4 +1,5 @@ #include +#include "WebIO.hpp" namespace Utils { diff --git a/src/Utils/Compression.cpp b/src/Utils/Compression.cpp index adf5aefd..d3b40963 100644 --- a/src/Utils/Compression.cpp +++ b/src/Utils/Compression.cpp @@ -1,4 +1,7 @@ #include +#include + +#include "Compression.hpp" namespace Utils::Compression { diff --git a/src/Utils/Cryptography.cpp b/src/Utils/Cryptography.cpp index c2e515cc..8209f152 100644 --- a/src/Utils/Cryptography.cpp +++ b/src/Utils/Cryptography.cpp @@ -18,12 +18,14 @@ namespace Utils std::string Rand::GenerateChallenge() { - std::string challenge; - challenge.append(String::VA("%X", GenerateInt())); - challenge.append(String::VA("%X", ~timeGetTime() ^ GenerateInt())); - challenge.append(String::VA("%X", GenerateInt())); + char buffer[512]{}; + int buffer_pos = 0; - return challenge; + buffer_pos += sprintf_s(&buffer[buffer_pos], sizeof(buffer) - buffer_pos, "%X", GenerateInt()); + buffer_pos += sprintf_s(&buffer[buffer_pos], sizeof(buffer) - buffer_pos, "%X", ~timeGetTime() ^ GenerateInt()); + buffer_pos += sprintf_s(&buffer[buffer_pos], sizeof(buffer) - buffer_pos, "%X", GenerateInt()); + + return std::string(buffer, static_cast(buffer_pos)); } std::uint32_t Rand::GenerateInt() diff --git a/src/Utils/IO.cpp b/src/Utils/IO.cpp index d05b887f..e3606955 100644 --- a/src/Utils/IO.cpp +++ b/src/Utils/IO.cpp @@ -46,12 +46,12 @@ namespace Utils::IO if (!stream.is_open()) return false; stream.seekg(0, std::ios::end); - std::streamsize size = stream.tellg(); + const std::streamsize size = stream.tellg(); stream.seekg(0, std::ios::beg); if (size > -1) { - data->resize(static_cast(size)); + data->resize(static_cast(size)); stream.read(data->data(), size); stream.close(); return true; @@ -97,13 +97,23 @@ namespace Utils::IO return std::filesystem::is_empty(directory); } - std::vector ListFiles(const std::filesystem::path& directory) + std::vector ListFiles(const std::filesystem::path& directory, const bool recursive) { - std::vector files; + std::vector files; - for (auto& file : std::filesystem::directory_iterator(directory)) + if (recursive) { - files.push_back(file.path().generic_string()); + for (auto& file : std::filesystem::recursive_directory_iterator(directory)) + { + files.push_back(file); + } + } + else + { + for (auto& file : std::filesystem::directory_iterator(directory)) + { + files.push_back(file); + } } return files; diff --git a/src/Utils/IO.hpp b/src/Utils/IO.hpp index 199a8e16..4d31ec31 100644 --- a/src/Utils/IO.hpp +++ b/src/Utils/IO.hpp @@ -2,14 +2,14 @@ namespace Utils::IO { - bool FileExists(const std::string& file); + [[nodiscard]] bool FileExists(const std::string& file); bool WriteFile(const std::string& file, const std::string& data, bool append = false); bool ReadFile(const std::string& file, std::string* data); - std::string ReadFile(const std::string& file); + [[nodiscard]] std::string ReadFile(const std::string& file); bool RemoveFile(const std::string& file); - std::size_t FileSize(const std::string& file); + [[nodiscard]] std::size_t FileSize(const std::string& file); bool CreateDir(const std::string& dir); - bool DirectoryExists(const std::filesystem::path& file); - bool DirectoryIsEmpty(const std::filesystem::path& file); - std::vector ListFiles(const std::filesystem::path& directory); + [[nodiscard]] bool DirectoryExists(const std::filesystem::path& directory); + [[nodiscard]] bool DirectoryIsEmpty(const std::filesystem::path& directory); + [[nodiscard]] std::vector ListFiles(const std::filesystem::path& directory, bool recursive = false); } diff --git a/src/Utils/InfoString.cpp b/src/Utils/InfoString.cpp index bfefdff2..523d7c0e 100644 --- a/src/Utils/InfoString.cpp +++ b/src/Utils/InfoString.cpp @@ -1,4 +1,5 @@ #include +#include "InfoString.hpp" namespace Utils { diff --git a/src/Utils/Json.cpp b/src/Utils/Json.cpp index eb36fff6..02961a27 100644 --- a/src/Utils/Json.cpp +++ b/src/Utils/Json.cpp @@ -1,6 +1,8 @@ #include #include +#include "Json.hpp" + namespace Utils::Json { std::string TypeToString(const nlohmann::json::value_t type) @@ -33,7 +35,7 @@ namespace Utils::Json } } - unsigned long ReadFlags(const std::string binaryFlags, size_t size) + unsigned long ReadFlags(const std::string binaryFlags, std::size_t size) { std::bitset<64> input; const auto binarySize = size * 8; @@ -53,11 +55,19 @@ namespace Utils::Json break; } - bool isOne = bit == '1'; + auto isOne = bit == '1'; input.set(i--, isOne); } return input.to_ulong(); } + Game::Bounds ReadBounds(const nlohmann::json& value) + { + Game::Bounds bounds{}; + CopyArray(bounds.midPoint, value["midPoint"]); + CopyArray(bounds.halfSize, value["halfSize"]); + + return bounds; + } } diff --git a/src/Utils/Json.hpp b/src/Utils/Json.hpp index 415309e7..bf208ea9 100644 --- a/src/Utils/Json.hpp +++ b/src/Utils/Json.hpp @@ -1,8 +1,25 @@ #pragma once +#include + namespace Utils::Json { std::string TypeToString(nlohmann::json::value_t type); unsigned long ReadFlags(const std::string binaryFlags, size_t size); + + Game::Bounds ReadBounds(const nlohmann::json& value); + + template void CopyArray(T* destination, const nlohmann::json& json_member, size_t count = 0) + { + if (count == 0) + { + count = json_member.size(); + } + + for (size_t i = 0; i < count; i++) + { + destination[i] = json_member[i].get(); + } + } } diff --git a/src/Utils/Maths.cpp b/src/Utils/Maths.cpp index 2b840002..35f12ff3 100644 --- a/src/Utils/Maths.cpp +++ b/src/Utils/Maths.cpp @@ -7,7 +7,7 @@ namespace Utils::Maths return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]; } - void VectorSubtract(float va[3], float vb[3], float out[3]) + void VectorSubtract(const float va[3], const float vb[3], float out[3]) { out[0] = va[0] - vb[0]; out[1] = va[1] - vb[1]; @@ -35,7 +35,7 @@ namespace Utils::Maths out[2] = v[2] * scale; } - float Vec3SqrDistance(float v1[3], float v2[3]) + float Vec3SqrDistance(const float v1[3], const float v2[3]) { float out[3]; diff --git a/src/Utils/Maths.hpp b/src/Utils/Maths.hpp index 47f37ed5..7d2111df 100644 --- a/src/Utils/Maths.hpp +++ b/src/Utils/Maths.hpp @@ -7,9 +7,9 @@ namespace Utils::Maths constexpr auto VectorNegate(float x[3]) { x[0] = -x[0]; x[1] = -x[1]; x[2] = -x[2]; } float DotProduct(float v1[3], float v2[3]); - void VectorSubtract(float va[3], float vb[3], float out[3]); + void VectorSubtract(const float va[3], const float vb[3], float out[3]); void VectorAdd(float va[3], float vb[3], float out[3]); void VectorCopy(float in[3], float out[3]); void VectorScale(float v[3], float scale, float out[3]); - float Vec3SqrDistance(float v1[3], float v2[3]); + float Vec3SqrDistance(const float v1[3], const float v2[3]); } diff --git a/src/Utils/Memory.cpp b/src/Utils/Memory.cpp index cf26b3ca..01c0b5b8 100644 --- a/src/Utils/Memory.cpp +++ b/src/Utils/Memory.cpp @@ -2,7 +2,7 @@ namespace Utils { - Utils::Memory::Allocator Memory::MemAllocator; + Memory::Allocator Memory::MemAllocator; void* Memory::AllocateAlign(std::size_t length, std::size_t alignment) { diff --git a/src/Utils/Stream.cpp b/src/Utils/Stream.cpp index dc50f51c..43b3aa98 100644 --- a/src/Utils/Stream.cpp +++ b/src/Utils/Stream.cpp @@ -16,14 +16,14 @@ namespace Utils const char* Stream::Reader::readCString() { - return this->allocator->duplicateString(this->readString()); + return this->allocator_->duplicateString(this->readString()); } char Stream::Reader::readByte() { - if ((this->position + 1) <= this->buffer.size()) + if ((this->position_ + 1) <= this->buffer_.size()) { - return this->buffer[this->position++]; + return this->buffer_[this->position_++]; } throw std::runtime_error("Reading past the buffer"); @@ -31,45 +31,44 @@ namespace Utils void* Stream::Reader::read(size_t size, std::size_t count) { - size_t bytes = size * count; + auto bytes = size * count; - if ((this->position + bytes) <= this->buffer.size()) + if ((this->position_ + bytes) <= this->buffer_.size()) { - void* _buffer = this->allocator->allocate(bytes); + auto* buffer = this->allocator_->allocate(bytes); + std::memcpy(buffer, this->buffer_.data() + this->position_, bytes); + this->position_ += bytes; - std::memcpy(_buffer, this->buffer.data() + this->position, bytes); - this->position += bytes; - - return _buffer; + return buffer; } throw std::runtime_error("Reading past the buffer"); } - bool Stream::Reader::end() + bool Stream::Reader::end() const { - return (this->buffer.size() == this->position); + return (this->buffer_.size() == this->position_); } - void Stream::Reader::seek(unsigned int _position) + void Stream::Reader::seek(unsigned int position) { - if (this->buffer.size() >= _position) + if (this->buffer_.size() >= position) { - this->position = _position; + this->position_ = position; } } - void Stream::Reader::seekRelative(unsigned int _position) + void Stream::Reader::seekRelative(unsigned int position) { - return this->seek(_position + this->position); + return this->seek(position + this->position_); } void* Stream::Reader::readPointer() { - void* pointer = this->read(); + auto* pointer = this->read(); if (!this->hasPointer(pointer)) { - this->pointerMap[pointer] = nullptr; + this->pointerMap_[pointer] = nullptr; } return pointer; } @@ -78,18 +77,18 @@ namespace Utils { if (this->hasPointer(oldPointer)) { - this->pointerMap[oldPointer] = newPointer; + this->pointerMap_[oldPointer] = newPointer; } } - bool Stream::Reader::hasPointer(void* pointer) + bool Stream::Reader::hasPointer(void* pointer) const { - return this->pointerMap.find(pointer) != this->pointerMap.end(); + return this->pointerMap_.contains(pointer); } Stream::Stream() : ptrAssertion(false), criticalSectionState(0) { - memset(this->blockSize, 0, sizeof(this->blockSize)); + std::memset(this->blockSize, 0, sizeof(this->blockSize)); #ifdef WRITE_LOGS this->structLevel = 0; @@ -99,12 +98,12 @@ namespace Utils Stream::Stream(size_t size) : Stream() { - this->buffer.reserve(size); + this->buffer_.reserve(size); } Stream::~Stream() { - this->buffer.clear(); + this->buffer_.clear(); if (this->criticalSectionState != 0) { @@ -114,12 +113,12 @@ namespace Utils std::size_t Stream::length() const { - return this->buffer.length(); + return this->buffer_.length(); } std::size_t Stream::capacity() const { - return this->buffer.capacity(); + return this->buffer_.capacity(); } void Stream::assertPointer(const void* pointer, std::size_t length) @@ -143,12 +142,12 @@ namespace Utils this->ptrList.push_back({ pointer, length }); } - char* Stream::save(const void* _str, std::size_t size, std::size_t count) + char* Stream::save(const void* str, std::size_t size, std::size_t count) { - return this->save(this->getCurrentBlock(), _str, size, count); + return this->save(this->getCurrentBlock(), str, size, count); } - char* Stream::save(Game::XFILE_BLOCK_TYPES stream, const void * _str, std::size_t size, std::size_t count) + char* Stream::save(Game::XFILE_BLOCK_TYPES stream, const void * str, std::size_t size, std::size_t count) { // Only those seem to actually write data. // everything else is allocated at runtime but XFILE_BLOCK_RUNTIME is the only one that actually allocates anything @@ -159,7 +158,7 @@ namespace Utils return this->at(); } - auto data = this->data(); + auto* data = this->data(); if (this->isCriticalSection() && this->length() + (size * count) > this->capacity()) { @@ -167,7 +166,7 @@ namespace Utils __debugbreak(); } - this->buffer.append(static_cast(_str), size * count); + this->buffer_.append(static_cast(str), size * count); if (this->data() != data && this->isCriticalSection()) { @@ -176,7 +175,7 @@ namespace Utils } this->increaseBlockSize(stream, size * count); - this->assertPointer(_str, size * count); + this->assertPointer(str, size * count); return this->at() - (size * count); } @@ -319,7 +318,7 @@ namespace Utils char* Stream::data() { - return const_cast(this->buffer.data()); + return const_cast(this->buffer_.data()); } unsigned int Stream::getBlockSize(Game::XFILE_BLOCK_TYPES stream) @@ -350,9 +349,9 @@ namespace Utils std::string Stream::toBuffer() { - std::string _buffer; - this->toBuffer(_buffer); - return _buffer; + std::string buffer; + this->toBuffer(buffer); + return buffer; } void Stream::enterCriticalSection() diff --git a/src/Utils/Stream.hpp b/src/Utils/Stream.hpp index 3739cc3a..1ef8d1c9 100644 --- a/src/Utils/Stream.hpp +++ b/src/Utils/Stream.hpp @@ -12,6 +12,9 @@ namespace Utils { + constexpr auto POINTER = 255; + constexpr auto FOLLOWING = 254; + class Stream { private: @@ -21,13 +24,13 @@ namespace Utils int criticalSectionState; unsigned int blockSize[Game::MAX_XFILE_COUNT]; std::vector streamStack; - std::string buffer; + std::string buffer_; public: class Reader { public: - Reader(Memory::Allocator* _allocator, const std::string& _buffer) : position(0), buffer(_buffer), allocator(_allocator) {} + Reader(Memory::Allocator* allocator, std::string buffer) : position_(0), buffer_(std::move(buffer)), allocator_(allocator) {} std::string readString(); const char* readCString(); @@ -42,9 +45,6 @@ namespace Utils template T* readArrayOnce(std::size_t count = 1) { - constexpr auto POINTER = 255; - constexpr auto FOLLOWING = 254; - auto b = static_cast(readByte()); switch (b) { @@ -53,18 +53,19 @@ namespace Utils auto ptr = read(); auto* voidPtr = reinterpret_cast(ptr); - if (allocator->isPointerMapped(voidPtr)) + if (allocator_->isPointerMapped(voidPtr)) { - return allocator->getPointer(voidPtr); + return allocator_->getPointer(voidPtr); } throw std::runtime_error("Bad data: missing ptr"); } case FOLLOWING: { - auto filePosition = position; + auto filePosition = position_; auto data = readArray(count); - allocator->mapPointer(reinterpret_cast(filePosition), data); + allocator_->mapPointer(reinterpret_cast(filePosition), data); + return data; } default: @@ -89,19 +90,19 @@ namespace Utils return obj; } - bool end(); + bool end() const; void seek(unsigned int position); void seekRelative(unsigned int position); void* readPointer(); void mapPointer(void* oldPointer, void* newPointer); - bool hasPointer(void* pointer); + bool hasPointer(void* pointer) const; private: - unsigned int position; - std::string buffer; - std::map pointerMap; - Memory::Allocator* allocator; + unsigned int position_; + std::string buffer_; + std::map pointerMap_; + Memory::Allocator* allocator_; }; enum Alignment @@ -123,11 +124,13 @@ namespace Utils Stream(size_t size); ~Stream(); + std::unordered_map dataPointers; + [[nodiscard]] std::size_t length() const; [[nodiscard]] std::size_t capacity() const; - char* save(const void * _str, std::size_t size, std::size_t count = 1); - char* save(Game::XFILE_BLOCK_TYPES stream, const void * _str, std::size_t size, std::size_t count); + char* save(const void * str, std::size_t size, std::size_t count = 1); + char* save(Game::XFILE_BLOCK_TYPES stream, const void * str, std::size_t size, std::size_t count); char* save(Game::XFILE_BLOCK_TYPES stream, int value, std::size_t count); template char* save(T* object) @@ -135,6 +138,38 @@ namespace Utils return saveArray(object, 1); } + template char* saveObject(T value) + { + return saveArray(&value, 1); + } + + template void saveArrayIfNotExisting(T* data, size_t count) + { + if (const auto itr = dataPointers.find(data); itr != dataPointers.end()) + { + saveByte(POINTER); + saveObject(itr->second); + } + else + { + saveByte(FOLLOWING); + dataPointers.insert_or_assign(reinterpret_cast(data), length()); + saveArray(data, count); + } + } + + char* save(int value, size_t count = 1) + { + auto ret = this->length(); + + for (size_t i = 0; i < count; ++i) + { + this->save(&value, 4, 1); + } + + return this->data() + ret; + } + template char* saveArray(T* array, std::size_t count) { return save(array, sizeof(T), count); diff --git a/src/Utils/Thread.cpp b/src/Utils/Thread.cpp index c6e5423c..70f81997 100644 --- a/src/Utils/Thread.cpp +++ b/src/Utils/Thread.cpp @@ -78,45 +78,4 @@ namespace Utils::Thread return ids; } - - void ForEachThread(const std::function& callback) - { - const auto ids = GetThreadIds(); - - for (const auto& id : ids) - { - auto* const thread = OpenThread(THREAD_ALL_ACCESS, FALSE, id); - if (thread != nullptr) - { - const auto _ = gsl::finally([thread]() - { - CloseHandle(thread); - }); - - callback(thread); - } - } - } - - void SuspendOtherThreads() - { - ForEachThread([](const HANDLE thread) - { - if (GetThreadId(thread) != GetCurrentThreadId()) - { - SuspendThread(thread); - } - }); - } - - void ResumeOtherThreads() - { - ForEachThread([](const HANDLE thread) - { - if (GetThreadId(thread) != GetCurrentThreadId()) - { - ResumeThread(thread); - } - }); - } } diff --git a/src/Utils/Thread.hpp b/src/Utils/Thread.hpp index 084eb2ce..02e76277 100644 --- a/src/Utils/Thread.hpp +++ b/src/Utils/Thread.hpp @@ -16,8 +16,4 @@ namespace Utils::Thread } std::vector GetThreadIds(); - void ForEachThread(const std::function& callback); - - void SuspendOtherThreads(); - void ResumeOtherThreads(); } diff --git a/src/Utils/WebIO.cpp b/src/Utils/WebIO.cpp index 645b77c9..852971c4 100644 --- a/src/Utils/WebIO.cpp +++ b/src/Utils/WebIO.cpp @@ -1,5 +1,7 @@ #include -#include +#include + +#include "WebIO.hpp" namespace Utils { @@ -10,16 +12,13 @@ namespace Utils this->setURL(url); } - WebIO::WebIO(const std::string& useragent) : cancel(false), hSession(nullptr), timeout(5000) // 5 seconds timeout by default + WebIO::WebIO(const std::string& useragent) : cancel_(false), hSession_(nullptr), timeout_(5000) // 5 seconds timeout by default { this->openSession(useragent); } WebIO::~WebIO() { - this->username.clear(); - this->password.clear(); - this->closeConnection(); this->closeSession(); } @@ -27,36 +26,36 @@ namespace Utils void WebIO::openSession(const std::string& useragent) { this->closeSession(); - this->hSession = InternetOpenA(useragent.data(), INTERNET_OPEN_TYPE_DIRECT, nullptr, nullptr, 0); + this->hSession_ = InternetOpenA(useragent.data(), INTERNET_OPEN_TYPE_DIRECT, nullptr, nullptr, 0); } void WebIO::closeSession() { - if (this->hSession) InternetCloseHandle(this->hSession); + if (this->hSession_) InternetCloseHandle(this->hSession_); } - void WebIO::setCredentials(const std::string& _username, const std::string& _password) + void WebIO::setCredentials(const std::string& username, const std::string& password) { - this->username = _username; - this->password = _password; + this->username_ = username; + this->password_ = password; } - void WebIO::setURL(std::string _url) + void WebIO::setURL(std::string url) { - this->url.server.clear(); - this->url.protocol.clear(); - this->url.document.clear(); + this->url_.server.clear(); + this->url_.protocol.clear(); + this->url_.document.clear(); // Insert protocol if none - if (_url.find("://") == std::string::npos) + if (url.find("://") == std::string::npos) { - _url = "http://" + _url; + url = "http://" + url; } PARSEDURLA pURL; ZeroMemory(&pURL, sizeof(pURL)); pURL.cbSize = sizeof(pURL); - ParseURLA(_url.data(), &pURL); + ParseURLA(url.data(), &pURL); // Parse protocol if (pURL.cchProtocol && pURL.pszProtocol) @@ -64,12 +63,12 @@ namespace Utils for (UINT i = 0; i < pURL.cchProtocol; ++i) { char lChar = static_cast(tolower(pURL.pszProtocol[i])); - this->url.protocol.append(&lChar, 1); + this->url_.protocol.append(&lChar, 1); } } else { - this->url.protocol.append("http"); + this->url_.protocol.append("http"); } // Parse suffix @@ -91,44 +90,43 @@ namespace Utils server = server.substr(2); } - size_t pos = server.find('/'); + auto pos = server.find('/'); if (pos == std::string::npos) { - this->url.server = server; - this->url.document = "/"; + this->url_.server = server; + this->url_.document = "/"; } else { - this->url.server = server.substr(0, pos); - this->url.document = server.substr(pos); + this->url_.server = server.substr(0, pos); + this->url_.document = server.substr(pos); } - this->url.port.clear(); + this->url_.port.clear(); - pos = this->url.server.find(':'); + pos = this->url_.server.find(':'); if (pos != std::string::npos) { - this->url.port = this->url.server.substr(pos + 1); - this->url.server = this->url.server.substr(0, pos); + this->url_.port = this->url_.server.substr(pos + 1); + this->url_.server = this->url_.server.substr(0, pos); } - this->url.raw.clear(); - this->url.raw.append(this->url.protocol); - this->url.raw.append("://"); - this->url.raw.append(this->url.server); + this->url_.raw.clear(); + this->url_.raw.append(this->url_.protocol); + this->url_.raw.append("://"); + this->url_.raw.append(this->url_.server); - if (!this->url.port.empty()) + if (!this->url_.port.empty()) { - this->url.raw.append(":"); - this->url.raw.append(this->url.port); + this->url_.raw.append(":"); + this->url_.raw.append(this->url_.port); } - this->url.raw.append(this->url.document); - - this->isFTP = (this->url.protocol == "ftp"); + this->url_.raw.append(this->url_.document); + this->isFTP_ = (this->url_.protocol == "ftp"); } - std::string WebIO::buildPostBody(Params params) + std::string WebIO::buildPostBody(const params& params) { std::string body; @@ -149,15 +147,15 @@ namespace Utils return body; } - std::string WebIO::postFile(const std::string& _url, const std::string& data, const std::string& fieldName, const std::string& fileName) + std::string WebIO::postFile(const std::string& url, const std::string& data, const std::string& fieldName, const std::string& fileName) { - this->setURL(_url); + this->setURL(url); return this->postFile(data, fieldName, fileName); } std::string WebIO::postFile(const std::string& data, std::string fieldName, std::string fileName) { - Params headers; + params headers; std::string boundary = "----WebKitFormBoundaryHoLVocRsBxs71fU6"; headers["Content-Type"] = "multipart/form-data, boundary=" + boundary; @@ -182,37 +180,39 @@ namespace Utils return this->execute("POST", body, headers); } - std::string WebIO::post(const std::string& _url, const std::string& body, bool* success) + std::string WebIO::post(const std::string& url, const std::string& body, bool* success) { - this->setURL(_url); + this->setURL(url); return this->post(body, success); } - std::string WebIO::post(const std::string& _url, Params params, bool* success) + std::string WebIO::post(const std::string& url, const params& params, bool* success) { - this->setURL(_url); + this->setURL(url); return this->post(params, success); } - std::string WebIO::post(Params params, bool* success) + std::string WebIO::post(const params& params, bool* success) { return this->post(this->buildPostBody(params), success); } std::string WebIO::post(const std::string& body, bool* success) { - return this->execute("POST", body, WebIO::Params(), success); + const params params; + return this->execute("POST", body, params, success); } - std::string WebIO::get(const std::string& _url, bool* success) + std::string WebIO::get(const std::string& url, bool* success) { - this->setURL(_url); + this->setURL(url); return this->get(success); } std::string WebIO::get(bool* success) { - return this->execute("GET", "", WebIO::Params(), success); + const params params; + return this->execute("GET", "", params, success); } bool WebIO::openConnection() @@ -221,7 +221,7 @@ namespace Utils DWORD dwService = INTERNET_SERVICE_HTTP; DWORD dwFlag = 0; - if (this->isFTP) + if (this->isFTP_) { wPort = INTERNET_DEFAULT_FTP_PORT; dwService = INTERNET_SERVICE_FTP; @@ -232,61 +232,58 @@ namespace Utils wPort = INTERNET_DEFAULT_HTTPS_PORT; } - if (!this->url.port.empty()) + if (!this->url_.port.empty()) { - wPort = static_cast(atoi(this->url.port.data())); + wPort = static_cast(atoi(this->url_.port.data())); } - const char* _username = (this->username.size() ? this->username.data() : NULL); - const char* _password = (this->password.size() ? this->password.data() : NULL); - this->hConnect = InternetConnectA(this->hSession, this->url.server.data(), wPort, _username, _password, dwService, dwFlag, 0); + this->hConnect_ = InternetConnectA(this->hSession_, this->url_.server.data(), wPort, this->username_.data(), this->password_.data(), dwService, dwFlag, NULL); - return (this->hConnect && this->hConnect != INVALID_HANDLE_VALUE); + return (this->hConnect_ && this->hConnect_ != INVALID_HANDLE_VALUE); } void WebIO::closeConnection() { - if (this->hFile && this->hFile != INVALID_HANDLE_VALUE) InternetCloseHandle(this->hFile); - if (this->hConnect && this->hConnect != INVALID_HANDLE_VALUE) InternetCloseHandle(this->hConnect); + if (this->hFile_ && this->hFile_ != INVALID_HANDLE_VALUE) InternetCloseHandle(this->hFile_); + if (this->hConnect_ && this->hConnect_ != INVALID_HANDLE_VALUE) InternetCloseHandle(this->hConnect_); } - WebIO* WebIO::setTimeout(DWORD mseconds) + WebIO* WebIO::setTimeout(DWORD msec) { - this->timeout = mseconds; + this->timeout_ = msec; return this; } - std::string WebIO::execute(const char* command, const std::string& body, WebIO::Params headers, bool* success) + std::string WebIO::execute(const char* command, const std::string& body, const params& headers, bool* success) { if (success) *success = false; if (!this->openConnection()) return {}; - const char *acceptTypes[] = { "application/x-www-form-urlencoded", nullptr }; + static const char* acceptTypes[] = { "application/x-www-form-urlencoded", nullptr }; DWORD dwFlag = INTERNET_FLAG_RELOAD | (this->isSecuredConnection() ? INTERNET_FLAG_SECURE : 0); - // This doesn't seem to actually do anything, half of those options don't even seem to be implemented. - // Good job microsoft... ( https://msdn.microsoft.com/en-us/library/windows/desktop/aa385328%28v=vs.85%29.aspx ) - //InternetSetOption(WebIO::m_hConnect, INTERNET_OPTION_CONNECT_TIMEOUT, &m_timeout, sizeof(m_timeout)); - //InternetSetOption(WebIO::m_hConnect, INTERNET_OPTION_RECEIVE_TIMEOUT, &m_timeout, sizeof(m_timeout)); - //InternetSetOption(WebIO::m_hConnect, INTERNET_OPTION_SEND_TIMEOUT, &m_timeout, sizeof(m_timeout)); + InternetSetOptionA(this->hConnect_, INTERNET_OPTION_CONNECT_TIMEOUT, &this->timeout_, sizeof(this->timeout_)); + InternetSetOptionA(this->hConnect_, INTERNET_OPTION_RECEIVE_TIMEOUT, &this->timeout_, sizeof(this->timeout_)); + InternetSetOptionA(this->hConnect_, INTERNET_OPTION_SEND_TIMEOUT, &this->timeout_, sizeof(this->timeout_)); - this->hFile = HttpOpenRequestA(this->hConnect, command, this->url.document.data(), nullptr, nullptr, acceptTypes, dwFlag, 0); + this->hFile_ = HttpOpenRequestA(this->hConnect_, command, this->url_.document.data(), nullptr, nullptr, acceptTypes, dwFlag, NULL); - if (!this->hFile || this->hFile == INVALID_HANDLE_VALUE) + if (!this->hFile_ || this->hFile_ == INVALID_HANDLE_VALUE) { this->closeConnection(); return {}; } - if (!headers.contains("Content-Type")) + params params = headers; + if (!params.contains("Content-Type")) { - headers["Content-Type"] = "application/x-www-form-urlencoded"; + params["Content-Type"] = "application/x-www-form-urlencoded"; } std::string finalHeaders; - for (auto i = headers.begin(); i != headers.end(); ++i) + for (auto i = params.begin(); i != params.end(); ++i) { finalHeaders.append(i->first); finalHeaders.append(": "); @@ -294,14 +291,14 @@ namespace Utils finalHeaders.append("\r\n"); } - if (HttpSendRequestA(this->hFile, finalHeaders.data(), finalHeaders.size(), const_cast(body.data()), body.size() + 1) == FALSE) + if (HttpSendRequestA(this->hFile_, finalHeaders.data(), finalHeaders.size(), const_cast(body.data()), body.size() + 1) == FALSE) { return {}; } DWORD statusCode = 404; DWORD length = sizeof(statusCode); - if (HttpQueryInfoA(this->hFile, HTTP_QUERY_FLAG_NUMBER | HTTP_QUERY_STATUS_CODE, &statusCode, &length, nullptr) == FALSE || (statusCode != 200 && statusCode != 201)) + if (HttpQueryInfoA(this->hFile_, HTTP_QUERY_FLAG_NUMBER | HTTP_QUERY_STATUS_CODE, &statusCode, &length, nullptr) == FALSE || (statusCode != 200 && statusCode != 201)) { this->closeConnection(); return {}; @@ -309,7 +306,7 @@ namespace Utils DWORD contentLength = 0; length = sizeof(statusCode); - if (HttpQueryInfoA(this->hFile, HTTP_QUERY_FLAG_NUMBER | HTTP_QUERY_CONTENT_LENGTH, &contentLength, &length, nullptr) == FALSE) + if (HttpQueryInfoA(this->hFile_, HTTP_QUERY_FLAG_NUMBER | HTTP_QUERY_CONTENT_LENGTH, &contentLength, &length, nullptr) == FALSE) { contentLength = 0; } @@ -320,9 +317,9 @@ namespace Utils DWORD size{}; char buffer[0x2001]{}; - while (InternetReadFile(this->hFile, buffer, sizeof(buffer) - 1, &size)) + while (InternetReadFile(this->hFile_, buffer, sizeof(buffer) - 1, &size)) { - if (this->cancel) + if (this->cancel_) { this->closeConnection(); return {}; @@ -341,7 +338,7 @@ namespace Utils bool WebIO::isSecuredConnection() const { - return this->url.protocol == "https"s; + return this->url_.protocol == "https"s; } bool WebIO::connect() @@ -356,23 +353,23 @@ namespace Utils bool WebIO::setDirectory(const std::string& directory) { - return (FtpSetCurrentDirectoryA(this->hConnect, directory.data()) == TRUE); + return (FtpSetCurrentDirectoryA(this->hConnect_, directory.data()) == TRUE); } bool WebIO::setRelativeDirectory(std::string directory) { std::string currentDir; - if (this->getDirectory(currentDir)) + if (this->getDirectory(¤tDir)) { - this->formatPath(directory, true); - this->formatPath(currentDir, true); + FormatPath(directory, true); + FormatPath(currentDir, true); char path[MAX_PATH]{}; PathCombineA(path, currentDir.data(), directory.data()); std::string newPath(path); - this->formatPath(newPath, false); + FormatPath(newPath, false); return this->setDirectory(newPath); } @@ -380,23 +377,23 @@ namespace Utils return false; } - bool WebIO::getDirectory(std::string &directory) + bool WebIO::getDirectory(std::string* directory) const { - directory.clear(); + directory->clear(); char currentDir[MAX_PATH]{}; DWORD size = sizeof(currentDir); - if (FtpGetCurrentDirectoryA(this->hConnect, currentDir, &size) == TRUE) + if (FtpGetCurrentDirectoryA(this->hConnect_, currentDir, &size) == TRUE) { - directory.append(currentDir, size); + directory->append(currentDir, size); return true; } return false; } - void WebIO::formatPath(std::string& path, bool win) + void WebIO::FormatPath(std::string& path, bool win) { std::size_t nPos; std::string find = "\\"; @@ -414,35 +411,45 @@ namespace Utils } } - bool WebIO::createDirectory(const std::string& directory) + std::string WebIO::GetCacheBuster() { - return (FtpCreateDirectoryA(this->hConnect, directory.data()) == TRUE); + return "?" + std::to_string( + std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()).count()); + } + + bool WebIO::createDirectory(const std::string& directory) const + { + return (FtpCreateDirectoryA(this->hConnect_, directory.data()) == TRUE); } // Recursively delete a directory bool WebIO::deleteDirectory(const std::string& directory) { std::string tempDir; - this->getDirectory(tempDir); + this->getDirectory(&tempDir); this->setRelativeDirectory(directory); std::vector list; this->listFiles(".", list); - for (auto file : list) this->deleteFile(file); + for (const auto& file : list) + { + this->deleteFile(file); + } this->listDirectories(".", list); for (auto& dir : list) this->deleteDirectory(dir); this->setDirectory(tempDir); - return (FtpRemoveDirectoryA(this->hConnect, directory.data()) == TRUE); + return (FtpRemoveDirectoryA(this->hConnect_, directory.data()) == TRUE); } - bool WebIO::renameDirectory(const std::string& directory, const std::string& newDir) + bool WebIO::renameDirectory(const std::string& directory, const std::string& newDir) const { - return (FtpRenameFileA(this->hConnect, directory.data(), newDir.data()) == TRUE); // According to the internetz, this should work + return (FtpRenameFileA(this->hConnect_, directory.data(), newDir.data()) == TRUE); // According to the internet, this should work } bool WebIO::listElements(const std::string& directory, std::vector& list, bool files) @@ -455,12 +462,12 @@ namespace Utils // Any filename. std::string tempDir; - this->getDirectory(tempDir); + this->getDirectory(&tempDir); this->setRelativeDirectory(directory); - this->hFile = FtpFindFirstFileA(this->hConnect, "*", &findFileData, INTERNET_FLAG_RELOAD, NULL); + this->hFile_ = FtpFindFirstFileA(this->hConnect_, "*", &findFileData, INTERNET_FLAG_RELOAD, NULL); - if (this->hFile && this->hFile != INVALID_HANDLE_VALUE) + if (this->hFile_ && this->hFile_ != INVALID_HANDLE_VALUE) { do { @@ -472,9 +479,9 @@ namespace Utils list.emplace_back(findFileData.cFileName); result = true; } - } while (InternetFindNextFileA(this->hFile, &findFileData)); + } while (InternetFindNextFileA(this->hFile_, &findFileData)); - InternetCloseHandle(this->hFile); + InternetCloseHandle(this->hFile_); } this->setDirectory(tempDir); @@ -492,40 +499,40 @@ namespace Utils return this->listElements(directory, list, true); } - bool WebIO::uploadFile(const std::string& file, const std::string& localfile) + bool WebIO::deleteFile(const std::string& file) const { - return (FtpPutFileA(this->hConnect, localfile.data(), file.data(), FTP_TRANSFER_TYPE_BINARY, NULL) == TRUE); + return (FtpDeleteFileA(this->hConnect_, file.data()) == TRUE); } - bool WebIO::deleteFile(const std::string& file) + bool WebIO::renameFile(const std::string& file, const std::string& newFile) const { - return (FtpDeleteFileA(this->hConnect, file.data()) == TRUE); + return (FtpRenameFileA(this->hConnect_, file.data(), newFile.data()) == TRUE); } - bool WebIO::renameFile(const std::string& file, const std::string& newFile) + bool WebIO::downloadFile(const std::string& file, const std::string& localFile) const { - return (FtpRenameFileA(this->hConnect, file.data(), newFile.data()) == TRUE); + return (FtpGetFileA(this->hConnect_, file.data(), localFile.data(), FALSE, NULL, FTP_TRANSFER_TYPE_BINARY, NULL) == TRUE); } - bool WebIO::downloadFile(const std::string& file, const std::string& localfile) + bool WebIO::uploadFile(const std::string& file, const std::string& localFile) const { - return (FtpGetFileA(this->hConnect, file.data(), localfile.data(), FALSE, NULL, FTP_TRANSFER_TYPE_BINARY, 0) == TRUE); + return (FtpPutFileA(this->hConnect_, localFile.data(), file.data(), FTP_TRANSFER_TYPE_BINARY, NULL) == TRUE); } bool WebIO::uploadFileData(const std::string& file, const std::string& data) { bool result = false; - this->hFile = FtpOpenFileA(this->hConnect, file.data(), GENERIC_WRITE, INTERNET_FLAG_TRANSFER_BINARY | INTERNET_FLAG_DONT_CACHE | INTERNET_FLAG_RELOAD, 0); + this->hFile_ = FtpOpenFileA(this->hConnect_, file.data(), GENERIC_WRITE, INTERNET_FLAG_TRANSFER_BINARY | INTERNET_FLAG_DONT_CACHE | INTERNET_FLAG_RELOAD, NULL); - if (this->hFile) + if (this->hFile_) { DWORD size = 0; - if (InternetWriteFile(this->hFile, data.data(), data.size(), &size) == TRUE) + if (InternetWriteFile(this->hFile_, data.data(), data.size(), &size) == TRUE) { result = (size == data.size()); } - InternetCloseHandle(this->hFile); + InternetCloseHandle(this->hFile_); } return result; @@ -535,20 +542,20 @@ namespace Utils { data.clear(); - this->hFile = FtpOpenFileA(this->hConnect, file.data(), GENERIC_READ, INTERNET_FLAG_TRANSFER_BINARY | INTERNET_FLAG_DONT_CACHE | INTERNET_FLAG_RELOAD, 0); + this->hFile_ = FtpOpenFileA(this->hConnect_, file.data(), GENERIC_READ, INTERNET_FLAG_TRANSFER_BINARY | INTERNET_FLAG_DONT_CACHE | INTERNET_FLAG_RELOAD, NULL); - if (this->hFile) + if (this->hFile_) { DWORD size = 0; - char buffer[0x2001] = { 0 }; + char buffer[0x2001]{}; - while (InternetReadFile(this->hFile, buffer, 0x2000, &size)) + while (InternetReadFile(this->hFile_, buffer, sizeof(buffer) - 1, &size)) { data.append(buffer, size); if (!size) break; } - InternetCloseHandle(this->hFile); + InternetCloseHandle(this->hFile_); return true; } @@ -559,4 +566,9 @@ namespace Utils { this->progressCallback = callback; } + + void WebIO::cancelDownload() + { + this->cancel_ = true; + } } diff --git a/src/Utils/WebIO.hpp b/src/Utils/WebIO.hpp index 712c60c3..52db1c42 100644 --- a/src/Utils/WebIO.hpp +++ b/src/Utils/WebIO.hpp @@ -16,7 +16,7 @@ namespace Utils class WebIO { public: - typedef std::map Params; + using params = std::map; WebIO(); WebIO(const std::string& useragent); @@ -30,15 +30,15 @@ namespace Utils std::string postFile(const std::string& url, const std::string& data, const std::string& fieldName, const std::string& fileName); std::string postFile(const std::string& data, std::string fieldName, std::string fileName); - std::string post(const std::string& url, Params params, bool* success = nullptr); + std::string post(const std::string& url, const params& params, bool* success = nullptr); std::string post(const std::string& url, const std::string& body, bool* success = nullptr); - std::string post(Params params, bool* success = nullptr); + std::string post(const params& params, bool* success = nullptr); std::string post(const std::string& body, bool* success = nullptr); std::string get(const std::string& url, bool* success = nullptr); std::string get(bool* success = nullptr); - WebIO* setTimeout(DWORD mseconds); + WebIO* setTimeout(DWORD msec); // FTP bool connect(); @@ -46,27 +46,28 @@ namespace Utils bool setDirectory(const std::string&directory); bool setRelativeDirectory(std::string directory); - bool getDirectory(std::string &directory); - bool createDirectory(const std::string& directory); + bool getDirectory(std::string* directory) const; + bool createDirectory(const std::string& directory) const; bool deleteDirectory(const std::string& directory); - bool renameDirectory(const std::string& directory, const std::string& newDir); + bool renameDirectory(const std::string& directory, const std::string& newDir) const; bool listDirectories(const std::string& directory, std::vector& list); bool listFiles(const std::string& directory, std::vector& list); - bool deleteFile(const std::string& file); - bool renameFile(const std::string& file, const std::string& newFile); - bool uploadFile(const std::string& file, const std::string& localfile); - bool downloadFile(const std::string& file, const std::string& localfile); + bool deleteFile(const std::string& file) const; + bool renameFile(const std::string& file, const std::string& newFile) const; + bool downloadFile(const std::string& file, const std::string& localFile) const; + bool uploadFile(const std::string& file, const std::string& localFile) const; bool uploadFileData(const std::string& file,const std::string& data); bool downloadFileData(const std::string& file, std::string& data); void setProgressCallback(const Slot& callback); - void cancelDownload() { this->cancel = true; } + void cancelDownload(); + + static std::string GetCacheBuster(); private: - enum Command { COMMAND_POST, @@ -82,27 +83,27 @@ namespace Utils std::string raw; }; - bool cancel; + bool cancel_; - bool isFTP; - std::string username; - std::string password; + bool isFTP_; + std::string username_; + std::string password_; - WebURL url; + WebURL url_; - HINTERNET hSession; - HINTERNET hConnect; - HINTERNET hFile; + HINTERNET hSession_; + HINTERNET hConnect_; + HINTERNET hFile_; - DWORD timeout; + DWORD timeout_; Slot progressCallback; - static std::string buildPostBody(Params params); + static std::string buildPostBody(const params& params); - bool isSecuredConnection() const; + [[nodiscard]] bool isSecuredConnection() const; - std::string execute(const char* command, const std::string& body, Params headers = {}, bool* success = nullptr); + std::string execute(const char* command, const std::string& body, const params& headers, bool* success = nullptr); bool listElements(const std::string& directory, std::vector& list, bool files); @@ -112,6 +113,6 @@ namespace Utils bool openConnection(); void closeConnection(); - static void formatPath(std::string& path, bool win); /* if (win == true): / -> \\ */ + static void FormatPath(std::string& path, bool win); /* if (win == true): / -> \\ */ }; }