Merge pull request #840 from XLabsProject/develop

Release v0.7.8
This commit is contained in:
Edo 2023-03-17 18:23:16 +00:00 committed by GitHub
commit 3cdc6a914b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
228 changed files with 9189 additions and 8605 deletions

View File

@ -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'

83
.gitmodules vendored
View File

@ -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

View File

@ -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

View File

@ -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

2
deps/GSL vendored

@ -1 +1 @@
Subproject commit f94c1f6f2b5e141d5f6eb3d284cd4a8cf9a81aac
Subproject commit 43d60c5e3891dab6491a76d0bac554a4a89d57f6

1
deps/discord-rpc vendored Submodule

@ -0,0 +1 @@
Subproject commit 963aa9f3e5ce81a4682c6ca3d136cddda614db33

1
deps/iw4-open-formats vendored Submodule

@ -0,0 +1 @@
Subproject commit cf45b460fe32a8c858b30445df779e29821cfdee

2
deps/libtomcrypt vendored

@ -1 +1 @@
Subproject commit 29986d04f2dca985ee64fbca1c7431ea3e3422f4
Subproject commit 2a1b284677a51f587ab7cd9d97395e0c0c93a447

2
deps/mongoose vendored

@ -1 +1 @@
Subproject commit 73813a838386f6ebca447eb54c626803163ee257
Subproject commit 4236405b90e051310aadda818e21c811e404b4d8

40
deps/premake/discord-rpc.lua vendored Normal file
View File

@ -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)

View File

@ -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"

View File

@ -8,7 +8,7 @@ end
function gsl.includes()
includedirs {
path.join(gsl.source, "include")
path.join(gsl.source, "include"),
}
end

44
deps/premake/iw4-open-formats.lua vendored Normal file
View File

@ -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)

View File

@ -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()

View File

@ -38,10 +38,6 @@ function protobuf.project()
}
rules {"ProtobufCompiler"}
defines {"_SCL_SECURE_NO_WARNINGS"}
linkoptions {"-IGNORE:4221"}
warnings "Off"
kind "StaticLib"

19
deps/premake/rapidjson.lua vendored Normal file
View File

@ -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)

2
deps/protobuf vendored

@ -1 +1 @@
Subproject commit 8d5fdedd42ef361dcfc1531fba4f33470273f375
Subproject commit 53a1c5c1d8b61984899b6877e491e5117ad486ba

1
deps/rapidjson vendored Submodule

@ -0,0 +1 @@
Subproject commit 012be8528783cdbf4b7a9e64f78bd8f056b97e24

2
deps/zlib vendored

@ -1 +1 @@
Subproject commit 02a6049eb3884c430268bb0fe3296d597a03174c
Subproject commit eb0e038b297f2c9877ed8b3515c6718a4b65d485

View File

@ -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.
------------------------------------------------------------------------------
*/
*/

View File

@ -1,4 +1,5 @@
#include <STDInclude.hpp>
#include <Utils/InfoString.hpp>
#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;

View File

@ -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"

View File

@ -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<Game::newMapArena_t*>(0x417807, &ArenaLength::NewArenas[0]);
Utils::Hook::Set<Game::newMapArena_t*>(0x420717, &ArenaLength::NewArenas[0]);
Utils::Hook::Set<Game::newMapArena_t*>(0x49BD22, &ArenaLength::NewArenas[0]);
Utils::Hook::Set<Game::newMapArena_t*>(0x4A9649, &ArenaLength::NewArenas[0]);
Utils::Hook::Set<Game::newMapArena_t*>(0x4A97C2, &ArenaLength::NewArenas[0]);
Utils::Hook::Set<Game::newMapArena_t*>(0x4D077E, &ArenaLength::NewArenas[0]);
Utils::Hook::Set<Game::newMapArena_t*>(0x630B00, &ArenaLength::NewArenas[0]);
Utils::Hook::Set<Game::newMapArena_t*>(0x630B2E, &ArenaLength::NewArenas[0]);
Utils::Hook::Set<Game::newMapArena_t*>(0x632782, &ArenaLength::NewArenas[0]);
// Reallocate ui_arenaInfos
Utils::Hook::Set<char**>(0x4A95F0 + 3, ArenaLength::NewArenaInfos);
Utils::Hook::Set<char**>(0x4A9620 + 3, ArenaLength::NewArenaInfos);
Utils::Hook::Set<char**>(0x4A9653 + 3, ArenaLength::NewArenaInfos);
Utils::Hook::Set<char**>(0x4A9684 + 3, ArenaLength::NewArenaInfos);
Utils::Hook::Set<char**>(0x4A96B7 + 3, ArenaLength::NewArenaInfos);
Utils::Hook::Set<char**>(0x4A97B3 + 3, ArenaLength::NewArenaInfos);
Utils::Hook::Set<char**>(0x630A9A + 3, ArenaLength::NewArenaInfos);
// Increase size - patch max arena count
Utils::Hook::Set<unsigned int>(0x630AA2 + 1, NEW_ARENA_COUNT);
// Reallocate sharedUiInfo.mapList
Utils::Hook::Set<Game::newMapArena_t*>(0x417807, ArenaLength::NewArenas);
Utils::Hook::Set<Game::newMapArena_t*>(0x420717, ArenaLength::NewArenas);
Utils::Hook::Set<Game::newMapArena_t*>(0x49BD22, ArenaLength::NewArenas);
Utils::Hook::Set<Game::newMapArena_t*>(0x4A9649, ArenaLength::NewArenas);
Utils::Hook::Set<Game::newMapArena_t*>(0x4A97C2, ArenaLength::NewArenas);
Utils::Hook::Set<Game::newMapArena_t*>(0x4D077E, ArenaLength::NewArenas);
Utils::Hook::Set<Game::newMapArena_t*>(0x630B00, ArenaLength::NewArenas);
Utils::Hook::Set<Game::newMapArena_t*>(0x630B2E, ArenaLength::NewArenas);
Utils::Hook::Set<Game::newMapArena_t*>(0x632782, ArenaLength::NewArenas);
Utils::Hook::Set<char*>(0x4A967A, ArenaLength::NewArenas[0].description);
Utils::Hook::Set<char*>(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<BYTE>(0x4A95F8, 32);
Utils::Hook::Set<BYTE>(0x4A95F8, sizeof(Game::newMapArena_t::mapName));
Utils::Hook::Set<int>(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<std::uint32_t>(0x630AA3, arenaCount);
}
}

View File

@ -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();

View File

@ -1,4 +1,5 @@
#include <STDInclude.hpp>
#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<json11::Json> 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<json11::Json> 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<json11::Json> techniques;
for (int technique = 0; technique < 48; technique++)
{
auto curTech = techset->techniques[technique];
if (curTech)
{
std::vector<json11::Json> passDataArray;
for (int pass = 0; pass < curTech->passCount; pass++)
{
auto curPass = &curTech->passArray[pass];
std::vector<json11::Json> 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<float> 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<int>(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<bool>() && type == Game::XAssetType::ASSET_TYPE_XMODEL && name == "void")
{
asset.model->numLods = 0;

View File

@ -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<int>();
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<Game::ComWorld>();
header->comWorld = asset;
if (asset->name)
{
asset->name = reader.readCString();
}
if (asset->primaryLights)
{
asset->primaryLights = reader.readArray<Game::ComPrimaryLight>(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::ComWorld>(Game::XAssetType::ASSET_TYPE_COMWORLD, _name);
}
void IComWorld::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)

View File

@ -2,7 +2,7 @@
#include "IFont_s.hpp"
#define STB_TRUETYPE_IMPLEMENTATION
#include <stb_truetype.h>
#include <stb_truetype.hpp>
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<Game::Font_s>();
auto* buffer = builder->getBuffer();
auto* asset = header.font;
auto* dest = buffer->dest<Game::Font_s>();
buffer->save(asset);

View File

@ -1,15 +1,15 @@
#include <STDInclude.hpp>
#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<int>();
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<Game::FxEffectDef>();
header->fx = asset;
if (asset->name)
{
asset->name = buffer.readCString();
}
if (asset->elemDefs)
{
asset->elemDefs = buffer.readArray<Game::FxElemDef>(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<Game::FxElemVelStateSample>(elemDef->velIntervalCount + 1);
}
if (elemDef->visSamples)
{
elemDef->visSamples = buffer.readArray<Game::FxElemVisStateSample>(elemDef->visStateIntervalCount + 1);
}
// Save_FxElemDefVisuals
{
if (elemDef->elemType == Game::FX_ELEM_TYPE_DECAL)
{
if (elemDef->visuals.markArray)
{
elemDef->visuals.markArray = buffer.readArray<Game::FxElemMarkVisuals>(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<Game::FxElemVisuals>(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<Game::FxTrailDef>();
elemDef->extended.trailDef = trailDef;
if (trailDef->verts)
{
trailDef->verts = buffer.readArray<Game::FxTrailVertex>(trailDef->vertCount);
}
if (trailDef->inds)
{
trailDef->inds = buffer.readArray<unsigned short>(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::FxEffectDef>(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
}

View File

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

View File

@ -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::FxWorld>(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<Game::FxWorld>();
map->name = builder->getAllocator()->duplicateString(name);
// No glass for you!
ZeroMemory(&map->glassSys, sizeof(map->glassSys));

View File

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

View File

@ -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<Game::GameWorldMp>();
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<int>() : 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<std::string>());
auto glassData = builder->getAllocator()->allocate<Game::G_GlassData>();
if (gameWorldJson["glassData"].is_object())
{
auto jsonGlassData = gameWorldJson["glassData"];
try
{
glassData->damageToDestroy = jsonGlassData["damageToDestroy"].get<unsigned short>();
glassData->damageToWeaken = jsonGlassData["damageToWeaken"].get<unsigned short>();
if (jsonGlassData["glassNames"].is_array())
{
nlohmann::json::array_t glassNames = jsonGlassData["glassNames"];
glassData->glassNameCount = glassNames.size();
glassData->glassNames = builder->getAllocator()->allocateArray<Game::G_GlassName>(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<unsigned short>();
if (jsonGlassName["piecesIndices"].is_array())
{
nlohmann::json::array_t jsonPiecesIndices = jsonGlassName["piecesIndices"];
glassData->glassNames[i].pieceCount = static_cast<unsigned short>(jsonPiecesIndices.size());
for (size_t j = 0; j < glassData->glassNames[i].pieceCount; j++)
{
glassData->glassNames[i].pieceIndices[j] = jsonPiecesIndices[j].get<unsigned short>();
}
}
}
}
if (gameWorldJson["glassPieces"].is_array())
{
nlohmann::json::array_t glassPieces = gameWorldJson["glassPieces"];
glassData->pieceCount = glassPieces.size();
glassData->glassPieces = builder->getAllocator()->allocateArray<Game::G_GlassPiece>(glassData->pieceCount);
for (size_t i = 0; i < glassData->pieceCount; i++)
{
glassData->glassPieces[i].collapseTime = glassPieces[i]["collapseTime"].get<unsigned short>();
glassData->glassPieces[i].damageTaken = glassPieces[i]["damageTaken"].get<unsigned short>();
glassData->glassPieces[i].lastStateChangeTime = glassPieces[i]["lastStateChangeTime"].get<int>();
glassData->glassPieces[i].impactDir = glassPieces[i]["impactDir"].get<char>();
nlohmann::json::array_t jsonPos = glassPieces[i]["impactPos"];
glassData->glassPieces[i].impactPos[0] = jsonPos[0].get<char>();
glassData->glassPieces[i].impactPos[1] = jsonPos[1].get<char>();
}
}
}
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::GameWorldMp>(Game::XAssetType::ASSET_TYPE_GAMEWORLD_MP, _name);
}
}

View File

@ -1,150 +1,11 @@
#include <STDInclude.hpp>
#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<Game::GfxImage>();
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<char>();
image->semantic = reader.read<Game::TextureSemantic>();
image->category = reader.read<char>();
int dataLength = reader.read<int>();
image->cardMemory.platform[0] = dataLength;
image->cardMemory.platform[1] = dataLength;
Game::GfxImageLoadDefIW3 loadDef;
image->texture.loadDef = reinterpret_cast<Game::GfxImageLoadDef*>(reader.readArray<char>(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<const Game::GfxImageFileHeader*>(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<Game::GfxImageLoadDef>();
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::GfxImage>(Game::XAssetType::ASSET_TYPE_IMAGE, name);
}
void IGfxImage::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)

View File

@ -1,44 +1,11 @@
#include <STDInclude.hpp>
#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<char>(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<char>());
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<Game::GfxLightDef>();
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::GfxLightDef>(Game::XAssetType::ASSET_TYPE_LIGHT_DEF, name);
}
void IGfxLightDef::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)

View File

@ -1,450 +1,16 @@
#include <STDInclude.hpp>
#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<unsigned short>(asset->staticSurfaceCount + asset->staticSurfaceCountNoDecal);
}
if (asset->smodelInsts)
{
asset->smodelInsts = reader->readArray<Game::GfxStaticModelInst>(asset->smodelCount);
}
if (asset->surfaces)
{
asset->surfaces = reader->readArray<Game::GfxSurface>(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<Game::GfxSurfaceBounds>(world->surfaceCount);
}
if (asset->smodelDrawInsts)
{
asset->smodelDrawInsts = reader->readArray<Game::GfxStaticModelDrawInst>(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<Game::GfxImage*>(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<Game::GfxReflectionProbe>(asset->reflectionProbeCount);
}
if (asset->lightmaps)
{
asset->lightmaps = reader->readArray<Game::GfxLightmapArray>(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<Game::GfxWorldVertex>(asset->vertexCount);
}
}
// saveGfxWorldVertexLayerData
{
if (asset->vld.data)
{
// no align for char
asset->vld.data = reader->readArray<char>(asset->vertexLayerDataSize);
}
}
if (asset->indices)
{
asset->indices = reader->readArray<unsigned short>(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<int>();
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<Game::GfxWorld>();
header->gfxWorld = asset;
if (asset->name)
{
asset->name = reader.readCString();
}
if (asset->baseName)
{
asset->baseName = reader.readCString();
}
if (asset->skies)
{
asset->skies = reader.readArray<Game::GfxSky>(asset->skyCount);
for (int i = 0; i < asset->skyCount; ++i)
{
Game::GfxSky* sky = &asset->skies[i];
if (sky->skyStartSurfs)
{
sky->skyStartSurfs = reader.readArray<int>(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<Game::cplane_s>(asset->planeCount);
if (builder->getAllocator()->isPointerMapped(oldPtr))
{
asset->dpvsPlanes.planes = builder->getAllocator()->getPointer<Game::cplane_s>(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<unsigned short>(asset->nodeCount);
}
}
int cellCount = asset->dpvsPlanes.cellCount;
if (asset->aabbTreeCounts)
{
asset->aabbTreeCounts = reader.readArray<Game::GfxCellTreeCount>(cellCount);
}
if (asset->aabbTrees)
{
asset->aabbTrees = reader.readArray<Game::GfxCellTree>(cellCount);
for (int i = 0; i < cellCount; ++i)
{
Game::GfxCellTree* cellTree = &asset->aabbTrees[i];
if (cellTree->aabbTree)
{
cellTree->aabbTree = reader.readArray<Game::GfxAabbTree>(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<unsigned short>(aabbTree->smodelIndexCount);
aabbTree->smodelIndexes = builder->getAllocator()->getPointer<unsigned short>(oldPointer);
}
else
{
aabbTree->smodelIndexes = reader.readArray<unsigned short>(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<Game::GfxCell>(cellCount);
for (int i = 0; i < cellCount; ++i)
{
Game::GfxCell* cell = &asset->cells[i];
if (cell->portals)
{
cell->portals = reader.readArray<Game::GfxPortal>(cell->portalCount);
for (int j = 0; j < cell->portalCount; ++j)
{
Game::GfxPortal* portal = &cell->portals[j];
if (portal->vertices)
{
portal->vertices = reader.readArray<Game::vec3_t>(portal->vertexCount);
}
}
}
if (cell->reflectionProbes)
{
cell->reflectionProbes = reader.readArray<char>(cell->reflectionProbeCount);
}
}
}
this->loadGfxWorldDraw(&asset->draw, builder, &reader);
// GfxLightGrid
{
if (asset->lightGrid.rowDataStart)
{
asset->lightGrid.rowDataStart = reader.readArray<unsigned short>((asset->lightGrid.maxs[asset->lightGrid.rowAxis] - asset->lightGrid.mins[asset->lightGrid.rowAxis]) + 1);
}
if (asset->lightGrid.rawRowData)
{
asset->lightGrid.rawRowData = reader.readArray<char>(asset->lightGrid.rawRowDataSize);
}
if (asset->lightGrid.entries)
{
asset->lightGrid.entries = reader.readArray<Game::GfxLightGridEntry>(asset->lightGrid.entryCount);
}
if (asset->lightGrid.colors)
{
asset->lightGrid.colors = reader.readArray<Game::GfxLightGridColors>(asset->lightGrid.colorCount);
}
}
if (asset->models)
{
asset->models = reader.readArray<Game::GfxBrushModel>(asset->modelCount);
}
if (asset->materialMemory)
{
asset->materialMemory = reader.readArray<Game::MaterialMemory>(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<Game::GfxShadowGeometry>(asset->primaryLightCount);
for (unsigned int i = 0; i < asset->primaryLightCount; ++i)
{
Game::GfxShadowGeometry* shadowGeometry = &asset->shadowGeom[i];
if (shadowGeometry->sortedSurfIndex)
{
shadowGeometry->sortedSurfIndex = reader.readArray<unsigned short>(shadowGeometry->surfaceCount);
}
if (shadowGeometry->smodelIndex)
{
shadowGeometry->smodelIndex = reader.readArray<unsigned short>(shadowGeometry->smodelCount);
}
}
}
if (asset->lightRegion)
{
asset->lightRegion = reader.readArray<Game::GfxLightRegion>(asset->primaryLightCount);
for (unsigned int i = 0; i < asset->primaryLightCount; ++i)
{
Game::GfxLightRegion* lightRegion = &asset->lightRegion[i];
if (lightRegion->hulls)
{
lightRegion->hulls = reader.readArray<Game::GfxLightRegionHull>(lightRegion->hullCount);
for (unsigned int j = 0; j < lightRegion->hullCount; ++j)
{
Game::GfxLightRegionHull* lightRegionHull = &lightRegion->hulls[j];
if (lightRegionHull->axis)
{
lightRegionHull->axis = reader.readArray<Game::GfxLightRegionAxis>(lightRegionHull->axisCount);
}
}
}
}
}
this->loadGfxWorldDpvsStatic(asset, &asset->dpvs, builder, &reader);
// Obsolete, IW3 has no support for that
if (asset->heroOnlyLights)
{
asset->heroOnlyLights = reader.readArray<Game::GfxHeroOnlyLight>(asset->heroOnlyLightCount);
}
}
header->gfxWorld = builder->getIW4OfApi()->read<Game::GfxWorld>(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<Game::GfxImage*>();
auto** imageDest = buffer->dest<Game::GfxImage*>();
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<Game::GfxLightmapArray>();
auto* lightmapArrayDestTable = buffer->dest<Game::GfxLightmapArray>();
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<Game::GfxSurface>();
auto* destSurfaceTable = buffer->dest<Game::GfxSurface>();
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<Game::GfxStaticModelDrawInst>();
auto* destModelTable = buffer->dest<Game::GfxStaticModelDrawInst>();
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<Game::GfxWorld>();
auto* asset = header.gfxWorld;
auto* dest = buffer->dest<Game::GfxWorld>();
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<Game::GfxSky>();
auto* destSkyTable = buffer->dest<Game::GfxSky>();
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<Game::GfxCellTree>();
auto* destCellTreeTable = buffer->dest<Game::GfxCellTree>();
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<Game::GfxAabbTree>();
auto* destAabbTreeTable = buffer->dest<Game::GfxAabbTree>();
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<Game::GfxCell>();
auto* destCellTable = buffer->dest<Game::GfxCell>();
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<Game::GfxPortal>();
auto* destPortalTable = buffer->dest<Game::GfxPortal>();
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<Game::MaterialMemory>();
auto* destMaterialMemoryTable = buffer->dest<Game::MaterialMemory>();
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<Game::GfxShadowGeometry>();
auto* destShadowGeometryTable = buffer->dest<Game::GfxShadowGeometry>();
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<Game::GfxLightRegion>();
auto* destLightRegionTable = buffer->dest<Game::GfxLightRegion>();
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<Game::GfxLightRegionHull>();
auto* destLightRegionHullTable = buffer->dest<Game::GfxLightRegionHull>();
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();
}

View File

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

View File

@ -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<Game::LoadedSound>();
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<unsigned int>();
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>();
unsigned int format = reader.read<unsigned int>();
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<unsigned int>();
chunkSize = reader.read<unsigned int>();
switch (chunkIDBuffer)
{
case 0x20746D66: // fmt
if (chunkSize >= 16)
{
sound->sound.info.format = reader.read<short>();
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<short>();
sound->sound.info.rate = reader.read<int>();
sound->sound.info.samples = reader.read<int>();
sound->sound.info.block_size = reader.read<short>();
sound->sound.info.bits = reader.read<short>();
// 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<char>(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::LoadedSound>(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<Game::LoadedSound>();
auto* buffer = builder->getBuffer();
auto* asset = header.loadSnd;
auto* dest = buffer->dest<Game::LoadedSound>();
buffer->save(asset);
buffer->pushBlock(Game::XFILE_BLOCK_VIRTUAL);

View File

@ -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>();
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<void**>(mapEnts))
{
*reinterpret_cast<Game::MapEnts**>(mapEnts) = header.mapEnts;
}
}, &orgEnts, false);
}
}
if (orgEnts)
{
std::memcpy(entites, orgEnts, sizeof Game::MapEnts);
}
else
{
entites->stageCount = 1;
entites->stages = builder->getAllocator()->allocate<Game::Stage>();
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::MapEnts>(Game::XAssetType::ASSET_TYPE_MAP_ENTS, _name);
}
void IMapEnts::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)

View File

@ -1,728 +1,18 @@
#include <STDInclude.hpp>
#include "IMaterial.hpp"
#define IW4X_MAT_BIN_VERSION "1"
#define IW4X_MAT_JSON_VERSION 1
namespace Assets
{
const std::unordered_map<std::string, std::string> 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<Game::Material>();
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<int>() != 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<std::string>());
return;
}
try
{
asset->info.name = builder->getAllocator()->duplicateString(materialJson["name"].get<std::string>());
asset->info.gameFlags = static_cast<char>(Utils::Json::ReadFlags(materialJson["gameFlags"].get<std::string>(), sizeof(char)));
asset->info.sortKey = materialJson["sortKey"].get<char>();
// * We do techset later * //
asset->info.textureAtlasRowCount = materialJson["textureAtlasRowCount"].get<unsigned char>();
asset->info.textureAtlasColumnCount = materialJson["textureAtlasColumnCount"].get<unsigned char>();
asset->info.surfaceTypeBits = static_cast<unsigned int>(Utils::Json::ReadFlags(materialJson["surfaceTypeBits"].get<std::string>(), sizeof(int)));
asset->info.hashIndex = materialJson["hashIndex"].get<unsigned short>();
asset->cameraRegion = materialJson["cameraRegion"].get<char>();
}
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<long long>();
asset->info.drawSurf.fields.hasGfxEntIndex = materialJson["gfxDrawSurface"]["hasGfxEntIndex"].get<long long>();
asset->info.drawSurf.fields.materialSortedIndex = materialJson["gfxDrawSurface"]["materialSortedIndex"].get<long long>();
asset->info.drawSurf.fields.objectId = materialJson["gfxDrawSurface"]["objectId"].get<long long>();
asset->info.drawSurf.fields.prepass = materialJson["gfxDrawSurface"]["prepass"].get<long long>();
asset->info.drawSurf.fields.primarySortKey = materialJson["gfxDrawSurface"]["primarySortKey"].get<long long>();
asset->info.drawSurf.fields.reflectionProbeIndex = materialJson["gfxDrawSurface"]["reflectionProbeIndex"].get<long long>();
asset->info.drawSurf.fields.sceneLightIndex = materialJson["gfxDrawSurface"]["sceneLightIndex"].get<long long>();
asset->info.drawSurf.fields.surfType = materialJson["gfxDrawSurface"]["surfType"].get<long long>();
asset->info.drawSurf.fields.unused = materialJson["gfxDrawSurface"]["unused"].get<long long>();
asset->info.drawSurf.fields.useHeroLighting = materialJson["gfxDrawSurface"]["useHeroLighting"].get<long long>();
}
asset->stateFlags = static_cast<char>(Utils::Json::ReadFlags(materialJson["stateFlags"].get<std::string>(), sizeof(char)));
if (materialJson["textureTable"].is_array())
{
nlohmann::json::array_t textureTable = materialJson["textureTable"];
asset->textureCount = static_cast<unsigned char>(textureTable.size());
asset->textureTable = builder->getAllocator()->allocateArray<Game::MaterialTextureDef>(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<Game::TextureSemantic>();
textureDef->samplerState = textureJson["samplerState"].get<char>();
textureDef->nameStart = textureJson["nameStart"].get<char>();
textureDef->nameEnd = textureJson["nameEnd"].get<char>();
textureDef->nameHash = textureJson["nameHash"].get<unsigned int>();
if (textureDef->semantic == Game::TextureSemantic::TS_WATER_MAP)
{
Game::water_t* water = builder->getAllocator()->allocate<Game::water_t>();
if (textureJson["water"].is_object())
{
auto& waterJson = textureJson["water"];
if (waterJson["image"].is_string())
{
auto imageName = waterJson["image"].get<std::string>();
water->image = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_IMAGE, imageName.data(), builder).image;
}
water->amplitude = waterJson["amplitude"].get<float>();
water->M = waterJson["M"].get<int>();
water->N = waterJson["N"].get<int>();
water->Lx = waterJson["Lx"].get<float>();
water->Lz = waterJson["Lz"].get<float>();
water->gravity = waterJson["gravity"].get<float>();
water->windvel = waterJson["windvel"].get<float>();
auto winddir = waterJson["winddir"].get<std::vector<float>>();
if (winddir.size() == 2)
{
std::copy(winddir.begin(), winddir.end(), water->winddir);
}
auto codeConstant = waterJson["codeConstant"].get<std::vector<float>>();
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<std::string>();
auto predictedSize = static_cast<unsigned long>(std::ceilf((h064.size() / 4.f) * 3.f));
assert(predictedSize >= idealSize);
auto h0 = reinterpret_cast<Game::complex_s*>(builder->getAllocator()->allocate(predictedSize));
[[maybe_unused]] auto h0Result = base64_decode(
h064.data(),
h064.size(),
reinterpret_cast<unsigned char*>(h0),
&predictedSize
);
assert(h0Result == CRYPT_OK);
water->H0 = h0;
/// WTerm
auto wTerm64 = waterJson["wTerm"].get<std::string>();
auto predictedWTermSize = static_cast<unsigned long>(std::ceilf((wTerm64.size() / 4.f) * 3.f));
auto wTerm = reinterpret_cast<float*>(builder->getAllocator()->allocate(predictedWTermSize));
[[maybe_unused]] auto wTermResult = base64_decode(
wTerm64.data(),
wTerm64.size(),
reinterpret_cast<unsigned char*>(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<std::string>(),
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<char>();
}
}
if (materialJson["stateBitsTable"].is_array())
{
nlohmann::json::array_t array = materialJson["stateBitsTable"];
asset->stateBitsCount = static_cast<unsigned char>(array.size());
asset->stateBitsTable = builder->getAllocator()->allocateArray<Game::GfxStateBits>(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<unsigned int>()
#define READ_BOOL_LB_FROM_JSON(x) bool x = jsonStateBitEntry[#x].get<bool>()
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<std::string>();
const auto cullFace = jsonStateBitEntry["cullFace"].get<std::string>();
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<char>(constants.size());
auto table = builder->getAllocator()->allocateArray<Game::MaterialConstantDef>(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::vector<float>>();
std::copy(litVec.begin(), litVec.end(), entry->literal);
auto constantName = constant["name"].get<std::string>();
std::copy(constantName.begin(), constantName.end(), entry->name);
entry->nameHash = constant["nameHash"].get<unsigned int>();
}
asset->constantTable = table;
}
if (materialJson["techniqueSet"].is_string())
{
const std::string techsetName = materialJson["techniqueSet"].get<std::string>();
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<char>(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<char>());
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<Game::Material>();
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<Game::MaterialTextureDef>(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<Game::water_t>();
textureDef->u.water = water;
// Save_water_t
if (water->H0)
{
water->H0 = reader.readArray<Game::complex_s>(water->M * water->N);
}
if (water->wTerm)
{
water->wTerm = reader.readArray<float>(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<Game::MaterialConstantDef>(asset->constantCount);
}
if (asset->stateBitsTable)
{
asset->stateBitsTable = reader.readArray<Game::GfxStateBits>(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::Material>(Game::XAssetType::ASSET_TYPE_MATERIAL, name);
}
void IMaterial::loadNative(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* /*builder*/)

View File

@ -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;
};
}

View File

@ -1,8 +1,6 @@
#include <STDInclude.hpp>
#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<Game::MaterialPixelShader>();
asset->name = builder->getAllocator()->duplicateString(name);
asset->prog.loadDef.loadForRenderer = GFX_RENDERER_SHADER_SM3;
asset->prog.loadDef.programSize = static_cast<unsigned short>(programSize);
asset->prog.loadDef.program = builder->getAllocator()->allocateArray<unsigned int>(programSize);
memcpy_s(asset->prog.loadDef.program, buff.size(), buff.data(), buff.size());
header->pixelShader = asset;
header->pixelShader = builder->getIW4OfApi()->read<Game::MaterialPixelShader>(Game::XAssetType::ASSET_TYPE_PIXELSHADER, name);
}
void IMaterialPixelShader::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)

View File

@ -1,6 +1,8 @@
#include <STDInclude.hpp>
#include "IMaterialTechniqueSet.hpp"
#include <Utils/Json.hpp>
#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::MaterialTechniqueSet>(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<int>();
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<unsigned short>(Utils::Json::ReadFlags(technique["flags"].get<std::string>(), sizeof(short)));
if (technique["passArray"].is_array())
{
nlohmann::json::array_t passArray = technique["passArray"];
Game::MaterialTechnique* asset = (Game::MaterialTechnique*)builder->getAllocator()->allocateArray<unsigned char>(sizeof(Game::MaterialTechnique) + (sizeof(Game::MaterialPass) * (passArray.size() - 1)));
asset->name = builder->getAllocator()->duplicateString(name);
asset->flags = flags;
asset->passCount = static_cast<unsigned short>(passArray.size());
Game::MaterialPass* passes = builder->getAllocator()->allocateArray<Game::MaterialPass>(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<std::string>();
pass->vertexDecl = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_VERTEXDECL, declName, builder).vertexDecl;
}
if (jsonPass["vertexShader"].is_string())
{
auto vsName = jsonPass["vertexShader"].get<std::string>();
pass->vertexShader = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_VERTEXSHADER, vsName, builder).vertexShader;
}
if (jsonPass["pixelShader"].is_string())
{
auto psName = jsonPass["pixelShader"].get<std::string>();
pass->pixelShader = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_PIXELSHADER, psName, builder).pixelShader;
}
pass->perPrimArgCount = jsonPass["perPrimArgCount"].get<char>();
pass->perObjArgCount = jsonPass["perObjArgCount"].get<char>();
pass->stableArgCount = jsonPass["stableArgCount"].get<char>();
pass->customSamplerFlags = jsonPass["customSamplerFlags"].get<char>();
if (jsonPass["arguments"].is_array())
{
nlohmann::json::array_t jsonAguments = jsonPass["arguments"];
pass->args = builder->getAllocator()->allocateArray<Game::MaterialShaderArgument>(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<Game::MaterialShaderArgumentType>();
argument->dest = jsonArgument["dest"].get<unsigned short>();
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<float>(4);
auto literals = jsonArgument["literals"].get<std::vector<float>>();
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<unsigned short>();
argument->u.codeConst.firstRow = codeConst["firstRow"].get<unsigned char>();
argument->u.codeConst.rowCount = codeConst["rowCount"].get<unsigned char>();
}
}
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<unsigned int>();
}
else if (argument->type == Game::MaterialShaderArgumentType::MTL_ARG_CODE_PIXEL_SAMPLER)
{
argument->u.codeSampler = jsonArgument["codeSampler"].get<unsigned int>();
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<int>();
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<Game::MaterialTechniqueSet>();
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<std::string>());
}
asset->hasBeenUploaded = techset["hasBeenUploaded"].get<bool>();
asset->worldVertFormat = techset["worldVertFormat"].get<char>();
if (techset["remappedTechniqueSet"].is_string())
{
auto remapped = techset["remappedTechniqueSet"].get<std::string>();
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<std::string>(), builder);
}
}
}
header->techniqueSet = asset;
}
void IMaterialTechniqueSet::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
{
Game::MaterialTechniqueSet* asset = header.techniqueSet;

View File

@ -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<char>(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<char>();
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<Game::MaterialVertexDeclaration>();
if (asset->name)
{
asset->name = reader.readCString();
}
header->vertexDecl = asset;
header->vertexDecl = builder->getIW4OfApi()->read<Game::MaterialVertexDeclaration>(Game::XAssetType::ASSET_TYPE_VERTEXDECL, name);
}
void IMaterialVertexDeclaration::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)

View File

@ -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<Game::MaterialVertexShader>();
asset->name = builder->getAllocator()->duplicateString(name);
asset->prog.loadDef.loadForRenderer = GFX_RENDERER_SHADER_SM3;
asset->prog.loadDef.programSize = static_cast<unsigned short>(programSize);
asset->prog.loadDef.program = builder->getAllocator()->allocateArray<unsigned int>(programSize);
memcpy_s(asset->prog.loadDef.program, buff.size(), buff.data(), buff.size());
header->vertexShader = asset;
header->vertexShader = builder->getIW4OfApi()->read<Game::MaterialVertexShader>(Game::XAssetType::ASSET_TYPE_VERTEXSHADER, name);
}
void IMaterialVertexShader::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)

View File

@ -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<Game::PhysPreset>();
auto* buffer = builder->getBuffer();
auto* asset = header.physPreset;
auto* dest = buffer->dest<Game::PhysPreset>();
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::PhysPreset>(Game::XAssetType::ASSET_TYPE_PHYSPRESET, name);
}
}

View File

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

View File

@ -1,42 +1,13 @@
#include <STDInclude.hpp>
#include "IRawFile.hpp"
#include <Utils/Compression.hpp>
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<Game::RawFile>();
if (!asset)
{
return;
}
asset->name = builder->getAllocator()->duplicateString(name);
asset->len = static_cast<int>(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<char>(compressedData.size());
std::memcpy(const_cast<char*>(asset->buffer), compressedData.data(), compressedData.size());
asset->compressedLen = static_cast<int>(compressedData.size());
}
else
{
asset->buffer = builder->getAllocator()->allocateArray<char>(rawFile.getBuffer().size() + 1);
std::memcpy(const_cast<char*>(asset->buffer), rawFile.getBuffer().data(), rawFile.getBuffer().size());
asset->compressedLen = 0;
}
header->rawfile = asset;
header->rawfile = builder->getIW4OfApi()->read<Game::RawFile>(Game::XAssetType::ASSET_TYPE_RAWFILE, name);
}
void IRawFile::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)

View File

@ -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<Game::SndCurve>();
auto* buffer = builder->getBuffer();
auto* asset = header.sndCurve;
auto* dest = buffer->dest<Game::SndCurve>();
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::SndCurve>(Game::XAssetType::ASSET_TYPE_SOUND_CURVE, name);
if (!header->sndCurve)
{
header->sndCurve = Components::AssetHandler::FindOriginalAsset(Game::XAssetType::ASSET_TYPE_SOUND_CURVE, name.data()).sndCurve;
}
}
}

View File

@ -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;
};
}

View File

@ -1,108 +1,11 @@
#include <STDInclude.hpp>
#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<int>();
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<Game::XAnimParts>();
if (xanim)
{
if (xanim->name)
{
xanim->name = reader.readCString();
}
if (xanim->names)
{
xanim->names = builder->getAllocator()->allocateArray<unsigned short>(xanim->boneCount[Game::PART_TYPE_ALL]);
for (int i = 0; i < xanim->boneCount[Game::PART_TYPE_ALL]; ++i)
{
xanim->names[i] = static_cast<std::uint16_t>(Game::SL_GetString(reader.readCString(), 0));
}
}
if (xanim->notify)
{
xanim->notify = reader.readArray<Game::XAnimNotifyInfo>(xanim->notifyCount);
for (int i = 0; i < xanim->notifyCount; ++i)
{
xanim->notify[i].name = static_cast<std::uint16_t>(Game::SL_GetString(reader.readCString(), 0));
}
}
if (xanim->dataByte)
{
xanim->dataByte = reader.readArray<char>(xanim->dataByteCount);
}
if (xanim->dataShort)
{
xanim->dataShort = reader.readArray<short>(xanim->dataShortCount);
}
if (xanim->dataInt)
{
xanim->dataInt = reader.readArray<int>(xanim->dataIntCount);
}
if (xanim->randomDataByte)
{
xanim->randomDataByte = reader.readArray<char>(xanim->randomDataByteCount);
}
if (xanim->randomDataShort)
{
xanim->randomDataShort = reader.readArray<short>(xanim->randomDataShortCount);
}
if (xanim->randomDataInt)
{
xanim->randomDataInt = reader.readArray<int>(xanim->randomDataIntCount);
}
if (xanim->indices.data)
{
if (xanim->numframes < 256)
{
xanim->indices._1 = reader.readArray<char>(xanim->indexCount);
}
else
{
xanim->indices._2 = reader.readArray<unsigned short>(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::XAnimParts>(Game::XAssetType::ASSET_TYPE_XANIMPARTS, name);
}
void IXAnimParts::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)

View File

@ -1,298 +1,30 @@
#include <STDInclude.hpp>
#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<Game::XSurfaceCollisionNode>(entry->nodeCount);
}
if (entry->leafs)
{
entry->leafs = reader->readArrayOnce<Game::XSurfaceCollisionLeaf>(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<unsigned short>(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<Game::GfxPackedVertex>(surf->vertCount);
}
// Save_XRigidVertListArray
if (surf->vertList)
{
surf->vertList = reader->readArrayOnce<Game::XRigidVertList>(surf->vertListCount);
for (unsigned int i = 0; i < surf->vertListCount; ++i)
{
Game::XRigidVertList* rigidVertList = &surf->vertList[i];
if (rigidVertList->collisionTree)
{
rigidVertList->collisionTree = reader->readObject<Game::XSurfaceCollisionTree>();
this->loadXSurfaceCollisionTree(rigidVertList->collisionTree, reader);
}
}
}
// Access index block
if (surf->triIndices)
{
surf->triIndices = reader->readArrayOnce<unsigned short>(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<Game::XSurface>(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::XModel>(Game::XAssetType::ASSET_TYPE_XMODEL, name);
if (!builder->isPrimaryAsset() && (!Components::ZoneBuilder::PreferDiskAssetsDvar.get<bool>() || !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<int>();
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<Game::XModel>();
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<unsigned short>(asset->numBones);
for (char i = 0; i < asset->numBones; ++i)
{
asset->boneNames[i] = static_cast<std::uint16_t>(Game::SL_GetString(reader.readCString(), 0));
}
}
if (asset->parentList)
{
asset->parentList = reader.readArrayOnce<unsigned char>(asset->numBones - asset->numRootBones);
}
if (asset->quats)
{
asset->quats = reader.readArrayOnce<short>((asset->numBones - asset->numRootBones) * 4);
}
if (asset->trans)
{
asset->trans = reader.readArrayOnce<float>((asset->numBones - asset->numRootBones) * 3);
}
if (asset->partClassification)
{
asset->partClassification = reader.readArrayOnce<unsigned char>(asset->numBones);
}
if (asset->baseMat)
{
asset->baseMat = reader.readArrayOnce<Game::DObjAnimMat>(asset->numBones);
}
if (asset->materialHandles)
{
asset->materialHandles = reader.readArray<Game::Material*>(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<Game::XModelSurfs>();
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<Game::XModelCollSurf_s>(asset->numCollSurfs);
for (int i = 0; i < asset->numCollSurfs; ++i)
{
Game::XModelCollSurf_s* collSurf = &asset->collSurfs[i];
if (collSurf->collTris)
{
collSurf->collTris = reader.readArray<Game::XModelCollTri_s>(collSurf->numCollTris);
}
}
}
if (asset->boneInfo)
{
asset->boneInfo = reader.readArray<Game::XBoneInfo>(asset->numBones);
}
if (asset->physPreset)
{
asset->physPreset = reader.readObject<Game::PhysPreset>();
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<Game::PhysCollmap>();
asset->physCollmap = collmap;
if (collmap->name)
{
collmap->name = reader.readCString();
}
if (collmap->geoms)
{
collmap->geoms = reader.readArray<Game::PhysGeomInfo>(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<Game::BrushWrapper>();
geom->brushWrapper = brush;
{
if (brush->brush.sides)
{
brush->brush.sides = reader.readArray<Game::cbrushside_t>(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<Game::cplane_s>();
}
}
}
if (brush->brush.baseAdjacentSide)
{
brush->brush.baseAdjacentSide = reader.readArray<char>(brush->totalEdgeCount);
}
}
// TODO: Add pointer support
if (brush->planes)
{
brush->planes = reader.readArray<Game::cplane_s>(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;
}
}

View File

@ -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<void*, void*> 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);
};
}

View File

@ -1,8 +1,6 @@
#include <STDInclude.hpp>
#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<Game::clipMap_t>();
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<void**>(clipMap))
{
*reinterpret_cast<Game::clipMap_t**>(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<int>();
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<int>();
clipMap->numStaticModels = reader.read<int>();
clipMap->numMaterials = reader.read<int>();
clipMap->numBrushSides = reader.read<int>();
clipMap->numBrushEdges = reader.read<int>();
clipMap->numNodes = reader.read<int>();
clipMap->numLeafs = reader.read<int>();
clipMap->leafbrushNodesCount = reader.read<int>();
clipMap->numLeafBrushes = reader.read<int>();
clipMap->numLeafSurfaces = reader.read<int>();
clipMap->vertCount = reader.read<int>();
clipMap->triCount = reader.read<int>();
clipMap->borderCount = reader.read<int>();
clipMap->partitionCount = reader.read<int>();
clipMap->aabbTreeCount = reader.read<int>();
clipMap->numSubModels = reader.read<int>();
clipMap->numBrushes = reader.read<short>();
clipMap->dynEntCount[0] = reader.read<unsigned __int16>();
clipMap->dynEntCount[1] = reader.read<unsigned __int16>();
if (clipMap->planeCount)
{
void* oldPtr = reader.read<void*>();
clipMap->planes = reader.readArray<Game::cplane_s>(clipMap->planeCount);
if (builder->getAllocator()->isPointerMapped(oldPtr))
{
clipMap->planes = builder->getAllocator()->getPointer<Game::cplane_s>(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<Game::cStaticModel_s>(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<float>(18);
memcpy(&clipMap->staticModelList[i].origin, buf, sizeof(float) * 18);
}
}
if (clipMap->numMaterials)
{
clipMap->materials = builder->getAllocator()->allocateArray<Game::ClipMaterial>(clipMap->numMaterials);
for (unsigned int j = 0; j < clipMap->numMaterials; ++j)
{
clipMap->materials[j].name = reader.readArray<char>(64);
clipMap->materials[j].surfaceFlags = reader.read<int>();
clipMap->materials[j].contents = reader.read<int>();
}
}
if (clipMap->numBrushSides)
{
clipMap->brushsides = builder->getAllocator()->allocateArray<Game::cbrushside_t>(clipMap->numBrushSides);
for (unsigned int i = 0; i < clipMap->numBrushSides; ++i)
{
int planeIndex = reader.read<int>();
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<unsigned short>(reader.read<int>()); // materialNum
clipMap->brushsides[i].firstAdjacentSideOffset = static_cast<char>(reader.read<short>()); // firstAdjacentSide
clipMap->brushsides[i].edgeCount = reader.read<char>(); // edgeCount
}
}
if (clipMap->numBrushEdges)
{
clipMap->brushEdges = reader.readArray<char>(clipMap->numBrushEdges);
}
if (clipMap->numNodes)
{
clipMap->nodes = builder->getAllocator()->allocateArray<Game::cNode_t>(clipMap->numNodes);
for (unsigned int i = 0; i < clipMap->numNodes; ++i)
{
int planeIndex = reader.read<int>();
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<short>();
clipMap->nodes[i].children[1] = reader.read<short>();
}
}
if (clipMap->numLeafs)
{
clipMap->leafs = reader.readArray<Game::cLeaf_t>(clipMap->numLeafs);
}
if (clipMap->leafbrushNodesCount)
{
clipMap->leafbrushNodes = builder->getAllocator()->allocateArray<Game::cLeafBrushNode_s>(clipMap->leafbrushNodesCount);
for (unsigned int i = 0; i < clipMap->leafbrushNodesCount; ++i)
{
clipMap->leafbrushNodes[i] = reader.read<Game::cLeafBrushNode_s>();
if (clipMap->leafbrushNodes[i].leafBrushCount > 0)
{
clipMap->leafbrushNodes[i].data.leaf.brushes = reader.readArray<unsigned short>(clipMap->leafbrushNodes[i].leafBrushCount);
}
}
}
if (clipMap->numLeafBrushes)
{
clipMap->leafbrushes = reader.readArray<unsigned short>(clipMap->numLeafBrushes);
}
if (clipMap->numLeafSurfaces)
{
clipMap->leafsurfaces = reader.readArray<unsigned int>(clipMap->numLeafSurfaces);
}
if (clipMap->vertCount)
{
clipMap->verts = reader.readArray<Game::vec3_t>(clipMap->vertCount);
}
if (clipMap->triCount)
{
clipMap->triIndices = reader.readArray<unsigned short>(clipMap->triCount * 3);
clipMap->triEdgeIsWalkable = reader.readArray<char>(4 * ((3 * clipMap->triCount + 31) >> 5));
}
if (clipMap->borderCount)
{
clipMap->borders = reader.readArray<Game::CollisionBorder>(clipMap->borderCount);
}
if (clipMap->partitionCount)
{
clipMap->partitions = builder->getAllocator()->allocateArray<Game::CollisionPartition>(clipMap->partitionCount);
for (int i = 0; i < clipMap->partitionCount; ++i)
{
clipMap->partitions[i].triCount = reader.read<char>();
clipMap->partitions[i].borderCount = reader.read<char>();
clipMap->partitions[i].firstTri = reader.read<int>();
if (clipMap->partitions[i].borderCount > 0)
{
int index = reader.read<int>();
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<Game::CollisionAabbTree>(clipMap->aabbTreeCount);
}
if (clipMap->numSubModels)
{
clipMap->cmodels = reader.readArray<Game::cmodel_t>(clipMap->numSubModels);
}
if (clipMap->numBrushes)
{
clipMap->brushes = builder->getAllocator()->allocateArray<Game::cbrush_t>(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<unsigned int>() & 0xFFFF; // todo: check for overflow here
if (clipMap->brushes[i].numsides > 0)
{
auto index = reader.read<unsigned int>();
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<unsigned int>();
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<char>(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<short>() & 0xFF;
}
}
tmp = reader.readArray<char>(6);
memcpy(&clipMap->brushes[i].edgeCount, tmp, 6);
}
clipMap->brushBounds = reader.readArray<Game::Bounds>(clipMap->numBrushes);
clipMap->brushContents = reader.readArray<int>(clipMap->numBrushes);
}
for (int x = 0; x < 2; ++x)
{
if (clipMap->dynEntCount[x])
{
clipMap->dynEntDefList[x] = builder->getAllocator()->allocateArray<Game::DynEntityDef>(clipMap->dynEntCount[x]);
for (int i = 0; i < clipMap->dynEntCount[x]; ++i)
{
clipMap->dynEntDefList[x][i].type = reader.read<Game::DynEntityType>();
clipMap->dynEntDefList[x][i].pose = reader.read<Game::GfxPlacement>();
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<short>();
clipMap->dynEntDefList[x][i].physicsBrushModel = reader.read<short>();
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<int>();
clipMap->dynEntDefList[x][i].mass = reader.read<Game::PhysMass>();
clipMap->dynEntDefList[x][i].contents = reader.read<int>();
}
}
}
clipMap->smodelNodeCount = reader.read<unsigned short>();
clipMap->smodelNodes = reader.readArray<Game::SModelAabbNode>(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<Game::TriggerHull>(clipMap->mapEnts->trigger.hullCount);
Game::TriggerModel* models = builder->getAllocator()->allocateArray<Game::TriggerModel>(clipMap->mapEnts->trigger.count);
for (unsigned int i = 0; i < clipMap->numSubModels; ++i)
{
models[i] = reader.read<Game::TriggerModel>();
hulls[i] = reader.read<Game::TriggerHull>();
}
size_t slabCount = reader.read<size_t>();
clipMap->mapEnts->trigger.slabCount = slabCount;
Game::TriggerSlab* slabs = builder->getAllocator()->allocateArray<Game::TriggerSlab>(clipMap->mapEnts->trigger.slabCount);
for (unsigned int i = 0; i < clipMap->mapEnts->trigger.slabCount; i++) {
slabs[i] = reader.read<Game::TriggerSlab>();
}
clipMap->mapEnts->trigger.models = &models[0];
clipMap->mapEnts->trigger.hulls = &hulls[0];
clipMap->mapEnts->trigger.slabs = &slabs[0];
}
}
clipMap->checksum = reader.read<int>();
// 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<Game::SModelAabbNode>(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::clipMap_t>(Game::XAssetType::ASSET_TYPE_CLIPMAP_MP, _name);
assert(header->data);
}
}

View File

@ -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

View File

@ -1,355 +1,18 @@
#include <STDInclude.hpp>
#include "Isnd_alias_list_t.hpp"
#include <Utils/Json.hpp>
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::snd_alias_list_t>(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<Game::snd_alias_list_t>();
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<Game::snd_alias_t>(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<Game::SoundFile>();
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<std::string>());
if (subtitle.is_string())
{
alias->subtitle = builder->getAllocator()->duplicateString(subtitle.get<std::string>());
}
if (secondaryAliasName.is_string())
{
alias->secondaryAliasName = builder->getAllocator()->duplicateString(secondaryAliasName.get<std::string>());
}
if (chainAliasName.is_string())
{
alias->chainAliasName = builder->getAllocator()->duplicateString(chainAliasName.get<std::string>());
}
alias->sequence = sequence.get<int>();
alias->volMin = volMin.get<float>();
alias->volMax = volMax.get<float>();
alias->pitchMin = pitchMin.get<float>();
alias->pitchMax = pitchMax.get<float>();
alias->distMin = distMin.get<float>();
alias->distMax = distMax.get<float>();
alias->flags = flags.get<int>();
alias->___u15.slavePercentage = slavePercentage.get<float>();
alias->probability = probability.get<float>();
alias->lfePercentage = lfePercentage.get<float>();
alias->centerPercentage = centerPercentage.get<float>();
alias->startDelay = startDelay.get<int>();
alias->envelopMin = envelopMin.get<float>();
alias->envelopMax = envelopMax.get<float>();
alias->envelopPercentage = envelopPercentage.get<float>();
// Speaker map object
if (!speakerMap.is_null())
{
alias->speakerMap = builder->getAllocator()->allocate<Game::SpeakerMap>();
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<std::string>());
alias->speakerMap->isDefault = speakerMap["isDefault"].get<bool>();
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<float>();
alias->speakerMap->channelMaps[channelMapIndex][subChannelIndex].speakers[speakerIndex].levels[1] = speaker["levels1"].get<float>();
alias->speakerMap->channelMaps[channelMapIndex][subChannelIndex].speakers[speakerIndex].numLevels = speaker["numLevels"].get<int>();
alias->speakerMap->channelMaps[channelMapIndex][subChannelIndex].speakers[speakerIndex].speaker = speaker["speaker"].get<int>();
}
}
}
}
}
if (volumeFalloffCurve.is_string())
{
auto fallOffCurve = volumeFalloffCurve.get<std::string>();
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<Game::snd_alias_type_t>(type.get<int>()) == 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<std::string>(), builder).loadSnd;
}
else if (static_cast<Game::snd_alias_type_t>(type.get<int>()) == Game::snd_alias_type_t::SAT_STREAMED) // Streamed
{
alias->soundFile->type = Game::SAT_STREAMED;
std::string streamedFile = soundFile.get<std::string>();
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<std::string>());
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)

View File

@ -1,4 +1,8 @@
#include <STDInclude.hpp>
#include <Utils/InfoString.hpp>
#include <proto/auth.pb.h>
#include "Bans.hpp"
namespace Components
@ -467,7 +471,7 @@ namespace Components
}
else
{
const auto level = static_cast<uint32_t>(atoi(params->get(1)));
const auto level = std::strtoul(params->get(1), nullptr, 10);
Auth::IncreaseSecurityLevel(level);
}
});

View File

@ -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<Utils::NamedMutex> 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)

View File

@ -9,8 +9,6 @@ namespace Components
Bans();
static std::unique_lock<Utils::NamedMutex> 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<Game::netIP_t> ipList;
};
static const char* BanListFile;
static std::unique_lock<Utils::NamedMutex> Lock();
static void LoadBans(BanList* list);
static void SaveBans(const BanList* list);
};

View File

@ -0,0 +1,155 @@
#include <STDInclude.hpp>
#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();
}
}

View File

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

View File

@ -1,13 +1,20 @@
#include <STDInclude.hpp>
#include <Utils/InfoString.hpp>
#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::botData> 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<bool>())
{
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<std::string>(); !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: <bot> BotStop();
GSC::Script::AddMethod("BotStop", [](Game::scr_entref_t entref) // Usage: <bot> 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: <bot> BotWeapon(<str>);
GSC::Script::AddMethod("BotWeapon", [](Game::scr_entref_t entref) // Usage: <bot> BotWeapon(<str>);
{
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: <bot> BotAction(<str action>);
GSC::Script::AddMethod("BotAction", [](Game::scr_entref_t entref) // Usage: <bot> BotAction(<str action>);
{
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: <bot> BotMovement(<int>, <int>);
GSC::Script::AddMethod("BotMovement", [](Game::scr_entref_t entref) // Usage: <bot> BotMovement(<int>, <int>);
{
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<bool>("sv_randomBotNames", false, Game::DVAR_NONE, "Randomize the bots' names");
Events::OnDvarInit([]
{
SVClanName = Dvar::Register<const char*>("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<unsigned int>(*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);
});

View File

@ -11,8 +11,11 @@ namespace Components
using botData = std::pair< std::string, std::string>;
static std::vector<botData> 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);

View File

@ -16,7 +16,7 @@ namespace Components
{
auto result = lookupResult;
const auto* username = Dvar::Var("name").get<const char*>();
const auto* username = Dvar::Name.get<const char*>();
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<const char*>("customTitle", "", Game::DVAR_USERINFO | Game::DVAR_ARCHIVE, "Custom card title");
}, Scheduler::Pipeline::MAIN);
});
std::memset(&CustomTitles, 0, sizeof(char[Game::MAX_CLIENTS][18]));

View File

@ -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<std::uint8_t>(0x4F6370, 0xC3);
// Remove 'Steam Start' checking for DRM IPC
Utils::Hook::Nop(0x451145, 5);
Utils::Hook::Set<BYTE>(0x45114C, 0xEB);
// Disable some checks on certain game events
Utils::Hook::Nop(0x43EC96, 9);
Utils::Hook::Nop(0x4675C6, 9);

View File

@ -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<Game::dvar_t**>(0x7ED398);
Game::dvar_t** Chat::cg_chatTime = reinterpret_cast<Game::dvar_t**>(0x9F5DE8);
bool Chat::SendChat;
Utils::Concurrency::Container<Chat::muteList> Chat::MutedList;
const char* Chat::MutedListFile = "userraw/muted-users.json";
bool Chat::CanAddCallback = true;
std::vector<Scripting::Function> Chat::SayCallbacks;
// Have only one instance of IW4x read/write the file
std::unique_lock<Utils::NamedMutex> 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<bool>())
{
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<bool>())
{
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<float>(cg_chatWidth.get<int>());
const auto chatTime = (*cg_chatTime)->current.integer;
const auto chatTime = (*Game::cg_chatTime)->current.integer;
if (chatHeight <= 0 || static_cast<unsigned>(chatHeight) > std::extent_v<decltype(Game::cgs_t::teamChatMsgs)> || 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<int>(TEXT_COLOR_DEFAULT);
auto lastColor = static_cast<std::underlying_type_t<TextColor>>(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<bool>([&](muteList& clients)
const auto result = MutedList.access<bool>([&](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<bool>([&](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<std::uint64_t>());
}
}
});
}
@ -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<std::uint16_t>(id));
return result;
}
void Chat::AddScriptFunctions()
{
Script::AddFunction("OnPlayerSay", [] // gsc: OnPlayerSay(<function>)
GSC::Script::AddFunction("OnPlayerSay", [] // gsc: OnPlayerSay(<function>)
{
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<bool>("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();

View File

@ -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<std::uint64_t>;
static Utils::Concurrency::Container<muteList> MutedList;
static const char* MutedListFile;
static bool CanAddCallback; // ClientCommand & GSC thread are the same
static std::vector<Scripting::Function> SayCallbacks;
static std::unique_lock<Utils::NamedMutex> 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();

View File

@ -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]));

View File

@ -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[];

View File

@ -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();
});

View File

@ -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<void()>& 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<void(Command::Params*)>& callback)
void Command::Add(const char* name, const std::function<void(Params*)>& 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<void(Command::Params*)>& callback)
void Command::AddSV(const char* name, const std::function<void(Params*)>& 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<Game::cmd_function_t>();
return Utils::Memory::GetAllocator()->allocate<Game::cmd_function_s>();
}
void Command::MainCallback()
@ -173,4 +173,52 @@ namespace Components
itr->second(&params);
}
}
const std::vector<std::string>& Command::GetExceptions()
{
static const auto exceptions = []() -> std::vector<std::string>
{
std::vector<std::string> 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
}
}

View File

@ -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<void()>& callback);
static void Add(const char* name, const std::function<void(Command::Params*)>& callback);
@ -57,7 +62,7 @@ namespace Components
static void AddSV(const char* name, const std::function<void(Command::Params*)>& 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<std::string, std::function<void(Command::Params*)>> FunctionMap;
@ -67,5 +72,8 @@ namespace Components
static void MainCallback();
static void MainCallbackSV();
static const std::vector<std::string>& GetExceptions();
static bool CL_ShouldSendNotify_Hk(const char* cmd);
};
}

View File

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

View File

@ -1,6 +1,8 @@
#include <STDInclude.hpp>
#include "Console.hpp"
#include "Terminus_4.49.1.ttf.hpp"
#include <version.hpp>
#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<int>();
//maxclientCount = Game::Party_GetMaxPlayers(*Game::partyIngame);
clientCount = Game::PartyHost_CountMembers(reinterpret_cast<Game::PartyData*>(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<int>(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<int>(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<float>(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<int>(GetTickCount64()); // Make our compiler happy
if ((currentTime - Console::LastRefresh) > 250)
auto currentTime = static_cast<int>(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<char>(c), static_cast<char>(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<int>(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<char>(c);
Console::LineBuffer[Console::LineBufferIndex] = '\0';
LineBuffer[LineBufferIndex++] = static_cast<char>(c);
LineBuffer[LineBufferIndex] = '\0';
wprintw(InputWindow, "%c", static_cast<char>(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<HWND>(0x64A3288);
float scale = GetDpiScale(parent);
auto scale = GetDpiScale(parent);
if (isInputBox)
{
int newX = childX; // No change!
int newY = static_cast<int>((newParentRect.bottom - newParentRect.top) - 65 * scale);
int newWidth = static_cast<int>((newParentRect.right - newParentRect.left) - 29 * scale);
int newHeight = static_cast<int>((childRect.bottom - childRect.top) * scale); // No change!
auto newX = childX; // No change!
auto newY = static_cast<int>((newParentRect.bottom - newParentRect.top) - 65 * scale);
auto newWidth = static_cast<int>((newParentRect.right - newParentRect.left) - 29 * scale);
auto newHeight = static_cast<int>((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<int>((newParentRect.right - newParentRect.left) - 29);
int margin = 70;
auto newX = childX; // No change!
auto newY = childY; // No change!
auto newWidth = static_cast<int>((newParentRect.right - newParentRect.left) - 29);
#ifdef REMOVE_HEADERBAR
margin = 10;
constexpr auto margin = 10;
#else
constexpr auto margin = 70;
#endif
int newHeight = static_cast<int>((newParentRect.bottom - newParentRect.top) - 74 * scale - margin);
auto newHeight = static_cast<int>((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<HWND>(0x64A328C);
unsigned int totalChars;
unsigned int totalClearLength = 0;
auto totalClearLength = 0;
char str[maxAffectedChars];
unsigned int fetchedCharacters = static_cast<unsigned int>(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<LPCVOID>(&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<LRESULT CALLBACK(HWND, UINT, WPARAM, unsigned int)>(0x64DC50)(hWnd, Msg, wParam, lParam);
}
@ -645,14 +631,14 @@ namespace Components
void Console::ApplyConsoleStyle()
{
Utils::Hook::Set<BYTE>(0x428A8E, 0); // Adjust logo Y pos
Utils::Hook::Set<BYTE>(0x428A90, 0); // Adjust logo X pos
Utils::Hook::Set<BYTE>(0x428AF2, 67); // Adjust output Y pos
Utils::Hook::Set<DWORD>(0x428AC5, 397); // Adjust input Y pos
Utils::Hook::Set<DWORD>(0x428951, 609); // Reduce window width
Utils::Hook::Set<DWORD>(0x42895D, 423); // Reduce window height
Utils::Hook::Set<DWORD>(0x428AC0, 597); // Reduce input width
Utils::Hook::Set<DWORD>(0x428AED, 596); // Reduce output width
Utils::Hook::Set<std::uint8_t>(0x428A8E, 0); // Adjust logo Y pos
Utils::Hook::Set<std::uint8_t>(0x428A90, 0); // Adjust logo X pos
Utils::Hook::Set<std::uint8_t>(0x428AF2, 67); // Adjust output Y pos
Utils::Hook::Set<std::uint32_t>(0x428AC5, 397); // Adjust input Y pos
Utils::Hook::Set<std::uint32_t>(0x428951, 609); // Reduce window width
Utils::Hook::Set<std::uint32_t>(0x42895D, 423); // Reduce window height
Utils::Hook::Set<std::uint32_t>(0x428AC0, 597); // Reduce input width
Utils::Hook::Set<std::uint32_t>(0x428AED, 596); // Reduce output width
DWORD fontsInstalled;
CustomConsoleFont = AddFontMemResourceEx(const_cast<void*>(reinterpret_cast<const void*>(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<int>(std::strlen(Game::g_consoleField->buffer));
Game::Field_AdjustScroll(Game::ScrPlace_GetFullPlacement(), Game::g_consoleField);
@ -878,24 +865,27 @@ namespace Components
Utils::Hook::Set<float*>(0x5A4400, consoleColor);
// Remove the need to type '\' or '/' to send a console command
Utils::Hook::Set<BYTE>(0x431565, 0xEB);
Utils::Hook::Set<std::uint8_t>(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<void()>(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();
}
}
}

View File

@ -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();

View File

@ -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<bool>())
if (RUseD3D9Ex.get<bool>())
{
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<bool>("r_useD3D9Ex", false, Game::DVAR_ARCHIVE, "Use extended d3d9 interface!");
RUseD3D9Ex = Dvar::Register<bool>("r_useD3D9Ex", false, Game::DVAR_ARCHIVE, "Use extended d3d9 interface!");
// Hook Interface creation
Utils::Hook::Set(0x6D74D0, D3D9Ex::Direct3DCreate9Stub);
Utils::Hook::Set(0x6D74D0, Direct3DCreate9Stub);
}
}

View File

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

View File

@ -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<int>::max(), font1, MY_X, 10.0f, 1, 1, 0.4f, ColorWhite, 3);
Game::UI_DrawText(scrPlace, strFinal, std::numeric_limits<int>::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<int>::max(), font2, MY_X, 35.0f, 1, 1, 0.4f, ColorWhite, 3);
Game::UI_DrawText(scrPlace, strFinal, std::numeric_limits<int>::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<int>::max(), font3, MY_X, 60.0f, 1, 1, 0.4f, ColorWhite, 3);
Game::UI_DrawText(scrPlace, strFinal, std::numeric_limits<int>::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<int>::max(), font5, MY_X, 85.0f, 1, 1, 0.4f, ColorWhite, 3);
Game::UI_DrawText(scrPlace, strFinal, std::numeric_limits<int>::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<int>::max(), font6, MY_X, 110.0f, 1, 1, 0.4f, ColorWhite, 3);
Game::UI_DrawText(scrPlace, strFinal, std::numeric_limits<int>::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();

View File

@ -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);

View File

@ -1,6 +1,9 @@
#include <STDInclude.hpp>
#include <Utils/InfoString.hpp>
#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<bool>();
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<bool>())
if (SVLanOnly.get<bool>())
{
return;
}
auto masterPort = Dvar::Var("masterPort").get<int>();
const auto* masterServerName = Dvar::Var("masterServerName").get<const char*>();
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<bool>("com_logFilter", true,
COMLogFilter = Dvar::Register<bool>("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<bool>("sv_lanOnly", false,
Game::DVAR_NONE, "Don't act as node");
SVLanOnly = Dvar::Register<bool>("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<BYTE>(0x683370, 0xC3); // steam sometimes doesn't like the server
@ -198,9 +225,6 @@ namespace Components
// isHost script call return 0
Utils::Hook::Set<DWORD>(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<BYTE>(0x519DDF, 0);
@ -217,18 +241,21 @@ namespace Components
Utils::Hook::Set<BYTE>(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<DWORD>(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<const char*>("sv_motd", "", Game::DVAR_NONE, "A custom message of the day for servers");
}, Scheduler::Pipeline::MAIN);
SVMOTD = Dvar::Register<const char*>("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);
}

View File

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

View File

@ -0,0 +1,148 @@
#include <STDInclude.hpp>
#include "Discord.hpp"
#include "Party.hpp"
#include <discord_rpc.h>
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<Network::Address> 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::seconds>(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();
}
}

View File

@ -0,0 +1,17 @@
#pragma once
namespace Components
{
class Discord : public Component
{
public:
Discord();
void preDestroy() override;
private:
static bool Initialized_;
static void UpdateDiscord();
};
}

View File

@ -1,4 +1,6 @@
#include <STDInclude.hpp>
#include <Utils/InfoString.hpp>
#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);
}
});

View File

@ -1,5 +1,10 @@
#include <STDInclude.hpp>
#include <Utils/InfoString.hpp>
#include <Utils/WebIO.hpp>
#include "Download.hpp"
#include "MapRotation.hpp"
#include "Party.hpp"
#include "ServerInfo.hpp"
#include <mongoose.h>
@ -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<std::string>();
auto fastHost = SV_wwwBaseUrl.get<std::string>();
if (Utils::String::StartsWith(fastHost, "https://"))
{
download->thread.detach();
@ -173,7 +185,7 @@ namespace Components
// -mod.ff
// /-mod2
// ...
if (Dvar::Var("sv_wwwDownload").get<bool>())
if (SV_wwwDownload.get<bool>())
{
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::dvar_t*>((*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<int>(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<std::string, nlohmann::json> info;
info["status"] = status.to_json();
info["host"] = host.to_json();
info["map_rotation"] = MapRotation::to_json();
std::vector<nlohmann::json> 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<nlohmann::json> fileList;
const auto path = Dvar::Var("fs_basepath").get<std::string>() / 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<std::string, nlohmann::json> jsonFileList;
auto fileBuffer = Utils::IO::ReadFile(filename.generic_string());
if (fileBuffer.empty())
{
continue;
}
std::unordered_map<std::string, nlohmann::json> 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<std::string>() : 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<std::string>());
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<std::string>() : 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<std::string>();
const auto path = Dvar::Var("fs_basepath").get<std::string>() + "\\" + (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<int>(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<mg_http_message*>(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<const char*>("ui_dl_timeLeft", "", Game::DVAR_NONE, "");
Dvar::Register<const char*>("ui_dl_progress", "", Game::DVAR_NONE, "");
Dvar::Register<const char*>("ui_dl_transRate", "", Game::DVAR_NONE, "");
}, Scheduler::Pipeline::MAIN);
UIDlTimeLeft = Dvar::Register<const char*>("ui_dl_timeLeft", "", Game::DVAR_NONE, "");
UIDlProgress = Dvar::Register<const char*>("ui_dl_progress", "", Game::DVAR_NONE, "");
UIDlTransRate = Dvar::Register<const char*>("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<bool>("sv_wwwDownload", false, Game::DVAR_NONE, "Set to true to enable downloading maps/mods from an external server.");
Dvar::Register<const char*>("sv_wwwBaseUrl", "", Game::DVAR_NONE, "Set to the base url for the external map download.");
}, Scheduler::Pipeline::MAIN);
SV_wwwDownload = Dvar::Register<bool>("sv_wwwDownload", false, Game::DVAR_NONE, "Set to true to enable downloading maps/mods from an external server.");
SV_wwwBaseUrl = Dvar::Register<const char*>("sv_wwwBaseUrl", "", Game::DVAR_NONE, "Set to the base url for the external map download.");
});
}
Download::~Download()

View File

@ -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
{

View File

@ -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_t*>(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<const char*>(name, username.data(), flags | Game::DVAR_ARCHIVE, description);
Name = Register<const char*>(dvarName, username.data(), flags | Game::DVAR_ARCHIVE, description);
return Name.get<Game::dvar_t*>();
}
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<const char*, 8> 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<bool> 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<bool> 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<std::uint32_t>(0x6431EA, Game::DVAR_INIT);
// cheat protect g_hardcore
Utils::Hook::Xor<std::uint32_t>(0x5E374F, Game::DVAR_CHEAT);
Utils::Hook::Xor<std::uint32_t>(0x4D3689, Game::DVAR_CHEAT);
Utils::Hook::Xor<std::uint32_t>(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<std::uint32_t>(0x42E3F5, Game::DVAR_ROM | Game::DVAR_ARCHIVE); //safeArea_adjusted_horizontal
Utils::Hook::Xor<std::uint32_t>(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);
}
}

View File

@ -32,25 +32,24 @@ namespace Components
};
Dvar();
~Dvar();
// Only strings and bools use this type of declaration
template<typename T> static Var Register(const char* dvarName, T value, std::uint16_t flag, const char* description);
template<typename T> 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);

View File

@ -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<int>();
const auto elevatorSetting = BG_Elevators.get<int>();
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<int>() == Elevators::EASY)
if (BG_Elevators.get<int>() == 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();
}
}

View File

@ -3,16 +3,23 @@
namespace Components
{
Utils::Signal<Events::ClientCallback> Events::ClientDisconnectSignal;
Utils::Signal<Events::ClientConnectCallback> Events::ClientConnectSignal;
Utils::Signal<Events::Callback> Events::SteamDisconnectSignal;
Utils::Signal<Events::Callback> Events::ShutdownSystemSignal;
Utils::Signal<Events::Callback> Events::ClientInitSignal;
Utils::Signal<Events::Callback> Events::ServerInitSignal;
Utils::Signal<Events::Callback> Events::DvarInitSignal;
void Events::OnClientDisconnect(const Utils::Slot<ClientCallback>& callback)
{
ClientDisconnectSignal.connect(callback);
}
void Events::OnClientConnect(const Utils::Slot<ClientConnectCallback>& callback)
{
ClientConnectSignal.connect(callback);
}
void Events::OnSteamDisconnect(const Utils::Slot<Callback>& callback)
{
SteamDisconnectSignal.connect(callback);
@ -33,6 +40,11 @@ namespace Components
ServerInitSignal.connect(callback);
}
void Events::OnDvarInit(const Utils::Slot<Callback>& 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<void(int)>(0x4AA430)(clientNum); // ClientDisconnect
}
void Events::SV_UserinfoChanged_Hk(Game::client_t* cl)
{
ClientConnectSignal(cl);
Utils::Hook::Call<void(Game::client_t*)>(0x401950)(cl); // SV_UserinfoChanged
}
void Events::SteamDisconnect_Hk()
{
SteamDisconnectSignal();
@ -74,10 +93,20 @@ namespace Components
Utils::Hook::Call<void()>(0x474320)(); // SV_InitGameMode
}
void Events::Com_InitDvars_Hk()
{
DvarInitSignal();
DvarInitSignal.clear();
Utils::Hook::Call<void()>(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
}
}

View File

@ -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<ClientCallback>& callback);
// Server side
static void OnClientConnect(const Utils::Slot<ClientConnectCallback>& callback);
// Client side
static void OnSteamDisconnect(const Utils::Slot<Callback>& callback);
@ -23,17 +27,24 @@ namespace Components
// Client & Server (triggered once)
static void OnSVInit(const Utils::Slot<Callback>& callback);
// Client & Server (triggered once)
static void OnDvarInit(const Utils::Slot<Callback>& callback);
private:
static Utils::Signal<ClientCallback> ClientDisconnectSignal;
static Utils::Signal<ClientConnectCallback> ClientConnectSignal;
static Utils::Signal<Callback> SteamDisconnectSignal;
static Utils::Signal<Callback> ShutdownSystemSignal;
static Utils::Signal<Callback> ClientInitSignal;
static Utils::Signal<Callback> ServerInitSignal;
static Utils::Signal<Callback> 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();
};
}

View File

@ -134,7 +134,9 @@ namespace Components
if (!MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, static_cast<MINIDUMP_TYPE>(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);
}

View File

@ -1,11 +1,17 @@
#include <STDInclude.hpp>
#include <zlib.h>
#include "FastFiles.hpp"
namespace Components
{
FastFiles::Key FastFiles::CurrentKey;
symmetric_CTR FastFiles::CurrentCTR;
std::vector<std::string> 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<Game::XZoneInfo> 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<Game::XZoneInfo> 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<Game::XZoneInfo> 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<Game::XZoneInfo> 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 char*>();
const auto* dir = (*Game::fs_basepath)->current.string;
std::vector<std::string> paths;
auto modDir = Dvar::Var("fs_game").get<std::string>();
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<int(unsigned char*, int, unsigned char*)>(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<int(z_streamp, const char*, int)>(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<bool>("ui_zoneDebug", false, Game::DVAR_ARCHIVE, "Display current loaded zone.");
g_loadingInitialZones = Dvar::Register<bool>("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<bool>()) 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<std::uint32_t*>(0x10AA5D8);
auto FFCurrentOffset = *reinterpret_cast<std::uint32_t*>(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<void(bool)>(0x4B8DB0)(true); // currently set to Load_GfxWorld

View File

@ -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<std::string> 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);

View File

@ -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<int>(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<std::size_t>(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<size_t>(this->size) < _size || Game::FS_Read(buffer, _size, this->handle) != static_cast<int>(_size))
if (!this->exists() || static_cast<std::size_t>(this->size) < _size || Game::FS_Read(buffer, static_cast<int>(_size), this->handle) != static_cast<int>(_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<int>(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<const char*>(), reinterpret_cast<char*>(0x63D0BB8), Utils::String::VA("%s/%s", folder.data(), file.data()), reinterpret_cast<char**>(&path));
char path[MAX_PATH]{};
Game::FS_BuildPathToFile((*Game::fs_basepath)->current.string, reinterpret_cast<char*>(0x63D0BB8), Utils::String::VA("%s/%s", folder.data(), file.data()), reinterpret_cast<char**>(&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<char>(size);
return MemAllocator.allocateArray<char>(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<std::string>();
const auto fs_basepath = Dvar::Var("fs_basepath").get<std::string>();
const auto fs_homepath = Dvar::Var("fs_homepath").get<std::string>();
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<void(const char*)>(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<void(int, int)>(0x461A50)(a1, a2); // FS_Restart
Utils::Hook::Call<void(int, int)>(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<void(int)>(0x4A46C0)(a1); // FS_Shutdown
Utils::Hook::Call<void(int)>(0x4A46C0)(closemfp); // FS_Shutdown
}
void FileSystem::DelayLoadImagesSync()
{
std::lock_guard<std::recursive_mutex> _(FileSystem::FSMutex);
std::lock_guard _(FSMutex);
return Utils::Hook::Call<void()>(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<WORD>(0x49AACE, 0xC35E);
Utils::Hook::Set<std::uint16_t>(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()

View File

@ -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);

View File

@ -5,8 +5,6 @@ namespace Components
class Flags : public Component
{
public:
Flags() = default;
static bool HasFlag(const std::string& flag);
private:

View File

@ -1,4 +1,11 @@
#include <STDInclude.hpp>
#pragma warning(push)
#pragma warning(disable: 4100)
#include <proto/friends.pb.h>
#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<bool>("ui_streamFriendly", false, Game::DVAR_ARCHIVE, "Stream friendly UI");
Friends::CLAnonymous = Dvar::Register<bool>("cl_anonymous", false, Game::DVAR_ARCHIVE, "Enable invisible mode for Steam");

View File

@ -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());
}
}

View File

@ -1,6 +1,6 @@
#pragma once
namespace Components
namespace Components::GSC
{
class GSC : public Component
{

View File

@ -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(<filepath>)
{
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;
}
}

Some files were not shown because too many files have changed in this diff Show More