Merge pull request #687 from XLabsProject/develop

Release v0.7.7
This commit is contained in:
Edo 2022-12-31 14:50:58 +01:00 committed by GitHub
commit dfd7f84ebc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
200 changed files with 6809 additions and 3768 deletions

View File

@ -9,6 +9,10 @@ on:
- "*" - "*"
types: [opened, synchronize, reopened] types: [opened, synchronize, reopened]
concurrency:
group: ${{ github.ref }}
cancel-in-progress: true
jobs: jobs:
build: build:
name: Build binaries name: Build binaries
@ -19,14 +23,6 @@ jobs:
- Debug - Debug
- Release - Release
steps: steps:
- name: Wait for previous workflows
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
uses: softprops/turnstyle@v1
with:
poll-interval-seconds: 10
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Check out files - name: Check out files
uses: actions/checkout@v3 uses: actions/checkout@v3
with: with:
@ -84,13 +80,6 @@ jobs:
- name: Add known hosts - name: Add known hosts
run: ssh-keyscan -H ${{ secrets.XLABS_MASTER_SSH_ADDRESS }} >> ~/.ssh/known_hosts run: ssh-keyscan -H ${{ secrets.XLABS_MASTER_SSH_ADDRESS }} >> ~/.ssh/known_hosts
- name: Wait for previous workflows
uses: softprops/turnstyle@v1
with:
poll-interval-seconds: 10
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# - name: Remove old data files # - name: Remove old data files
# run: ssh ${{ secrets.XLABS_MASTER_SSH_USER }}@${{ secrets.XLABS_MASTER_SSH_ADDRESS }} rm -rf ${{ env.XLABS_MASTER_PATH }}/iw4x/data/* # run: ssh ${{ secrets.XLABS_MASTER_SSH_USER }}@${{ secrets.XLABS_MASTER_SSH_ADDRESS }} rm -rf ${{ env.XLABS_MASTER_PATH }}/iw4x/data/*

3
.gitmodules vendored
View File

@ -32,5 +32,6 @@
path = deps/GSL path = deps/GSL
url = https://github.com/microsoft/GSL.git url = https://github.com/microsoft/GSL.git
[submodule "deps/nlohmannjson"] [submodule "deps/nlohmannjson"]
path = deps/nlohmannjson path = deps/json
url = https://github.com/nlohmann/json.git url = https://github.com/nlohmann/json.git
branch = v3.11.2

View File

@ -4,6 +4,51 @@ 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/). 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.7] - 2022-12-31
### Added
- Add `r_forceTechnique` Dvar to debug techniques on materials.
- Add `IsSprinting` GSC method (#587)
- Add `StorageLoad` GSC function (#595)
- Add `bg_climbAnything` Dvar (#663)
- Add ClanTag support for bots (#645)
- Add `sv_randomBotNames` Dvar (#665)
- Add support for parsing localized strings files (.str & .json) (#621)
- Add `callvote` menus (#613)
- IW3x-port converts Technique Sets between CoD4 and CoD6 properly.
- Add new map porting utility tool that makes the map porting process between CoD4 to CoD6 easy.
### Changed
- `r_drawModelNames` now uses ground lighting when available.
- Speculars are now enabled on custom maps.
- Techset(s) get loaded from disk first rather than memory.
- Use CSO format for Vertex Shaders & Pixel Shaders on ZoneBuilder to allow replacement/swapping.
- New map porting utility tool builds teams directly into the map.
### Fixed
- Fix bug where ZoneBuilder would not build loaded sounds correctly.
- Fix bug where ZoneBuilder no longer differentiates assets depending on their name.
- Fix building FX with ZoneBuilder.
- Fix branding in ZoneBuilder generated zones.
- Fix Script String crash when building zones.
- Fix the changelog menu (#583)
- Fix bug when adding commands (#609)
- Fix bug where some ported maps would either crash or lag (mp_zavod, mp_kowloon, ...)
- Fix GSC conversion from CoD4 to CoD6 (Specular Scale, Create Exp Fog, Create FX, ...)
- The map porting process from CoD4 to IW4x has improved.
- Ported map zones are about 40% lighter than before.
- Static models are now lit correctly depending on their position and ground lighting.
- New map porting utility ports sounds and effects properly.
### Known issues
- HTTPS is not supported for fast downloads at the moment.
- Sound issue fix is experimental as the bug is not fully understood.
- `reloadmenus` command does not free resources used by custom menus.
## [0.7.6] - 2022-11-22 ## [0.7.6] - 2022-11-22
### Added ### Added

View File

@ -21,7 +21,6 @@
| `--copy-to=PATH` | Optional, copy the DLL to a custom folder after build, define the path here if wanted. | | `--copy-to=PATH` | Optional, copy the DLL to a custom folder after build, define the path here if wanted. |
| `--copy-pdb` | Copy debug information for binaries as well to the path given via --copy-to. | | `--copy-pdb` | Copy debug information for binaries as well to the path given via --copy-to. |
| `--force-unit-tests` | Always compile unit tests. | | `--force-unit-tests` | Always compile unit tests. |
| `--force-exception-handler` | Install custom unhandled exception handler even for Debug builds. |
| `--disable-binary-check` | Do not perform integrity checks on the exe. | | `--disable-binary-check` | Do not perform integrity checks on the exe. |
## Command line arguments ## Command line arguments

2
deps/GSL vendored

@ -1 +1 @@
Subproject commit 517ed29228d18cf2c5004d10826090108e06f049 Subproject commit f94c1f6f2b5e141d5f6eb3d284cd4a8cf9a81aac

1
deps/json vendored Submodule

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

2
deps/mongoose vendored

@ -1 +1 @@
Subproject commit db81c30d24df98031e41e33423a5eef0e89ba8c6 Subproject commit 73813a838386f6ebca447eb54c626803163ee257

1
deps/nlohmannjson vendored

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

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

@ -0,0 +1,19 @@
json = {
source = path.join(dependencies.basePath, "json"),
}
function json.import()
json.includes()
end
function json.includes()
includedirs {
path.join(json.source, "single_include/nlohmann")
}
end
function json.project()
end
table.insert(dependencies, json)

View File

@ -17,6 +17,8 @@ function libtomcrypt.includes()
"LTC_NO_FAST", "LTC_NO_FAST",
"LTC_NO_PROTOTYPES", "LTC_NO_PROTOTYPES",
"LTC_NO_RSA_BLINDING", "LTC_NO_RSA_BLINDING",
"LTC_NO_FILE",
"ARGTYPE=4",
} }
end end
@ -32,6 +34,7 @@ function libtomcrypt.project()
} }
removefiles { removefiles {
path.join(libtomcrypt.source, "src/**/*_test.c"),
path.join(libtomcrypt.source, "src/**/*tab.c"), path.join(libtomcrypt.source, "src/**/*tab.c"),
path.join(libtomcrypt.source, "src/encauth/ocb3/**.c"), path.join(libtomcrypt.source, "src/encauth/ocb3/**.c"),
} }

View File

@ -1,18 +0,0 @@
nlohmannjson = {
source = path.join(dependencies.basePath, "nlohmannjson"),
}
function nlohmannjson.import()
nlohmannjson.includes()
end
function nlohmannjson.includes()
includedirs {
path.join(nlohmannjson.source, "single_include/nlohmann")
}
end
function nlohmannjson.project()
end
table.insert(dependencies, nlohmannjson)

2
deps/protobuf vendored

@ -1 +1 @@
Subproject commit 57786d126249b5ed4f42b579047941805e742949 Subproject commit 8d5fdedd42ef361dcfc1531fba4f33470273f375

2
deps/zlib vendored

@ -1 +1 @@
Subproject commit 04f42ceca40f73e2978b50e93806c2a18c1281fc Subproject commit 02a6049eb3884c430268bb0fe3296d597a03174c

View File

@ -76,11 +76,6 @@ newoption {
description = "Always compile unit tests." description = "Always compile unit tests."
} }
newoption {
trigger = "force-exception-handler",
description = "Install custom unhandled exception handler even for Debug builds."
}
newoption { newoption {
trigger = "disable-binary-check", trigger = "disable-binary-check",
description = "Do not perform integrity checks on the exe." description = "Do not perform integrity checks on the exe."
@ -257,9 +252,6 @@ workspace "iw4x"
if _OPTIONS["force-unit-tests"] then if _OPTIONS["force-unit-tests"] then
defines {"FORCE_UNIT_TESTS"} defines {"FORCE_UNIT_TESTS"}
end end
if _OPTIONS["force-exception-handler"] then
defines {"FORCE_EXCEPTION_HANDLER"}
end
if _OPTIONS["disable-binary-check"] then if _OPTIONS["disable-binary-check"] then
defines {"DISABLE_BINARY_CHECK"} defines {"DISABLE_BINARY_CHECK"}
end end

View File

@ -1,5 +1,55 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "Modules/Bans.hpp"
#include "Modules/Bots.hpp"
#include "Modules/Branding.hpp"
#include "Modules/Bullet.hpp"
#include "Modules/CardTitles.hpp"
#include "Modules/Ceg.hpp"
#include "Modules/Changelog.hpp"
#include "Modules/Chat.hpp"
#include "Modules/ClanTags.hpp"
#include "Modules/ClientCommand.hpp"
#include "Modules/ConnectProtocol.hpp"
#include "Modules/Console.hpp"
#include "Modules/D3D9Ex.hpp"
#include "Modules/Debug.hpp"
#include "Modules/Discovery.hpp"
#include "Modules/Download.hpp"
#include "Modules/Elevators.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/PlayerMovement.hpp"
#include "Modules/PlayerName.hpp"
#include "Modules/Playlist.hpp"
#include "Modules/QuickPatch.hpp"
#include "Modules/RawFiles.hpp"
#include "Modules/RawMouse.hpp"
#include "Modules/RCon.hpp"
#include "Modules/Security.hpp"
#include "Modules/ServerCommands.hpp"
#include "Modules/ServerInfo.hpp"
#include "Modules/ServerList.hpp"
#include "Modules/Session.hpp"
#include "Modules/SlowMotion.hpp"
#include "Modules/SoundMutexFix.hpp"
#include "Modules/StartupMessages.hpp"
#include "Modules/Stats.hpp"
#include "Modules/StringTable.hpp"
#include "Modules/StructuredData.hpp"
#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"
namespace Components namespace Components
{ {
bool Loader::Pregame = true; bool Loader::Pregame = true;
@ -9,126 +59,127 @@ namespace Components
bool Loader::IsPregame() bool Loader::IsPregame()
{ {
return Loader::Pregame; return Pregame;
} }
bool Loader::IsPostgame() bool Loader::IsPostgame()
{ {
return Loader::Postgame; return Postgame;
} }
bool Loader::IsUninitializing() bool Loader::IsUninitializing()
{ {
return Loader::Uninitializing; return Uninitializing;
} }
void Loader::Initialize() void Loader::Initialize()
{ {
Loader::Pregame = true; Pregame = true;
Loader::Postgame = false; Postgame = false;
Loader::Uninitializing = false; Uninitializing = false;
Utils::Memory::GetAllocator()->clear(); Utils::Memory::GetAllocator()->clear();
Loader::Register(new Flags()); Register(new Auth());
Loader::Register(new Singleton()); Register(new Command());
// Install our exception handler as early as posssible to get better debug dumps from startup crashes Register(new Dvar());
Loader::Register(new Exception()); Register(new Exception()); // Install our exception handler as early as posssible to get better debug dumps from startup crashes
Loader::Register(new Auth()); Register(new Flags());
Loader::Register(new Bans()); Register(new Network());
Loader::Register(new Bots()); Register(new Logger());
Loader::Register(new Dvar()); Register(new Singleton());
Loader::Register(new Lean()); Register(new UIScript());
Loader::Register(new Maps()); Register(new ZoneBuilder());
Loader::Register(new News());
Loader::Register(new Node());
Loader::Register(new RCon());
Loader::Register(new Stats());
Loader::Register(new Menus());
Loader::Register(new Toast());
Loader::Register(new Party());
Loader::Register(new Zones());
Loader::Register(new D3D9Ex());
Loader::Register(new Logger());
Loader::Register(new Weapon());
Loader::Register(new Window());
Loader::Register(new Command());
Loader::Register(new Console());
Loader::Register(new Friends());
Loader::Register(new IPCPipe());
Loader::Register(new MapDump());
Loader::Register(new ModList());
Loader::Register(new Network());
Loader::Register(new Session());
Loader::Register(new Theatre());
Loader::Register(new ClanTags());
Loader::Register(new Download());
Loader::Register(new Playlist());
Loader::Register(new RawFiles());
Loader::Register(new Renderer());
Loader::Register(new UIFeeder());
Loader::Register(new UIScript());
Loader::Register(new Changelog());
Loader::Register(new Dedicated());
Loader::Register(new Discovery());
Loader::Register(new FastFiles());
Loader::Register(new Gametypes());
Loader::Register(new Materials());
Loader::Register(new Scheduler());
Loader::Register(new CardTitles());
Loader::Register(new FileSystem());
Loader::Register(new ModelSurfs());
Loader::Register(new PlayerName());
Loader::Register(new QuickPatch());
Loader::Register(new Security());
Loader::Register(new ServerInfo());
Loader::Register(new ServerList());
Loader::Register(new SlowMotion());
Loader::Register(new ArenaLength());
Loader::Register(new StringTable());
Loader::Register(new ZoneBuilder());
Loader::Register(new AssetHandler());
Loader::Register(new Localization());
Loader::Register(new ServerCommands());
Loader::Register(new StructuredData());
Loader::Register(new ConnectProtocol());
Loader::Register(new StartupMessages());
Loader::Register(new SoundMutexFix());
Loader::Register(new Gamepad());
Loader::Register(new Chat());
Loader::Register(new TextRenderer());
Loader::Register(new Movement());
Loader::Register(new Elevators());
Loader::Register(new ClientCommand());
Loader::Register(new VisionFile());
Loader::Register(new Branding());
Loader::Register(new Debug());
Loader::Register(new RawMouse());
Loader::Register(new Bullet());
Loader::Register(new MapRotation());
Loader::Register(new Ceg());
Loader::Register(new UserInfo());
Loader::Register(new Events());
Loader::Register(new Voice());
Loader::Register(new Vote());
Loader::Register(new GSC()); Register(new ArenaLength());
Register(new AssetHandler());
Register(new Bans());
Register(new Bots());
Register(new Branding());
Register(new Bullet());
Register(new CardTitles());
Register(new Ceg());
Register(new Changelog());
Register(new Chat());
Register(new ClanTags());
Register(new ClientCommand());
Register(new ConnectProtocol());
Register(new Console());
Register(new D3D9Ex());
Register(new Debug());
Register(new Dedicated());
Register(new Discovery());
Register(new Download());
Register(new Elevators());
Register(new Events());
Register(new FastFiles());
Register(new FileSystem());
Register(new Friends());
Register(new Gamepad());
Register(new IPCPipe());
Register(new Lean());
Register(new Localization());
Register(new MapDump());
Register(new MapRotation());
Register(new Maps());
Register(new Materials());
Register(new Menus());
Register(new ModList());
Register(new ModelSurfs());
Register(new NetworkDebug());
Register(new News());
Register(new Node());
Register(new Party());
Register(new PlayerMovement());
Register(new PlayerName());
Register(new Playlist());
Register(new QuickPatch());
Register(new RawFiles());
Register(new RawMouse());
Register(new RCon());
Register(new Renderer());
Register(new Scheduler());
Register(new Security());
Register(new ServerCommands());
Register(new ServerInfo());
Register(new ServerList());
Register(new Session());
Register(new SlowMotion());
Register(new SoundMutexFix());
Register(new StartupMessages());
Register(new Stats());
Register(new StringTable());
Register(new StructuredData());
Register(new TextRenderer());
Register(new Theatre());
Register(new Threading());
Register(new Toast());
Register(new UIFeeder());
Register(new UserInfo());
Register(new VisionFile());
Register(new Voice());
Register(new Vote());
Register(new Weapon());
Register(new Window());
Register(new Zones());
Loader::Pregame = false; Register(new GSC());
Pregame = false;
// Make sure preDestroy is called when the game shuts down // Make sure preDestroy is called when the game shuts down
Scheduler::OnGameShutdown(Loader::PreDestroy); Scheduler::OnGameShutdown(PreDestroy);
} }
void Loader::Uninitialize() void Loader::Uninitialize()
{ {
Loader::Uninitializing = true; Uninitializing = true;
Loader::PreDestroyNoPostGame(); PreDestroyNoPostGame();
std::reverse(Loader::Components.begin(), Loader::Components.end()); std::reverse(Components.begin(), Components.end());
for (auto component : Loader::Components) for (auto& component : Components)
{ {
#ifdef DEBUG #ifdef DEBUG
if (!Loader::IsPerformingUnitTests()) if (!IsPerformingUnitTests())
{ {
Logger::Print("Unregister component: {}\n", component->getName()); Logger::Print("Unregister component: {}\n", component->getName());
} }
@ -136,21 +187,21 @@ namespace Components
delete component; delete component;
} }
Loader::Components.clear(); Components.clear();
Utils::Memory::GetAllocator()->clear(); Utils::Memory::GetAllocator()->clear();
Loader::Uninitializing = false; Uninitializing = false;
} }
void Loader::PreDestroy() void Loader::PreDestroy()
{ {
if (!Loader::Postgame) if (!Postgame)
{ {
Loader::Postgame = true; Postgame = true;
auto components = Loader::Components; auto components = Components;
std::reverse(components.begin(), components.end()); std::reverse(components.begin(), components.end());
for (auto component : components) for (auto& component : components)
{ {
component->preDestroy(); component->preDestroy();
} }
@ -159,17 +210,17 @@ namespace Components
void Loader::PreDestroyNoPostGame() void Loader::PreDestroyNoPostGame()
{ {
if (!Loader::Postgame) if (!Postgame)
{ {
auto components = Loader::Components; auto components = Components;
std::reverse(components.begin(), components.end()); std::reverse(components.begin(), components.end());
for (auto component : components) for (auto& component : components)
{ {
component->preDestroy(); component->preDestroy();
} }
Loader::Postgame = true; Postgame = true;
} }
} }
@ -179,7 +230,7 @@ namespace Components
Logger::Print("Performing unit tests for components:\n"); Logger::Print("Performing unit tests for components:\n");
for (const auto component : Loader::Components) for (const auto& component : Components)
{ {
#if defined(FORCE_UNIT_TESTS) #if defined(FORCE_UNIT_TESTS)
Logger::Debug("Testing '{}'...\n", component->getName()); Logger::Debug("Testing '{}'...\n", component->getName());
@ -208,12 +259,12 @@ namespace Components
if (component) if (component)
{ {
#if defined(DEBUG) || defined(FORCE_UNIT_TESTS) #if defined(DEBUG) || defined(FORCE_UNIT_TESTS)
if (!Loader::IsPerformingUnitTests()) if (!IsPerformingUnitTests())
{ {
Logger::Print("Component registered: {}\n", component->getName()); Logger::Print("Component registered: {}\n", component->getName());
} }
#endif #endif
Loader::Components.push_back(component); Components.push_back(component);
} }
} }
} }

View File

@ -42,7 +42,7 @@ namespace Components
template <typename T> template <typename T>
static T* GetInstance() static T* GetInstance()
{ {
for (auto& component : Loader::Components) for (auto& component : Components)
{ {
if (typeid(*component) == typeid(T)) if (typeid(*component) == typeid(T))
{ {
@ -61,85 +61,39 @@ namespace Components
}; };
} }
#include "Modules/Scheduler.hpp" // Priority
#include "Modules/Auth.hpp" #include "Modules/Auth.hpp"
#include "Modules/Bans.hpp"
#include "Modules/Bots.hpp"
#include "Modules/Dvar.hpp"
#include "Modules/Lean.hpp"
#include "Modules/Maps.hpp"
#include "Modules/News.hpp"
#include "Modules/Flags.hpp"
#include "Modules/Menus.hpp"
#include "Modules/Toast.hpp"
#include "Modules/Zones.hpp"
#include "Modules/D3D9Ex.hpp"
#include "Modules/Weapon.hpp"
#include "Modules/Window.hpp"
#include "Modules/Command.hpp" #include "Modules/Command.hpp"
#include "Modules/Console.hpp" #include "Modules/Dvar.hpp"
#include "Modules/UIScript.hpp" #include "Modules/Exception.hpp"
#include "Modules/ModList.hpp" #include "Modules/Flags.hpp"
#include "Modules/Network.hpp" #include "Modules/Network.hpp"
#include "Modules/Theatre.hpp"
#include "Modules/QuickPatch.hpp"
#include "Modules/Security.hpp"
#include "Modules/Node.hpp"
#include "Modules/RCon.hpp"
#include "Modules/Party.hpp" // Destroys the order, but requires network classes :D
#include "Modules/Logger.hpp" #include "Modules/Logger.hpp"
#include "Modules/Singleton.hpp"
#include "Modules/UIScript.hpp"
#include "Modules/ZoneBuilder.hpp"
#include "Modules/ArenaLength.hpp"
#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/Friends.hpp"
#include "Modules/IPCPipe.hpp" #include "Modules/IPCPipe.hpp"
#include "Modules/MapDump.hpp"
#include "Modules/Session.hpp"
#include "Modules/ClanTags.hpp"
#include "Modules/Download.hpp"
#include "Modules/Playlist.hpp"
#include "Modules/RawFiles.hpp"
#include "Modules/Renderer.hpp"
#include "Modules/UIFeeder.hpp"
#include "Modules/Changelog.hpp"
#include "Modules/Dedicated.hpp"
#include "Modules/Discovery.hpp"
#include "Modules/Exception.hpp"
#include "Modules/FastFiles.hpp"
#include "Modules/Gametypes.hpp"
#include "Modules/Materials.hpp"
#include "Modules/Singleton.hpp"
#include "Modules/CardTitles.hpp"
#include "Modules/FileSystem.hpp"
#include "Modules/ModelSurfs.hpp"
#include "Modules/PlayerName.hpp"
#include "Modules/ServerInfo.hpp"
#include "Modules/ServerList.hpp"
#include "Modules/SlowMotion.hpp"
#include "Modules/ArenaLength.hpp"
#include "Modules/StringTable.hpp"
#include "Modules/ZoneBuilder.hpp"
#include "Modules/AssetHandler.hpp"
#include "Modules/Localization.hpp" #include "Modules/Localization.hpp"
#include "Modules/ServerCommands.hpp" #include "Modules/Maps.hpp"
#include "Modules/StructuredData.hpp" #include "Modules/Materials.hpp"
#include "Modules/ConnectProtocol.hpp" #include "Modules/Menus.hpp"
#include "Modules/StartupMessages.hpp" #include "Modules/ModList.hpp"
#include "Modules/Stats.hpp" #include "Modules/ModelSurfs.hpp"
#include "Modules/SoundMutexFix.hpp" #include "Modules/Node.hpp"
#include "Modules/Chat.hpp" #include "Modules/Party.hpp"
#include "Modules/Renderer.hpp"
#include "Modules/Scheduler.hpp"
#include "Modules/TextRenderer.hpp" #include "Modules/TextRenderer.hpp"
#include "Modules/Movement.hpp" #include "Modules/Toast.hpp"
#include "Modules/Elevators.hpp" #include "Modules/Window.hpp"
#include "Modules/ClientCommand.hpp" #include "Modules/Zones.hpp"
#include "Modules/VisionFile.hpp"
#include "Modules/Gamepad.hpp"
#include "Modules/Branding.hpp"
#include "Modules/Debug.hpp"
#include "Modules/RawMouse.hpp"
#include "Modules/Bullet.hpp"
#include "Modules/MapRotation.hpp"
#include "Modules/Ceg.hpp"
#include "Modules/UserInfo.hpp"
#include "Modules/Events.hpp"
#include "Modules/Voice.hpp"
#include "Modules/Vote.hpp"
#include "Modules/GSC/GSC.hpp" #include "Modules/GSC/GSC.hpp"

View File

@ -1,4 +1,5 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "Weapon.hpp"
#include "AssetInterfaces/IFont_s.hpp" #include "AssetInterfaces/IFont_s.hpp"
#include "AssetInterfaces/IWeapon.hpp" #include "AssetInterfaces/IWeapon.hpp"
@ -257,7 +258,7 @@ namespace Components
for (int k = 0; k < (pass->perPrimArgCount + pass->perObjArgCount + pass->stableArgCount); ++k) for (int k = 0; k < (pass->perPrimArgCount + pass->perObjArgCount + pass->stableArgCount); ++k)
{ {
if (pass->args[k].type == D3DSHADER_PARAM_REGISTER_TYPE::D3DSPR_CONSTINT) if (pass->args[k].type == Game::MaterialShaderArgumentType::MTL_ARG_LITERAL_PIXEL_CONST)
{ {
if (pass->args[k].u.codeConst.index == -28132) if (pass->args[k].u.codeConst.index == -28132)
{ {

View File

@ -11,7 +11,7 @@ namespace Assets
Utils::String::Replace(name, "maps/mp/", ""); Utils::String::Replace(name, "maps/mp/", "");
Utils::String::Replace(name, ".d3dbsp", ""); Utils::String::Replace(name, ".d3dbsp", "");
Components::FileSystem::File mapFile(Utils::String::VA("comworld/%s.iw4xComWorld", name.data())); Components::FileSystem::File mapFile(std::format("comworld/{}.iw4xComWorld", name));
if (mapFile.exists()) if (mapFile.exists())
{ {

View File

@ -95,22 +95,22 @@ namespace Assets
void IFont_s::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) void IFont_s::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
{ {
Components::FileSystem::File fontDefFile(Utils::String::VA("%s.json", name.data())); Components::FileSystem::File fontDefFile(std::format("{}.json", name));
Components::FileSystem::File fontFile(Utils::String::VA("%s.ttf", name.data())); Components::FileSystem::File fontFile(std::format("{}.ttf", name));
if (!fontDefFile.exists() || !fontFile.exists()) if (!fontDefFile.exists() || !fontFile.exists())
{ {
return; return;
} }
nlohmann::json fontDef = nlohmann::json::parse(fontDefFile.getBuffer()); nlohmann::json fontDef;
try try
{ {
fontDef = nlohmann::json::parse(fontDefFile.getBuffer()); fontDef = nlohmann::json::parse(fontDefFile.getBuffer());
} }
catch (const nlohmann::json::parse_error& ex) catch (const nlohmann::json::parse_error& ex)
{ {
Components::Logger::Error(Game::ERR_FATAL, "Json Parse Error: {}. Font {} is invalid", ex.what(), name); Components::Logger::Error(Game::ERR_FATAL, "Json Parse Error: {}. Font {} is invalid\n", ex.what(), name);
return; return;
} }
@ -254,7 +254,7 @@ namespace Assets
rgbaPixels[i + 3] = static_cast<char>(pixels[i / 4]); rgbaPixels[i + 3] = static_cast<char>(pixels[i / 4]);
} }
Utils::IO::WriteFile(Utils::String::VA("userraw\\images\\%s.iwi", texName), outIwi); Utils::IO::WriteFile(std::format("userraw\\images\\{}.iwi", texName), outIwi);
} }
void IFont_s::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) void IFont_s::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)

View File

@ -64,7 +64,7 @@ namespace Assets
void IFxEffectDef::loadBinary(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) void IFxEffectDef::loadBinary(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
{ {
Components::FileSystem::File fxFile(Utils::String::VA("fx/%s.iw4xFx", name.data())); Components::FileSystem::File fxFile(std::format("fx/{}.iw4xFx", name));
if (fxFile.exists()) if (fxFile.exists())
{ {
@ -391,7 +391,7 @@ namespace Assets
switch (elemType) switch (elemType)
{ {
case 7: case Game::FX_ELEM_TYPE_MODEL:
{ {
if (visuals->model) if (visuals->model)
{ {
@ -401,11 +401,11 @@ namespace Assets
break; break;
} }
case 8: case Game::FX_ELEM_TYPE_OMNI_LIGHT:
case 9: case Game::FX_ELEM_TYPE_SPOT_LIGHT:
break; break;
case 0xA: case Game::FX_ELEM_TYPE_SOUND:
{ {
if (visuals->soundName) if (visuals->soundName)
{ {
@ -416,7 +416,7 @@ namespace Assets
break; break;
} }
case 0xC: case Game::FX_ELEM_TYPE_RUNNER:
{ {
if (visuals->effectDef.handle) if (visuals->effectDef.handle)
{ {
@ -491,7 +491,7 @@ namespace Assets
// Save_FxElemDefVisuals // Save_FxElemDefVisuals
{ {
if (elemDef->elemType == 11) if (elemDef->elemType == Game::FX_ELEM_TYPE_DECAL)
{ {
if (elemDef->visuals.markArray) if (elemDef->visuals.markArray)
{ {
@ -501,7 +501,7 @@ namespace Assets
Game::FxElemMarkVisuals* destMarkArray = buffer->dest<Game::FxElemMarkVisuals>(); Game::FxElemMarkVisuals* destMarkArray = buffer->dest<Game::FxElemMarkVisuals>();
buffer->saveArray(elemDef->visuals.markArray, elemDef->visualCount); buffer->saveArray(elemDef->visuals.markArray, elemDef->visualCount);
for (char j = 0; j < elemDef->visualCount; ++j) for (auto j = 0; j < elemDef->visualCount; ++j)
{ {
if (elemDef->visuals.markArray[j].materials[0]) if (elemDef->visuals.markArray[j].materials[0])
{ {
@ -563,7 +563,7 @@ namespace Assets
{ {
AssertSize(Game::FxElemExtendedDefPtr, 4); AssertSize(Game::FxElemExtendedDefPtr, 4);
if (elemDef->elemType == 3) if (elemDef->elemType == Game::FX_ELEM_TYPE_TRAIL)
{ {
// Save_FxTrailDef // Save_FxTrailDef
{ {
@ -597,7 +597,7 @@ namespace Assets
} }
} }
} }
else if (elemDef->elemType == 6) else if (elemDef->elemType == Game::FX_ELEM_TYPE_SPARK_FOUNTAIN)
{ {
if (elemDef->extended.sparkFountainDef) if (elemDef->extended.sparkFountainDef)
{ {

View File

@ -186,11 +186,20 @@ namespace Assets
} }
} }
void IFxWorld::load(Game::XAssetHeader* /*header*/, const std::string& name, Components::ZoneBuilder::Zone* /*builder*/) void IFxWorld::load(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; Game::FxWorld* map = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_FXWORLD, name.data()).fxWorld;
if (map) return; if (map) return;
Components::Logger::Error(Game::ERR_FATAL, "Missing fx_map {}... you can't make them yet you idiot.", name); // Generate
map = builder->getAllocator()->allocate<Game::FxWorld>();
map->name = builder->getAllocator()->duplicateString(name);
// No glass for you!
ZeroMemory(&map->glassSys, sizeof(map->glassSys));
map->glassSys.firstFreePiece = 0xFFFF; // That's how rust has it (no glass)
header->fxWorld = map;
} }
} }

View File

@ -1,6 +1,8 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "IGameWorldMp.hpp" #include "IGameWorldMp.hpp"
#define IW4X_GAMEWORLD_VERSION 1
namespace Assets namespace Assets
{ {
void IGameWorldMp::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) void IGameWorldMp::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
@ -75,4 +77,118 @@ namespace Assets
buffer->popBlock(); buffer->popBlock();
} }
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;
}
}
} }

View File

@ -8,5 +8,6 @@ namespace Assets
Game::XAssetType getType() override { return Game::XAssetType::ASSET_TYPE_GAMEWORLD_MP; } Game::XAssetType getType() override { return Game::XAssetType::ASSET_TYPE_GAMEWORLD_MP; }
void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override; 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

@ -151,11 +151,11 @@ namespace Assets
void IGameWorldSp::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) void IGameWorldSp::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
{ {
AssertSize(Game::GameWorldMp, 8); AssertSize(Game::GameWorldSp, 0x38);
Utils::Stream* buffer = builder->getBuffer(); Utils::Stream* buffer = builder->getBuffer();
Game::GameWorldSp* asset = header.gameWorldSp; auto* asset = header.gameWorldSp;
Game::GameWorldSp* dest = buffer->dest<Game::GameWorldSp>(); auto* dest = buffer->dest<Game::GameWorldSp>();
buffer->save(asset); buffer->save(asset);
buffer->pushBlock(Game::XFILE_BLOCK_VIRTUAL); buffer->pushBlock(Game::XFILE_BLOCK_VIRTUAL);
@ -187,7 +187,7 @@ namespace Assets
for (char j = 0; j < 5; ++j) for (char j = 0; j < 5; ++j)
{ {
builder->mapScriptString(&(&node->constant.targetname)[j]); builder->mapScriptString((&node->constant.targetname)[j]);
} }
if (node->constant.Links) if (node->constant.Links)

View File

@ -18,12 +18,12 @@ namespace Assets
} }
image->name = builder->getAllocator()->duplicateString(name); image->name = builder->getAllocator()->duplicateString(name);
image->semantic = 2; image->semantic = Game::TextureSemantic::TS_COLOR_MAP;
const char* tempName = image->name; const char* tempName = image->name;
if (tempName[0] == '*') tempName++; if (tempName[0] == '*') tempName++;
Components::FileSystem::File imageFile(Utils::String::VA("images/%s.iw4xImage", tempName)); Components::FileSystem::File imageFile(std::format("images/{}.iw4xImage", tempName));
if (imageFile.exists()) if (imageFile.exists())
{ {
Utils::Stream::Reader reader(builder->getAllocator(), imageFile.getBuffer()); Utils::Stream::Reader reader(builder->getAllocator(), imageFile.getBuffer());
@ -35,7 +35,7 @@ namespace Assets
} }
image->mapType = reader.read<char>(); image->mapType = reader.read<char>();
image->semantic = reader.read<char>(); image->semantic = reader.read<Game::TextureSemantic>();
image->category = reader.read<char>(); image->category = reader.read<char>();
int dataLength = reader.read<int>(); int dataLength = reader.read<int>();

View File

@ -7,7 +7,7 @@ namespace Assets
{ {
void IGfxLightDef::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) void IGfxLightDef::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
{ {
Components::FileSystem::File mapFile(Utils::String::VA("lights/%s.iw4xLight", name.data())); Components::FileSystem::File mapFile(std::format("lights/{}.iw4xLight", name));
if (mapFile.exists()) if (mapFile.exists())
{ {

View File

@ -3,6 +3,27 @@
#define IW4X_GFXMAP_VERSION 1 #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 namespace Assets
{ {
void IGfxWorld::loadGfxWorldDpvsStatic(Game::GfxWorld* world, Game::GfxWorldDpvsStatic* asset, Components::ZoneBuilder::Zone* builder, Utils::Stream::Reader* reader) void IGfxWorld::loadGfxWorldDpvsStatic(Game::GfxWorld* world, Game::GfxWorldDpvsStatic* asset, Components::ZoneBuilder::Zone* builder, Utils::Stream::Reader* reader)
@ -47,7 +68,11 @@ namespace Assets
if (model->model) if (model->model)
{ {
model->model = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_XMODEL, reader->readString().data(), builder).model; auto name = reader->readString();
model->model = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_XMODEL, name.data(), builder).model;
assert(model->model);
} }
} }
} }
@ -129,7 +154,7 @@ namespace Assets
Utils::String::Replace(name, "maps/mp/", ""); Utils::String::Replace(name, "maps/mp/", "");
Utils::String::Replace(name, ".d3dbsp", ""); Utils::String::Replace(name, ".d3dbsp", "");
Components::FileSystem::File mapFile(Utils::String::VA("gfxworld/%s.iw4xGfxWorld", name.data())); Components::FileSystem::File mapFile(std::format("gfxworld/{}.iw4xGfxWorld", name));
if (mapFile.exists()) if (mapFile.exists())
{ {

View File

@ -5,7 +5,7 @@ namespace Assets
{ {
void ILoadedSound::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) void ILoadedSound::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
{ {
Components::FileSystem::File soundFile(Utils::String::VA("loaded_sound/%s", name.data())); Components::FileSystem::File soundFile(std::format("loaded_sound/{}", name));
if (!soundFile.exists()) if (!soundFile.exists())
{ {
header->loadSnd = Components::AssetHandler::FindOriginalAsset(this->getType(), name.data()).loadSnd; header->loadSnd = Components::AssetHandler::FindOriginalAsset(this->getType(), name.data()).loadSnd;
@ -120,18 +120,9 @@ namespace Assets
if (asset->sound.data) if (asset->sound.data)
{ {
if (builder->hasPointer(asset->sound.data))
{
dest->sound.data = builder->getPointer(asset->sound.data);
}
else
{
builder->storePointer(asset->sound.data);
buffer->saveArray(asset->sound.data, asset->sound.info.data_len); buffer->saveArray(asset->sound.data, asset->sound.info.data_len);
Utils::Stream::ClearPointer(&dest->sound.data); Utils::Stream::ClearPointer(&dest->sound.data);
} }
}
buffer->popBlock(); buffer->popBlock();
} }

View File

@ -81,4 +81,51 @@ namespace Assets
builder->addRawAsset(type, entry); builder->addRawAsset(type, entry);
} }
} }
void ILocalizeEntry::ParseLocalizedStringsJson(Components::ZoneBuilder::Zone* builder, Components::FileSystem::File& file)
{
nlohmann::json localize;
try
{
Components::Logger::Debug("Parsing localized string \"{}\"...", file.getName());
localize = nlohmann::json::parse(file.getBuffer());
}
catch (const std::exception& ex)
{
Components::Logger::PrintError(Game::CON_CHANNEL_ERROR, "{}\n", ex.what());
return;
}
if (!localize.is_object())
{
Components::Logger::PrintError(Game::CON_CHANNEL_ERROR, "Localized strings json file '{}' should be an object!", file.getName());
return;
}
std::vector<Game::LocalizeEntry*> assets;
try
{
for (const auto& [key, value] : localize.items())
{
const auto valueStr = value.get<std::string>();
auto* entry = builder->getAllocator()->allocate<Game::LocalizeEntry>();
entry->name = builder->getAllocator()->duplicateString(key);
entry->value = builder->getAllocator()->duplicateString(valueStr);
assets.emplace_back(entry);
}
}
catch (const std::exception& ex)
{
Components::Logger::PrintError(Game::CON_CHANNEL_ERROR, "{}: Localized strings json file '{}' contains invalid data!", ex.what(), file.getName());
}
auto type = Game::DB_GetXAssetNameType("localize");
for (const auto& entry : assets)
{
builder->addRawAsset(type, entry);
}
}
} }

View File

@ -11,5 +11,6 @@ namespace Assets
void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override; void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override;
static void ParseLocalizedStringsFile(Components::ZoneBuilder::Zone* builder, const std::string& name, const std::string& filename); static void ParseLocalizedStringsFile(Components::ZoneBuilder::Zone* builder, const std::string& name, const std::string& filename);
static void ParseLocalizedStringsJson(Components::ZoneBuilder::Zone* builder, Components::FileSystem::File& file);
}; };
} }

View File

@ -10,7 +10,7 @@ namespace Assets
Utils::String::Replace(name, "mp/", ""); Utils::String::Replace(name, "mp/", "");
Utils::String::Replace(name, ".d3dbsp", ""); Utils::String::Replace(name, ".d3dbsp", "");
Components::FileSystem::File ents(Utils::String::VA("mapents/%s.ents", name.data())); Components::FileSystem::File ents(std::format("mapents/{}.ents", name));
if (ents.exists()) if (ents.exists())
{ {
Game::MapEnts* entites = builder->getAllocator()->allocate<Game::MapEnts>(); Game::MapEnts* entites = builder->getAllocator()->allocate<Game::MapEnts>();
@ -48,7 +48,7 @@ namespace Assets
std::string entityString = ents.getBuffer(); std::string entityString = ents.getBuffer();
entites->name = builder->getAllocator()->duplicateString(Utils::String::VA("maps/mp/%s.d3dbsp", name.data())); entites->name = builder->getAllocator()->duplicateString(std::format("maps/mp/{}.d3dbsp", name));
entites->entityString = builder->getAllocator()->duplicateString(entityString); entites->entityString = builder->getAllocator()->duplicateString(entityString);
entites->numEntityChars = entityString.size() + 1; entites->numEntityChars = entityString.size() + 1;

View File

@ -1,15 +1,439 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "IMaterial.hpp" #include "IMaterial.hpp"
#define IW4X_MAT_VERSION "1" #define IW4X_MAT_BIN_VERSION "1"
#define IW4X_MAT_JSON_VERSION 1
namespace Assets 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) 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 override materials 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->loadNative(header, name, builder); // Check if there is a native one if (!header->data) this->loadNative(header, name, builder); // Check if there is a native one
if (!header->data) this->loadBinary(header, name, builder); // Check if we need to import a new one into the game }
void IMaterial::loadJson(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) void IMaterial::loadBinary(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
@ -30,30 +454,7 @@ namespace Assets
"_add_lin_nofog", "_add_lin_nofog",
}; };
static std::unordered_map<std::string, std::string> techSetCorrespondance = Components::FileSystem::File materialFile(std::format("materials/{}.iw4xMaterial", name));
{
{"effect", "effect_blend"},
{"effect", "effect_blend"},
{"effect_nofog", "effect_blend_nofog"},
{"effect_zfeather", "effect_zfeather_blend"},
{"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_ua"},
{"wc_unlit", "wc_unlit_replace_lin"},
{"wc_unlit_alphatest", "wc_unlit_blend_lin"},
{"wc_unlit_blend", "wc_unlit_blend_lin_ua"},
{"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"}
};
Components::FileSystem::File materialFile(Utils::String::VA("materials/%s.iw4xMaterial", name.data()));
if (!materialFile.exists()) return; if (!materialFile.exists()) return;
Utils::Stream::Reader reader(builder->getAllocator(), materialFile.getBuffer()); Utils::Stream::Reader reader(builder->getAllocator(), materialFile.getBuffer());
@ -66,9 +467,9 @@ namespace Assets
std::string version; std::string version;
version.push_back(reader.read<char>()); version.push_back(reader.read<char>());
if (version != IW4X_MAT_VERSION) if (version != IW4X_MAT_BIN_VERSION)
{ {
Components::Logger::Error(Game::ERR_FATAL, "Reading material '{}' failed, expected version is {}, but it was {}!", name, IW4X_MAT_VERSION, 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>(); auto* asset = reader.readObject<Game::Material>();
@ -127,7 +528,7 @@ namespace Assets
{ {
Game::MaterialTextureDef* textureDef = &asset->textureTable[i]; Game::MaterialTextureDef* textureDef = &asset->textureTable[i];
if (textureDef->semantic == SEMANTIC_WATER_MAP) if (textureDef->semantic == Game::TextureSemantic::TS_WATER_MAP)
{ {
if (textureDef->u.water) if (textureDef->u.water)
{ {
@ -190,7 +591,7 @@ namespace Assets
// This is temp, as nobody has time to fix materials // This is temp, as nobody has time to fix materials
asset->stateBitsCount = header.material->stateBitsCount; asset->stateBitsCount = header.material->stateBitsCount;
asset->stateBitsTable = header.material->stateBitsTable; asset->stateBitsTable = header.material->stateBitsTable;
std::memcpy(asset->stateBitsEntry, header.material->stateBitsEntry, 48); std::memcpy(asset->stateBitsEntry, header.material->stateBitsEntry, ARRAYSIZE(asset->stateBitsEntry));
asset->constantCount = header.material->constantCount; asset->constantCount = header.material->constantCount;
asset->constantTable = header.material->constantTable; asset->constantTable = header.material->constantTable;
Components::Logger::Print("For {}, copied constants & statebits from {}\n", asset->info.name, header.material->info.name); Components::Logger::Print("For {}, copied constants & statebits from {}\n", asset->info.name, header.material->info.name);
@ -241,10 +642,10 @@ namespace Assets
{ {
Components::Logger::Print("No replacement found for material {} with techset {}\n", asset->info.name, asset->techniqueSet->name); Components::Logger::Print("No replacement found for material {} with techset {}\n", asset->info.name, asset->techniqueSet->name);
std::string techName = asset->techniqueSet->name; std::string techName = asset->techniqueSet->name;
if (techSetCorrespondance.contains(techName)) if (const auto itr = techSetCorrespondance.find(techName); itr != techSetCorrespondance.end())
{ {
auto iw4TechSetName = techSetCorrespondance[techName]; auto& iw4TechSetName = itr->second;
Game::XAssetEntry* iw4TechSet = Game::DB_FindXAssetEntry(Game::XAssetType::ASSET_TYPE_TECHNIQUE_SET, iw4TechSetName.data()); auto* iw4TechSet = Game::DB_FindXAssetEntry(Game::ASSET_TYPE_TECHNIQUE_SET, iw4TechSetName.data());
if (iw4TechSet) if (iw4TechSet)
{ {
@ -253,9 +654,11 @@ namespace Assets
if (!replacementFound) if (!replacementFound)
{ {
Game::XAssetHeader header = entry->asset.header; 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);
if (header.material->techniqueSet == iw4TechSet->asset.header.techniqueSet // Yeah this has a tendency to fuck up a LOT of transparent materials
&& std::string(header.material->info.name).find("icon") == std::string::npos) // 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", 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.name, asset->techniqueSet->name, header.material->techniqueSet->name, header.material->info.name);
@ -327,15 +730,6 @@ namespace Assets
header->material = Components::AssetHandler::FindOriginalAsset(this->getType(), name.data()).material; header->material = Components::AssetHandler::FindOriginalAsset(this->getType(), name.data()).material;
} }
void IMaterial::loadJson(Game::XAssetHeader* header, const std::string& name, [[maybe_unused]] Components::ZoneBuilder::Zone* builder)
{
Components::FileSystem::File materialInfo(Utils::String::VA("materials/%s.json", name.data()));
if (!materialInfo.exists()) return;
header->material = nullptr;
}
void IMaterial::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) void IMaterial::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
{ {
Game::Material* asset = header.material; Game::Material* asset = header.material;
@ -351,7 +745,7 @@ namespace Assets
{ {
if (asset->textureTable[i].u.image) if (asset->textureTable[i].u.image)
{ {
if (asset->textureTable[i].semantic == SEMANTIC_WATER_MAP) if (asset->textureTable[i].semantic == Game::TextureSemantic::TS_WATER_MAP)
{ {
if (asset->textureTable[i].u.water->image) if (asset->textureTable[i].u.water->image)
{ {
@ -411,7 +805,7 @@ namespace Assets
auto* destTextureDef = &destTextureTable[i]; auto* destTextureDef = &destTextureTable[i];
auto* textureDef = &asset->textureTable[i]; auto* textureDef = &asset->textureTable[i];
if (textureDef->semantic == SEMANTIC_WATER_MAP) if (textureDef->semantic == Game::TextureSemantic::TS_WATER_MAP)
{ {
AssertSize(Game::water_t, 68); AssertSize(Game::water_t, 68);

View File

@ -13,5 +13,8 @@ namespace Assets
void loadJson(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder); void loadJson(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 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 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,15 +1,15 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "IMaterialPixelShader.hpp" #include "IMaterialPixelShader.hpp"
#define IW4X_TECHSET_VERSION "0" #define GFX_RENDERER_SHADER_SM3 0
namespace Assets namespace Assets
{ {
void IMaterialPixelShader::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) void IMaterialPixelShader::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
{ {
if (!header->data) this->loadNative(header, name, builder); // Check if there is a native one
if (!header->data) this->loadBinary(header, name, builder); // Check if we need to import a new one into the game if (!header->data) this->loadBinary(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
} }
void IMaterialPixelShader::loadNative(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* /*builder*/) void IMaterialPixelShader::loadNative(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* /*builder*/)
@ -19,36 +19,19 @@ namespace Assets
void IMaterialPixelShader::loadBinary(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) void IMaterialPixelShader::loadBinary(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
{ {
Components::FileSystem::File psFile(Utils::String::VA("ps/%s.iw4xPS", name.data())); Components::FileSystem::File psFile(std::format("ps/{}.cso", name));
if (!psFile.exists()) return; if (!psFile.exists()) return;
Utils::Stream::Reader reader(builder->getAllocator(), psFile.getBuffer()); auto buff = psFile.getBuffer();
auto programSize = buff.size() / 4;
Game::MaterialPixelShader* asset = builder->getAllocator()->allocate<Game::MaterialPixelShader>();
char* magic = reader.readArray<char>(8); asset->name = builder->getAllocator()->duplicateString(name);
if (std::memcmp(magic, "IW4xPIXL", 8)) asset->prog.loadDef.loadForRenderer = GFX_RENDERER_SHADER_SM3;
{ asset->prog.loadDef.programSize = static_cast<unsigned short>(programSize);
Components::Logger::Error(Game::ERR_FATAL, "Reading pixel shader '{}' failed, header is invalid!", name); asset->prog.loadDef.program = builder->getAllocator()->allocateArray<unsigned int>(programSize);
} memcpy_s(asset->prog.loadDef.program, buff.size(), buff.data(), buff.size());
std::string version;
version.push_back(reader.read<char>());
if (version != IW4X_TECHSET_VERSION)
{
Components::Logger::Error(Game::ERR_FATAL,
"Reading pixel shader '{}' failed, expected version is {}, but it was {}!", name, IW4X_TECHSET_VERSION, version);
}
Game::MaterialPixelShader* asset = reader.readObject<Game::MaterialPixelShader>();
if (asset->name)
{
asset->name = reader.readCString();
}
if (asset->prog.loadDef.program)
{
asset->prog.loadDef.program = reader.readArray<unsigned int>(asset->prog.loadDef.programSize);
}
header->pixelShader = asset; header->pixelShader = asset;
} }

View File

@ -1,14 +1,14 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "IMaterialTechniqueSet.hpp" #include "IMaterialTechniqueSet.hpp"
#define IW4X_TECHSET_VERSION "0" #define IW4X_TECHSET_VERSION 1
namespace Assets namespace Assets
{ {
void IMaterialTechniqueSet::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) void IMaterialTechniqueSet::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
{ {
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) this->loadNative(header, name, builder); // Check if there is a native one
if (!header->data) this->loadBinary(header, name, builder); // Check if we need to import a new one into the game
} }
void IMaterialTechniqueSet::loadNative(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* /*builder*/) void IMaterialTechniqueSet::loadNative(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* /*builder*/)
@ -16,126 +16,196 @@ namespace Assets
header->techniqueSet = Components::AssetHandler::FindOriginalAsset(this->getType(), name.data()).techniqueSet; header->techniqueSet = Components::AssetHandler::FindOriginalAsset(this->getType(), name.data()).techniqueSet;
} }
void IMaterialTechniqueSet::loadBinaryTechnique(Game::MaterialTechnique** tech, const std::string& name, Components::ZoneBuilder::Zone* builder) void IMaterialTechniqueSet::loadTechniqueFromDisk(Game::MaterialTechnique** tech, const std::string& name, Components::ZoneBuilder::Zone* builder)
{ {
AssertSize(Game::MaterialPass, 20); AssertSize(Game::MaterialPass, 20);
Components::FileSystem::File techFile(Utils::String::VA("techniques/%s.iw4xTech", name.data())); Components::FileSystem::File techFile(std::format("techniques/{}.iw4x.json", name));
if (!techFile.exists()) { if (!techFile.exists())
{
*tech = nullptr; *tech = nullptr;
Components::Logger::Warning(Game::CON_CHANNEL_DONT_FILTER, "Missing technique '{}'\n", name); Components::Logger::Warning(Game::CON_CHANNEL_DONT_FILTER, "Missing technique '{}'\n", name);
return; return;
} }
Utils::Stream::Reader reader(builder->getAllocator(), techFile.getBuffer()); nlohmann::json technique;
char* magic = reader.readArray<char>(8); try
if (std::memcmp(magic, "IW4xTECH", 8))
{ {
Components::Logger::Error(Game::ERR_FATAL, "Reading technique '{}' failed, header is invalid!", name); 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());
} }
std::string version; int version = technique["version"].get<int>();
version.push_back(reader.read<char>());
if (version != IW4X_TECHSET_VERSION) if (version != IW4X_TECHSET_VERSION)
{ {
Components::Logger::Error(Game::ERR_FATAL, Components::Logger::Error(Game::ERR_FATAL,
"Reading technique '{}' failed, expected version is {}, but it was {}!", name, IW4X_TECHSET_VERSION, version.data()); "Reading technique '{}' failed, expected version is {}, but it was {}!", name, IW4X_TECHSET_VERSION, version);
} }
unsigned short flags = reader.read<unsigned short>(); unsigned short flags = static_cast<unsigned short>(Utils::Json::ReadFlags(technique["flags"].get<std::string>(), sizeof(short)));
unsigned short passCount = reader.read<unsigned short>();
Game::MaterialTechnique* asset = (Game::MaterialTechnique*)builder->getAllocator()->allocateArray<unsigned char>(sizeof(Game::MaterialTechnique) + (sizeof(Game::MaterialPass) * (passCount - 1))); 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->name = builder->getAllocator()->duplicateString(name);
asset->flags = flags; asset->flags = flags;
asset->passCount = passCount; asset->passCount = static_cast<unsigned short>(passArray.size());
Game::MaterialPass* passes = reader.readArray<Game::MaterialPass>(passCount); Game::MaterialPass* passes = builder->getAllocator()->allocateArray<Game::MaterialPass>(asset->passCount);
std::memcpy(asset->passArray, passes, sizeof(Game::MaterialPass) * passCount); std::memcpy(asset->passArray, passes, sizeof(Game::MaterialPass) * asset->passCount);
for (unsigned short i = 0; i < asset->passCount; i++) for (unsigned short i = 0; i < asset->passCount; i++)
{ {
Game::MaterialPass* pass = &asset->passArray[i]; Game::MaterialPass* pass = &asset->passArray[i];
auto jsonPass = passArray[i];
if (pass->vertexDecl) if (jsonPass["vertexDeclaration"].is_string())
{ {
const char* declName = reader.readCString(); auto declName = jsonPass["vertexDeclaration"].get<std::string>();
pass->vertexDecl = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_VERTEXDECL, declName, builder).vertexDecl; pass->vertexDecl = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_VERTEXDECL, declName, builder).vertexDecl;
} }
if (pass->vertexShader) if (jsonPass["vertexShader"].is_string())
{ {
const char* vsName = reader.readCString(); auto vsName = jsonPass["vertexShader"].get<std::string>();
pass->vertexShader = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_VERTEXSHADER, vsName, builder).vertexShader; pass->vertexShader = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_VERTEXSHADER, vsName, builder).vertexShader;
} }
if (pass->pixelShader) if (jsonPass["pixelShader"].is_string())
{ {
const char* psName = reader.readCString(); auto psName = jsonPass["pixelShader"].get<std::string>();
pass->pixelShader = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_PIXELSHADER, psName, builder).pixelShader; pass->pixelShader = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_PIXELSHADER, psName, builder).pixelShader;
} }
pass->args = reader.readArray<Game::MaterialShaderArgument>(pass->perPrimArgCount + pass->perObjArgCount + pass->stableArgCount); pass->perPrimArgCount = jsonPass["perPrimArgCount"].get<char>();
pass->perObjArgCount = jsonPass["perObjArgCount"].get<char>();
pass->stableArgCount = jsonPass["stableArgCount"].get<char>();
pass->customSamplerFlags = jsonPass["customSamplerFlags"].get<char>();
for (int j = 0; j < pass->perPrimArgCount + pass->perObjArgCount + pass->stableArgCount; j++)
if (jsonPass["arguments"].is_array())
{ {
if (pass->args[j].type == 1 || pass->args[j].type == 7) 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++)
{ {
pass->args[j].u.literalConst = reader.readArray<float>(4); 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)
{
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 ||
if (pass->args[j].type == 3 || pass->args[j].type == 5) argument->type == Game::MaterialShaderArgumentType::MTL_ARG_CODE_PIXEL_CONST)
{ {
pass->args[j].u.codeConst.index = *reader.readObject<unsigned short>(); if (jsonArgument["codeConst"].is_object())
pass->args[j].u.codeConst.firstRow = *reader.readObject<unsigned char>(); {
pass->args[j].u.codeConst.rowCount = *reader.readObject<unsigned char>(); 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>();
}
} }
} }
} }
*tech = asset; *tech = asset;
} }
void IMaterialTechniqueSet::loadBinary(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
{
Components::FileSystem::File tsFile(Utils::String::VA("techsets/%s.iw4xTS", name.data()));
if (!tsFile.exists()) return;
Utils::Stream::Reader reader(builder->getAllocator(), tsFile.getBuffer());
char* magic = reader.readArray<char>(8);
if (std::memcmp(magic, "IW4xTSET", 8))
{
Components::Logger::Error(Game::ERR_FATAL, "Reading techset '{}' failed, header is invalid!", name);
} }
std::string version; void IMaterialTechniqueSet::loadFromDisk(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
version.push_back(reader.read<char>()); {
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) if (version != IW4X_TECHSET_VERSION)
{ {
Components::Logger::Error(Game::ERR_FATAL, "Reading techset '{}' failed, expected version is {}, but it was {}!", Components::Logger::Error(Game::ERR_FATAL, "Reading techset '{}' failed, expected version is {}, but it was {}!",
name, IW4X_TECHSET_VERSION, version); name, IW4X_TECHSET_VERSION, version);
} }
Game::MaterialTechniqueSet* asset = reader.readObject<Game::MaterialTechniqueSet>(); Game::MaterialTechniqueSet* asset = builder->getAllocator()->allocate<Game::MaterialTechniqueSet>();
if (asset->name) if (asset == nullptr)
{ {
asset->name = reader.readCString(); Components::Logger::Error(Game::ERR_FATAL, "Reading techset '{}' failed, allocation failed!", name);
return;
} }
for (int i = 0; i < 48; i++) if (techset["name"].is_string())
{ {
if (asset->techniques[i]) 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())
{ {
const char* techName = reader.readCString(); auto remapped = techset["remappedTechniqueSet"].get<std::string>();
this->loadBinaryTechnique(&asset->techniques[i], techName, builder);
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; header->techniqueSet = asset;
} }
@ -177,8 +247,10 @@ namespace Assets
AssertSize(Game::MaterialTechniqueSet, 204); AssertSize(Game::MaterialTechniqueSet, 204);
Utils::Stream* buffer = builder->getBuffer(); Utils::Stream* buffer = builder->getBuffer();
Game::MaterialTechniqueSet* asset = header.techniqueSet; Game::MaterialTechniqueSet* asset = header.techniqueSet;
Game::MaterialTechniqueSet* dest = buffer->dest<Game::MaterialTechniqueSet>(); Game::MaterialTechniqueSet* dest = buffer->dest<Game::MaterialTechniqueSet>();
buffer->save(asset); buffer->save(asset);
buffer->pushBlock(Game::XFILE_BLOCK_VIRTUAL); buffer->pushBlock(Game::XFILE_BLOCK_VIRTUAL);

View File

@ -12,8 +12,8 @@ namespace Assets
void load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) override; void load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) override;
void loadNative(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 loadFromDisk(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder);
void loadBinaryTechnique(Game::MaterialTechnique** tech, const std::string& name, Components::ZoneBuilder::Zone* builder); void loadTechniqueFromDisk(Game::MaterialTechnique** tech, const std::string& name, Components::ZoneBuilder::Zone* builder);
}; };
} }

View File

@ -1,14 +1,14 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "IMaterialVertexDeclaration.hpp" #include "IMaterialVertexDeclaration.hpp"
#define IW4X_TECHSET_VERSION "0" #define IW4X_TECHSET_VERSION 1
namespace Assets namespace Assets
{ {
void IMaterialVertexDeclaration::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) void IMaterialVertexDeclaration::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
{ {
if (!header->data) this->loadNative(header, name, builder); // Check if there is a native one
if (!header->data) this->loadBinary(header, name, builder); // Check if we need to import a new one into the game if (!header->data) this->loadBinary(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
} }
void IMaterialVertexDeclaration::loadNative(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* /*builder*/) void IMaterialVertexDeclaration::loadNative(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* /*builder*/)
@ -18,7 +18,7 @@ namespace Assets
void IMaterialVertexDeclaration::loadBinary(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) void IMaterialVertexDeclaration::loadBinary(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
{ {
Components::FileSystem::File declFile(Utils::String::VA("decl/%s.iw4xDECL", name.data())); Components::FileSystem::File declFile(std::format("decl/{}.iw4xDECL", name));
if (!declFile.exists()) return; if (!declFile.exists()) return;
Utils::Stream::Reader reader(builder->getAllocator(), declFile.getBuffer()); Utils::Stream::Reader reader(builder->getAllocator(), declFile.getBuffer());
@ -29,12 +29,11 @@ namespace Assets
Components::Logger::Error(Game::ERR_FATAL, "Reading vertex declaration '{}' failed, header is invalid!", name); Components::Logger::Error(Game::ERR_FATAL, "Reading vertex declaration '{}' failed, header is invalid!", name);
} }
std::string version; auto version = reader.read<char>();
version.push_back(reader.read<char>());
if (version != IW4X_TECHSET_VERSION) if (version != IW4X_TECHSET_VERSION)
{ {
Components::Logger::Error(Game::ERR_FATAL, "Reading vertex declaration '{}' failed, expected version is {}, but it was {}!", Components::Logger::Error(Game::ERR_FATAL, "Reading vertex declaration '{}' failed, expected version is {}, but it was {:d}!",
name, IW4X_TECHSET_VERSION, version.data()); name, IW4X_TECHSET_VERSION, version);
} }
Game::MaterialVertexDeclaration* asset = reader.readObject<Game::MaterialVertexDeclaration>(); Game::MaterialVertexDeclaration* asset = reader.readObject<Game::MaterialVertexDeclaration>();

View File

@ -1,14 +1,14 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "IMaterialVertexShader.hpp" #include "IMaterialVertexShader.hpp"
#define IW4X_TECHSET_VERSION "0" #define GFX_RENDERER_SHADER_SM3 0
namespace Assets namespace Assets
{ {
void IMaterialVertexShader::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) void IMaterialVertexShader::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
{ {
if (!header->data) this->loadNative(header, name, builder); // Check if there is a native one
if (!header->data) this->loadBinary(header, name, builder); // Check if we need to import a new one into the game if (!header->data) this->loadBinary(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
} }
void IMaterialVertexShader::loadNative(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* /*builder*/) void IMaterialVertexShader::loadNative(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* /*builder*/)
@ -18,36 +18,18 @@ namespace Assets
void IMaterialVertexShader::loadBinary(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) void IMaterialVertexShader::loadBinary(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
{ {
Components::FileSystem::File vsFile(Utils::String::VA("vs/%s.iw4xVS", name.data())); Components::FileSystem::File vsFile(std::format("vs/{}.cso", name));
if (!vsFile.exists()) return; if (!vsFile.exists()) return;
Utils::Stream::Reader reader(builder->getAllocator(), vsFile.getBuffer()); auto buff = vsFile.getBuffer();
auto programSize = buff.size() / 4;
Game::MaterialVertexShader* asset = builder->getAllocator()->allocate<Game::MaterialVertexShader>();
char* magic = reader.readArray<char>(8); asset->name = builder->getAllocator()->duplicateString(name);
if (std::memcmp(magic, "IW4xVERT", 8)) asset->prog.loadDef.loadForRenderer = GFX_RENDERER_SHADER_SM3;
{ asset->prog.loadDef.programSize = static_cast<unsigned short>(programSize);
Components::Logger::Error(Game::ERR_FATAL, "Reading vertex shader '{}' failed, header is invalid!", name); asset->prog.loadDef.program = builder->getAllocator()->allocateArray<unsigned int>(programSize);
} memcpy_s(asset->prog.loadDef.program, buff.size(), buff.data(), buff.size());
std::string version;
version.push_back(reader.read<char>());
if (version != IW4X_TECHSET_VERSION)
{
Components::Logger::Error(Game::ERR_FATAL, "Reading vertex shader '{}' failed, expected version is {}, but it was {}!",
name, IW4X_TECHSET_VERSION, version);
}
Game::MaterialVertexShader* asset = reader.readObject<Game::MaterialVertexShader>();
if (asset->name)
{
asset->name = reader.readCString();
}
if (asset->prog.loadDef.program)
{
asset->prog.loadDef.program = reader.readArray<unsigned int>(asset->prog.loadDef.programSize);
}
header->vertexShader = asset; header->vertexShader = asset;
} }

View File

@ -12,7 +12,7 @@ namespace Assets
if (menus.empty()) return; if (menus.empty()) return;
// Allocate new menu list // Allocate new menu list
Game::MenuList* newList = allocator->allocate<Game::MenuList>(); auto* newList = allocator->allocate<Game::MenuList>();
if (!newList) return; if (!newList) return;
newList->menus = allocator->allocateArray<Game::menuDef_t*>(menus.size()); newList->menus = allocator->allocateArray<Game::menuDef_t*>(menus.size());
@ -35,7 +35,7 @@ namespace Assets
} }
void IMenuList::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) void IMenuList::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
{ {
Game::MenuList *asset = header.menuList; auto* asset = header.menuList;
for (int i = 0; i < asset->menuCount; ++i) for (int i = 0; i < asset->menuCount; ++i)
{ {
@ -51,7 +51,7 @@ namespace Assets
Utils::Stream* buffer = builder->getBuffer(); Utils::Stream* buffer = builder->getBuffer();
Game::MenuList* asset = header.menuList; Game::MenuList* asset = header.menuList;
Game::MenuList* dest = buffer->dest<Game::MenuList>(); auto* dest = buffer->dest<Game::MenuList>();
buffer->save(asset); buffer->save(asset);
@ -67,7 +67,7 @@ namespace Assets
{ {
buffer->align(Utils::Stream::ALIGN_4); buffer->align(Utils::Stream::ALIGN_4);
Game::menuDef_t **destMenus = buffer->dest<Game::menuDef_t*>(); auto** destMenus = buffer->dest<Game::menuDef_t*>();
buffer->saveArray(asset->menus, asset->menuCount); buffer->saveArray(asset->menus, asset->menuCount);
for (int i = 0; i < asset->menuCount; ++i) for (int i = 0; i < asset->menuCount; ++i)

View File

@ -18,23 +18,24 @@ namespace Assets
return; return;
} }
const auto data = Utils::Compression::ZLib::Compress(rawFile.getBuffer());
asset->name = builder->getAllocator()->duplicateString(name); asset->name = builder->getAllocator()->duplicateString(name);
asset->len = static_cast<int>(rawFile.getBuffer().size());
if (data.size() < 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()->duplicateString(data); asset->buffer = builder->getAllocator()->allocateArray<char>(compressedData.size());
asset->compressedLen = static_cast<int>(data.size()); std::memcpy(const_cast<char*>(asset->buffer), compressedData.data(), compressedData.size());
asset->compressedLen = static_cast<int>(compressedData.size());
} }
else else
{ {
asset->buffer = builder->getAllocator()->duplicateString(rawFile.getBuffer()); 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; asset->compressedLen = 0;
} }
asset->len = static_cast<int>(rawFile.getBuffer().size());
header->rawfile = asset; header->rawfile = asset;
} }

View File

@ -6,7 +6,7 @@ namespace Assets
void IWeapon::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* /*builder*/) void IWeapon::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* /*builder*/)
{ {
// Try loading raw weapon // Try loading raw weapon
if (Components::FileSystem::File(Utils::String::VA("weapons/mp/%s", name.data())).exists()) if (Components::FileSystem::File(std::format("weapons/mp/{}", name)))
{ {
// let the function see temporary assets when calling DB_FindXAssetHeader during the loading function // let the function see temporary assets when calling DB_FindXAssetHeader during the loading function
// otherwise it fails to link things properly // otherwise it fails to link things properly
@ -280,7 +280,7 @@ namespace Assets
unsigned short* scriptStringTable = buffer->dest<unsigned short>(); unsigned short* scriptStringTable = buffer->dest<unsigned short>();
buffer->saveArray(def->notetrackSoundMapKeys, 16); buffer->saveArray(def->notetrackSoundMapKeys, 16);
for (int i = 0; i < 16; i++) { for (int i = 0; i < 16; i++) {
builder->mapScriptString(&scriptStringTable[i]); builder->mapScriptString(scriptStringTable[i]);
} }
Utils::Stream::ClearPointer(&dest->notetrackSoundMapKeys); Utils::Stream::ClearPointer(&dest->notetrackSoundMapKeys);
@ -292,7 +292,7 @@ namespace Assets
unsigned short* scriptStringTable = buffer->dest<unsigned short>(); unsigned short* scriptStringTable = buffer->dest<unsigned short>();
buffer->saveArray(def->notetrackSoundMapValues, 16); buffer->saveArray(def->notetrackSoundMapValues, 16);
for (int i = 0; i < 16; i++) { for (int i = 0; i < 16; i++) {
builder->mapScriptString(&scriptStringTable[i]); builder->mapScriptString(scriptStringTable[i]);
} }
Utils::Stream::ClearPointer(&dest->notetrackSoundMapValues); Utils::Stream::ClearPointer(&dest->notetrackSoundMapValues);
@ -304,7 +304,7 @@ namespace Assets
unsigned short* scriptStringTable = buffer->dest<unsigned short>(); unsigned short* scriptStringTable = buffer->dest<unsigned short>();
buffer->saveArray(def->notetrackRumbleMapKeys, 16); buffer->saveArray(def->notetrackRumbleMapKeys, 16);
for (int i = 0; i < 16; i++) { for (int i = 0; i < 16; i++) {
builder->mapScriptString(&scriptStringTable[i]); builder->mapScriptString(scriptStringTable[i]);
} }
Utils::Stream::ClearPointer(&dest->notetrackRumbleMapKeys); Utils::Stream::ClearPointer(&dest->notetrackRumbleMapKeys);
@ -316,7 +316,7 @@ namespace Assets
unsigned short* scriptStringTable = buffer->dest<unsigned short>(); unsigned short* scriptStringTable = buffer->dest<unsigned short>();
buffer->saveArray(def->notetrackRumbleMapValues, 16); buffer->saveArray(def->notetrackRumbleMapValues, 16);
for (int i = 0; i < 16; i++) { for (int i = 0; i < 16; i++) {
builder->mapScriptString(&scriptStringTable[i]); builder->mapScriptString(scriptStringTable[i]);
} }
Utils::Stream::ClearPointer(&dest->notetrackRumbleMapValues); Utils::Stream::ClearPointer(&dest->notetrackRumbleMapValues);
@ -725,7 +725,7 @@ namespace Assets
unsigned short* scriptStringTable = buffer->dest<unsigned short>(); unsigned short* scriptStringTable = buffer->dest<unsigned short>();
buffer->saveArray(asset->hideTags, 32); buffer->saveArray(asset->hideTags, 32);
for (int i = 0; i < 32; i++) { for (int i = 0; i < 32; i++) {
builder->mapScriptString(&scriptStringTable[i]); builder->mapScriptString(scriptStringTable[i]);
} }
Utils::Stream::ClearPointer(&dest->hideTags); Utils::Stream::ClearPointer(&dest->hideTags);

View File

@ -7,7 +7,7 @@ namespace Assets
{ {
void IXAnimParts::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) void IXAnimParts::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
{ {
Components::FileSystem::File animFile(Utils::String::VA("xanim/%s.iw4xAnim", name.data())); Components::FileSystem::File animFile(std::format("xanim/{}.iw4xAnim", name));
if (animFile.exists()) if (animFile.exists())
{ {
@ -39,7 +39,7 @@ namespace Assets
xanim->names = builder->getAllocator()->allocateArray<unsigned short>(xanim->boneCount[Game::PART_TYPE_ALL]); 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) for (int i = 0; i < xanim->boneCount[Game::PART_TYPE_ALL]; ++i)
{ {
xanim->names[i] = Game::SL_GetString(reader.readCString(), 0); xanim->names[i] = static_cast<std::uint16_t>(Game::SL_GetString(reader.readCString(), 0));
} }
} }
@ -49,7 +49,7 @@ namespace Assets
for (int i = 0; i < xanim->notifyCount; ++i) for (int i = 0; i < xanim->notifyCount; ++i)
{ {
xanim->notify[i].name = Game::SL_GetString(reader.readCString(), 0); xanim->notify[i].name = static_cast<std::uint16_t>(Game::SL_GetString(reader.readCString(), 0));
} }
} }
@ -264,7 +264,7 @@ namespace Assets
for (char i = 0; i < asset->boneCount[Game::PART_TYPE_ALL]; ++i) for (char i = 0; i < asset->boneCount[Game::PART_TYPE_ALL]; ++i)
{ {
builder->mapScriptString(&destTagnames[i]); builder->mapScriptString(destTagnames[i]);
} }
Utils::Stream::ClearPointer(&dest->names); Utils::Stream::ClearPointer(&dest->names);
@ -280,7 +280,7 @@ namespace Assets
for (char i = 0; i < asset->notifyCount; ++i) for (char i = 0; i < asset->notifyCount; ++i)
{ {
builder->mapScriptString(&destNotetracks[i].name); builder->mapScriptString(destNotetracks[i].name);
} }
Utils::Stream::ClearPointer(&dest->notify); Utils::Stream::ClearPointer(&dest->notify);

View File

@ -1,7 +1,7 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "IXModel.hpp" #include "IXModel.hpp"
#define IW4X_MODEL_VERSION 5 #define IW4X_MODEL_VERSION 8
namespace Assets namespace Assets
{ {
@ -9,32 +9,32 @@ namespace Assets
{ {
if (entry->nodes) if (entry->nodes)
{ {
entry->nodes = reader->readArray<Game::XSurfaceCollisionNode>(entry->nodeCount); entry->nodes = reader->readArrayOnce<Game::XSurfaceCollisionNode>(entry->nodeCount);
} }
if (entry->leafs) if (entry->leafs)
{ {
entry->leafs = reader->readArray<Game::XSurfaceCollisionLeaf>(entry->leafCount); entry->leafs = reader->readArrayOnce<Game::XSurfaceCollisionLeaf>(entry->leafCount);
} }
} }
void IXModel::loadXSurface(Game::XSurface* surf, Utils::Stream::Reader* reader, Components::ZoneBuilder::Zone* builder) void IXModel::loadXSurface(Game::XSurface* surf, Utils::Stream::Reader* reader, [[maybe_unused]] Components::ZoneBuilder::Zone* builder)
{ {
if (surf->vertInfo.vertsBlend) if (surf->vertInfo.vertsBlend)
{ {
surf->vertInfo.vertsBlend = reader->readArray<unsigned short>(surf->vertInfo.vertCount[0] + (surf->vertInfo.vertCount[1] * 3) + (surf->vertInfo.vertCount[2] * 5) + (surf->vertInfo.vertCount[3] * 7)); 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 // Access vertex block
if (surf->verts0) if (surf->verts0)
{ {
surf->verts0 = reader->readArray<Game::GfxPackedVertex>(surf->vertCount); surf->verts0 = reader->readArrayOnce<Game::GfxPackedVertex>(surf->vertCount);
} }
// Save_XRigidVertListArray // Save_XRigidVertListArray
if (surf->vertList) if (surf->vertList)
{ {
surf->vertList = reader->readArray<Game::XRigidVertList>(surf->vertListCount); surf->vertList = reader->readArrayOnce<Game::XRigidVertList>(surf->vertListCount);
for (unsigned int i = 0; i < surf->vertListCount; ++i) for (unsigned int i = 0; i < surf->vertListCount; ++i)
{ {
@ -51,17 +51,7 @@ namespace Assets
// Access index block // Access index block
if (surf->triIndices) if (surf->triIndices)
{ {
void* oldPtr = surf->triIndices; surf->triIndices = reader->readArrayOnce<unsigned short>(surf->triCount * 3);
surf->triIndices = reader->readArray<unsigned short>(surf->triCount * 3);
if (builder->getAllocator()->isPointerMapped(oldPtr))
{
surf->triIndices = builder->getAllocator()->getPointer<unsigned short>(oldPtr);
}
else
{
builder->getAllocator()->mapPointer(oldPtr, surf->triIndices);
}
} }
} }
@ -74,7 +64,7 @@ namespace Assets
if (asset->surfs) if (asset->surfs)
{ {
asset->surfs = reader->readArray<Game::XSurface>(asset->numsurfs); asset->surfs = reader->readArrayOnce<Game::XSurface>(asset->numsurfs);
for (int i = 0; i < asset->numsurfs; ++i) for (int i = 0; i < asset->numsurfs; ++i)
{ {
@ -85,7 +75,7 @@ namespace Assets
void IXModel::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) void IXModel::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
{ {
Components::FileSystem::File modelFile(Utils::String::VA("xmodel/%s.iw4xModel", name.data())); Components::FileSystem::File modelFile(std::format("xmodel/{}.iw4xModel", name));
if (!builder->isPrimaryAsset() && (!Components::ZoneBuilder::PreferDiskAssetsDvar.get<bool>() || !modelFile.exists())) if (!builder->isPrimaryAsset() && (!Components::ZoneBuilder::PreferDiskAssetsDvar.get<bool>() || !modelFile.exists()))
{ {
@ -110,11 +100,6 @@ namespace Assets
Components::Logger::Error(Game::ERR_FATAL, "Reading model '{}' failed, expected version is {}, but it was {}!", name, IW4X_MODEL_VERSION, version); Components::Logger::Error(Game::ERR_FATAL, "Reading model '{}' failed, expected version is {}, but it was {}!", name, IW4X_MODEL_VERSION, version);
} }
if (version == 4)
{
Components::Logger::Warning(Game::CON_CHANNEL_DONT_FILTER, "Model '{}' is in legacy format, please update it!\n", name);
}
Game::XModel* asset = reader.readObject<Game::XModel>(); Game::XModel* asset = reader.readObject<Game::XModel>();
if (asset->name) if (asset->name)
@ -128,33 +113,33 @@ namespace Assets
for (char i = 0; i < asset->numBones; ++i) for (char i = 0; i < asset->numBones; ++i)
{ {
asset->boneNames[i] = Game::SL_GetString(reader.readCString(), 0); asset->boneNames[i] = static_cast<std::uint16_t>(Game::SL_GetString(reader.readCString(), 0));
} }
} }
if (asset->parentList) if (asset->parentList)
{ {
asset->parentList = reader.readArray<char>(asset->numBones - asset->numRootBones); asset->parentList = reader.readArrayOnce<unsigned char>(asset->numBones - asset->numRootBones);
} }
if (asset->quats) if (asset->quats)
{ {
asset->quats = reader.readArray<short>((asset->numBones - asset->numRootBones) * 4); asset->quats = reader.readArrayOnce<short>((asset->numBones - asset->numRootBones) * 4);
} }
if (asset->trans) if (asset->trans)
{ {
asset->trans = reader.readArray<float>((asset->numBones - asset->numRootBones) * 3); asset->trans = reader.readArrayOnce<float>((asset->numBones - asset->numRootBones) * 3);
} }
if (asset->partClassification) if (asset->partClassification)
{ {
asset->partClassification = reader.readArray<char>(asset->numBones); asset->partClassification = reader.readArrayOnce<unsigned char>(asset->numBones);
} }
if (asset->baseMat) if (asset->baseMat)
{ {
asset->baseMat = reader.readArray<Game::DObjAnimMat>(asset->numBones); asset->baseMat = reader.readArrayOnce<Game::DObjAnimMat>(asset->numBones);
} }
if (asset->materialHandles) if (asset->materialHandles)
@ -172,7 +157,7 @@ namespace Assets
// Save_XModelLodInfoArray // Save_XModelLodInfoArray
{ {
for (int i = 0; i < 4; ++i) for (unsigned int i = 0; i < 4; ++i)
{ {
if (asset->lodInfo[i].modelSurfs) if (asset->lodInfo[i].modelSurfs)
{ {
@ -246,12 +231,6 @@ namespace Assets
} }
if (asset->physCollmap) if (asset->physCollmap)
{
if (version == 4)
{
asset->physCollmap = nullptr;
}
else
{ {
Game::PhysCollmap* collmap = reader.readObject<Game::PhysCollmap>(); Game::PhysCollmap* collmap = reader.readObject<Game::PhysCollmap>();
asset->physCollmap = collmap; asset->physCollmap = collmap;
@ -302,7 +281,6 @@ namespace Assets
} }
} }
} }
}
Components::AssetHandler::StoreTemporaryAsset(Game::XAssetType::ASSET_TYPE_PHYSCOLLMAP, { asset->physCollmap }); Components::AssetHandler::StoreTemporaryAsset(Game::XAssetType::ASSET_TYPE_PHYSCOLLMAP, { asset->physCollmap });
// asset->physCollmap = nullptr; // asset->physCollmap = nullptr;
@ -386,7 +364,7 @@ namespace Assets
for (char i = 0; i < asset->numBones; ++i) for (char i = 0; i < asset->numBones; ++i)
{ {
builder->mapScriptString(&destBoneNames[i]); builder->mapScriptString(destBoneNames[i]);
} }
Utils::Stream::ClearPointer(&dest->boneNames); Utils::Stream::ClearPointer(&dest->boneNames);
@ -394,39 +372,79 @@ namespace Assets
if (asset->parentList) if (asset->parentList)
{ {
if (builder->hasPointer(asset->parentList))
{
dest->parentList = builder->getPointer(asset->parentList);
}
else
{
builder->storePointer(asset->parentList);
buffer->save(asset->parentList, asset->numBones - asset->numRootBones); buffer->save(asset->parentList, asset->numBones - asset->numRootBones);
Utils::Stream::ClearPointer(&dest->parentList); Utils::Stream::ClearPointer(&dest->parentList);
} }
}
if (asset->quats) if (asset->quats)
{
if (builder->hasPointer(asset->quats))
{
dest->quats = builder->getPointer(asset->quats);
}
else
{ {
buffer->align(Utils::Stream::ALIGN_2); buffer->align(Utils::Stream::ALIGN_2);
builder->storePointer(asset->quats);
buffer->saveArray(asset->quats, (asset->numBones - asset->numRootBones) * 4); buffer->saveArray(asset->quats, (asset->numBones - asset->numRootBones) * 4);
Utils::Stream::ClearPointer(&dest->quats); Utils::Stream::ClearPointer(&dest->quats);
} }
}
if (asset->trans) if (asset->trans)
{
if (builder->hasPointer(asset->trans))
{
dest->trans = builder->getPointer(asset->trans);
}
else
{ {
buffer->align(Utils::Stream::ALIGN_4); buffer->align(Utils::Stream::ALIGN_4);
builder->storePointer(asset->trans);
buffer->saveArray(asset->trans, (asset->numBones - asset->numRootBones) * 3); buffer->saveArray(asset->trans, (asset->numBones - asset->numRootBones) * 3);
Utils::Stream::ClearPointer(&dest->trans); Utils::Stream::ClearPointer(&dest->trans);
} }
}
if (asset->partClassification) if (asset->partClassification)
{ {
if (builder->hasPointer(asset->partClassification))
{
dest->partClassification = builder->getPointer(asset->partClassification);
}
else
{
builder->storePointer(asset->partClassification);
buffer->save(asset->partClassification, asset->numBones); buffer->save(asset->partClassification, asset->numBones);
Utils::Stream::ClearPointer(&dest->partClassification); Utils::Stream::ClearPointer(&dest->partClassification);
} }
}
if (asset->baseMat) if (asset->baseMat)
{ {
AssertSize(Game::DObjAnimMat, 32); AssertSize(Game::DObjAnimMat, 32);
if (builder->hasPointer(asset->baseMat))
{
dest->baseMat = builder->getPointer(asset->baseMat);
}
else
{
buffer->align(Utils::Stream::ALIGN_4); buffer->align(Utils::Stream::ALIGN_4);
builder->storePointer(asset->baseMat);
buffer->saveArray(asset->baseMat, asset->numBones); buffer->saveArray(asset->baseMat, asset->numBones);
Utils::Stream::ClearPointer(&dest->baseMat); Utils::Stream::ClearPointer(&dest->baseMat);
} }
}
if (asset->materialHandles) if (asset->materialHandles)
{ {
buffer->align(Utils::Stream::ALIGN_4); buffer->align(Utils::Stream::ALIGN_4);

View File

@ -37,10 +37,19 @@ namespace Assets
if (surf->vertInfo.vertsBlend) if (surf->vertInfo.vertsBlend)
{ {
if (builder->hasPointer(surf->vertInfo.vertsBlend))
{
destSurf->vertInfo.vertsBlend = builder->getPointer(surf->vertInfo.vertsBlend);
}
else
{
buffer->align(Utils::Stream::ALIGN_2); buffer->align(Utils::Stream::ALIGN_2);
builder->storePointer(surf->vertInfo.vertsBlend);
buffer->saveArray(surf->vertInfo.vertsBlend, surf->vertInfo.vertCount[0] + (surf->vertInfo.vertCount[1] * 3) + (surf->vertInfo.vertCount[2] * 5) + (surf->vertInfo.vertCount[3] * 7)); buffer->saveArray(surf->vertInfo.vertsBlend, surf->vertInfo.vertCount[0] + (surf->vertInfo.vertCount[1] * 3) + (surf->vertInfo.vertCount[2] * 5) + (surf->vertInfo.vertCount[3] * 7));
Utils::Stream::ClearPointer(&destSurf->vertInfo.vertsBlend); Utils::Stream::ClearPointer(&destSurf->vertInfo.vertsBlend);
} }
}
// Access vertex block // Access vertex block
buffer->pushBlock(Game::XFILE_BLOCK_VERTEX); buffer->pushBlock(Game::XFILE_BLOCK_VERTEX);
@ -48,10 +57,18 @@ namespace Assets
{ {
AssertSize(Game::GfxPackedVertex, 32); AssertSize(Game::GfxPackedVertex, 32);
if (builder->hasPointer(surf->verts0))
{
destSurf->verts0 = builder->getPointer(surf->verts0);
}
else
{
buffer->align(Utils::Stream::ALIGN_16); buffer->align(Utils::Stream::ALIGN_16);
builder->storePointer(surf->verts0);
buffer->saveArray(surf->verts0, surf->vertCount); buffer->saveArray(surf->verts0, surf->vertCount);
Utils::Stream::ClearPointer(&destSurf->verts0); Utils::Stream::ClearPointer(&destSurf->verts0);
} }
}
buffer->popBlock(); buffer->popBlock();
// Save_XRigidVertListArray // Save_XRigidVertListArray
@ -59,7 +76,14 @@ namespace Assets
{ {
AssertSize(Game::XRigidVertList, 12); AssertSize(Game::XRigidVertList, 12);
if (builder->hasPointer(surf->vertList))
{
destSurf->vertList = builder->getPointer(surf->vertList);
}
else
{
buffer->align(Utils::Stream::ALIGN_4); buffer->align(Utils::Stream::ALIGN_4);
builder->storePointer(surf->vertList);
Game::XRigidVertList* destCt = buffer->dest<Game::XRigidVertList>(); Game::XRigidVertList* destCt = buffer->dest<Game::XRigidVertList>();
buffer->saveArray(surf->vertList, surf->vertListCount); buffer->saveArray(surf->vertList, surf->vertListCount);
@ -71,14 +95,22 @@ namespace Assets
if (rigidVertList->collisionTree) if (rigidVertList->collisionTree)
{ {
if (builder->hasPointer(rigidVertList->collisionTree))
{
destRigidVertList->collisionTree = builder->getPointer(rigidVertList->collisionTree);
}
else {
buffer->align(Utils::Stream::ALIGN_4); buffer->align(Utils::Stream::ALIGN_4);
builder->storePointer(rigidVertList->collisionTree);
this->saveXSurfaceCollisionTree(rigidVertList->collisionTree, builder); this->saveXSurfaceCollisionTree(rigidVertList->collisionTree, builder);
Utils::Stream::ClearPointer(&destRigidVertList->collisionTree); Utils::Stream::ClearPointer(&destRigidVertList->collisionTree);
} }
} }
}
Utils::Stream::ClearPointer(&destSurf->vertList); Utils::Stream::ClearPointer(&destSurf->vertList);
} }
}
// Access index block // Access index block
buffer->pushBlock(Game::XFILE_BLOCK_INDEX); buffer->pushBlock(Game::XFILE_BLOCK_INDEX);
@ -89,6 +121,7 @@ namespace Assets
else else
{ {
buffer->align(Utils::Stream::ALIGN_16); buffer->align(Utils::Stream::ALIGN_16);
builder->storePointer(surf->triIndices);
buffer->saveArray(surf->triIndices, surf->triCount * 3); buffer->saveArray(surf->triIndices, surf->triCount * 3);
Utils::Stream::ClearPointer(&destSurf->triIndices); Utils::Stream::ClearPointer(&destSurf->triIndices);
} }

View File

@ -573,7 +573,7 @@ namespace Assets
Utils::String::Replace(name, "maps/mp/", ""); Utils::String::Replace(name, "maps/mp/", "");
Utils::String::Replace(name, ".d3dbsp", ""); Utils::String::Replace(name, ".d3dbsp", "");
Components::FileSystem::File clipFile(Utils::String::VA("clipmap/%s.iw4xClipMap", name.data())); Components::FileSystem::File clipFile(std::format("clipmap/{}.iw4xClipMap", name));
if (!clipFile.exists()) if (!clipFile.exists())
{ {
return; return;
@ -882,7 +882,7 @@ namespace Assets
clipMap->smodelNodeCount = reader.read<unsigned short>(); clipMap->smodelNodeCount = reader.read<unsigned short>();
clipMap->smodelNodes = reader.readArray<Game::SModelAabbNode>(clipMap->smodelNodeCount); clipMap->smodelNodes = reader.readArray<Game::SModelAabbNode>(clipMap->smodelNodeCount);
clipMap->mapEnts = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_MAP_ENTS, Utils::String::VA("maps/mp/%s.d3dbsp", name.data()), builder).mapEnts; clipMap->mapEnts = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_MAP_ENTS, std::format("maps/mp/{}.d3dbsp", name), builder).mapEnts;
// add triggers to mapEnts // add triggers to mapEnts
if (version >= 2) { if (version >= 2) {

View File

@ -9,9 +9,9 @@ namespace Assets
void ImenuDef_t::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* /*builder*/) void ImenuDef_t::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* /*builder*/)
{ {
// load from disk // load from disk
auto menus = Components::Menus::LoadMenu(Utils::String::VA("ui_mp/%s.menu", name.data())); auto menus = Components::Menus::LoadMenu(std::format("ui_mp/{}.menu", name));
if (menus.size() == 0) return; if (menus.empty()) return;
if (menus.size() > 1) Components::Logger::Print("Menu '{}' on disk has more than one menudef in it. Only saving the first one\n", name); if (menus.size() > 1) Components::Logger::Print("Menu '{}' on disk has more than one menudef in it. Only saving the first one\n", name);
header->menu = menus[0].second; header->menu = menus[0].second;
@ -20,7 +20,7 @@ namespace Assets
void ImenuDef_t::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) void ImenuDef_t::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
{ {
Game::menuDef_t *asset = header.menu; auto* asset = header.menu;
if (asset->window.background) if (asset->window.background)
{ {
@ -59,14 +59,14 @@ namespace Assets
buffer->align(Utils::Stream::ALIGN_4); buffer->align(Utils::Stream::ALIGN_4);
Game::ExpressionSupportingData *dest = buffer->dest<Game::ExpressionSupportingData>(); auto* dest = buffer->dest<Game::ExpressionSupportingData>();
buffer->save(asset); buffer->save(asset);
if (asset->uifunctions.functions) if (asset->uifunctions.functions)
{ {
buffer->align(Utils::Stream::ALIGN_4); buffer->align(Utils::Stream::ALIGN_4);
Game::Statement_s **destStatement = buffer->dest<Game::Statement_s*>(); auto** destStatement = buffer->dest<Game::Statement_s*>();
buffer->saveArray(asset->uifunctions.functions, asset->uifunctions.totalFunctions); buffer->saveArray(asset->uifunctions.functions, asset->uifunctions.totalFunctions);
for (int i = 0; i < asset->uifunctions.totalFunctions; ++i) for (int i = 0; i < asset->uifunctions.totalFunctions; ++i)
@ -87,17 +87,17 @@ namespace Assets
{ {
buffer->align(Utils::Stream::ALIGN_4); buffer->align(Utils::Stream::ALIGN_4);
Game::StaticDvar **destStaticDvars = buffer->dest<Game::StaticDvar*>(); auto** destStaticDvars = buffer->dest<Game::StaticDvar*>();
buffer->saveArray(asset->staticDvarList.staticDvars, asset->staticDvarList.numStaticDvars); buffer->saveArray(asset->staticDvarList.staticDvars, asset->staticDvarList.numStaticDvars);
for (int i = 0; i < asset->staticDvarList.numStaticDvars; ++i) for (auto i = 0; i < asset->staticDvarList.numStaticDvars; ++i)
{ {
if (asset->staticDvarList.staticDvars[i]) if (asset->staticDvarList.staticDvars[i])
{ {
Utils::Stream::ClearPointer(&destStaticDvars[i]); Utils::Stream::ClearPointer(&destStaticDvars[i]);
buffer->align(Utils::Stream::ALIGN_4); buffer->align(Utils::Stream::ALIGN_4);
Game::StaticDvar *destStaticDvar = buffer->dest<Game::StaticDvar>(); auto* destStaticDvar = buffer->dest<Game::StaticDvar>();
buffer->save(asset->staticDvarList.staticDvars[i]); buffer->save(asset->staticDvarList.staticDvars[i]);
if (asset->staticDvarList.staticDvars[i]->dvarName) if (asset->staticDvarList.staticDvars[i]->dvarName)
@ -115,7 +115,7 @@ namespace Assets
{ {
buffer->align(Utils::Stream::ALIGN_4); buffer->align(Utils::Stream::ALIGN_4);
const char **destuiStrings = buffer->dest<const char*>(); const auto** destUIStrings = buffer->dest<const char*>();
buffer->saveArray(asset->uiStrings.strings, asset->uiStrings.totalStrings); buffer->saveArray(asset->uiStrings.strings, asset->uiStrings.totalStrings);
for (int i = 0; i < asset->uiStrings.totalStrings; ++i) for (int i = 0; i < asset->uiStrings.totalStrings; ++i)
@ -123,7 +123,7 @@ namespace Assets
if (asset->uiStrings.strings[i]) if (asset->uiStrings.strings[i])
{ {
buffer->saveString(asset->uiStrings.strings[i]); buffer->saveString(asset->uiStrings.strings[i]);
Utils::Stream::ClearPointer(&destuiStrings[i]); Utils::Stream::ClearPointer(&destUIStrings[i]);
} }
} }
} }
@ -143,7 +143,7 @@ namespace Assets
#endif #endif
// Write header data // Write header data
Game::Statement_s *dest = buffer->dest<Game::Statement_s>(); auto* dest = buffer->dest<Game::Statement_s>();
buffer->save(asset); buffer->save(asset);
// Write statement entries // Write statement entries
@ -155,7 +155,7 @@ namespace Assets
buffer->align(Utils::Stream::ALIGN_4); buffer->align(Utils::Stream::ALIGN_4);
// Write entries // Write entries
Game::expressionEntry *destEntries = buffer->dest<Game::expressionEntry>(); auto* destEntries = buffer->dest<Game::expressionEntry>();
buffer->save(asset->entries, sizeof(Game::expressionEntry), asset->numEntries); buffer->save(asset->entries, sizeof(Game::expressionEntry), asset->numEntries);
// Loop through entries // Loop through entries
@ -222,7 +222,7 @@ namespace Assets
#endif #endif
// Write header data // Write header data
Game::MenuEventHandlerSet *destset = buffer->dest<Game::MenuEventHandlerSet>(); auto* destset = buffer->dest<Game::MenuEventHandlerSet>();
buffer->save(asset); buffer->save(asset);
// Event handlers // Event handlers
@ -234,7 +234,7 @@ namespace Assets
buffer->save(asset->eventHandlers, sizeof(Game::MenuEventHandler*), asset->eventHandlerCount); buffer->save(asset->eventHandlers, sizeof(Game::MenuEventHandler*), asset->eventHandlerCount);
// Loop through eventHandlers // Loop through eventHandlers
for (int i = 0; i < asset->eventHandlerCount; ++i) for (auto i = 0; i < asset->eventHandlerCount; ++i)
{ {
if (asset->eventHandlers[i]) if (asset->eventHandlers[i])
{ {
@ -244,7 +244,7 @@ namespace Assets
#endif #endif
// Write menu event handler // Write menu event handler
Game::MenuEventHandler *dest = buffer->dest<Game::MenuEventHandler>(); auto* dest = buffer->dest<Game::MenuEventHandler>();
buffer->save(asset->eventHandlers[i]); buffer->save(asset->eventHandlers[i]);
// Write additional data based on type // Write additional data based on type
@ -264,7 +264,7 @@ namespace Assets
if (asset->eventHandlers[i]->eventData.conditionalScript) if (asset->eventHandlers[i]->eventData.conditionalScript)
{ {
buffer->align(Utils::Stream::ALIGN_4); buffer->align(Utils::Stream::ALIGN_4);
Game::ConditionalScript *destConditionalScript = buffer->dest<Game::ConditionalScript>(); auto* destConditionalScript = buffer->dest<Game::ConditionalScript>();
buffer->save(asset->eventHandlers[i]->eventData.conditionalScript); buffer->save(asset->eventHandlers[i]->eventData.conditionalScript);
// eventExpression // eventExpression
@ -307,7 +307,7 @@ namespace Assets
buffer->align(Utils::Stream::ALIGN_4); buffer->align(Utils::Stream::ALIGN_4);
// header data // header data
Game::SetLocalVarData *destLocalVarData = buffer->dest<Game::SetLocalVarData>(); auto* destLocalVarData = buffer->dest<Game::SetLocalVarData>();
buffer->save(asset->eventHandlers[i]->eventData.setLocalVarData); buffer->save(asset->eventHandlers[i]->eventData.setLocalVarData);
// localVarName // localVarName
@ -354,7 +354,7 @@ namespace Assets
while (asset) while (asset)
{ {
// Write header // Write header
Game::ItemKeyHandler* dest = buffer->dest<Game::ItemKeyHandler>(); auto* dest = buffer->dest<Game::ItemKeyHandler>();
buffer->save(asset); buffer->save(asset);
// MenuEventHandlerSet // MenuEventHandlerSet
@ -367,7 +367,7 @@ namespace Assets
if (asset->next) if (asset->next)
{ {
// align every indice, besides the first one? // align every index, besides the first one?
buffer->align(Utils::Stream::ALIGN_4); buffer->align(Utils::Stream::ALIGN_4);
} }
@ -379,20 +379,20 @@ namespace Assets
#endif #endif
} }
#define EVENTHANDLERSET(__indice) \ #define EVENTHANDLERSET(__index) \
if (asset->__indice) \ if (asset->__index) \
{ \ { \
buffer->align(Utils::Stream::ALIGN_4); \ buffer->align(Utils::Stream::ALIGN_4); \
this->save_MenuEventHandlerSet(asset->__indice, builder); \ this->save_MenuEventHandlerSet(asset->__index, builder); \
Utils::Stream::ClearPointer(&dest->__indice); \ Utils::Stream::ClearPointer(&dest->__index); \
} }
#define STATEMENT(__indice) \ #define STATEMENT(__index) \
if (asset->__indice) \ if (asset->__index) \
{ \ { \
buffer->align(Utils::Stream::ALIGN_4); \ buffer->align(Utils::Stream::ALIGN_4); \
this->save_Statement_s(asset->__indice, builder); \ this->save_Statement_s(asset->__index, builder); \
Utils::Stream::ClearPointer(&dest->__indice); \ Utils::Stream::ClearPointer(&dest->__index); \
} }
void ImenuDef_t::save_itemDefData_t(Game::itemDefData_t* asset, int type, Game::itemDef_s* dest, Components::ZoneBuilder::Zone* builder) void ImenuDef_t::save_itemDefData_t(Game::itemDefData_t* asset, int type, Game::itemDef_s* dest, Components::ZoneBuilder::Zone* builder)
@ -412,7 +412,7 @@ namespace Assets
if (type == 6) if (type == 6)
{ {
buffer->align(Utils::Stream::ALIGN_4); buffer->align(Utils::Stream::ALIGN_4);
Game::listBoxDef_s* destlb = buffer->dest<Game::listBoxDef_s>(); auto* destlb = buffer->dest<Game::listBoxDef_s>();
buffer->save(asset->listBox); buffer->save(asset->listBox);
if (asset->listBox->onDoubleClick) if (asset->listBox->onDoubleClick)
@ -427,17 +427,7 @@ namespace Assets
} }
} }
// HexRays spaghetti // HexRays spaghetti
else if (type != 4 else if (type != 4 && type != 9 && type != 16 && type != 18 && type != 11 && type != 14 && type != 10 && type != 17 && type != 22 && type != 23 && type != 0)
&& type != 9
&& type != 16
&& type != 18
&& type != 11
&& type != 14
&& type != 10
&& type != 17
&& type != 22
&& type != 23
&& type != 0)
{ {
switch (type) switch (type)
{ {
@ -457,7 +447,7 @@ namespace Assets
break; break;
case 12: case 12:
buffer->align(Utils::Stream::ALIGN_4); buffer->align(Utils::Stream::ALIGN_4);
Game::multiDef_s* destdef = buffer->dest<Game::multiDef_s>(); auto* destdef = buffer->dest<Game::multiDef_s>();
buffer->save(asset->multi); buffer->save(asset->multi);
for (int i = 0; i < 32; ++i) for (int i = 0; i < 32; ++i)
@ -497,10 +487,10 @@ namespace Assets
void ImenuDef_t::save_itemDef_s(Game::itemDef_s *asset, Components::ZoneBuilder::Zone* builder) void ImenuDef_t::save_itemDef_s(Game::itemDef_s *asset, Components::ZoneBuilder::Zone* builder)
{ {
AssertSize(Game::itemDef_s, 380); AssertSize(Game::itemDef_s, 0x17C);
Utils::Stream* buffer = builder->getBuffer(); Utils::Stream* buffer = builder->getBuffer();
Game::itemDef_s* dest = buffer->dest<Game::itemDef_s>(); auto* dest = buffer->dest<Game::itemDef_s>();
#ifdef WRITE_LOGS #ifdef WRITE_LOGS
if (asset->window.name) if (asset->window.name)
@ -587,7 +577,7 @@ namespace Assets
buffer->enterStruct("floatExpressions"); buffer->enterStruct("floatExpressions");
#endif #endif
Game::ItemFloatExpression* destExp = buffer->dest<Game::ItemFloatExpression>(); auto* destExp = buffer->dest<Game::ItemFloatExpression>();
buffer->saveArray(asset->floatExpressions, asset->floatExpressionCount); buffer->saveArray(asset->floatExpressions, asset->floatExpressionCount);
for (int i = 0; i < asset->floatExpressionCount; ++i) for (int i = 0; i < asset->floatExpressionCount; ++i)
@ -618,14 +608,15 @@ namespace Assets
void ImenuDef_t::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) void ImenuDef_t::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
{ {
AssertSize(Game::menuDef_t, 400); AssertSize(Game::menuDef_t, 400);
AssertSize(Game::windowDef_t, 0xA4);
#ifdef WRITE_LOGS #ifdef WRITE_LOGS
buffer->enterStruct("ImenuDef_t"); buffer->enterStruct("ImenuDef_t");
#endif #endif
Utils::Stream* buffer = builder->getBuffer(); Utils::Stream* buffer = builder->getBuffer();
Game::menuDef_t* asset = header.menu; auto* asset = header.menu;
Game::menuDef_t* dest = buffer->dest<Game::menuDef_t>(); auto* dest = buffer->dest<Game::menuDef_t>();
buffer->save(asset); buffer->save(asset);
buffer->pushBlock(Game::XFILE_BLOCK_VIRTUAL); buffer->pushBlock(Game::XFILE_BLOCK_VIRTUAL);

View File

@ -5,7 +5,7 @@ namespace Assets
{ {
void Isnd_alias_list_t::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) void Isnd_alias_list_t::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
{ {
Components::FileSystem::File aliasFile(Utils::String::VA("sounds/%s.json", name.data())); Components::FileSystem::File aliasFile(std::format("sounds/{}.json", name));
if (!aliasFile.exists()) if (!aliasFile.exists())
{ {
header->sound = Components::AssetHandler::FindOriginalAsset(this->getType(), name.data()).sound; header->sound = Components::AssetHandler::FindOriginalAsset(this->getType(), name.data()).sound;

View File

@ -1,4 +1,5 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "Bans.hpp"
namespace Components namespace Components
{ {

View File

@ -1,4 +1,5 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "Bans.hpp"
namespace Components namespace Components
{ {

View File

@ -1,16 +1,20 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "Bots.hpp"
#include "GSC/Script.hpp" #include "GSC/Script.hpp"
namespace Components namespace Components
{ {
std::vector<std::string> Bots::BotNames; std::vector<Bots::botData> Bots::BotNames;
Dvar::Var Bots::SVRandomBotNames;
struct BotMovementInfo struct BotMovementInfo
{ {
int buttons; // Actions std::int32_t buttons; // Actions
int8_t forward; std::int8_t forward;
int8_t right; std::int8_t right;
uint16_t weapon; std::uint16_t weapon;
bool active; bool active;
}; };
@ -19,67 +23,94 @@ namespace Components
struct BotAction struct BotAction
{ {
std::string action; std::string action;
int key; std::int32_t key;
}; };
static const BotAction BotActions[] = static const BotAction BotActions[] =
{ {
{ "gostand", Game::usercmdButtonBits::CMD_BUTTON_UP }, { "gostand", Game::CMD_BUTTON_UP },
{ "gocrouch", Game::usercmdButtonBits::CMD_BUTTON_CROUCH }, { "gocrouch", Game::CMD_BUTTON_CROUCH },
{ "goprone", Game::usercmdButtonBits::CMD_BUTTON_PRONE }, { "goprone", Game::CMD_BUTTON_PRONE },
{ "fire", Game::usercmdButtonBits::CMD_BUTTON_ATTACK }, { "fire", Game::CMD_BUTTON_ATTACK },
{ "melee", Game::usercmdButtonBits::CMD_BUTTON_MELEE }, { "melee", Game::CMD_BUTTON_MELEE },
{ "frag", Game::usercmdButtonBits::CMD_BUTTON_FRAG }, { "frag", Game::CMD_BUTTON_FRAG },
{ "smoke", Game::usercmdButtonBits::CMD_BUTTON_OFFHAND_SECONDARY }, { "smoke", Game::CMD_BUTTON_OFFHAND_SECONDARY },
{ "reload", Game::usercmdButtonBits::CMD_BUTTON_RELOAD }, { "reload", Game::CMD_BUTTON_RELOAD },
{ "sprint", Game::usercmdButtonBits::CMD_BUTTON_SPRINT }, { "sprint", Game::CMD_BUTTON_SPRINT },
{ "leanleft", Game::usercmdButtonBits::CMD_BUTTON_LEAN_LEFT }, { "leanleft", Game::CMD_BUTTON_LEAN_LEFT },
{ "leanright", Game::usercmdButtonBits::CMD_BUTTON_LEAN_RIGHT }, { "leanright", Game::CMD_BUTTON_LEAN_RIGHT },
{ "ads", Game::usercmdButtonBits::CMD_BUTTON_ADS }, { "ads", Game::CMD_BUTTON_ADS },
{ "holdbreath", Game::usercmdButtonBits::CMD_BUTTON_BREATH }, { "holdbreath", Game::CMD_BUTTON_BREATH },
{ "usereload", Game::usercmdButtonBits::CMD_BUTTON_USE_RELOAD }, { "usereload", Game::CMD_BUTTON_USE_RELOAD },
{ "activate", Game::usercmdButtonBits::CMD_BUTTON_ACTIVATE }, { "activate", Game::CMD_BUTTON_ACTIVATE },
}; };
int Bots::BuildConnectString(char* buffer, const char* connectString, int num, int, int protocol, int checksum, int statVer, int statStuff, int port) 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; static size_t botId = 0; // Loop over the BotNames vector
static bool loadedNames = false; // Load file only once static bool loadedNames = false; // Load file only once
const char* botName; const char* botName;
const char* clanName;
if (Bots::BotNames.empty() && !loadedNames) if (BotNames.empty() && !loadedNames)
{ {
FileSystem::File bots("bots.txt"); FileSystem::File bots("bots.txt");
loadedNames = true; loadedNames = true;
if (bots.exists()) if (bots.exists())
{ {
auto names = Utils::String::Split(bots.getBuffer(), '\n'); auto data = Utils::String::Split(bots.getBuffer(), '\n');
for (auto& name : names) if (SVRandomBotNames.get<bool>())
{ {
Utils::String::Replace(name, "\r", ""); std::random_device rd;
name = Utils::String::Trim(name); std::mt19937 gen(rd());
std::ranges::shuffle(data, gen);
}
if (!name.empty()) for (auto& entry : data)
{ {
Bots::BotNames.push_back(name); // 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));
} }
} }
} }
} }
if (!Bots::BotNames.empty()) if (!BotNames.empty())
{ {
botId %= Bots::BotNames.size(); botId %= BotNames.size();
botName = Bots::BotNames[botId++].data(); const auto index = botId++;
botName = BotNames[index].first.data();
clanName = BotNames[index].second.data();
} }
else else
{ {
botName = Utils::String::VA("bot%d", ++botId); botName = Utils::String::VA("bot%d", ++botId);
clanName = "BOT";
} }
return _snprintf_s(buffer, 0x400, _TRUNCATE, connectString, num, botName, protocol, checksum, statVer, statStuff, port); return _snprintf_s(buffer, 0x400, _TRUNCATE, connectString, num, botName, clanName, protocol, checksum, statVer, statStuff, port);
} }
void Bots::Spawn(unsigned int count) void Bots::Spawn(unsigned int count)
@ -96,13 +127,13 @@ namespace Components
{ {
Game::Scr_AddString("autoassign"); Game::Scr_AddString("autoassign");
Game::Scr_AddString("team_marinesopfor"); Game::Scr_AddString("team_marinesopfor");
Game::Scr_Notify(ent, Game::SL_GetString("menuresponse", 0), 2); Game::Scr_Notify(ent, static_cast<std::uint16_t>(Game::SL_GetString("menuresponse", 0)), 2);
Scheduler::Once([ent] Scheduler::Once([ent]
{ {
Game::Scr_AddString(Utils::String::VA("class%u", Utils::Cryptography::Rand::GenerateInt() % 5u)); Game::Scr_AddString(Utils::String::VA("class%u", Utils::Cryptography::Rand::GenerateInt() % 5u));
Game::Scr_AddString("changeclass"); Game::Scr_AddString("changeclass");
Game::Scr_Notify(ent, Game::SL_GetString("menuresponse", 0), 2); Game::Scr_Notify(ent, static_cast<std::uint16_t>(Game::SL_GetString("menuresponse", 0)), 2);
}, Scheduler::Pipeline::SERVER, 1s); }, Scheduler::Pipeline::SERVER, 1s);
}, Scheduler::Pipeline::SERVER, 1s); }, Scheduler::Pipeline::SERVER, 1s);
@ -111,16 +142,21 @@ namespace Components
} }
} }
void Bots::GScr_isTestClient(Game::scr_entref_t entref) void Bots::GScr_isTestClient(const Game::scr_entref_t entref)
{ {
const auto* ent = Game::GetPlayerEntity(entref); const auto* ent = Game::GetEntity(entref);
if (!ent->client)
{
Game::Scr_Error("isTestClient: entity must be a player entity");
return;
}
Game::Scr_AddBool(Game::SV_IsTestClient(ent->s.number) != 0); Game::Scr_AddBool(Game::SV_IsTestClient(ent->s.number) != 0);
} }
void Bots::AddMethods() void Bots::AddMethods()
{ {
Script::AddMethod("IsBot", Bots::GScr_isTestClient); // Usage: self IsBot(); Script::AddMethMultiple(GScr_isTestClient, false, {"IsTestClient", "IsBot"}); // Usage: self IsTestClient();
Script::AddMethod("IsTestClient", Bots::GScr_isTestClient); // Usage: self IsTestClient();
Script::AddMethod("BotStop", [](Game::scr_entref_t entref) // Usage: <bot> BotStop(); Script::AddMethod("BotStop", [](Game::scr_entref_t entref) // Usage: <bot> BotStop();
{ {
@ -234,7 +270,8 @@ namespace Components
return; return;
} }
Game::usercmd_s userCmd = {0}; Game::usercmd_s userCmd;
ZeroMemory(&userCmd, sizeof(Game::usercmd_s));
userCmd.serverTime = *Game::svs_time; userCmd.serverTime = *Game::svs_time;
@ -254,7 +291,7 @@ namespace Components
pushad pushad
push edi push edi
call Bots::BotAiAction call BotAiAction
add esp, 4 add esp, 4
popad popad
@ -278,7 +315,7 @@ namespace Components
push [esp + 0x20 + 0x8] push [esp + 0x20 + 0x8]
push [esp + 0x20 + 0x8] push [esp + 0x20 + 0x8]
call Bots::G_SelectWeaponIndex call G_SelectWeaponIndex
add esp, 0x8 add esp, 0x8
popad popad
@ -298,15 +335,17 @@ namespace Components
AssertOffset(Game::client_t, ping, 0x212C8); AssertOffset(Game::client_t, ping, 0x212C8);
// Replace connect string // Replace connect string
Utils::Hook::Set<const char*>(0x48ADA6, "connect bot%d \"\\cg_predictItems\\1\\cl_anonymous\\0\\color\\4\\head\\default\\model\\multi\\snaps\\20\\rate\\5000\\name\\%s\\protocol\\%d\\checksum\\%d\\statver\\%d %u\\qport\\%d\""); Utils::Hook::Set<const char*>(0x48ADA6, "connect bot%d \"\\cg_predictItems\\1\\cl_anonymous\\0\\color\\4\\head\\default\\model\\multi\\snaps\\20\\rate\\5000\\name\\%s\\clanAbbrev\\%s\\protocol\\%d\\checksum\\%d\\statver\\%d %u\\qport\\%d\"");
// Intercept sprintf for the connect string // Intercept sprintf for the connect string
Utils::Hook(0x48ADAB, Bots::BuildConnectString, HOOK_CALL).install()->quick(); Utils::Hook(0x48ADAB, BuildConnectString, HOOK_CALL).install()->quick();
Utils::Hook(0x627021, Bots::SV_BotUserMove_Hk, HOOK_CALL).install()->quick(); Utils::Hook(0x627021, SV_BotUserMove_Hk, HOOK_CALL).install()->quick();
Utils::Hook(0x627241, Bots::SV_BotUserMove_Hk, HOOK_CALL).install()->quick(); Utils::Hook(0x627241, SV_BotUserMove_Hk, HOOK_CALL).install()->quick();
Utils::Hook(0x441B80, Bots::G_SelectWeaponIndex_Hk, HOOK_JUMP).install()->quick(); Utils::Hook(0x441B80, G_SelectWeaponIndex_Hk, HOOK_JUMP).install()->quick();
SVRandomBotNames = Dvar::Register<bool>("sv_randomBotNames", false, Game::DVAR_NONE, "Randomize the bots' names");
// Reset BotMovementInfo.active when client is dropped // Reset BotMovementInfo.active when client is dropped
Events::OnClientDisconnect([](const int clientNum) Events::OnClientDisconnect([](const int clientNum)
@ -359,10 +398,10 @@ namespace Components
Toast::Show("cardicon_headshot", "^2Success", Utils::String::VA("Spawning %d %s...", count, (count == 1 ? "bot" : "bots")), 3000); 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::Debug("Spawning {} {}", count, (count == 1 ? "bot" : "bots"));
Bots::Spawn(count); Spawn(count);
}); });
Bots::AddMethods(); AddMethods();
// In case a loaded mod didn't call "BotStop" before the VM shutdown // In case a loaded mod didn't call "BotStop" before the VM shutdown
Events::OnVMShutdown([] Events::OnVMShutdown([]

View File

@ -8,7 +8,10 @@ namespace Components
Bots(); Bots();
private: private:
static std::vector<std::string> BotNames; using botData = std::pair< std::string, std::string>;
static std::vector<botData> BotNames;
static Dvar::Var SVRandomBotNames;
static int BuildConnectString(char* buffer, const char* connectString, int num, int, int protocol, int checksum, int statVer, int statStuff, int port); static int BuildConnectString(char* buffer, const char* connectString, int num, int, int protocol, int checksum, int statVer, int statStuff, int port);

View File

@ -1,4 +1,7 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "Branding.hpp"
#include <version.hpp>
namespace Components namespace Components
{ {
@ -125,5 +128,19 @@ namespace Components
// Hook CG_DrawFullScreenDebugOverlays so we may render the version when it's appropriate // Hook CG_DrawFullScreenDebugOverlays so we may render the version when it's appropriate
Utils::Hook(0x5AC975, Branding::CG_DrawVersion_Hk, HOOK_CALL).install()->quick(); Utils::Hook(0x5AC975, Branding::CG_DrawVersion_Hk, HOOK_CALL).install()->quick();
// Console title
if (ZoneBuilder::IsEnabled())
{
Utils::Hook::Set<const char*>(0x4289E8, "IW4x (" VERSION "): ZoneBuilder");
}
else if (Dedicated::IsEnabled())
{
Utils::Hook::Set<const char*>(0x4289E8, "IW4x (" VERSION "): Dedicated");
}
else
{
Utils::Hook::Set<const char*>(0x4289E8, "IW4x (" VERSION "): Console");
}
} }
} }

View File

@ -1,10 +1,19 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "Bullet.hpp"
namespace Components namespace Components
{ {
Dvar::Var Bullet::BGSurfacePenetration; Dvar::Var Bullet::BGSurfacePenetration;
Game::dvar_t* Bullet::BGBulletRange; Game::dvar_t* Bullet::BGBulletRange;
float Bullet::ContactPointSave[3];
float Bullet::VCSave[3];
float Bullet::CalcRicochetSave[3];
float Bullet::ColorYellow[] = {1.0f, 1.0f, 0.0f, 1.0f};
float Bullet::ColorBlue[] = {0.0f, 0.0f, 1.0f, 1.0f};
float Bullet::ColorOrange[] = {1.0f, 0.7f, 0.0f, 1.0f};
float Bullet::BG_GetSurfacePenetrationDepthStub(const Game::WeaponDef* weapDef, int surfaceType) float Bullet::BG_GetSurfacePenetrationDepthStub(const Game::WeaponDef* weapDef, int surfaceType)
{ {
assert(weapDef); assert(weapDef);
@ -47,6 +56,63 @@ namespace Components
*pHoldrand = static_cast<unsigned int>(std::rand()); *pHoldrand = static_cast<unsigned int>(std::rand());
} }
void Bullet::BulletRicochet_Save(const float* contactPoint)
{
std::memcpy(ContactPointSave, contactPoint, sizeof(float[3]));
}
__declspec(naked) void Bullet::BulletRicochet_Stub()
{
__asm
{
pushad
push [esp + 0x20 + 0xC]
call BulletRicochet_Save
add esp, 0x4
popad
// Game's code
sub esp, 0x4C
push ebp
mov ebp, dword ptr [esp + 0x60]
push 0x5D5B08
ret
}
}
void Bullet::_VectorMA_Stub(float* va, float scale, float* vb, float* vc)
{
vc[0] = va[0] + scale * vb[0];
vc[1] = va[1] + scale * vb[1];
vc[2] = va[2] + scale * vb[2];
std::memcpy(VCSave, vc, sizeof(float[3]));
}
void Bullet::CalcRicochet_Stub(const float* incoming, const float* normal, float* result)
{
Utils::Hook::Call<void(const float*, const float*, float*)>(0x5D59F0)(incoming, normal, result);
std::memcpy(CalcRicochetSave, result, sizeof(float[3]));
}
int Bullet::Bullet_Fire_Stub(Game::gentity_s* attacker, [[maybe_unused]] float spread, Game::weaponParms* wp, Game::gentity_s* weaponEnt, Game::PlayerHandIndex hand, int gameTime)
{
float tmp[3];
Game::G_DebugStar(ContactPointSave, ColorYellow);
tmp[0] = (CalcRicochetSave[0] * 100.0f) + VCSave[0];
tmp[1] = (CalcRicochetSave[1] * 100.0f) + VCSave[1];
tmp[2] = (CalcRicochetSave[2] * 100.0f) + VCSave[1];
Game::G_DebugLineWithDuration(VCSave, tmp, ColorOrange, 1, 100);
Game::G_DebugStar(tmp, ColorBlue);
// Set the spread to 0 when drawing
return Game::Bullet_Fire(attacker, 0.0f, wp, weaponEnt, hand, gameTime);
}
Bullet::Bullet() Bullet::Bullet()
{ {
BGSurfacePenetration = Dvar::Register<float>("bg_surfacePenetration", 0.0f, BGSurfacePenetration = Dvar::Register<float>("bg_surfacePenetration", 0.0f,
@ -58,5 +124,20 @@ namespace Components
Utils::Hook(0x440340, Bullet_FireStub, HOOK_JUMP).install()->quick(); Utils::Hook(0x440340, Bullet_FireStub, HOOK_JUMP).install()->quick();
Utils::Hook(0x440368, BG_srand_Hk, HOOK_CALL).install()->quick(); Utils::Hook(0x440368, BG_srand_Hk, HOOK_CALL).install()->quick();
std::memset(ContactPointSave, 0, sizeof(float[3]));
std::memset(VCSave, 0, sizeof(float[3]));
std::memset(CalcRicochetSave, 0, sizeof(float[3]));
#ifdef DEBUG_RIOT_SHIELD
Utils::Hook(0x5D5B00, BulletRicochet_Stub, HOOK_JUMP).install()->quick();
Utils::Hook::Nop(0x5D5B00 + 5, 3);
Utils::Hook(0x5D5BBA, CalcRicochet_Stub, HOOK_CALL).install()->quick();
Utils::Hook(0x5D5BD7, _VectorMA_Stub, HOOK_CALL).install()->quick();
Utils::Hook(0x5D5C0B, Bullet_Fire_Stub, HOOK_CALL).install()->quick();
#endif
} }
} }

View File

@ -12,10 +12,27 @@ namespace Components
// Can't use Var class inside assembly stubs // Can't use Var class inside assembly stubs
static Game::dvar_t* BGBulletRange; static Game::dvar_t* BGBulletRange;
static float ContactPointSave[];
static float VCSave[];
static float CalcRicochetSave[];
static float ColorYellow[];
static float ColorBlue[];
static float ColorOrange[];
static float BG_GetSurfacePenetrationDepthStub(const Game::WeaponDef* weapDef, int surfaceType); static float BG_GetSurfacePenetrationDepthStub(const Game::WeaponDef* weapDef, int surfaceType);
static void Bullet_FireStub(); static void Bullet_FireStub();
static void BG_srand_Hk(unsigned int* pHoldrand); static void BG_srand_Hk(unsigned int* pHoldrand);
static void BulletRicochet_Save(const float* contactPoint);
static void BulletRicochet_Stub();
static void CalcRicochet_Stub(const float* incoming, const float* normal, float* result);
static void _VectorMA_Stub(float* va, float scale, float* vb, float* vc);
static int Bullet_Fire_Stub(Game::gentity_s* attacker, float spread, Game::weaponParms* wp, Game::gentity_s* weaponEnt, Game::PlayerHandIndex hand, int gameTime);
}; };
} }

View File

@ -1,8 +1,10 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "CardTitles.hpp"
#include "ServerCommands.hpp"
namespace Components namespace Components
{ {
std::string CardTitles::CustomTitles[18]; char CardTitles::CustomTitles[Game::MAX_CLIENTS][18];
Dvar::Var CardTitles::CustomTitle; Dvar::Var CardTitles::CustomTitle;
CClient* CardTitles::GetClientByIndex(std::uint32_t index) CClient* CardTitles::GetClientByIndex(std::uint32_t index)
@ -10,35 +12,34 @@ namespace Components
return &reinterpret_cast<CClient*>(0x8E77B0)[index]; return &reinterpret_cast<CClient*>(0x8E77B0)[index];
} }
std::int32_t CardTitles::GetPlayerCardClientInfo(std::int32_t lookupResult, Game::PlayerCardData* data) int CardTitles::GetPlayerCardClientInfo(int lookupResult, Game::PlayerCardData* data)
{ {
std::int32_t returnResult = lookupResult; auto result = lookupResult;
std::string username = Dvar::Var("name").get<std::string>(); const auto* username = Dvar::Var("name").get<const char*>();
if (std::strcmp(data->name, username) == 0)
if (data->name == username)
{ {
returnResult += 0xFE000000; result += 0xFE000000;
} }
else else
{ {
for (std::size_t clientNum = 0; clientNum < Game::MAX_CLIENTS; ++clientNum) for (std::size_t i = 0; i < Game::MAX_CLIENTS; ++i)
{ {
CClient* c = GetClientByIndex(clientNum); CClient* c = GetClientByIndex(i);
if (c != nullptr) if (c != nullptr)
{ {
if (!std::strcmp(data->name, c->Name)) if (!std::strcmp(data->name, c->Name))
{ {
// Since a 4 byte integer is overkill for a row num: We can use it to store the customprefix + clientNum and use a 2 byte integer for the row number // Since a 4 byte integer is overkill for a row num: We can use it to store the customprefix + clientNum and use a 2 byte integer for the row number
returnResult += 0xFF000000; result += 0xFF000000;
returnResult += clientNum * 0x10000; result += i * 0x10000;
break; break;
} }
} }
} }
} }
return returnResult; return result;
} }
void __declspec(naked) CardTitles::GetPlayerCardClientInfoStub() void __declspec(naked) CardTitles::GetPlayerCardClientInfoStub()
@ -71,7 +72,7 @@ namespace Components
std::uint8_t prefix = (request->tableRow >> (8 * 3)) & 0xFF; std::uint8_t prefix = (request->tableRow >> (8 * 3)) & 0xFF;
std::uint8_t data = (request->tableRow >> (8 * 2)) & 0xFF; std::uint8_t data = (request->tableRow >> (8 * 2)) & 0xFF;
if (data >= ARRAYSIZE(CardTitles::CustomTitles)) return nullptr; if (data >= Game::MAX_CLIENTS) return nullptr;
if (request->tablename == "mp/cardTitleTable.csv"s) if (request->tablename == "mp/cardTitleTable.csv"s)
{ {
@ -82,10 +83,10 @@ namespace Components
{ {
if (prefix == 0xFE) if (prefix == 0xFE)
{ {
if (!CardTitles::CustomTitle.get<std::string>().empty()) if (!CustomTitle.get<std::string>().empty())
{ {
// 0xFF in front of the title to skip localization. Or else it will wait for a couple of seconds for the asset of type localize // 0xFF in front of the title to skip localization. Or else it will wait for a couple of seconds for the asset of type localize
const char* title = Utils::String::VA("\x15%s", CardTitles::CustomTitle.get<const char*>()); const auto* title = Utils::String::VA("\x15%s", CustomTitle.get<const char*>());
// prepare return value // prepare return value
operand->internals.stringVal.string = title; operand->internals.stringVal.string = title;
@ -96,9 +97,9 @@ namespace Components
} }
else if (prefix == 0xFF) else if (prefix == 0xFF)
{ {
if (!CardTitles::CustomTitles[data].empty()) if (CustomTitles[data][0] != '\0')
{ {
const char* title = Utils::String::VA("\x15%s", CardTitles::CustomTitles[data].data()); const auto* title = Utils::String::VA("\x15%s", CustomTitles[data]);
// prepare return value // prepare return value
operand->internals.stringVal.string = title; operand->internals.stringVal.string = title;
@ -156,11 +157,11 @@ namespace Components
{ {
std::string list; std::string list;
for (std::size_t i = 0; i < Game::MAX_CLIENTS; i++) for (std::size_t i = 0; i < Game::MAX_CLIENTS; ++i)
{ {
char playerTitle[18]; char playerTitle[18]{};
if (Game::svs_clients[i].header.state >= Game::CS_CONNECTED) if (Game::svs_clients[i].userinfo[0] != '\0')
{ {
strncpy_s(playerTitle, Game::Info_ValueForKey(Game::svs_clients[i].userinfo, "customTitle"), _TRUNCATE); strncpy_s(playerTitle, Game::Info_ValueForKey(Game::svs_clients[i].userinfo, "customTitle"), _TRUNCATE);
} }
@ -169,10 +170,10 @@ namespace Components
playerTitle[0] = '\0'; playerTitle[0] = '\0';
} }
list.append(Utils::String::VA("\\%s\\%s", std::to_string(i).data(), playerTitle)); list.append(std::format("\\{}\\{}", std::to_string(i), playerTitle));
} }
const auto* command = Utils::String::VA("%c customTitles \"%s\"", 21, list.data()); const auto* command = Utils::String::Format("{:c} customTitles \"{}\"", 21, list);
Game::SV_GameSendServerCommand(-1, Game::SV_CMD_CAN_IGNORE, command); Game::SV_GameSendServerCommand(-1, Game::SV_CMD_CAN_IGNORE, command);
} }
@ -180,10 +181,17 @@ namespace Components
{ {
for (std::size_t i = 0; i < Game::MAX_CLIENTS; ++i) for (std::size_t i = 0; i < Game::MAX_CLIENTS; ++i)
{ {
const char* playerTitle = Game::Info_ValueForKey(msg, std::to_string(i).c_str()); const auto index = std::to_string(i);
const auto* playerTitle = Game::Info_ValueForKey(msg, index.data());
if (playerTitle) CardTitles::CustomTitles[i] = playerTitle; if (playerTitle[0] == '\0')
else CardTitles::CustomTitles[i].clear(); {
CustomTitles[i][0] = '\0';
}
else
{
Game::I_strncpyz(CustomTitles[i], playerTitle, sizeof(CustomTitles[0]) / sizeof(char));
}
} }
} }
@ -191,16 +199,18 @@ namespace Components
{ {
Scheduler::Once([] Scheduler::Once([]
{ {
CardTitles::CustomTitle = Dvar::Register<const char*>("customTitle", "", Game::DVAR_USERINFO | Game::DVAR_ARCHIVE, "Custom card title"); CustomTitle = Dvar::Register<const char*>("customTitle", "", Game::DVAR_USERINFO | Game::DVAR_ARCHIVE, "Custom card title");
}, Scheduler::Pipeline::MAIN); }, Scheduler::Pipeline::MAIN);
std::memset(&CustomTitles, 0, sizeof(char[Game::MAX_CLIENTS][18]));
ServerCommands::OnCommand(21, [](Command::Params* params) ServerCommands::OnCommand(21, [](Command::Params* params)
{ {
if (params->get(1) == "customTitles"s && !Dedicated::IsEnabled()) if (std::strcmp(params->get(1), "customTitles") == 0)
{ {
if (params->size() == 3) if (params->size() == 3)
{ {
CardTitles::ParseCustomTitles(params->get(2)); ParseCustomTitles(params->get(2));
return true; return true;
} }
} }
@ -209,10 +219,10 @@ namespace Components
}); });
Utils::Hook(0x62EB26, CardTitles::GetPlayerCardClientInfoStub).install()->quick(); Utils::Hook(0x62EB26, GetPlayerCardClientInfoStub).install()->quick();
// Table lookup stuff // Table lookup stuff
Utils::Hook(0x62DCC1, CardTitles::TableLookupByRowHookStub).install()->quick(); Utils::Hook(0x62DCC1, TableLookupByRowHookStub).install()->quick();
Utils::Hook::Nop(0x62DCC6, 1); Utils::Hook::Nop(0x62DCC6, 1);
} }
} }

View File

@ -44,19 +44,20 @@ namespace Components
public: public:
AssertOffset(Game::PlayerCardData, Game::PlayerCardData::name, 0x1C); AssertOffset(Game::PlayerCardData, Game::PlayerCardData::name, 0x1C);
static Dvar::Var CustomTitle;
static std::string CustomTitles[18];
static void SendCustomTitlesToClients(); static void SendCustomTitlesToClients();
static void ParseCustomTitles(const char* msg);
CardTitles(); CardTitles();
private: private:
static Dvar::Var CustomTitle;
static char CustomTitles[Game::MAX_CLIENTS][18];
static CClient* GetClientByIndex(std::uint32_t index); static CClient* GetClientByIndex(std::uint32_t index);
static std::int32_t GetPlayerCardClientInfo(std::int32_t lookupResult, Game::PlayerCardData* data); static int GetPlayerCardClientInfo(int lookupResult, Game::PlayerCardData* data);
static void GetPlayerCardClientInfoStub(); static void GetPlayerCardClientInfoStub();
static const char* TableLookupByRowHook(Game::Operand* operand, tablelookuprequest_s* request); static const char* TableLookupByRowHook(Game::Operand* operand, tablelookuprequest_s* request);
static void TableLookupByRowHookStub(); static void TableLookupByRowHookStub();
static void ParseCustomTitles(const char* msg);
}; };
} }

View File

@ -1,4 +1,5 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "Ceg.hpp"
namespace Components namespace Components
{ {

View File

@ -1,4 +1,6 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "Changelog.hpp"
#include "UIFeeder.hpp"
namespace Components namespace Components
{ {
@ -7,44 +9,44 @@ namespace Components
void Changelog::LoadChangelog() void Changelog::LoadChangelog()
{ {
//if (!Changelog::Lines.empty()) std::lock_guard _(Mutex);
// return; Lines.clear();
std::lock_guard<std::mutex> _(Changelog::Mutex); const auto data = Utils::Cache::GetFile("/develop/CHANGELOG.md");
Changelog::Lines.clear();
std::string data = Utils::Cache::GetFile("/develop/CHANGELOG.md");
if (data.empty()) if (data.empty())
{ {
data = "^1Unable to get changelog."; Lines.emplace_back("^1Unable to get changelog.");
return;
} }
Changelog::Lines = Utils::String::Split(data, '\n'); auto buffer = Utils::String::Split(data, '\n');
for (auto& line : buffer)
for (auto& line : Changelog::Lines)
{ {
Utils::String::Replace(line, "\r", ""); Utils::String::Replace(line, "\r", "");
} }
Lines = buffer;
} }
unsigned int Changelog::GetChangelogCount() unsigned int Changelog::GetChangelogCount()
{ {
return Changelog::Lines.size(); return Lines.size();
} }
// Omit column here // Omit column here
const char* Changelog::GetChangelogText(unsigned int item, int /*column*/) const char* Changelog::GetChangelogText(unsigned int item, [[maybe_unused]] int column)
{ {
std::lock_guard<std::mutex> _(Changelog::Mutex); std::lock_guard _(Mutex);
if (item < Changelog::Lines.size()) if (item < Lines.size())
{ {
return Utils::String::VA("%s", Changelog::Lines[item].data()); return Utils::String::Format("{}", Lines[item]);
} }
return ""; return "";
} }
void Changelog::SelectChangelog(unsigned int /*index*/) void Changelog::SelectChangelog([[maybe_unused]] unsigned int index)
{ {
// Don't do anything in here // Don't do anything in here
} }
@ -54,6 +56,6 @@ namespace Components
if (Dedicated::IsEnabled()) return; if (Dedicated::IsEnabled()) return;
// Changelog // Changelog
UIFeeder::Add(62.0f, Changelog::GetChangelogCount, Changelog::GetChangelogText, Changelog::SelectChangelog); UIFeeder::Add(62.0f, GetChangelogCount, GetChangelogText, SelectChangelog);
} }
} }

View File

@ -1,4 +1,8 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "Chat.hpp"
#include "PlayerName.hpp"
#include "Voice.hpp"
#include "GSC/Script.hpp" #include "GSC/Script.hpp"
namespace Components namespace Components
@ -73,7 +77,7 @@ namespace Components
Game::Scr_AddEntity(player); Game::Scr_AddEntity(player);
Game::Scr_AddString(text + msgIndex); Game::Scr_AddString(text + msgIndex);
Game::Scr_NotifyLevel(Game::SL_GetString("say", 0), 2); Game::Scr_NotifyLevel(static_cast<std::uint16_t>(Game::SL_GetString("say", 0)), 2);
return text; return text;
} }
@ -268,8 +272,7 @@ namespace Components
}); });
Logger::Print("{} was muted\n", client->name); Logger::Print("{} was muted\n", client->name);
Game::SV_GameSendServerCommand(client - Game::svs_clients, Game::SV_CMD_CAN_IGNORE, Game::SV_GameSendServerCommand(client - Game::svs_clients, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"You were muted\"", 0x65));
Utils::String::VA("%c \"You were muted\"", 0x65));
} }
void Chat::UnmuteClient(const Game::client_t* client) void Chat::UnmuteClient(const Game::client_t* client)
@ -277,8 +280,7 @@ namespace Components
UnmuteInternal(client->steamID); UnmuteInternal(client->steamID);
Logger::Print("{} was unmuted\n", client->name); Logger::Print("{} was unmuted\n", client->name);
Game::SV_GameSendServerCommand(client - Game::svs_clients, Game::SV_CMD_CAN_IGNORE, Game::SV_GameSendServerCommand(client - Game::svs_clients, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"You were unmuted\"", 0x65));
Utils::String::VA("%c \"You were unmuted\"", 0x65));
} }
void Chat::UnmuteInternal(const std::uint64_t id, bool everyone) void Chat::UnmuteInternal(const std::uint64_t id, bool everyone)
@ -369,12 +371,12 @@ namespace Components
if (!name.empty()) if (!name.empty())
{ {
Game::SV_GameSendServerCommand(-1, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"%s: %s\"", 0x68, name.data(), message.data())); Game::SV_GameSendServerCommand(-1, Game::SV_CMD_CAN_IGNORE, Utils::String::Format("{:c} \"{}: {}\"", 0x68, name, message));
Logger::Print(Game::CON_CHANNEL_SERVER, "{}: {}\n", name, message); Logger::Print(Game::CON_CHANNEL_SERVER, "{}: {}\n", name, message);
} }
else else
{ {
Game::SV_GameSendServerCommand(-1, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"Console: %s\"", 0x68, message.data())); Game::SV_GameSendServerCommand(-1, Game::SV_CMD_CAN_IGNORE, Utils::String::Format("{:c} \"Console: {}\"", 0x68, message));
Logger::Print(Game::CON_CHANNEL_SERVER, "Console: {}\n", message); Logger::Print(Game::CON_CHANNEL_SERVER, "Console: {}\n", message);
} }
}); });
@ -395,12 +397,12 @@ namespace Components
if (!name.empty()) if (!name.empty())
{ {
Game::SV_GameSendServerCommand(client, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"%s: %s\"", 0x68, name.data(), message.data())); Game::SV_GameSendServerCommand(client, Game::SV_CMD_CAN_IGNORE, Utils::String::Format("{:c} \"{}: {}\"", 0x68, name.data(), message));
Logger::Print(Game::CON_CHANNEL_SERVER, "{} -> {}: {}\n", name, client, message); Logger::Print(Game::CON_CHANNEL_SERVER, "{} -> {}: {}\n", name, client, message);
} }
else else
{ {
Game::SV_GameSendServerCommand(client, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"Console: %s\"", 104, message.data())); Game::SV_GameSendServerCommand(client, Game::SV_CMD_CAN_IGNORE, Utils::String::Format("{:c} \"Console: {}\"", 0x68, message));
Logger::Print(Game::CON_CHANNEL_SERVER, "Console -> {}: {}\n", client, message); Logger::Print(Game::CON_CHANNEL_SERVER, "Console -> {}: {}\n", client, message);
} }
}); });
@ -416,7 +418,7 @@ namespace Components
if (params->size() < 2) return; if (params->size() < 2) return;
auto message = params->join(1); auto message = params->join(1);
Game::SV_GameSendServerCommand(-1, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"%s\"", 0x68, message.data())); Game::SV_GameSendServerCommand(-1, Game::SV_CMD_CAN_IGNORE, Utils::String::Format("{:c} \"{}\"", 0x68, message));
Logger::Print(Game::CON_CHANNEL_SERVER, "Raw: {}\n", message); Logger::Print(Game::CON_CHANNEL_SERVER, "Raw: {}\n", message);
}); });
@ -432,7 +434,7 @@ namespace Components
const auto client = atoi(params->get(1)); const auto client = atoi(params->get(1));
std::string message = params->join(2); std::string message = params->join(2);
Game::SV_GameSendServerCommand(client, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"%s\"", 0x68, message.data())); Game::SV_GameSendServerCommand(client, Game::SV_CMD_CAN_IGNORE, Utils::String::Format("{:c} \"{}\"", 0x68, message));
Logger::Print(Game::CON_CHANNEL_SERVER, "Raw -> {}: {}\n", client, message); Logger::Print(Game::CON_CHANNEL_SERVER, "Raw -> {}: {}\n", client, message);
}); });

View File

@ -1,4 +1,7 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "ClanTags.hpp"
#include "PlayerName.hpp"
#include "ServerCommands.hpp"
namespace Components namespace Components
{ {
@ -28,15 +31,15 @@ namespace Components
list.append(std::format("\\{}\\{}", std::to_string(i), ClientState[i])); list.append(std::format("\\{}\\{}", std::to_string(i), ClientState[i]));
} }
const auto* command = Utils::String::VA("%c clanNames \"%s\"", 22, list.data()); Game::SV_GameSendServerCommand(-1, Game::SV_CMD_CAN_IGNORE, Utils::String::Format("{:c} clanNames \"{}\"", 22, list));
Game::SV_GameSendServerCommand(-1, Game::SV_CMD_CAN_IGNORE, command);
} }
void ClanTags::ParseClanTags(const char* infoString) void ClanTags::ParseClanTags(const char* infoString)
{ {
for (std::size_t i = 0; i < Game::MAX_CLIENTS; ++i) for (std::size_t i = 0; i < Game::MAX_CLIENTS; ++i)
{ {
const auto* clanTag = Game::Info_ValueForKey(infoString, std::to_string(i).data()); const auto index = std::to_string(i);
const auto* clanTag = Game::Info_ValueForKey(infoString, index.data());
if (clanTag[0] == '\0') if (clanTag[0] == '\0')
{ {
@ -70,9 +73,7 @@ namespace Components
void ClanTags::CL_SanitizeClanName() void ClanTags::CL_SanitizeClanName()
{ {
char saneNameBuf[5]; char saneNameBuf[5]{};
std::memset(saneNameBuf, 0, sizeof(saneNameBuf));
auto* saneName = saneNameBuf; auto* saneName = saneNameBuf;
assert(ClanName); assert(ClanName);

View File

@ -1,6 +1,10 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "ClientCommand.hpp"
#include "GSC/Script.hpp" #include "GSC/Script.hpp"
using namespace Utils::String;
namespace Components namespace Components
{ {
std::unordered_map<std::string, std::function<void(Game::gentity_s*, const Command::ServerParams*)>> ClientCommand::HandlersSV; std::unordered_map<std::string, std::function<void(Game::gentity_s*, const Command::ServerParams*)>> ClientCommand::HandlersSV;
@ -12,14 +16,14 @@ namespace Components
if (!(*Game::g_cheats)->current.enabled) if (!(*Game::g_cheats)->current.enabled)
{ {
Logger::Debug("Cheats are disabled!"); Logger::Debug("Cheats are disabled!");
Game::SV_GameSendServerCommand(entNum, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"GAME_CHEATSNOTENABLED\"", 0x65)); Game::SV_GameSendServerCommand(entNum, Game::SV_CMD_CAN_IGNORE, VA("%c \"GAME_CHEATSNOTENABLED\"", 0x65));
return false; return false;
} }
if (ent->health < 1) if (ent->health < 1)
{ {
Logger::Debug("Entity {} must be alive to use this command!", entNum); Logger::Debug("Entity {} must be alive to use this command!", entNum);
Game::SV_GameSendServerCommand(entNum, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"GAME_MUSTBEALIVECOMMAND\"", 0x65)); Game::SV_GameSendServerCommand(entNum, Game::SV_CMD_CAN_IGNORE, VA("%c \"GAME_MUSTBEALIVECOMMAND\"", 0x65));
return false; return false;
} }
@ -37,9 +41,9 @@ namespace Components
{ {
const auto ent = &Game::g_entities[clientNum]; const auto ent = &Game::g_entities[clientNum];
if (ent->client == nullptr) if (!ent->client)
{ {
Logger::Debug("ClientCommand: client {} is not fully in game yet", clientNum); Logger::Debug("ClientCommand: client {} is not fully connected", clientNum);
return; return;
} }
@ -67,8 +71,7 @@ namespace Components
const auto entNum = ent->s.number; const auto entNum = ent->s.number;
Logger::Debug("Noclip toggled for entity {}", entNum); Logger::Debug("Noclip toggled for entity {}", entNum);
Game::SV_GameSendServerCommand(entNum, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"%s\"", 0x65, Game::SV_GameSendServerCommand(entNum, Game::SV_CMD_CAN_IGNORE, VA("%c \"%s\"", 0x65, (ent->client->flags & Game::PLAYER_FLAG_NOCLIP) ? "GAME_NOCLIPON" : "GAME_NOCLIPOFF"));
(ent->client->flags & Game::PLAYER_FLAG_NOCLIP) ? "GAME_NOCLIPON" : "GAME_NOCLIPOFF"));
}); });
Add("ufo", [](Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params) Add("ufo", [](Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params)
@ -81,8 +84,7 @@ namespace Components
const auto entNum = ent->s.number; const auto entNum = ent->s.number;
Logger::Debug("UFO toggled for entity {}", entNum); Logger::Debug("UFO toggled for entity {}", entNum);
Game::SV_GameSendServerCommand(entNum, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"%s\"", 0x65, Game::SV_GameSendServerCommand(entNum, Game::SV_CMD_CAN_IGNORE, VA("%c \"%s\"", 0x65, (ent->client->flags & Game::PLAYER_FLAG_UFO) ? "GAME_UFOON" : "GAME_UFOOFF"));
(ent->client->flags & Game::PLAYER_FLAG_UFO) ? "GAME_UFOON" : "GAME_UFOOFF"));
}); });
Add("god", [](Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params) Add("god", [](Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params)
@ -95,8 +97,7 @@ namespace Components
const auto entNum = ent->s.number; const auto entNum = ent->s.number;
Logger::Debug("God toggled for entity {}", entNum); Logger::Debug("God toggled for entity {}", entNum);
Game::SV_GameSendServerCommand(entNum, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"%s\"", 0x65, Game::SV_GameSendServerCommand(entNum, Game::SV_CMD_CAN_IGNORE, VA("%c \"%s\"", 0x65, (ent->flags & Game::FL_GODMODE) ? "GAME_GODMODE_ON" : "GAME_GODMODE_OFF"));
(ent->flags & Game::FL_GODMODE) ? "GAME_GODMODE_ON" : "GAME_GODMODE_OFF"));
}); });
Add("demigod", [](Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params) Add("demigod", [](Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params)
@ -109,8 +110,7 @@ namespace Components
const auto entNum = ent->s.number; const auto entNum = ent->s.number;
Logger::Debug("Demigod toggled for entity {}", entNum); Logger::Debug("Demigod toggled for entity {}", entNum);
Game::SV_GameSendServerCommand(entNum, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"%s\"", 0x65, Game::SV_GameSendServerCommand(entNum, Game::SV_CMD_CAN_IGNORE, VA("%c \"%s\"", 0x65, (ent->flags & Game::FL_DEMI_GODMODE) ? "GAME_DEMI_GODMODE_ON" : "GAME_DEMI_GODMODE_OFF"));
(ent->flags & Game::FL_DEMI_GODMODE) ? "GAME_DEMI_GODMODE_ON" : "GAME_DEMI_GODMODE_OFF"));
}); });
Add("notarget", [](Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params) Add("notarget", [](Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params)
@ -123,13 +123,12 @@ namespace Components
const auto entNum = ent->s.number; const auto entNum = ent->s.number;
Logger::Debug("Notarget toggled for entity {}", entNum); Logger::Debug("Notarget toggled for entity {}", entNum);
Game::SV_GameSendServerCommand(entNum, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"%s\"", 0x65, Game::SV_GameSendServerCommand(entNum, Game::SV_CMD_CAN_IGNORE, VA("%c \"%s\"", 0x65, (ent->flags & Game::FL_NOTARGET) ? "GAME_NOTARGETON" : "GAME_NOTARGETOFF"));
(ent->flags & Game::FL_NOTARGET) ? "GAME_NOTARGETON" : "GAME_NOTARGETOFF"));
}); });
Add("setviewpos", [](Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params) Add("setviewpos", [](Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params)
{ {
assert(ent != nullptr); assert(ent);
if (!CheatsOk(ent)) if (!CheatsOk(ent))
return; return;
@ -138,8 +137,7 @@ namespace Components
if (params->size() < 4 || params->size() > 6) if (params->size() < 4 || params->size() > 6)
{ {
Game::SV_GameSendServerCommand(ent->s.number, Game::SV_CMD_CAN_IGNORE, Game::SV_GameSendServerCommand(ent->s.number, Game::SV_CMD_CAN_IGNORE, VA("%c \"GAME_USAGE\x15: setviewpos x y z [yaw] [pitch]\n\"", 0x65));
Utils::String::VA("%c \"GAME_USAGE\x15: setviewpos x y z [yaw] [pitch]\n\"", 0x65));
return; return;
} }
@ -170,8 +168,7 @@ namespace Components
if (params->size() < 2) if (params->size() < 2)
{ {
Game::SV_GameSendServerCommand(ent->s.number, Game::SV_CMD_CAN_IGNORE, Game::SV_GameSendServerCommand(ent->s.number, Game::SV_CMD_CAN_IGNORE, VA("%c \"GAME_USAGE\x15: give <weapon name>\"", 0x65));
Utils::String::VA("%c \"GAME_USAGE\x15: give <weapon name>\"", 0x65));
return; return;
} }
@ -245,19 +242,26 @@ namespace Components
Add("kill", []([[maybe_unused]] Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params) Add("kill", []([[maybe_unused]] Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params)
{ {
assert(ent->client != nullptr); assert(ent->client);
assert(ent->client->sess.connected != Game::CON_DISCONNECTED); assert(ent->client->sess.connected != Game::CON_DISCONNECTED);
if (ent->client->sess.sessionState != Game::SESS_STATE_PLAYING || !CheatsOk(ent)) if (ent->client->sess.sessionState != Game::SESS_STATE_PLAYING || !CheatsOk(ent))
return; return;
Scheduler::Once([ent] auto** bgs = Game::Sys::GetTls<Game::bgs_t*>(Game::Sys::TLS_OFFSET::LEVEL_BGS);
{
assert(*bgs == nullptr);
*bgs = Game::level_bgs;
ent->flags &= ~(Game::FL_GODMODE | Game::FL_DEMI_GODMODE); ent->flags &= ~(Game::FL_GODMODE | Game::FL_DEMI_GODMODE);
ent->health = 0; ent->health = 0;
ent->client->ps.stats[0] = 0; ent->client->ps.stats[0] = 0;
Game::player_die(ent, ent, ent, 100000, Game::MOD_SUICIDE, 0, nullptr, Game::HITLOC_NONE, 0); Game::player_die(ent, ent, ent, 100000, Game::MOD_SUICIDE, 0, nullptr, Game::HITLOC_NONE, 0);
}, Scheduler::Pipeline::SERVER);
assert(*bgs == Game::level_bgs);
*bgs = nullptr;
}); });
} }
@ -299,7 +303,7 @@ namespace Components
duration = static_cast<int>(std::floorf(input * 1000.0f + 0.5f)); duration = static_cast<int>(std::floorf(input * 1000.0f + 0.5f));
} }
assert(ent->client != nullptr); assert(ent->client);
constexpr auto visMode = Game::visionSetMode_t::VISIONSET_NORMAL; constexpr auto visMode = Game::visionSetMode_t::VISIONSET_NORMAL;
const auto* name = params->get(1); const auto* name = params->get(1);
@ -308,8 +312,7 @@ namespace Components
strncpy_s(ent->client->visionName[visMode], strncpy_s(ent->client->visionName[visMode],
sizeof(Game::gclient_t::visionName[0]) / sizeof(char), name, _TRUNCATE); sizeof(Game::gclient_t::visionName[0]) / sizeof(char), name, _TRUNCATE);
Game::SV_GameSendServerCommand(ent->s.number, Game::SV_CMD_RELIABLE, Game::SV_GameSendServerCommand(ent->s.number, Game::SV_CMD_RELIABLE, VA("%c \"%s\" %i", Game::MY_CMDS[visMode], name, duration));
Utils::String::VA("%c \"%s\" %i", Game::MY_CMDS[visMode], name, duration));
}); });
Add("visionsetnight", []([[maybe_unused]] Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params) Add("visionsetnight", []([[maybe_unused]] Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params)
@ -327,7 +330,7 @@ namespace Components
duration = static_cast<int>(std::floorf(input * 1000.0f + 0.5f)); duration = static_cast<int>(std::floorf(input * 1000.0f + 0.5f));
} }
assert(ent->client != nullptr); assert(ent->client);
constexpr auto visMode = Game::visionSetMode_t::VISIONSET_NIGHT; constexpr auto visMode = Game::visionSetMode_t::VISIONSET_NIGHT;
const auto* name = params->get(1); const auto* name = params->get(1);
@ -336,13 +339,12 @@ namespace Components
strncpy_s(ent->client->visionName[visMode], strncpy_s(ent->client->visionName[visMode],
sizeof(Game::gclient_t::visionName[0]) / sizeof(char), name, _TRUNCATE); sizeof(Game::gclient_t::visionName[0]) / sizeof(char), name, _TRUNCATE);
Game::SV_GameSendServerCommand(ent->s.number, Game::SV_CMD_RELIABLE, Game::SV_GameSendServerCommand(ent->s.number, Game::SV_CMD_RELIABLE, VA("%c \"%s\" %i", Game::MY_CMDS[visMode], name, duration));
Utils::String::VA("%c \"%s\" %i", Game::MY_CMDS[visMode], name, duration));
}); });
Add("g_testCmd", []([[maybe_unused]] Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params) Add("g_testCmd", []([[maybe_unused]] Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params)
{ {
assert(ent != nullptr); assert(ent);
ent->client->ps.stunTime = 1000 + Game::level->time; // 1000 is the default test stun time ent->client->ps.stunTime = 1000 + Game::level->time; // 1000 is the default test stun time
Logger::Debug("playerState_s.stunTime is {}", ent->client->ps.stunTime); Logger::Debug("playerState_s.stunTime is {}", ent->client->ps.stunTime);
@ -406,7 +408,7 @@ namespace Components
// See description of the format string in the function G_DumpEntityDebugInfoToCSV // See description of the format string in the function G_DumpEntityDebugInfoToCSV
// If empty it means the item does not exist in the current version of the game or it was not possible to reverse it // If empty it means the item does not exist in the current version of the game or it was not possible to reverse it
return Utils::String::VA("%i,%s,%.0f,%s,%s,%s,%s,%s,%s,%s,%s,%s,%.0f %.0f %.0f,%.0f %.0f %.0f,%i\n", return VA("%i,%s,%.0f,%s,%s,%s,%s,%s,%s,%s,%s,%s,%.0f %.0f %.0f,%.0f %.0f %.0f,%i\n",
entNum, eventType, distance, classname, codeClassname, (model) ? model->name : "", entNum, eventType, distance, classname, codeClassname, (model) ? model->name : "",
targetName, target, "", scriptLinkName, team, "", targetName, target, "", scriptLinkName, team, "",
point[0], point[1], point[2], angles[0], angles[1], angles[2], 0); point[0], point[1], point[2], angles[0], angles[1], angles[2], 0);
@ -442,7 +444,7 @@ namespace Components
{ {
assert(filenameSuffix); assert(filenameSuffix);
const auto* fileName = Utils::String::VA("%s%s%s%s", "EntInfo", (*filenameSuffix) ? "_" : "", filenameSuffix, ".csv"); const auto* fileName = VA("%s%s%s%s", "EntInfo", (*filenameSuffix) ? "_" : "", filenameSuffix, ".csv");
Logger::Print(Game::CON_CHANNEL_SERVER, "Opening file \"{}\" for writing.\n", fileName); Logger::Print(Game::CON_CHANNEL_SERVER, "Opening file \"{}\" for writing.\n", fileName);
auto h = Game::FS_FOpenTextFileWrite(fileName); auto h = Game::FS_FOpenTextFileWrite(fileName);
@ -464,9 +466,11 @@ namespace Components
const auto* line = EntInfoLine(i); const auto* line = EntInfoLine(i);
const auto lineLen = std::strlen(line); const auto lineLen = std::strlen(line);
assert(line); assert(line);
assert(lineLen); assert(lineLen);
Game::FS_Write(line, lineLen, h);
Game::FS_Write(line, static_cast<int>(lineLen), h);
} }
Game::FS_FCloseFile(h); Game::FS_FCloseFile(h);

View File

@ -1,4 +1,5 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "ConnectProtocol.hpp"
namespace Components namespace Components
{ {
@ -7,17 +8,17 @@ namespace Components
bool ConnectProtocol::IsEvaluated() bool ConnectProtocol::IsEvaluated()
{ {
return ConnectProtocol::Evaluated; return Evaluated;
} }
bool ConnectProtocol::Used() bool ConnectProtocol::Used()
{ {
if (!ConnectProtocol::IsEvaluated()) if (!IsEvaluated())
{ {
ConnectProtocol::EvaluateProtocol(); EvaluateProtocol();
} }
return (!ConnectProtocol::ConnectString.empty()); return (!ConnectString.empty());
} }
bool ConnectProtocol::InstallProtocol() bool ConnectProtocol::InstallProtocol()
@ -25,8 +26,8 @@ namespace Components
HKEY hKey = nullptr; HKEY hKey = nullptr;
std::string data; std::string data;
char ownPth[MAX_PATH] = {0}; char ownPth[MAX_PATH]{};
char workdir[MAX_PATH] = {0}; char workdir[MAX_PATH]{};
DWORD dwsize = MAX_PATH; DWORD dwsize = MAX_PATH;
HMODULE hModule = GetModuleHandleA(nullptr); HMODULE hModule = GetModuleHandleA(nullptr);
@ -65,13 +66,13 @@ namespace Components
LONG openRes = RegOpenKeyExA(HKEY_CURRENT_USER, "SOFTWARE\\Classes\\iw4x\\shell\\open\\command", 0, KEY_ALL_ACCESS, &hKey); LONG openRes = RegOpenKeyExA(HKEY_CURRENT_USER, "SOFTWARE\\Classes\\iw4x\\shell\\open\\command", 0, KEY_ALL_ACCESS, &hKey);
if (openRes == ERROR_SUCCESS) if (openRes == ERROR_SUCCESS)
{ {
char regred[MAX_PATH] = { 0 }; char regred[MAX_PATH]{};
// Check if the game has been moved. // Check if the game has been moved.
openRes = RegQueryValueExA(hKey, nullptr, nullptr, nullptr, reinterpret_cast<BYTE*>(regred), &dwsize); openRes = RegQueryValueExA(hKey, nullptr, nullptr, nullptr, reinterpret_cast<BYTE*>(regred), &dwsize);
if (openRes == ERROR_SUCCESS) if (openRes == ERROR_SUCCESS)
{ {
char* endPtr = strstr(regred, "\" \"%1\""); auto* endPtr = std::strstr(regred, "\" \"%1\"");
if (endPtr != nullptr) if (endPtr != nullptr)
{ {
*endPtr = 0; *endPtr = 0;
@ -82,7 +83,8 @@ namespace Components
} }
RegCloseKey(hKey); RegCloseKey(hKey);
if (strcmp(regred + 1, ownPth))
if (std::strcmp(regred + 1, ownPth) != 0)
{ {
RegDeleteKeyA(HKEY_CURRENT_USER, "SOFTWARE\\Classes\\iw4x"); RegDeleteKeyA(HKEY_CURRENT_USER, "SOFTWARE\\Classes\\iw4x");
} }
@ -173,8 +175,8 @@ namespace Components
void ConnectProtocol::EvaluateProtocol() void ConnectProtocol::EvaluateProtocol()
{ {
if (ConnectProtocol::Evaluated) return; if (Evaluated) return;
ConnectProtocol::Evaluated = true; Evaluated = true;
std::string cmdLine = GetCommandLineA(); std::string cmdLine = GetCommandLineA();
@ -190,15 +192,15 @@ namespace Components
cmdLine = cmdLine.substr(0, pos); cmdLine = cmdLine.substr(0, pos);
} }
ConnectProtocol::ConnectString = cmdLine; ConnectString = cmdLine;
} }
} }
void ConnectProtocol::Invocation() void ConnectProtocol::Invocation()
{ {
if (ConnectProtocol::Used()) if (Used())
{ {
Command::Execute(Utils::String::VA("connect %s", ConnectProtocol::ConnectString.data()), false); Command::Execute(std::format("connect {}", ConnectString), false);
} }
} }
@ -209,28 +211,28 @@ namespace Components
// IPC handler // IPC handler
IPCPipe::On("connect", [](const std::string& data) IPCPipe::On("connect", [](const std::string& data)
{ {
Command::Execute(Utils::String::VA("connect %s", data.data()), false); Command::Execute(std::format("connect {}", data), false);
}); });
// Invocation handler // Invocation handler
Scheduler::OnGameInitialized(ConnectProtocol::Invocation, Scheduler::Pipeline::MAIN); Scheduler::OnGameInitialized(Invocation, Scheduler::Pipeline::MAIN);
ConnectProtocol::InstallProtocol(); InstallProtocol();
ConnectProtocol::EvaluateProtocol(); EvaluateProtocol();
// Fire protocol handlers // Fire protocol handlers
// Make sure this happens after the pipe-initialization! // Make sure this happens after the pipe-initialization!
if (ConnectProtocol::Used()) if (Used())
{ {
if (!Singleton::IsFirstInstance()) if (!Singleton::IsFirstInstance())
{ {
IPCPipe::Write("connect", ConnectProtocol::ConnectString); IPCPipe::Write("connect", ConnectString);
ExitProcess(0); ExitProcess(0);
} }
else else
{ {
// Only skip intro here, invocation will be done later. // Only skip intro here, invocation will be done later.
Utils::Hook::Set<BYTE>(0x60BECF, 0xEB); Utils::Hook::Set<std::uint8_t>(0x60BECF, 0xEB);
Scheduler::Once([] Scheduler::Once([]
{ {

View File

@ -1,5 +1,12 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#undef MOUSE_MOVED #include "Console.hpp"
#include <version.hpp>
#ifdef MOUSE_MOVED
#undef MOUSE_MOVED
#endif
#include <curses.h> #include <curses.h>
#define REMOVE_HEADERBAR 1 #define REMOVE_HEADERBAR 1

View File

@ -1,4 +1,5 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "D3D9Ex.hpp"
namespace Components namespace Components
{ {

View File

@ -1,4 +1,6 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "Debug.hpp"
#include "Game/Engine/ScopedCriticalSection.hpp" #include "Game/Engine/ScopedCriticalSection.hpp"
namespace Components namespace Components
@ -6,7 +8,7 @@ namespace Components
const Game::dvar_t* Debug::DebugOverlay; const Game::dvar_t* Debug::DebugOverlay;
const Game::dvar_t* Debug::BugName; const Game::dvar_t* Debug::BugName;
Game::dvar_t** Debug::PlayerDebugHealth = reinterpret_cast<Game::dvar_t**>(0x7A9F7C); const Game::dvar_t* Debug::PlayerDebugHealth;
const char* Debug::PMFlagsValues[] = const char* Debug::PMFlagsValues[] =
{ {
@ -92,15 +94,15 @@ namespace Components
"EF_SOFT", "EF_SOFT",
}; };
const char Debug::strButtons[] = const char Debug::StrButtons[] =
{ {
'\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x0E', '\x0F', '\x10', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x0E', '\x0F', '\x10',
'\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', '\0' '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', '\0'
}; };
const char Debug::strTemplate[] = "%s: %s All those moments will be lost in time, like tears in rain."; 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}; const float Debug::ColorWhite[] = {1.0f, 1.0f, 1.0f, 1.0f};
std::string Debug::BuildPMFlagsString(const Game::playerState_s* ps) std::string Debug::BuildPMFlagsString(const Game::playerState_s* ps)
{ {
@ -161,19 +163,19 @@ namespace Components
auto* const font2 = Game::UI_GetFontHandle(scrPlace, 6, MY_SCALE2); 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, Game::UI_DrawText(scrPlace, "Client View of Flags", maxChars, font2, -60.0f, 0, 1, 1,
MY_SCALE2, colorWhite, 1); MY_SCALE2, ColorWhite, 1);
const auto pmf = BuildPMFlagsString(&cgameGlob->predictedPlayerState); 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, ColorWhite, 3);
const auto pof = BuildPOFlagsString(&cgameGlob->predictedPlayerState); 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, ColorWhite, 3);
const auto plf = BuildPLFlagsString(&cgameGlob->predictedPlayerState); 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, ColorWhite, 3);
const auto pef = BuildPEFlagsString(&cgameGlob->predictedPlayerState); 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, ColorWhite, 3);
} }
void Debug::CG_DrawDebugPlayerHealth(const int localClientNum) void Debug::CG_DrawDebugPlayerHealth(const int localClientNum)
@ -182,7 +184,7 @@ namespace Components
constexpr float color1[] = {0.0f, 0.0f, 0.0f, 1.0f}; constexpr float color1[] = {0.0f, 0.0f, 0.0f, 1.0f};
constexpr float color2[] = {0.0f, 1.0f, 0.0f, 1.0f}; constexpr float color2[] = {0.0f, 1.0f, 0.0f, 1.0f};
assert((*PlayerDebugHealth)->current.enabled); assert(PlayerDebugHealth->current.enabled);
const auto* cgameGlob = Game::cgArray; const auto* cgameGlob = Game::cgArray;
if (cgameGlob->predictedPlayerState.stats[0] && cgameGlob->predictedPlayerState.stats[2]) if (cgameGlob->predictedPlayerState.stats[0] && cgameGlob->predictedPlayerState.stats[2])
@ -219,25 +221,25 @@ namespace Components
auto* const font5 = Game::UI_GetFontHandle(scrPlace, 5, 0.4f); auto* const font5 = Game::UI_GetFontHandle(scrPlace, 5, 0.4f);
auto* const font6 = Game::UI_GetFontHandle(scrPlace, 6, 0.4f); auto* const font6 = Game::UI_GetFontHandle(scrPlace, 6, 0.4f);
sprintf_s(strFinal, strTemplate, font1->fontName, strButtons); sprintf_s(strFinal, StrTemplate, font1->fontName, StrButtons);
Game::UI_FilterStringForButtonAnimation(strFinal, sizeof(strFinal)); 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, ColorWhite, 3);
sprintf_s(strFinal, strTemplate, font2->fontName, strButtons); sprintf_s(strFinal, StrTemplate, font2->fontName, StrButtons);
Game::UI_FilterStringForButtonAnimation(strFinal, sizeof(strFinal)); 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, ColorWhite, 3);
sprintf_s(strFinal, strTemplate, font3->fontName, strButtons); sprintf_s(strFinal, StrTemplate, font3->fontName, StrButtons);
Game::UI_FilterStringForButtonAnimation(strFinal, sizeof(strFinal)); 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, ColorWhite, 3);
sprintf_s(strFinal, strTemplate, font5->fontName, strButtons); sprintf_s(strFinal, StrTemplate, font5->fontName, StrButtons);
Game::UI_FilterStringForButtonAnimation(strFinal, sizeof(strFinal)); 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, ColorWhite, 3);
sprintf_s(strFinal, strTemplate, font6->fontName, strButtons); sprintf_s(strFinal, StrTemplate, font6->fontName, StrButtons);
Game::UI_FilterStringForButtonAnimation(strFinal, sizeof(strFinal)); 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, ColorWhite, 3);
} }
void Debug::CG_DrawDebugOverlays_Hk(const int localClientNum) void Debug::CG_DrawDebugOverlays_Hk(const int localClientNum)
@ -254,7 +256,7 @@ namespace Components
break; break;
} }
if ((*PlayerDebugHealth)->current.enabled) if (PlayerDebugHealth->current.enabled)
{ {
CG_DrawDebugPlayerHealth(localClientNum); CG_DrawDebugPlayerHealth(localClientNum);
} }
@ -304,10 +306,31 @@ namespace Components
if (!result) if (!result)
{ {
Logger::PrintError(1, "CopyFile failed({}) {} {}\n", GetLastError(), "console_mp.log", newFileName); Logger::PrintError(Game::CON_CHANNEL_ERROR, "CopyFile failed({}) {} {}\n", GetLastError(), "console_mp.log", newFileName);
} }
} }
void Debug::Com_BugNameInc_f()
{
char buf[260]{};
if (std::strlen(BugName->current.string) < 4)
{
Game::Dvar_SetString(BugName, "bug0");
return;
}
if (std::strncmp(BugName->current.string, "bug", 3) != 0)
{
Game::Dvar_SetString(BugName, "bug0");
return;
}
const auto n = std::strtol(BugName->current.string + 3, nullptr, 10);
sprintf_s(buf, "bug%d", n + 1);
Game::Dvar_SetString(BugName, buf);
}
void Debug::CL_InitDebugDvars() void Debug::CL_InitDebugDvars()
{ {
static const char* debugOverlayNames_0[] = static const char* debugOverlayNames_0[] =
@ -327,6 +350,12 @@ namespace Components
Game::DVAR_CHEAT | Game::DVAR_CODINFO, "Name appended to the copied console log"); Game::DVAR_CHEAT | Game::DVAR_CODINFO, "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)
{
PlayerDebugHealth = Game::Dvar_RegisterBool(name, value, Game::DVAR_NONE, description);
return PlayerDebugHealth;
}
Debug::Debug() Debug::Debug()
{ {
Scheduler::Once(CL_InitDebugDvars, Scheduler::Pipeline::MAIN); Scheduler::Once(CL_InitDebugDvars, Scheduler::Pipeline::MAIN);
@ -336,8 +365,11 @@ namespace Components
Utils::Hook::Set<void(*)()>(0x60BCEA, Com_Assert_f); Utils::Hook::Set<void(*)()>(0x60BCEA, Com_Assert_f);
Utils::Hook(0x4487F7, Dvar_Register_PlayerDebugHealth, HOOK_CALL).install()->quick();
#ifdef _DEBUG #ifdef _DEBUG
Command::Add("bug", Com_Bug_f); Command::Add("bug", Com_Bug_f);
Command::Add("bug_name_inc", Com_BugNameInc_f);
#endif #endif
} }
} }

View File

@ -12,15 +12,15 @@ namespace Components
static const Game::dvar_t* BugName; static const Game::dvar_t* BugName;
// Game dvars // Game dvars
static Game::dvar_t** PlayerDebugHealth; static const Game::dvar_t* PlayerDebugHealth;
static const char* PMFlagsValues[]; static const char* PMFlagsValues[];
static const char* POFlagsValues[]; static const char* POFlagsValues[];
static const char* PLFlagsValues[]; static const char* PLFlagsValues[];
static const char* PEFlagsValues[]; static const char* PEFlagsValues[];
static const char strButtons[]; static const char StrButtons[];
static const char strTemplate[]; static const char StrTemplate[];
static constexpr auto MY_SCALE2 = 0.5f; static constexpr auto MY_SCALE2 = 0.5f;
static constexpr auto MY_SCALE_2 = 0.201f; static constexpr auto MY_SCALE_2 = 0.201f;
@ -28,7 +28,7 @@ namespace Components
static constexpr auto MY_X = -25.0f; static constexpr auto MY_X = -25.0f;
static constexpr auto MY_Y = 20.0f; static constexpr auto MY_Y = 20.0f;
static const float colorWhite[]; static const float ColorWhite[];
static std::string BuildPMFlagsString(const Game::playerState_s* ps); static std::string BuildPMFlagsString(const Game::playerState_s* ps);
static std::string BuildPOFlagsString(const Game::playerState_s* ps); static std::string BuildPOFlagsString(const Game::playerState_s* ps);
@ -43,7 +43,9 @@ namespace Components
static void Com_Assert_f(); static void Com_Assert_f();
static void Com_Bug_f(Command::Params* params); static void Com_Bug_f(Command::Params* params);
static void Com_BugNameInc_f();
static void CL_InitDebugDvars(); static void CL_InitDebugDvars();
static const Game::dvar_t* Dvar_Register_PlayerDebugHealth(const char* name, bool value, std::uint16_t flags, const char* description);
}; };
} }

View File

@ -1,4 +1,7 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "CardTitles.hpp"
#include "ClanTags.hpp"
#include "ServerCommands.hpp"
namespace Components namespace Components
{ {
@ -114,7 +117,7 @@ namespace Components
if (!partyEnable) // Time wrapping should not occur in party servers, but yeah... if (!partyEnable) // Time wrapping should not occur in party servers, but yeah...
{ {
if (mapname.empty()) mapname = "mp_rust"; if (mapname.empty()) mapname = "mp_rust";
Command::Execute(Utils::String::VA("map %s", mapname.data()), true); Command::Execute(std::format("map {}", mapname), true);
} }
}, Scheduler::Pipeline::SERVER); }, Scheduler::Pipeline::SERVER);

View File

@ -1,4 +1,6 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "Discovery.hpp"
#include "ServerList.hpp"
namespace Components namespace Components
{ {
@ -7,39 +9,42 @@ namespace Components
std::thread Discovery::Thread; std::thread Discovery::Thread;
std::string Discovery::Challenge; std::string Discovery::Challenge;
Dvar::Var Discovery::NetDiscoveryPortRangeMin;
Dvar::Var Discovery::NetDiscoveryPortRangeMax;
void Discovery::Perform() void Discovery::Perform()
{ {
Discovery::IsPerforming = true; IsPerforming = true;
} }
Discovery::Discovery() Discovery::Discovery()
{ {
Dvar::Register<int>("net_discoveryPortRangeMin", 25000, 0, 65535, Game::DVAR_ARCHIVE, "Minimum scan range port for local server discovery"); NetDiscoveryPortRangeMin = Dvar::Register<int>("net_discoveryPortRangeMin", 25000, 0, 65535, Game::DVAR_NONE, "Minimum scan range port for local server discovery");
Dvar::Register<int>("net_discoveryPortRangeMax", 35000, 1, 65536, Game::DVAR_ARCHIVE, "Maximum scan range port for local server discovery"); NetDiscoveryPortRangeMax = Dvar::Register<int>("net_discoveryPortRangeMax", 35000, 1, 65536, Game::DVAR_NONE, "Maximum scan range port for local server discovery");
// An additional thread prevents lags // An additional thread prevents lags
// Not sure if that's the best way though // Not sure if that's the best way though
Discovery::IsPerforming = false; IsPerforming = false;
Discovery::IsTerminating = false; IsTerminating = false;
Discovery::Thread = std::thread([]() Thread = std::thread([]
{ {
while (!Discovery::IsTerminating) while (!IsTerminating)
{ {
if (Discovery::IsPerforming) if (IsPerforming)
{ {
int start = Game::Sys_Milliseconds(); const auto start = Game::Sys_Milliseconds();
Logger::Print("Starting local server discovery...\n"); Logger::Print("Starting local server discovery...\n");
Discovery::Challenge = Utils::Cryptography::Rand::GenerateChallenge(); Challenge = Utils::Cryptography::Rand::GenerateChallenge();
unsigned int minPort = Dvar::Var("net_discoveryPortRangeMin").get<unsigned int>(); const auto minPort = NetDiscoveryPortRangeMin.get<unsigned int>();
unsigned int maxPort = Dvar::Var("net_discoveryPortRangeMax").get<unsigned int>(); const auto maxPort = NetDiscoveryPortRangeMax.get<unsigned int>();
Network::BroadcastRange(minPort, maxPort, Utils::String::VA("discovery %s", Discovery::Challenge.data())); Network::BroadcastRange(minPort, maxPort, std::format("discovery {}", Challenge));
Logger::Print("Discovery sent within {}ms, awaiting responses...\n", Game::Sys_Milliseconds() - start); Logger::Print("Discovery sent within {}ms, awaiting responses...\n", Game::Sys_Milliseconds() - start);
Discovery::IsPerforming = false; IsPerforming = false;
} }
std::this_thread::sleep_for(50ms); std::this_thread::sleep_for(50ms);
@ -70,7 +75,7 @@ namespace Components
return; return;
} }
if (Utils::ParseChallenge(data) != Discovery::Challenge) if (Utils::ParseChallenge(data) != Challenge)
{ {
Logger::Print("Received discovery with invalid challenge from: {}\n", address.getString()); Logger::Print("Received discovery with invalid challenge from: {}\n", address.getString());
return; return;
@ -87,12 +92,12 @@ namespace Components
void Discovery::preDestroy() void Discovery::preDestroy()
{ {
Discovery::IsPerforming = false; IsPerforming = false;
Discovery::IsTerminating = true; IsTerminating = true;
if (Discovery::Thread.joinable()) if (Thread.joinable())
{ {
Discovery::Thread.join(); Thread.join();
} }
} }
} }

View File

@ -16,5 +16,8 @@ namespace Components
static bool IsPerforming; static bool IsPerforming;
static std::thread Thread; static std::thread Thread;
static std::string Challenge; static std::string Challenge;
static Dvar::Var NetDiscoveryPortRangeMin;
static Dvar::Var NetDiscoveryPortRangeMax;
}; };
} }

View File

@ -1,5 +1,6 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "GSC/Script.hpp" #include "Download.hpp"
#include "ServerInfo.hpp"
#include <mongoose.h> #include <mongoose.h>
@ -10,19 +11,19 @@ namespace Components
Download::ClientDownload Download::CLDownload; Download::ClientDownload Download::CLDownload;
std::thread Download::ServerThread; std::thread Download::ServerThread;
bool Download::Terminate; volatile bool Download::Terminate;
bool Download::ServerRunning; bool Download::ServerRunning;
#pragma region Client #pragma region Client
void Download::InitiateMapDownload(const std::string& map, bool needPassword) void Download::InitiateMapDownload(const std::string& map, bool needPassword)
{ {
Download::InitiateClientDownload(map, needPassword, true); InitiateClientDownload(map, needPassword, true);
} }
void Download::InitiateClientDownload(const std::string& mod, bool needPassword, bool map) void Download::InitiateClientDownload(const std::string& mod, bool needPassword, bool map)
{ {
if (Download::CLDownload.running) return; if (CLDownload.running) return;
Scheduler::Once([] Scheduler::Once([]
{ {
@ -43,20 +44,20 @@ namespace Components
return; return;
} }
Download::CLDownload.hashedPassword = Utils::String::DumpHex(Utils::Cryptography::SHA256::Compute(password), ""); CLDownload.hashedPassword = Utils::String::DumpHex(Utils::Cryptography::SHA256::Compute(password), "");
} }
Download::CLDownload.running = true; CLDownload.running = true;
Download::CLDownload.isMap = map; CLDownload.isMap = map;
Download::CLDownload.mod = mod; CLDownload.mod = mod;
Download::CLDownload.terminateThread = false; CLDownload.terminateThread = false;
Download::CLDownload.totalBytes = 0; CLDownload.totalBytes = 0;
Download::CLDownload.lastTimeStamp = 0; CLDownload.lastTimeStamp = 0;
Download::CLDownload.downBytes = 0; CLDownload.downBytes = 0;
Download::CLDownload.timeStampBytes = 0; CLDownload.timeStampBytes = 0;
Download::CLDownload.isPrivate = needPassword; CLDownload.isPrivate = needPassword;
Download::CLDownload.target = Party::Target(); CLDownload.target = Party::Target();
Download::CLDownload.thread = std::thread(Download::ModDownloader, &Download::CLDownload); CLDownload.thread = std::thread(ModDownloader, &CLDownload);
} }
bool Download::ParseModList(ClientDownload* download, const std::string& list) bool Download::ParseModList(ClientDownload* download, const std::string& list)
@ -93,7 +94,7 @@ namespace Components
const auto name = file.at("name").get<std::string>(); const auto name = file.at("name").get<std::string>();
const auto size = file.at("size").get<std::size_t>(); const auto size = file.at("size").get<std::size_t>();
Download::ClientDownload::File fileEntry; ClientDownload::File fileEntry;
fileEntry.name = name; fileEntry.name = name;
fileEntry.hash = hash; fileEntry.hash = hash;
fileEntry.size = size; fileEntry.size = size;
@ -120,7 +121,7 @@ namespace Components
auto file = download->files[index]; auto file = download->files[index];
std::string path = download->mod + "/" + file.name; auto path = download->mod + "/" + file.name;
if (download->isMap) if (download->isMap)
{ {
path = "usermaps/" + path; path = "usermaps/" + path;
@ -128,8 +129,7 @@ namespace Components
if (Utils::IO::FileExists(path)) if (Utils::IO::FileExists(path))
{ {
std::string data = Utils::IO::ReadFile(path); auto data = Utils::IO::ReadFile(path);
if (data.size() == file.size && Utils::String::DumpHex(Utils::Cryptography::SHA256::Compute(data), "") == file.hash) if (data.size() == file.size && Utils::String::DumpHex(Utils::Cryptography::SHA256::Compute(data), "") == file.hash)
{ {
download->totalBytes += file.size; download->totalBytes += file.size;
@ -186,7 +186,7 @@ namespace Components
Logger::Print("Downloading from url {}\n", url); Logger::Print("Downloading from url {}\n", url);
Download::FileDownload fDownload; FileDownload fDownload;
fDownload.file = file; fDownload.file = file;
fDownload.index = index; fDownload.index = index;
fDownload.download = download; fDownload.download = download;
@ -200,7 +200,7 @@ namespace Components
fDownload.downloading = true; fDownload.downloading = true;
Utils::WebIO webIO; Utils::WebIO webIO;
webIO.setProgressCallback([&fDownload, &webIO](size_t bytes, size_t) webIO.setProgressCallback([&fDownload, &webIO](std::size_t bytes, std::size_t)
{ {
if(!fDownload.downloading || fDownload.download->terminateThread) if(!fDownload.downloading || fDownload.download->terminateThread)
{ {
@ -208,7 +208,7 @@ namespace Components
return; return;
} }
Download::DownloadProgress(&fDownload, bytes - fDownload.receivedBytes); DownloadProgress(&fDownload, bytes - fDownload.receivedBytes);
}); });
bool result = false; bool result = false;
@ -232,13 +232,13 @@ namespace Components
void Download::ModDownloader(ClientDownload* download) void Download::ModDownloader(ClientDownload* download)
{ {
if (!download) download = &Download::CLDownload; if (!download) download = &CLDownload;
std::string host = "http://" + download->target.getString(); const auto host = "http://" + download->target.getString();
std::string listUrl = host + (download->isMap ? "/map" : "/list") + (download->isPrivate ? ("?password=" + download->hashedPassword) : ""); const auto listUrl = host + (download->isMap ? "/map" : "/list") + (download->isPrivate ? ("?password=" + download->hashedPassword) : "");
std::string list = Utils::WebIO("IW4x", listUrl).setTimeout(5000)->get(); const auto list = Utils::WebIO("IW4x", listUrl).setTimeout(5000)->get();
if (list.empty()) if (list.empty())
{ {
if (download->terminateThread) return; if (download->terminateThread) return;
@ -257,7 +257,7 @@ namespace Components
if (download->terminateThread) return; if (download->terminateThread) return;
if (!Download::ParseModList(download, list)) if (!ParseModList(download, list))
{ {
if (download->terminateThread) return; if (download->terminateThread) return;
@ -278,15 +278,15 @@ namespace Components
static std::string mod; static std::string mod;
mod = download->mod; mod = download->mod;
for (unsigned int i = 0; i < download->files.size(); ++i) for (std::size_t i = 0; i < download->files.size(); ++i)
{ {
if (download->terminateThread) return; if (download->terminateThread) return;
if (!Download::DownloadFile(download, i)) if (!DownloadFile(download, i))
{ {
if (download->terminateThread) return; if (download->terminateThread) return;
mod = Utils::String::VA("Failed to download file: %s!", download->files[i].name.data()); mod = std::format("Failed to download file: {}!", download->files[i].name);
download->thread.detach(); download->thread.detach();
download->clear(); download->clear();
@ -321,7 +321,6 @@ namespace Components
Scheduler::Once([] Scheduler::Once([]
{ {
Game::Dvar_SetString(*Game::fs_gameDirVar, mod.data()); Game::Dvar_SetString(*Game::fs_gameDirVar, mod.data());
const_cast<Game::dvar_t*>(*Game::fs_gameDirVar)->modified = true;
mod.clear(); mod.clear();
@ -370,7 +369,7 @@ namespace Components
}, Scheduler::Pipeline::CLIENT); }, Scheduler::Pipeline::CLIENT);
} }
int delta = Game::Sys_Milliseconds() - fDownload->download->lastTimeStamp; auto delta = Game::Sys_Milliseconds() - fDownload->download->lastTimeStamp;
if (delta > 300) if (delta > 300)
{ {
bool doFormat = fDownload->download->lastTimeStamp != 0; bool doFormat = fDownload->download->lastTimeStamp != 0;
@ -387,7 +386,7 @@ namespace Components
if (doFormat) if (doFormat)
{ {
static size_t dlTsBytes; static std::size_t dlTsBytes;
static int dlDelta, dlTimeLeft; static int dlDelta, dlTimeLeft;
dlTimeLeft = timeLeft; dlTimeLeft = timeLeft;
dlDelta = delta; dlDelta = delta;
@ -435,7 +434,7 @@ namespace Components
{ {
// Score and ping are irrelevant // Score and ping are irrelevant
const auto* name = Game::PartyHost_GetMemberName(Game::g_lobbyData, i); const auto* name = Game::PartyHost_GetMemberName(Game::g_lobbyData, i);
if (!name || *name == '\0') continue; if (name == nullptr || *name == '\0') continue;
playerInfo["name"] = name; playerInfo["name"] = name;
} }
@ -452,7 +451,7 @@ namespace Components
static nlohmann::json jsonList; static nlohmann::json jsonList;
static auto handled = false; static auto handled = false;
const std::string fs_gameDirVar = (*Game::fs_gameDirVar)->current.string; const std::filesystem::path fs_gameDirVar((*Game::fs_gameDirVar)->current.string);
if (!fs_gameDirVar.empty() && !handled) if (!fs_gameDirVar.empty() && !handled)
{ {
@ -460,17 +459,20 @@ namespace Components
std::vector<nlohmann::json> fileList; std::vector<nlohmann::json> fileList;
const auto path = Dvar::Var("fs_basepath").get<std::string>() + "\\" + fs_gameDirVar; const auto path = Dvar::Var("fs_basepath").get<std::string>() / fs_gameDirVar;
auto list = FileSystem::GetSysFileList(path, "iwd", false); auto list = FileSystem::GetSysFileList(path.generic_string(), "iwd", false);
list.emplace_back("mod.ff"); list.emplace_back("mod.ff");
for (const auto& file : list) for (const auto& file : list)
{ {
std::string filename = path + "\\" + file; auto filename = path / file;
if (file.find("_svr_") == std::string::npos) if (file.find("_svr_") != std::string::npos)
{ {
continue;
}
std::unordered_map<std::string, nlohmann::json> jsonFileList; std::unordered_map<std::string, nlohmann::json> jsonFileList;
std::string fileBuffer = Utils::IO::ReadFile(filename); auto fileBuffer = Utils::IO::ReadFile(filename.generic_string());
if (fileBuffer.empty()) if (fileBuffer.empty())
{ {
continue; continue;
@ -482,7 +484,6 @@ namespace Components
fileList.emplace_back(jsonFileList); fileList.emplace_back(jsonFileList);
} }
}
jsonList = fileList; jsonList = fileList;
} }
@ -507,14 +508,15 @@ namespace Components
mapNamePre = mapName; mapNamePre = mapName;
const auto path = Dvar::Var("fs_basepath").get<std::string>() + "\\usermaps\\" + mapName; const std::filesystem::path basePath(Dvar::Var("fs_basepath").get<std::string>());
const auto path = basePath / "usermaps" / mapName;
for (auto i = 0; i < ARRAYSIZE(Maps::UserMapFiles); ++i) for (std::size_t i = 0; i < ARRAYSIZE(Maps::UserMapFiles); ++i)
{ {
const auto filename = path + "\\" + mapName + Maps::UserMapFiles[i]; const auto filename = std::format("{}\\{}{}", path.generic_string(), mapName, Maps::UserMapFiles[i]);
std::map<std::string, nlohmann::json> file; std::unordered_map<std::string, nlohmann::json> file;
std::string fileBuffer = Utils::IO::ReadFile(filename); auto fileBuffer = Utils::IO::ReadFile(filename);
if (fileBuffer.empty()) if (fileBuffer.empty())
{ {
continue; continue;
@ -551,7 +553,7 @@ namespace Components
auto mapName = (Party::IsInUserMapLobby() ? Dvar::Var("ui_mapname").get<std::string>() : Maps::GetUserMap()->getName()); auto mapName = (Party::IsInUserMapLobby() ? Dvar::Var("ui_mapname").get<std::string>() : Maps::GetUserMap()->getName());
auto isValidFile = false; auto isValidFile = false;
for (auto i = 0; i < ARRAYSIZE(Maps::UserMapFiles); ++i) for (std::size_t i = 0; i < ARRAYSIZE(Maps::UserMapFiles); ++i)
{ {
if (url == (mapName + Maps::UserMapFiles[i])) if (url == (mapName + Maps::UserMapFiles[i]))
{ {
@ -672,6 +674,10 @@ namespace Components
Download::Download() Download::Download()
{ {
AssertSize(Game::va_info_t, 0x804);
AssertSize(jmp_buf, 0x40);
AssertSize(Game::TraceThreadInfo, 0x8);
if (Dedicated::IsEnabled()) if (Dedicated::IsEnabled())
{ {
mg_mgr_init(&Mgr); mg_mgr_init(&Mgr);
@ -685,11 +691,13 @@ namespace Components
} }
}); });
Download::ServerRunning = true; ServerRunning = true;
Download::Terminate = false; Terminate = false;
Download::ServerThread = std::thread([] ServerThread = Utils::Thread::CreateNamedThread("Mongoose", []
{ {
while (!Download::Terminate) Com_InitThreadData();
while (!Terminate)
{ {
mg_mgr_poll(&Mgr, 100); mg_mgr_poll(&Mgr, 100);
} }
@ -706,7 +714,7 @@ namespace Components
UIScript::Add("mod_download_cancel", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) UIScript::Add("mod_download_cancel", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
{ {
Download::CLDownload.clear(); CLDownload.clear();
}); });
} }
@ -715,14 +723,11 @@ namespace Components
Dvar::Register<bool>("sv_wwwDownload", false, Game::DVAR_NONE, "Set to true to enable downloading maps/mods from an external server."); 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."); Dvar::Register<const char*>("sv_wwwBaseUrl", "", Game::DVAR_NONE, "Set to the base url for the external map download.");
}, Scheduler::Pipeline::MAIN); }, Scheduler::Pipeline::MAIN);
Script::AddFunction("HttpGet", Script::ShowDeprecationWarning);
Script::AddFunction("HttpCancel", Script::ShowDeprecationWarning);
} }
Download::~Download() Download::~Download()
{ {
if (Download::ServerRunning) if (ServerRunning)
{ {
mg_mgr_free(&Mgr); mg_mgr_free(&Mgr);
} }
@ -730,15 +735,15 @@ namespace Components
void Download::preDestroy() void Download::preDestroy()
{ {
Download::Terminate = true; Terminate = true;
if (Download::ServerThread.joinable()) if (ServerThread.joinable())
{ {
Download::ServerThread.join(); ServerThread.join();
} }
if (!Dedicated::IsEnabled()) if (!Dedicated::IsEnabled())
{ {
Download::CLDownload.clear(); CLDownload.clear();
} }
} }
} }

View File

@ -81,7 +81,7 @@ namespace Components
static ClientDownload CLDownload; static ClientDownload CLDownload;
static std::thread ServerThread; static std::thread ServerThread;
static bool Terminate; static volatile bool Terminate;
static bool ServerRunning; static bool ServerRunning;
static void DownloadProgress(FileDownload* fDownload, std::size_t bytes); static void DownloadProgress(FileDownload* fDownload, std::size_t bytes);

View File

@ -186,24 +186,24 @@ namespace Components
} }
} }
template<> Dvar::Var Dvar::Register(const char* dvarName, bool value, Flag flag, const char* description) template<> Dvar::Var Dvar::Register(const char* dvarName, bool value, std::uint16_t flag, const char* description)
{ {
return Game::Dvar_RegisterBool(dvarName, value, flag.val, description); return Game::Dvar_RegisterBool(dvarName, value, flag, description);
} }
template<> Dvar::Var Dvar::Register(const char* dvarName, const char* value, Flag flag, const char* description) template<> Dvar::Var Dvar::Register(const char* dvarName, const char* value, std::uint16_t flag, const char* description)
{ {
return Game::Dvar_RegisterString(dvarName, value, flag.val, description); return Game::Dvar_RegisterString(dvarName, value, flag, description);
} }
template<> Dvar::Var Dvar::Register(const char* dvarName, int value, int min, int max, Flag flag, const char* description) template<> Dvar::Var Dvar::Register(const char* dvarName, int value, int min, int max, std::uint16_t flag, const char* description)
{ {
return Game::Dvar_RegisterInt(dvarName, value, min, max, flag.val, description); return Game::Dvar_RegisterInt(dvarName, value, min, max, flag, description);
} }
template<> Dvar::Var Dvar::Register(const char* dvarName, float value, float min, float max, Flag flag, const char* description) template<> Dvar::Var Dvar::Register(const char* dvarName, float value, float min, float max, std::uint16_t flag, const char* description)
{ {
return Game::Dvar_RegisterFloat(dvarName, value, min, max, flag.val, description); return Game::Dvar_RegisterFloat(dvarName, value, min, max, flag, description);
} }
void Dvar::ResetDvarsValue() void Dvar::ResetDvarsValue()
@ -229,7 +229,8 @@ namespace Components
// Don't perform any checks if name didn't change // Don't perform any checks if name didn't change
if (name == lastValidName) return; if (name == lastValidName) return;
std::string saneName = TextRenderer::StripAllTextIcons(TextRenderer::StripColors(Utils::String::Trim(name))); Utils::String::Trim(name);
auto saneName = TextRenderer::StripAllTextIcons(TextRenderer::StripColors(name));
if (saneName.size() < 3 || (saneName[0] == '[' && saneName[1] == '{')) if (saneName.size() < 3 || (saneName[0] == '[' && saneName[1] == '{'))
{ {
Logger::PrintError(Game::CON_CHANNEL_ERROR, "Username '{}' is invalid. It must at least be 3 characters long and not appear empty!\n", name); Logger::PrintError(Game::CON_CHANNEL_ERROR, "Username '{}' is invalid. It must at least be 3 characters long and not appear empty!\n", name);
@ -240,7 +241,7 @@ namespace Components
lastValidName = name; lastValidName = name;
Friends::UpdateName(); Friends::UpdateName();
} }
}, Scheduler::CLIENT, 3s); // Don't need to do this every frame }, Scheduler::Pipeline::CLIENT, 3s); // Don't need to do this every frame
} }
std::string username = "Unknown Soldier"; std::string username = "Unknown Soldier";
@ -306,10 +307,10 @@ namespace Components
{ {
if (!Utils::IO::FileExists(ArchiveDvarPath)) if (!Utils::IO::FileExists(ArchiveDvarPath))
{ {
Utils::IO::WriteFile(ArchiveDvarPath, "// generated by IW4x, do not modify\n"); Utils::IO::WriteFile(ArchiveDvarPath, "// generated by IW4x, do not modify\n", false);
} }
Utils::IO::WriteFile(ArchiveDvarPath, Utils::String::VA("seta %s \"%s\"\n", var->name, Game::Dvar_DisplayableValue(var)), true); Utils::IO::WriteFile(ArchiveDvarPath, std::format("set {} \"{}\"\n", var->name, Game::Dvar_DisplayableValue(var)), true);
} }
void Dvar::DvarSetFromStringByName_Stub(const char* dvarName, const char* value) void Dvar::DvarSetFromStringByName_Stub(const char* dvarName, const char* value)
@ -320,7 +321,7 @@ namespace Components
{ {
if (AreArchiveDvarsProtected()) if (AreArchiveDvarsProtected())
{ {
Logger::Print(Game::CON_CHANNEL_CONSOLEONLY, "Not allowing server to override saved dvar '{}'\n", dvarName); Logger::Print(Game::CON_CHANNEL_CONSOLEONLY, "Not allowing server to override saved dvar '{}'\n", dvar->name);
return; return;
} }
@ -330,6 +331,12 @@ namespace Components
SaveArchiveDvar(dvar); SaveArchiveDvar(dvar);
} }
if (dvar != nullptr && std::strcmp(dvar->name, "com_errorResolveCommand") == 0)
{
Logger::Print(Game::CON_CHANNEL_CONSOLEONLY, "Not allowing server to set '{}'\n", dvar->name);
return;
}
Utils::Hook::Call<void(const char*, const char*)>(0x4F52E0)(dvarName, value); Utils::Hook::Call<void(const char*, const char*)>(0x4F52E0)(dvarName, value);
} }
@ -392,15 +399,29 @@ namespace Components
// un-cheat cg_fovscale and add archive flags // un-cheat cg_fovscale and add archive flags
Utils::Hook::Xor<std::uint8_t>(0x4F8E68, Game::DVAR_CHEAT | Game::DVAR_ARCHIVE); Utils::Hook::Xor<std::uint8_t>(0x4F8E68, Game::DVAR_CHEAT | Game::DVAR_ARCHIVE);
// un-cheat cg_fovMin and add archive flags
Utils::Hook::Xor<std::uint8_t>(0x4F8E9D, Game::DVAR_CHEAT | Game::DVAR_ARCHIVE);
// un-cheat cg_debugInfoCornerOffset and add archive flags // un-cheat cg_debugInfoCornerOffset and add archive flags
Utils::Hook::Xor<std::uint8_t>(0x4F8FC2, Game::DVAR_CHEAT | Game::DVAR_ARCHIVE); Utils::Hook::Xor<std::uint8_t>(0x4F8FC2, Game::DVAR_CHEAT | Game::DVAR_ARCHIVE);
// remove archive flags for cg_hudchatposition // un-cheat cg_drawGun
Utils::Hook::Set<std::uint8_t>(0x4F8DC6, Game::DVAR_NONE);
// un-cheat cg_draw2D
Utils::Hook::Set<std::uint8_t>(0x4F8EEE, Game::DVAR_NONE);
// remove archive flags for cg_hudChatPosition
Utils::Hook::Xor<std::uint8_t>(0x4F9992, Game::DVAR_ARCHIVE); Utils::Hook::Xor<std::uint8_t>(0x4F9992, Game::DVAR_ARCHIVE);
// remove write protection from fs_game // remove write protection from fs_game
Utils::Hook::Xor<std::uint32_t>(0x6431EA, Game::DVAR_INIT); 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 // set cg_fov max to 160.0
// because that's the max on SP // because that's the max on SP
static float cg_Fov = 160.0f; static float cg_Fov = 160.0f;

View File

@ -5,15 +5,6 @@ namespace Components
class Dvar : public Component class Dvar : public Component
{ {
public: public:
class Flag
{
public:
Flag(Game::DvarFlags flag) : val(flag) {}
Flag(std::uint16_t flag) : Flag(static_cast<Game::DvarFlags>(flag)) {}
Game::DvarFlags val;
};
class Var class Var
{ {
public: public:
@ -44,8 +35,8 @@ namespace Components
~Dvar(); ~Dvar();
// Only strings and bools use this type of declaration // Only strings and bools use this type of declaration
template<typename T> static Var Register(const char* dvarName, T value, Flag flag, const char* description); 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, Flag 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(); static void ResetDvarsValue();

View File

@ -1,4 +1,5 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "Elevators.hpp"
namespace Components namespace Components
{ {

View File

@ -1,24 +1,17 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "Console.hpp"
#include <version.hpp>
namespace Components namespace Components
{ {
Utils::Hook Exception::SetFilterHook; Utils::Hook Exception::SetFilterHook;
int Exception::MiniDumpType; int Exception::MiniDumpType;
__declspec(noreturn) void Exception::ErrorLongJmp(jmp_buf _Buf, int _Value) __declspec(noreturn) void Exception::LongJmp_Internal_Stub(jmp_buf env, int status)
{
if (!*reinterpret_cast<DWORD*>(0x1AD7EB4))
{
TerminateProcess(GetCurrentProcess(), 1337);
}
longjmp(_Buf, _Value);
}
__declspec(noreturn) void Exception::LongJmp(jmp_buf _Buf, int _Value)
{ {
AssetHandler::ResetBypassState(); AssetHandler::ResetBypassState();
longjmp(_Buf, _Value); Game::longjmp_internal(env, status);
} }
void Exception::SuspendProcess() void Exception::SuspendProcess()
@ -74,7 +67,7 @@ namespace Components
return; return;
} }
auto lock = GlobalLock(hMem); auto* lock = GlobalLock(hMem);
if (lock != nullptr) if (lock != nullptr)
{ {
std::memcpy(lock, error.data(), error.size() + 1); std::memcpy(lock, error.data(), error.size() + 1);
@ -105,17 +98,15 @@ namespace Components
errorStr = Utils::String::VA("Fatal error (0x%08X) at 0x%08X.\nCopy exception address to clipboard?", ExceptionInfo->ExceptionRecord->ExceptionCode, ExceptionInfo->ExceptionRecord->ExceptionAddress); errorStr = Utils::String::VA("Fatal error (0x%08X) at 0x%08X.\nCopy exception address to clipboard?", ExceptionInfo->ExceptionRecord->ExceptionCode, ExceptionInfo->ExceptionRecord->ExceptionAddress);
} }
//Exception::SuspendProcess();
// Message should be copied to the keyboard if no button is pressed // Message should be copied to the keyboard if no button is pressed
if (MessageBoxA(nullptr, errorStr.data(), nullptr, MB_YESNO | MB_ICONERROR) == IDYES) if (MessageBoxA(nullptr, errorStr.data(), nullptr, MB_YESNO | MB_ICONERROR) == IDYES)
{ {
Exception::CopyMessageToClipboard(Utils::String::VA("0x%08X", ExceptionInfo->ExceptionRecord->ExceptionAddress)); CopyMessageToClipboard(Utils::String::VA("0x%08X", ExceptionInfo->ExceptionRecord->ExceptionAddress));
} }
if (Flags::HasFlag("bigminidumps")) if (Flags::HasFlag("bigminidumps"))
{ {
Exception::SetMiniDumpType(true, false); SetMiniDumpType(true, false);
} }
// Current executable name // Current executable name
@ -125,30 +116,29 @@ namespace Components
PathRemoveExtensionA(exeFileName); PathRemoveExtensionA(exeFileName);
// Generate filename // Generate filename
char filenameFriendlyTime[MAX_PATH]; char filenameFriendlyTime[MAX_PATH]{};
__time64_t time; __time64_t time;
tm ltime; tm ltime;
_time64(&time); _time64(&time);
_localtime64_s(&ltime, &time); _localtime64_s(&ltime, &time);
strftime(filenameFriendlyTime, sizeof(filenameFriendlyTime) - 1, "%Y%m%d%H%M%S", &ltime); strftime(filenameFriendlyTime, sizeof(filenameFriendlyTime) - 1, "%Y%m%d%H%M%S", &ltime);
// Combine with queuedMinidumpsFolder // Combine with queued MinidumpsFolder
char filename[MAX_PATH] = { 0 }; char filename[MAX_PATH]{};
Utils::IO::CreateDir("minidumps"); CreateDirectoryA("minidumps", nullptr);
PathCombineA(filename, "minidumps\\", Utils::String::VA("%s-" VERSION "-%s.dmp", exeFileName, filenameFriendlyTime)); PathCombineA(filename, "minidumps\\", Utils::String::VA("%s-" VERSION "-%s.dmp", exeFileName, filenameFriendlyTime));
DWORD fileShare = FILE_SHARE_READ | FILE_SHARE_WRITE; constexpr auto fileShare = FILE_SHARE_READ | FILE_SHARE_WRITE;
HANDLE hFile = CreateFileA(filename, GENERIC_WRITE | GENERIC_READ, fileShare, nullptr, (fileShare & FILE_SHARE_WRITE) > 0 ? OPEN_ALWAYS : OPEN_EXISTING, NULL, nullptr); HANDLE hFile = CreateFileA(filename, GENERIC_WRITE | GENERIC_READ, fileShare, nullptr, (fileShare & FILE_SHARE_WRITE) > 0 ? OPEN_ALWAYS : OPEN_EXISTING, NULL, nullptr);
MINIDUMP_EXCEPTION_INFORMATION ex = { GetCurrentThreadId(), ExceptionInfo, FALSE }; MINIDUMP_EXCEPTION_INFORMATION ex = { GetCurrentThreadId(), ExceptionInfo, FALSE };
if (!MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, static_cast<MINIDUMP_TYPE>(Exception::MiniDumpType), &ex, nullptr, nullptr)) if (!MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, static_cast<MINIDUMP_TYPE>(MiniDumpType), &ex, nullptr, nullptr))
{ {
MessageBoxA(nullptr, Utils::String::VA("There was an error creating the minidump (%s)! Hit OK to close the program.", Utils::GetLastWindowsError().data()), "Minidump Error", MB_OK | MB_ICONERROR); 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);
OutputDebugStringA("Failed to create new minidump!"); OutputDebugStringA("Failed to create new minidump!");
Utils::OutputDebugLastError(); Utils::OutputDebugLastError();
TerminateProcess(GetCurrentProcess(), ExceptionInfo->ExceptionRecord->ExceptionCode); TerminateProcess(GetCurrentProcess(), ExceptionInfo->ExceptionRecord->ExceptionCode);
} }
//if (ExceptionInfo->ExceptionRecord->ExceptionFlags == EXCEPTION_NONCONTINUABLE)
{ {
TerminateProcess(GetCurrentProcess(), ExceptionInfo->ExceptionRecord->ExceptionCode); TerminateProcess(GetCurrentProcess(), ExceptionInfo->ExceptionRecord->ExceptionCode);
} }
@ -156,54 +146,50 @@ namespace Components
return EXCEPTION_CONTINUE_SEARCH; return EXCEPTION_CONTINUE_SEARCH;
} }
LPTOP_LEVEL_EXCEPTION_FILTER WINAPI Exception::SetUnhandledExceptionFilterStub(LPTOP_LEVEL_EXCEPTION_FILTER)
{
Exception::SetFilterHook.uninstall();
LPTOP_LEVEL_EXCEPTION_FILTER retval = SetUnhandledExceptionFilter(&Exception::ExceptionFilter);
Exception::SetFilterHook.install();
return retval;
}
LPTOP_LEVEL_EXCEPTION_FILTER Exception::Hook()
{
return SetUnhandledExceptionFilter(&Exception::ExceptionFilter);
}
void Exception::SetMiniDumpType(bool codeseg, bool dataseg) void Exception::SetMiniDumpType(bool codeseg, bool dataseg)
{ {
Exception::MiniDumpType = MiniDumpIgnoreInaccessibleMemory; MiniDumpType = MiniDumpIgnoreInaccessibleMemory;
Exception::MiniDumpType |= MiniDumpWithHandleData; MiniDumpType |= MiniDumpWithHandleData;
Exception::MiniDumpType |= MiniDumpScanMemory; MiniDumpType |= MiniDumpScanMemory;
Exception::MiniDumpType |= MiniDumpWithProcessThreadData; MiniDumpType |= MiniDumpWithProcessThreadData;
Exception::MiniDumpType |= MiniDumpWithFullMemoryInfo; MiniDumpType |= MiniDumpWithFullMemoryInfo;
Exception::MiniDumpType |= MiniDumpWithThreadInfo; MiniDumpType |= MiniDumpWithThreadInfo;
//Exception::MiniDumpType |= MiniDumpWithModuleHeaders;
if (codeseg) if (codeseg)
{ {
Exception::MiniDumpType |= MiniDumpWithCodeSegs; MiniDumpType |= MiniDumpWithCodeSegs;
} }
if (dataseg) if (dataseg)
{ {
Exception::MiniDumpType |= MiniDumpWithDataSegs; MiniDumpType |= MiniDumpWithDataSegs;
} }
} }
LPTOP_LEVEL_EXCEPTION_FILTER WINAPI Exception::SetUnhandledExceptionFilter_Stub(LPTOP_LEVEL_EXCEPTION_FILTER)
{
SetFilterHook.uninstall();
LPTOP_LEVEL_EXCEPTION_FILTER result = ::SetUnhandledExceptionFilter(&ExceptionFilter);
SetFilterHook.install();
return result;
}
Exception::Exception() Exception::Exception()
{ {
Exception::SetMiniDumpType(Flags::HasFlag("bigminidumps"), Flags::HasFlag("reallybigminidumps")); SetMiniDumpType(Flags::HasFlag("bigminidumps"), Flags::HasFlag("reallybigminidumps"));
#if !defined(DEBUG) || defined(FORCE_EXCEPTION_HANDLER) SetFilterHook.initialize(::SetUnhandledExceptionFilter, SetUnhandledExceptionFilter_Stub, HOOK_JUMP);
Exception::SetFilterHook.initialize(SetUnhandledExceptionFilter, Exception::SetUnhandledExceptionFilterStub, HOOK_JUMP); SetFilterHook.install();
Exception::SetFilterHook.install();
SetUnhandledExceptionFilter(&Exception::ExceptionFilter); ::SetUnhandledExceptionFilter(&ExceptionFilter);
#endif
//Utils::Hook(0x4B241F, Exception::ErrorLongJmp, HOOK_CALL).install()->quick(); Utils::Hook(0x4B241F, LongJmp_Internal_Stub, HOOK_CALL).install()->quick();
Utils::Hook(0x6B8898, Exception::LongJmp, HOOK_JUMP).install()->quick(); Utils::Hook(0x61DB44, LongJmp_Internal_Stub, HOOK_CALL).install()->quick();
Utils::Hook(0x61F17D, LongJmp_Internal_Stub, HOOK_CALL).install()->quick();
Utils::Hook(0x61F248, LongJmp_Internal_Stub, HOOK_CALL).install()->quick();
Utils::Hook(0x61F5E7, LongJmp_Internal_Stub, HOOK_CALL).install()->quick();
#ifdef _DEBUG #ifdef MAP_TEST
Command::Add("mapTest", [](Command::Params* params) Command::Add("mapTest", [](Command::Params* params)
{ {
Game::UI_UpdateArenas(); Game::UI_UpdateArenas();
@ -225,6 +211,6 @@ namespace Components
Exception::~Exception() Exception::~Exception()
{ {
Exception::SetFilterHook.uninstall(); SetFilterHook.uninstall();
} }
} }

View File

@ -15,12 +15,12 @@ namespace Components
private: private:
static void SuspendProcess(); static void SuspendProcess();
static LONG WINAPI ExceptionFilter(LPEXCEPTION_POINTERS ExceptionInfo); static LONG WINAPI ExceptionFilter(LPEXCEPTION_POINTERS ExceptionInfo);
static LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFilterStub(LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter); static __declspec(noreturn) void LongJmp_Internal_Stub(jmp_buf env, int status);
static __declspec(noreturn) void ErrorLongJmp(jmp_buf _Buf, int _Value);
static __declspec(noreturn) void LongJmp(jmp_buf _Buf, int _Value);
static void CopyMessageToClipboard(const std::string& error); static void CopyMessageToClipboard(const std::string& error);
static LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFilter_Stub(LPTOP_LEVEL_EXCEPTION_FILTER);
static int MiniDumpType; static int MiniDumpType;
static Utils::Hook SetFilterHook; static Utils::Hook SetFilterHook;
}; };

View File

@ -235,10 +235,11 @@ namespace Components
const char* dir = Dvar::Var("fs_basepath").get<const char*>(); const char* dir = Dvar::Var("fs_basepath").get<const char*>();
std::vector<std::string> paths; std::vector<std::string> paths;
std::string modDir = Dvar::Var("fs_game").get<std::string>(); auto modDir = Dvar::Var("fs_game").get<std::string>();
if ((file == "mod"s || file == "mod.ff"s) && !modDir.empty()) if ((file == "mod"s || file == "mod.ff"s) && !modDir.empty())
{ {
paths.push_back(Utils::String::VA("%s\\", modDir.data())); paths.push_back(std::format("{}\\", modDir));
} }
if (Utils::String::StartsWith(file, "mp_")) if (Utils::String::StartsWith(file, "mp_"))
@ -256,17 +257,17 @@ namespace Components
Utils::String::Replace(zone, "_load", ""); Utils::String::Replace(zone, "_load", "");
} }
if (Utils::IO::FileExists(Utils::String::VA("usermaps\\%s\\%s.ff", zone.data(), filename.data()))) if (Utils::IO::FileExists(std::format("usermaps\\{}\\{}.ff", zone, filename)))
{ {
return Utils::String::VA("usermaps\\%s\\", zone.data()); return Utils::String::Format("usermaps\\{}\\", zone);
} }
} }
Utils::Merge(&paths, FastFiles::ZonePaths); Utils::Merge(&paths, FastFiles::ZonePaths);
for (auto &path : paths) for (auto& path : paths)
{ {
std::string absoluteFile = Utils::String::VA("%s\\%s%s", dir, path.data(), file); auto absoluteFile = std::format("{}\\{}{}", dir, path, file);
// No ".ff" appended, append it manually // No ".ff" appended, append it manually
if (!Utils::String::EndsWith(absoluteFile, ".ff")) if (!Utils::String::EndsWith(absoluteFile, ".ff"))
@ -277,11 +278,11 @@ namespace Components
// Check if FastFile exists // Check if FastFile exists
if (Utils::IO::FileExists(absoluteFile)) if (Utils::IO::FileExists(absoluteFile))
{ {
return Utils::String::VA("%s", path.data()); return Utils::String::Format("{}", path);
} }
} }
return Utils::String::VA("zone\\%s\\", Game::Win_GetLanguage()); return Utils::String::Format("zone\\{}\\", Game::Win_GetLanguage());
} }
void FastFiles::AddZonePath(const std::string& path) void FastFiles::AddZonePath(const std::string& path)

View File

@ -78,19 +78,19 @@ namespace Components
Utils::Memory::Allocator allocator; Utils::Memory::Allocator allocator;
if (!this->exists()) return std::string(); if (!this->exists()) return std::string();
int position = Game::FS_FTell(this->handle); const auto position = Game::FS_FTell(this->handle);
this->seek(0, Game::FS_SEEK_SET); this->seek(0, Game::FS_SEEK_SET);
char* buffer = allocator.allocateArray<char>(this->size); char* buffer = allocator.allocateArray<char>(this->size);
if (!this->read(buffer, this->size)) if (!this->read(buffer, this->size))
{ {
this->seek(position, Game::FS_SEEK_SET); this->seek(position, Game::FS_SEEK_SET);
return std::string(); return {};
} }
this->seek(position, Game::FS_SEEK_SET); this->seek(position, Game::FS_SEEK_SET);
return std::string(buffer, this->size); return {buffer, static_cast<std::size_t>(this->size)};
} }
bool FileSystem::FileReader::read(void* buffer, size_t _size) bool FileSystem::FileReader::read(void* buffer, size_t _size)

View File

@ -13,6 +13,11 @@ namespace Components
virtual bool exists() = 0; virtual bool exists() = 0;
virtual std::string getName() = 0; virtual std::string getName() = 0;
virtual std::string& getBuffer() = 0; virtual std::string& getBuffer() = 0;
virtual explicit operator bool()
{
return this->exists();
}
}; };
class File : public AbstractFile class File : public AbstractFile

View File

@ -6,9 +6,9 @@ namespace Components
bool Flags::HasFlag(const std::string& flag) bool Flags::HasFlag(const std::string& flag)
{ {
Flags::ParseFlags(); ParseFlags();
for (const auto& entry : Flags::EnabledFlags) for (const auto& entry : EnabledFlags)
{ {
if (Utils::String::ToLower(entry) == Utils::String::ToLower(flag)) if (Utils::String::ToLower(entry) == Utils::String::ToLower(flag))
{ {
@ -40,7 +40,7 @@ namespace Components
if (wFlag[0] == L'-') if (wFlag[0] == L'-')
{ {
wFlag.erase(wFlag.begin()); wFlag.erase(wFlag.begin());
Flags::EnabledFlags.emplace_back(Utils::String::Convert(wFlag)); EnabledFlags.emplace_back(Utils::String::Convert(wFlag));
} }
} }
@ -48,9 +48,9 @@ namespace Components
} }
// Workaround for wine // Workaround for wine
if (Utils::IsWineEnvironment() && Dedicated::IsEnabled() && !Flags::HasFlag("console") && !Flags::HasFlag("stdout")) if (Utils::IsWineEnvironment() && Dedicated::IsEnabled() && !HasFlag("console") && !HasFlag("stdout"))
{ {
Flags::EnabledFlags.emplace_back("stdout"); EnabledFlags.emplace_back("stdout");
} }
} }
} }

View File

@ -1,4 +1,5 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "UIFeeder.hpp"
namespace Components namespace Components
{ {

View File

@ -4,6 +4,7 @@
#include "IO.hpp" #include "IO.hpp"
#include "Script.hpp" #include "Script.hpp"
#include "ScriptExtension.hpp" #include "ScriptExtension.hpp"
#include "ScriptPatches.hpp"
#include "ScriptStorage.hpp" #include "ScriptStorage.hpp"
namespace Components namespace Components
@ -14,6 +15,7 @@ namespace Components
Loader::Register(new IO()); Loader::Register(new IO());
Loader::Register(new Script()); Loader::Register(new Script());
Loader::Register(new ScriptExtension()); Loader::Register(new ScriptExtension());
Loader::Register(new ScriptPatches());
Loader::Register(new ScriptStorage()); Loader::Register(new ScriptStorage());
} }
} }

View File

@ -126,7 +126,7 @@ namespace Components
} }
} }
const auto p = "scriptdata" / std::filesystem::path(path); const auto p = "scriptdata"s / std::filesystem::path(path);
const auto folder = p.parent_path().string(); const auto folder = p.parent_path().string();
const auto file = p.filename().string(); const auto file = p.filename().string();
Game::Scr_AddInt(FileSystem::_DeleteFile(folder, file)); Game::Scr_AddInt(FileSystem::_DeleteFile(folder, file));

View File

@ -3,8 +3,8 @@
namespace Components namespace Components
{ {
std::unordered_map<std::string, Script::ScriptFunction> Script::CustomScrFunctions; std::vector<Script::ScriptFunction> Script::CustomScrFunctions;
std::unordered_map<std::string, Script::ScriptMethod> Script::CustomScrMethods; std::vector<Script::ScriptMethod> Script::CustomScrMethods;
std::string Script::ScriptName; std::string Script::ScriptName;
std::vector<std::string> Script::ScriptNameStack; std::vector<std::string> Script::ScriptNameStack;
@ -15,29 +15,21 @@ namespace Components
std::unordered_map<const char*, const char*> Script::ReplacedFunctions; std::unordered_map<const char*, const char*> Script::ReplacedFunctions;
const char* Script::ReplacedPos = nullptr; const char* Script::ReplacedPos = nullptr;
std::vector<int> Script::ScriptMainHandles; std::unordered_map<std::string, int> Script::ScriptMainHandles;
std::vector<int> Script::ScriptInitHandles; std::unordered_map<std::string, int> Script::ScriptInitHandles;
void Script::ShowDeprecationWarning()
{
Toast::Show("cardicon_gumby", "WARNING!", "You are using deprecated HttpGet/HttpCancel GSC function.", 2048);
Logger::Print(Game::CON_CHANNEL_SCRIPT, "*** DEPRECATION WARNING ***\n");
Logger::PrintError(Game::CON_CHANNEL_ERROR, "Attempted to execute deprecated built-in HttpGet/HttpCancel! These functions have been deemed unsafe and are scheduled for removal. Please update your mod!\n");
Logger::Print(Game::CON_CHANNEL_SCRIPT, "***************************\n");
}
void Script::FunctionError() void Script::FunctionError()
{ {
const auto* funcName = Game::SL_ConvertToString(Script::FunctionName); const auto* funcName = Game::SL_ConvertToString(FunctionName);
Game::Scr_ShutdownAllocNode(); Game::Scr_ShutdownAllocNode();
Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "\n"); Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "\n");
Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "******* script compile error *******\n"); Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "******* script compile error *******\n");
Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "Error: unknown function {} in {}\n", funcName, Script::ScriptName); Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "Error: unknown function {} in {}\n", funcName, ScriptName);
Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "************************************\n"); Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "************************************\n");
Logger::Error(Game::ERR_SCRIPT_DROP, "script compile error\nunknown function {}\n{}\n\n", funcName, Script::ScriptName); Logger::Error(Game::ERR_SCRIPT_DROP, "script compile error\nunknown function {}\n{}\n\n", funcName, ScriptName);
} }
__declspec(naked) void Script::StoreFunctionNameStub() __declspec(naked) void Script::StoreFunctionNameStub()
@ -45,7 +37,7 @@ namespace Components
__asm __asm
{ {
mov eax, [esp - 8h] mov eax, [esp - 8h]
mov Script::FunctionName, ax mov FunctionName, ax
sub esp, 0Ch sub esp, 0Ch
push 0 push 0
@ -87,12 +79,12 @@ namespace Components
void Script::StoreScriptName(const char* name) void Script::StoreScriptName(const char* name)
{ {
Script::ScriptNameStack.push_back(Script::ScriptName); ScriptNameStack.push_back(ScriptName);
Script::ScriptName = name; ScriptName = name;
if (!Utils::String::EndsWith(Script::ScriptName, ".gsc")) if (!Utils::String::EndsWith(ScriptName, ".gsc"))
{ {
Script::ScriptName.append(".gsc"); ScriptName.append(".gsc");
} }
} }
@ -105,7 +97,7 @@ namespace Components
lea ecx, [esp + 30h] lea ecx, [esp + 30h]
push ecx push ecx
call Script::StoreScriptName call StoreScriptName
add esp, 4h add esp, 4h
popad popad
@ -120,8 +112,8 @@ namespace Components
void Script::RestoreScriptName() void Script::RestoreScriptName()
{ {
Script::ScriptName = Script::ScriptNameStack.back(); ScriptName = ScriptNameStack.back();
Script::ScriptNameStack.pop_back(); ScriptNameStack.pop_back();
} }
__declspec(naked) void Script::RestoreScriptNameStub() __declspec(naked) void Script::RestoreScriptNameStub()
@ -129,7 +121,7 @@ namespace Components
__asm __asm
{ {
pushad pushad
call Script::RestoreScriptName call RestoreScriptName
popad popad
mov ds:1CDEAA8h, ebp mov ds:1CDEAA8h, ebp
@ -205,17 +197,19 @@ namespace Components
Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "\n"); Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "\n");
Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "******* script compile error *******\n"); Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "******* script compile error *******\n");
Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "Error: {} ", msgbuf); Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "Error: {} ", msgbuf);
Script::PrintSourcePos(Script::ScriptName.data(), offset); PrintSourcePos(ScriptName.data(), offset);
Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "************************************\n\n"); Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "************************************\n\n");
Logger::Error(Game::ERR_SCRIPT_DROP, "script compile error\n{}\n{}\n(see console for actual details)\n", msgbuf, Script::ScriptName); Logger::Error(Game::ERR_SCRIPT_DROP, "script compile error\n{}\n{}\n(see console for actual details)\n", msgbuf, ScriptName);
} }
void Script::Scr_LoadGameType_Stub() void Script::Scr_LoadGameType_Stub()
{ {
for (const auto& handle : Script::ScriptMainHandles) for (const auto& handle : ScriptMainHandles)
{ {
const auto id = Game::Scr_ExecThread(handle, 0); Logger::Print("Executing '{}::main'\n", handle.first.data());
const auto id = Game::Scr_ExecThread(handle.second, 0);
Game::Scr_FreeThread(static_cast<std::uint16_t>(id)); Game::Scr_FreeThread(static_cast<std::uint16_t>(id));
} }
@ -224,9 +218,11 @@ namespace Components
void Script::Scr_StartupGameType_Stub() void Script::Scr_StartupGameType_Stub()
{ {
for (const auto& handle : Script::ScriptInitHandles) for (const auto& handle : ScriptInitHandles)
{ {
const auto id = Game::Scr_ExecThread(handle, 0); Logger::Print("Executing '{}::init'\n", handle.first.data());
const auto id = Game::Scr_ExecThread(handle.second, 0);
Game::Scr_FreeThread(static_cast<std::uint16_t>(id)); Game::Scr_FreeThread(static_cast<std::uint16_t>(id));
} }
@ -237,8 +233,8 @@ namespace Components
void Script::GScr_LoadGameTypeScript_Stub() void Script::GScr_LoadGameTypeScript_Stub()
{ {
// Clear handles (from previous GSC loading session) // Clear handles (from previous GSC loading session)
Script::ScriptMainHandles.clear(); ScriptMainHandles.clear();
Script::ScriptInitHandles.clear(); ScriptInitHandles.clear();
char path[MAX_PATH]{}; char path[MAX_PATH]{};
@ -262,18 +258,17 @@ namespace Components
} }
Logger::Print("Script {}.gsc loaded successfully.\n", path); Logger::Print("Script {}.gsc loaded successfully.\n", path);
Logger::Debug("Finding script handle main or init...");
const auto initHandle = Game::Scr_GetFunctionHandle(path, "init"); const auto initHandle = Game::Scr_GetFunctionHandle(path, "init");
if (initHandle != 0) if (initHandle != 0)
{ {
Script::ScriptInitHandles.push_back(initHandle); ScriptInitHandles.insert_or_assign(path, initHandle);
} }
const auto mainHandle = Game::Scr_GetFunctionHandle(path, "main"); const auto mainHandle = Game::Scr_GetFunctionHandle(path, "main");
if (mainHandle != 0) if (mainHandle != 0)
{ {
Script::ScriptMainHandles.push_back(mainHandle); ScriptMainHandles.insert_or_assign(path, mainHandle);
} }
// Allow scripts with no handles // Allow scripts with no handles
@ -283,43 +278,74 @@ namespace Components
Game::GScr_LoadGameTypeScript(); Game::GScr_LoadGameTypeScript();
} }
void Script::AddFunction(const std::string& name, Game::BuiltinFunction func, bool type) void Script::AddFunction(const std::string& name, const Game::BuiltinFunction func, const bool type)
{ {
Script::ScriptFunction toAdd; ScriptFunction toAdd;
toAdd.actionFunc = func; toAdd.actionFunc = func;
toAdd.type = type; toAdd.type = type;
toAdd.aliases.push_back({Utils::String::ToLower(name)});
CustomScrFunctions.insert_or_assign(Utils::String::ToLower(name), toAdd); CustomScrFunctions.emplace_back(toAdd);
} }
void Script::AddMethod(const std::string& name, Game::BuiltinMethod func, bool type) void Script::AddMethod(const std::string& name, const Game::BuiltinMethod func, const bool type)
{ {
Script::ScriptMethod toAdd; ScriptMethod toAdd;
toAdd.actionFunc = func; toAdd.actionFunc = func;
toAdd.type = type; toAdd.type = type;
toAdd.aliases.push_back({Utils::String::ToLower(name)});
CustomScrMethods.insert_or_assign(Utils::String::ToLower(name), toAdd); CustomScrMethods.emplace_back(toAdd);
}
void Script::AddFuncMultiple(Game::BuiltinFunction func, bool type, scriptNames aliases)
{
ScriptFunction toAdd;
auto aliasesToAdd = Utils::String::ApplyToLower(aliases);
toAdd.actionFunc = func;
toAdd.type = type;
toAdd.aliases = std::move(aliasesToAdd);
CustomScrFunctions.emplace_back(toAdd);
}
void Script::AddMethMultiple(Game::BuiltinMethod func, bool type, scriptNames aliases)
{
ScriptMethod toAdd;
auto aliasesToAdd = Utils::String::ApplyToLower(aliases);
toAdd.actionFunc = func;
toAdd.type = type;
toAdd.aliases = std::move(aliasesToAdd);
CustomScrMethods.emplace_back(toAdd);
} }
Game::BuiltinFunction Script::BuiltIn_GetFunctionStub(const char** pName, int* type) Game::BuiltinFunction Script::BuiltIn_GetFunctionStub(const char** pName, int* type)
{ {
if (pName != nullptr) if (pName != nullptr)
{ {
// If no function was found let's call game's function const auto name = Utils::String::ToLower(*pName);
if (const auto itr = Script::CustomScrFunctions.find(Utils::String::ToLower(*pName)); itr != Script::CustomScrFunctions.end()) for (const auto& func : CustomScrFunctions)
{ {
*type = itr->second.type; if (Utils::Contains(&func.aliases, name))
return itr->second.actionFunc; {
*type = func.type;
return func.actionFunc;
}
} }
} }
else else
{ {
for (const auto& [name, builtin] : Script::CustomScrFunctions) for (const auto& func : CustomScrFunctions)
{ {
Game::Scr_RegisterFunction(reinterpret_cast<int>(builtin.actionFunc), name.data()); const auto& name = func.aliases.at(0);
Game::Scr_RegisterFunction(reinterpret_cast<int>(func.actionFunc), name.data());
} }
} }
// If no function was found let's call game's function
return Utils::Hook::Call<Game::BuiltinFunction(const char**, int*)>(0x5FA2B0)(pName, type); // BuiltIn_GetFunction return Utils::Hook::Call<Game::BuiltinFunction(const char**, int*)>(0x5FA2B0)(pName, type); // BuiltIn_GetFunction
} }
@ -327,27 +353,32 @@ namespace Components
{ {
if (pName != nullptr) if (pName != nullptr)
{ {
// If no method was found let's call game's function const auto name = Utils::String::ToLower(*pName);
if (const auto itr = Script::CustomScrMethods.find(Utils::String::ToLower(*pName)); itr != Script::CustomScrMethods.end()) for (const auto& meth : CustomScrMethods)
{ {
*type = itr->second.type; if (Utils::Contains(&meth.aliases, name))
return itr->second.actionFunc; {
*type = meth.type;
return meth.actionFunc;
}
} }
} }
else else
{ {
for (const auto& [name, builtin] : Script::CustomScrMethods) for (const auto& meth : CustomScrMethods)
{ {
Game::Scr_RegisterFunction(reinterpret_cast<int>(builtin.actionFunc), name.data()); const auto& name = meth.aliases.at(0);
Game::Scr_RegisterFunction(reinterpret_cast<int>(meth.actionFunc), name.data());
} }
} }
// If no method was found let's call game's function
return Utils::Hook::Call<Game::BuiltinMethod(const char**, int*)>(0x5FA360)(pName, type); // Player_GetMethod return Utils::Hook::Call<Game::BuiltinMethod(const char**, int*)>(0x5FA360)(pName, type); // Player_GetMethod
} }
void Script::StoreScriptBaseProgramNum() void Script::StoreScriptBaseProgramNum()
{ {
Script::ScriptBaseProgramNum.insert_or_assign(Utils::Hook::Get<int>(0x1CFEEF8), Script::ScriptName); ScriptBaseProgramNum.insert_or_assign(Utils::Hook::Get<int>(0x1CFEEF8), ScriptName);
} }
void Script::Scr_PrintPrevCodePos(int scriptPos) void Script::Scr_PrintPrevCodePos(int scriptPos)
@ -355,7 +386,7 @@ namespace Components
auto bestCodePos = -1, nextCodePos = -1, offset = -1; auto bestCodePos = -1, nextCodePos = -1, offset = -1;
std::string file; std::string file;
for (const auto& [key, value] : Script::ScriptBaseProgramNum) for (const auto& [key, value] : ScriptBaseProgramNum)
{ {
const auto codePos = key; const auto codePos = key;
@ -388,7 +419,7 @@ namespace Components
__asm __asm
{ {
push esi push esi
call Script::Scr_PrintPrevCodePos call Scr_PrintPrevCodePos
add esp, 4h add esp, 4h
pop esi pop esi
@ -402,7 +433,7 @@ namespace Components
{ {
// execute our hook // execute our hook
pushad pushad
call Script::StoreScriptBaseProgramNum call StoreScriptBaseProgramNum
popad popad
// execute overwritten code caused by the jump hook // execute overwritten code caused by the jump hook
@ -452,9 +483,9 @@ namespace Components
void Script::GetReplacedPos(const char* pos) void Script::GetReplacedPos(const char* pos)
{ {
if (Script::ReplacedFunctions.contains(pos)) if (ReplacedFunctions.contains(pos))
{ {
Script::ReplacedPos = Script::ReplacedFunctions[pos]; ReplacedPos = ReplacedFunctions[pos];
} }
} }
@ -466,12 +497,12 @@ namespace Components
return; return;
} }
if (Script::ReplacedFunctions.contains(what)) if (ReplacedFunctions.contains(what))
{ {
Logger::Warning(Game::CON_CHANNEL_SCRIPT, "ReplacedFunctions already contains codePosValue for a function\n"); Logger::Warning(Game::CON_CHANNEL_SCRIPT, "ReplacedFunctions already contains codePosValue for a function\n");
} }
Script::ReplacedFunctions[what] = with; ReplacedFunctions[what] = with;
} }
__declspec(naked) void Script::VMExecuteInternalStub() __declspec(naked) void Script::VMExecuteInternalStub()
@ -481,12 +512,12 @@ namespace Components
pushad pushad
push edx push edx
call Script::GetReplacedPos call GetReplacedPos
pop edx pop edx
popad popad
cmp Script::ReplacedPos, 0 cmp ReplacedPos, 0
jne SetPos jne SetPos
movzx eax, byte ptr [edx] movzx eax, byte ptr [edx]
@ -509,8 +540,8 @@ namespace Components
retn retn
SetPos: SetPos:
mov edx, Script::ReplacedPos mov edx, ReplacedPos
mov Script::ReplacedPos, 0 mov ReplacedPos, 0
movzx eax, byte ptr [edx] movzx eax, byte ptr [edx]
inc edx inc edx
@ -521,7 +552,7 @@ namespace Components
Game::client_t* Script::GetClient(const Game::gentity_t* ent) Game::client_t* Script::GetClient(const Game::gentity_t* ent)
{ {
assert(ent != nullptr); assert(ent);
if (ent->client == nullptr) if (ent->client == nullptr)
{ {
@ -529,7 +560,7 @@ namespace Components
return nullptr; return nullptr;
} }
if (ent->s.number >= *Game::svs_clientCount) if (static_cast<std::size_t>(ent->s.number) >= Game::MAX_CLIENTS)
{ {
Game::Scr_ObjectError(Utils::String::VA("Entity %i is out of bounds", ent->s.number)); Game::Scr_ObjectError(Utils::String::VA("Entity %i is out of bounds", ent->s.number));
return nullptr; return nullptr;
@ -540,7 +571,7 @@ namespace Components
void Script::AddFunctions() void Script::AddFunctions()
{ {
Script::AddFunction("ReplaceFunc", [] // gsc: ReplaceFunc(<function>, <function>) AddFunction("ReplaceFunc", [] // gsc: ReplaceFunc(<function>, <function>)
{ {
if (Game::Scr_GetNumParam() != 2) if (Game::Scr_GetNumParam() != 2)
{ {
@ -548,14 +579,14 @@ namespace Components
return; return;
} }
const auto what = Script::GetCodePosForParam(0); const auto what = GetCodePosForParam(0);
const auto with = Script::GetCodePosForParam(1); const auto with = GetCodePosForParam(1);
Script::SetReplacedPos(what, with); SetReplacedPos(what, with);
}); });
// System time // System time
Script::AddFunction("GetSystemMilliseconds", [] // gsc: GetSystemMilliseconds() AddFunction("GetSystemMilliseconds", [] // gsc: GetSystemMilliseconds()
{ {
SYSTEMTIME time; SYSTEMTIME time;
GetSystemTime(&time); GetSystemTime(&time);
@ -564,7 +595,7 @@ namespace Components
}); });
// Executes command to the console // Executes command to the console
Script::AddFunction("Exec", [] // gsc: Exec(<string>) AddFunction("Exec", [] // gsc: Exec(<string>)
{ {
const auto str = Game::Scr_GetString(0); const auto str = Game::Scr_GetString(0);
@ -578,7 +609,7 @@ namespace Components
}); });
// Allow printing to the console even when developer is 0 // Allow printing to the console even when developer is 0
Script::AddFunction("PrintConsole", [] // gsc: PrintConsole(<string>) AddFunction("PrintConsole", [] // gsc: PrintConsole(<string>)
{ {
for (std::size_t i = 0; i < Game::Scr_GetNumParam(); ++i) for (std::size_t i = 0; i < Game::Scr_GetNumParam(); ++i)
{ {
@ -595,9 +626,9 @@ namespace Components
}); });
// PlayerCmd_AreControlsFrozen GSC function from Black Ops 2 // PlayerCmd_AreControlsFrozen GSC function from Black Ops 2
Script::AddMethod("AreControlsFrozen", [](Game::scr_entref_t entref) // Usage: self AreControlsFrozen(); AddMethod("AreControlsFrozen", [](Game::scr_entref_t entref) // Usage: self AreControlsFrozen();
{ {
const auto* ent = Game::GetPlayerEntity(entref); const auto* ent = Scr_GetPlayerEntity(entref);
Game::Scr_AddBool((ent->client->flags & Game::PLAYER_FLAG_FROZEN) != 0); Game::Scr_AddBool((ent->client->flags & Game::PLAYER_FLAG_FROZEN) != 0);
}); });
@ -605,34 +636,34 @@ namespace Components
Script::Script() Script::Script()
{ {
Utils::Hook(0x612DB0, Script::StoreFunctionNameStub, HOOK_JUMP).install()->quick(); Utils::Hook(0x612DB0, StoreFunctionNameStub, HOOK_JUMP).install()->quick();
Utils::Hook(0x427E71, Script::RestoreScriptNameStub, HOOK_JUMP).install()->quick(); Utils::Hook(0x427E71, RestoreScriptNameStub, HOOK_JUMP).install()->quick();
Utils::Hook(0x427DBC, Script::StoreScriptNameStub, HOOK_JUMP).install()->quick(); Utils::Hook(0x427DBC, StoreScriptNameStub, HOOK_JUMP).install()->quick();
Utils::Hook(0x426C2D, Script::StoreScriptBaseProgramNumStub, HOOK_JUMP).install()->quick(); Utils::Hook(0x426C2D, StoreScriptBaseProgramNumStub, HOOK_JUMP).install()->quick();
Utils::Hook(0x42281B, Script::Scr_PrintPrevCodePosStub, HOOK_JUMP).install()->quick(); Utils::Hook(0x42281B, Scr_PrintPrevCodePosStub, HOOK_JUMP).install()->quick();
Utils::Hook(0x61E3AD, Script::RuntimeError, HOOK_CALL).install()->quick(); Utils::Hook(0x61E3AD, RuntimeError, HOOK_CALL).install()->quick();
Utils::Hook(0x621976, Script::RuntimeError, HOOK_CALL).install()->quick(); Utils::Hook(0x621976, RuntimeError, HOOK_CALL).install()->quick();
Utils::Hook(0x62246E, Script::RuntimeError, HOOK_CALL).install()->quick(); Utils::Hook(0x62246E, RuntimeError, HOOK_CALL).install()->quick();
// Skip check in GScr_CheckAllowedToSetPersistentData to prevent log spam in RuntimeError. // Skip check in GScr_CheckAllowedToSetPersistentData to prevent log spam in RuntimeError.
// On IW5 the function is entirely nullsubbed // On IW5 the function is entirely nullsubbed
Utils::Hook::Set<BYTE>(0x5F8DBF, 0xEB); Utils::Hook::Set<std::uint8_t>(0x5F8DBF, 0xEB);
Utils::Hook(0x612E8D, Script::FunctionError, HOOK_CALL).install()->quick(); Utils::Hook(0x612E8D, FunctionError, HOOK_CALL).install()->quick();
Utils::Hook(0x612EA2, Script::FunctionError, HOOK_CALL).install()->quick(); Utils::Hook(0x612EA2, FunctionError, HOOK_CALL).install()->quick();
Utils::Hook(0x434260, Script::CompileError, HOOK_JUMP).install()->quick(); Utils::Hook(0x434260, CompileError, HOOK_JUMP).install()->quick();
Utils::Hook(0x48EFFE, Script::Scr_LoadGameType_Stub, HOOK_CALL).install()->quick(); Utils::Hook(0x48EFFE, Scr_LoadGameType_Stub, HOOK_CALL).install()->quick();
Utils::Hook(0x48F008, Script::Scr_StartupGameType_Stub, HOOK_CALL).install()->quick(); Utils::Hook(0x48F008, Scr_StartupGameType_Stub, HOOK_CALL).install()->quick();
Utils::Hook(0x45D44A, Script::GScr_LoadGameTypeScript_Stub, HOOK_CALL).install()->quick(); Utils::Hook(0x45D44A, GScr_LoadGameTypeScript_Stub, HOOK_CALL).install()->quick();
// Fetch custom functions // Fetch custom functions
Utils::Hook(0x44E72E, Script::BuiltIn_GetFunctionStub, HOOK_CALL).install()->quick(); // Scr_GetFunction Utils::Hook(0x44E72E, BuiltIn_GetFunctionStub, HOOK_CALL).install()->quick(); // Scr_GetFunction
Utils::Hook(0x4EC8DD, Script::BuiltIn_GetMethodStub, HOOK_CALL).install()->quick(); // Scr_GetMethod Utils::Hook(0x4EC8DD, BuiltIn_GetMethodStub, HOOK_CALL).install()->quick(); // Scr_GetMethod
Utils::Hook(0x5F41A3, Script::SetExpFogStub, HOOK_CALL).install()->quick(); Utils::Hook(0x5F41A3, SetExpFogStub, HOOK_CALL).install()->quick();
Utils::Hook(0x61E92E, Script::VMExecuteInternalStub, HOOK_JUMP).install()->quick(); Utils::Hook(0x61E92E, VMExecuteInternalStub, HOOK_JUMP).install()->quick();
Utils::Hook::Nop(0x61E933, 1); Utils::Hook::Nop(0x61E933, 1);
Scheduler::Loop([] Scheduler::Loop([]
@ -642,9 +673,9 @@ namespace Components
const auto nowMs = Game::Sys_Milliseconds(); const auto nowMs = Game::Sys_Milliseconds();
if (Script::LastFrameTime != -1) if (LastFrameTime != -1)
{ {
const auto timeTaken = (nowMs - Script::LastFrameTime) * static_cast<int>((*Game::com_timescale)->current.value); const auto timeTaken = (nowMs - LastFrameTime) * static_cast<int>((*Game::com_timescale)->current.value);
if (timeTaken >= 500) if (timeTaken >= 500)
{ {
@ -652,11 +683,11 @@ namespace Components
} }
} }
Script::LastFrameTime = nowMs; LastFrameTime = nowMs;
}, Scheduler::Pipeline::SERVER); }, Scheduler::Pipeline::SERVER);
#ifdef _DEBUG #ifdef _DEBUG
Script::AddFunction("DebugBox", [] AddFunction("DebugBox", []
{ {
const auto* message = Game::Scr_GetString(0); const auto* message = Game::Scr_GetString(0);
@ -666,14 +697,14 @@ namespace Components
} }
MessageBoxA(nullptr, message, "DEBUG", MB_OK); MessageBoxA(nullptr, message, "DEBUG", MB_OK);
}, 1); }, true);
#endif #endif
Script::AddFunctions(); AddFunctions();
Events::OnVMShutdown([] Events::OnVMShutdown([]
{ {
Script::ReplacedFunctions.clear(); ReplacedFunctions.clear();
}); });
} }
} }

View File

@ -7,30 +7,55 @@ namespace Components
public: public:
Script(); Script();
using scriptNames = std::vector<std::string>;
static void AddFunction(const std::string& name, Game::BuiltinFunction func, bool type = false); static void AddFunction(const std::string& name, Game::BuiltinFunction func, bool type = false);
static void AddMethod(const std::string& name, Game::BuiltinMethod func, bool type = false); static void AddMethod(const std::string& name, Game::BuiltinMethod func, bool type = false);
static void AddFuncMultiple(Game::BuiltinFunction func, bool type, scriptNames);
static void AddMethMultiple(Game::BuiltinMethod func, bool type, scriptNames);
static Game::client_t* GetClient(const Game::gentity_t* gentity); static Game::client_t* GetClient(const Game::gentity_t* gentity);
static const char* GetCodePosForParam(int index); static const char* GetCodePosForParam(int index);
static void ShowDeprecationWarning(); // Probably a macro 'originally' but this is fine
static Game::gentity_s* Scr_GetPlayerEntity(Game::scr_entref_t entref)
{
if (entref.classnum != 0)
{
Game::Scr_ObjectError("not an entity");
return nullptr;
}
assert(entref.entnum < Game::MAX_GENTITIES);
auto* ent = &Game::g_entities[entref.entnum];
if (ent->client == nullptr)
{
Game::Scr_ObjectError(Utils::String::VA("entity %i is not a player", entref.entnum));
return nullptr;
}
return ent;
}
private: private:
struct ScriptFunction struct ScriptFunction
{ {
Game::BuiltinFunction actionFunc; Game::BuiltinFunction actionFunc;
bool type; bool type;
scriptNames aliases;
}; };
struct ScriptMethod struct ScriptMethod
{ {
Game::BuiltinMethod actionFunc; Game::BuiltinMethod actionFunc;
bool type; bool type;
scriptNames aliases;
}; };
static std::unordered_map<std::string, ScriptFunction> CustomScrFunctions; static std::vector<ScriptFunction> CustomScrFunctions;
static std::unordered_map<std::string, ScriptMethod> CustomScrMethods; static std::vector<ScriptMethod> CustomScrMethods;
static std::string ScriptName; static std::string ScriptName;
static std::vector<std::string> ScriptNameStack; static std::vector<std::string> ScriptNameStack;
@ -38,8 +63,8 @@ namespace Components
static std::unordered_map<int, std::string> ScriptBaseProgramNum; static std::unordered_map<int, std::string> ScriptBaseProgramNum;
static int LastFrameTime; static int LastFrameTime;
static std::vector<int> ScriptMainHandles; static std::unordered_map<std::string, int> ScriptMainHandles;
static std::vector<int> ScriptInitHandles; static std::unordered_map<std::string, int> ScriptInitHandles;
static std::unordered_map<const char*, const char*> ReplacedFunctions; static std::unordered_map<const char*, const char*> ReplacedFunctions;
static const char* ReplacedPos; static const char* ReplacedPos;

View File

@ -273,30 +273,6 @@ namespace Components
}); });
} }
void ScriptExtension::Scr_TableLookupIStringByRow()
{
if (Game::Scr_GetNumParam() < 3)
{
Game::Scr_Error("USAGE: tableLookupIStringByRow( filename, rowNum, returnValueColumnNum )\n");
return;
}
const auto* fileName = Game::Scr_GetString(0);
const auto rowNum = Game::Scr_GetInt(1);
const auto returnValueColumnNum = Game::Scr_GetInt(2);
const auto* table = Game::DB_FindXAssetHeader(Game::ASSET_TYPE_STRINGTABLE, fileName).stringTable;
if (table == nullptr)
{
Game::Scr_ParamError(0, Utils::String::VA("%s does not exist\n", fileName));
return;
}
const auto* value = Game::StringTable_GetColumnValueForRow(table, rowNum, returnValueColumnNum);
Game::Scr_AddIString(value);
}
void ScriptExtension::AddEntityFields() void ScriptExtension::AddEntityFields()
{ {
AddEntityField("entityflags", Game::F_INT, AddEntityField("entityflags", Game::F_INT,
@ -330,16 +306,10 @@ namespace Components
AddEntityFields(); AddEntityFields();
AddClientFields(); AddClientFields();
// Correct builtin function pointer
Utils::Hook::Set<Game::BuiltinFunction>(0x79A90C, Scr_TableLookupIStringByRow);
Utils::Hook(0x4EC721, GScr_AddFieldsForEntityStub, HOOK_CALL).install()->quick(); // GScr_AddFieldsForEntity Utils::Hook(0x4EC721, GScr_AddFieldsForEntityStub, HOOK_CALL).install()->quick(); // GScr_AddFieldsForEntity
Utils::Hook(0x41BED2, Scr_SetObjectFieldStub, HOOK_CALL).install()->quick(); // SetEntityFieldValue Utils::Hook(0x41BED2, Scr_SetObjectFieldStub, HOOK_CALL).install()->quick(); // SetEntityFieldValue
Utils::Hook(0x5FBF01, Scr_SetClientFieldStub, HOOK_CALL).install()->quick(); // Scr_SetObjectField Utils::Hook(0x5FBF01, Scr_SetClientFieldStub, HOOK_CALL).install()->quick(); // Scr_SetObjectField
Utils::Hook(0x4FF413, Scr_GetEntityFieldStub, HOOK_CALL).install()->quick(); // Scr_GetObjectField Utils::Hook(0x4FF413, Scr_GetEntityFieldStub, HOOK_CALL).install()->quick(); // Scr_GetObjectField
// Fix format string in Scr_RandomFloatRange
Utils::Hook::Set<const char*>(0x5F10C6, "Scr_RandomFloatRange parms: %f %f ");
} }
} }

View File

@ -27,6 +27,5 @@ namespace Components
static void AddMethods(); static void AddMethods();
static void AddEntityFields(); static void AddEntityFields();
static void AddClientFields(); static void AddClientFields();
static void Scr_TableLookupIStringByRow();
}; };
} }

View File

@ -0,0 +1,66 @@
#include <STDInclude.hpp>
#include "ScriptPatches.hpp"
#include "Script.hpp"
using namespace Utils::String;
namespace Components
{
constexpr auto offset = 511;
Game::game_hudelem_s* ScriptPatches::HECmd_GetHudElem(Game::scr_entref_t entref)
{
if (entref.classnum != 1)
{
Game::Scr_ObjectError("not a hud element");
return nullptr;
}
assert(entref.entnum < 1024);
return &Game::g_hudelems[entref.entnum];
}
void ScriptPatches::Scr_TableLookupIStringByRow_Hk()
{
if (Game::Scr_GetNumParam() < 3)
{
Game::Scr_Error("USAGE: tableLookupIStringByRow( filename, rowNum, returnValueColumnNum )\n");
return;
}
const auto* fileName = Game::Scr_GetString(0);
const auto rowNum = Game::Scr_GetInt(1);
const auto returnValueColumnNum = Game::Scr_GetInt(2);
const auto* table = Game::DB_FindXAssetHeader(Game::ASSET_TYPE_STRINGTABLE, fileName).stringTable;
if (table == nullptr)
{
Game::Scr_ParamError(0, Utils::String::VA("%s does not exist\n", fileName));
return;
}
const auto* value = Game::StringTable_GetColumnValueForRow(table, rowNum, returnValueColumnNum);
Game::Scr_AddIString(value);
}
ScriptPatches::ScriptPatches()
{
// Fix format string in Scr_RandomFloatRange
Utils::Hook::Set<const char*>(0x5F10C6, "Scr_RandomFloatRange parms: %f %f ");
// Correct builtin function pointer
Utils::Hook::Set<Game::BuiltinFunction>(0x79A90C, Scr_TableLookupIStringByRow_Hk);
Script::AddMethod("ClearHudText", [](Game::scr_entref_t entref) -> void
{
auto* hud = HECmd_GetHudElem(entref);
// Frees config string up
if ((hud->elem).text)
{
Game::SV_SetConfigstring((hud->elem).text + offset, nullptr);
}
});
}
}

View File

@ -0,0 +1,15 @@
#pragma once
namespace Components
{
class ScriptPatches : public Component
{
public:
ScriptPatches();
private:
static Game::game_hudelem_s* HECmd_GetHudElem(Game::scr_entref_t entref);
static void Scr_TableLookupIStringByRow_Hk();
};
}

View File

@ -86,6 +86,27 @@ namespace Components
FileSystem::FileWriter("scriptdata/scriptstorage.json").write(json.dump()); FileSystem::FileWriter("scriptdata/scriptstorage.json").write(json.dump());
}); });
Script::AddFunction("StorageLoad", [] // gsc: StorageLoad();
{
FileSystem::File storageFile("scriptdata/scriptstorage.json");
if (!storageFile.exists())
{
return;
}
const auto& buffer = storageFile.getBuffer();
try
{
const nlohmann::json storageDef = nlohmann::json::parse(buffer);
const auto& newData = storageDef.get<std::unordered_map<std::string, std::string>>();
Data.insert(newData.begin(), newData.end());
}
catch (const std::exception& ex)
{
Logger::PrintError(Game::CON_CHANNEL_ERROR, "Json Parse Error: {}. File {} is invalid\n", ex.what(), storageFile.getName());
}
});
Script::AddFunction("StorageClear", [] // gsc: StorageClear(); Script::AddFunction("StorageClear", [] // gsc: StorageClear();
{ {
Data.clear(); Data.clear();

View File

@ -1,4 +1,6 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "Gamepad.hpp"
#include "RawMouse.hpp"
namespace Components namespace Components
{ {
@ -749,7 +751,7 @@ namespace Components
-yawRight -yawRight
}; };
Game::cgArray[0].selectedLocationAngle = Game::AngleNormalize360(Game::vectoyaw(&vec)); Game::cgArray[0].selectedLocationAngle = Game::AngleNormalize360(Game::vectoryaw(&vec));
Game::cgArray[0].selectedAngleLocation[0] = Game::cgArray[0].selectedLocation[0]; Game::cgArray[0].selectedAngleLocation[0] = Game::cgArray[0].selectedLocation[0];
Game::cgArray[0].selectedAngleLocation[1] = Game::cgArray[0].selectedLocation[1]; Game::cgArray[0].selectedAngleLocation[1] = Game::cgArray[0].selectedLocation[1];
} }
@ -788,7 +790,7 @@ namespace Components
bool Gamepad::CG_ShouldUpdateViewAngles(const int localClientNum) bool Gamepad::CG_ShouldUpdateViewAngles(const int localClientNum)
{ {
return !Game::Key_IsKeyCatcherActive(localClientNum, Game::KEYCATCH_MASK_ANY) || Game::UI_GetActiveMenu(localClientNum) == Game::UIMENU_SCOREBOARD; return !Game::Key_IsCatcherActive(localClientNum, Game::KEYCATCH_MASK_ANY) || Game::UI_GetActiveMenu(localClientNum) == Game::UIMENU_SCOREBOARD;
} }
float Gamepad::CL_GamepadAxisValue(const int gamePadIndex, const Game::GamepadVirtualAxis virtualAxis) float Gamepad::CL_GamepadAxisValue(const int gamePadIndex, const Game::GamepadVirtualAxis virtualAxis)
@ -1096,7 +1098,7 @@ namespace Components
auto& gamePadGlobal = gamePadGlobals[gamePadIndex]; auto& gamePadGlobal = gamePadGlobals[gamePadIndex];
if (Game::Key_IsKeyCatcherActive(gamePadIndex, Game::KEYCATCH_UI)) if (Game::Key_IsCatcherActive(gamePadIndex, Game::KEYCATCH_UI))
{ {
const int scrollDelayFirst = gpad_menu_scroll_delay_first.get<int>(); const int scrollDelayFirst = gpad_menu_scroll_delay_first.get<int>();
const int scrollDelayRest = gpad_menu_scroll_delay_rest.get<int>(); const int scrollDelayRest = gpad_menu_scroll_delay_rest.get<int>();
@ -1149,7 +1151,7 @@ namespace Components
if (pressedOrUpdated && CL_CheckForIgnoreDueToRepeat(gamePadIndex, key, keyState.keys[key].repeats, time)) if (pressedOrUpdated && CL_CheckForIgnoreDueToRepeat(gamePadIndex, key, keyState.keys[key].repeats, time))
return; return;
if (Game::Key_IsKeyCatcherActive(gamePadIndex, Game::KEYCATCH_LOCATION_SELECTION) && pressedOrUpdated) if (Game::Key_IsCatcherActive(gamePadIndex, Game::KEYCATCH_LOCATION_SELECTION) && pressedOrUpdated)
{ {
if (key == Game::K_BUTTON_B || keyState.keys[key].binding && strcmp(keyState.keys[key].binding, "+actionslot 4") == 0) if (key == Game::K_BUTTON_B || keyState.keys[key].binding && strcmp(keyState.keys[key].binding, "+actionslot 4") == 0)
{ {
@ -1176,7 +1178,7 @@ namespace Components
char cmd[1024]; char cmd[1024];
if (pressedOrUpdated) if (pressedOrUpdated)
{ {
if (Game::Key_IsKeyCatcherActive(gamePadIndex, Game::KEYCATCH_UI)) if (Game::Key_IsCatcherActive(gamePadIndex, Game::KEYCATCH_UI))
{ {
UI_GamepadKeyEvent(gamePadIndex, key, pressedOrUpdated); UI_GamepadKeyEvent(gamePadIndex, key, pressedOrUpdated);
return; return;
@ -1203,7 +1205,7 @@ namespace Components
Game::Cbuf_AddText(gamePadIndex, cmd); Game::Cbuf_AddText(gamePadIndex, cmd);
} }
if (Game::Key_IsKeyCatcherActive(gamePadIndex, Game::KEYCATCH_UI)) if (Game::Key_IsCatcherActive(gamePadIndex, Game::KEYCATCH_UI))
{ {
UI_GamepadKeyEvent(gamePadIndex, key, pressedOrUpdated); UI_GamepadKeyEvent(gamePadIndex, key, pressedOrUpdated);
} }
@ -1218,7 +1220,7 @@ namespace Components
gamePad.inUse = true; gamePad.inUse = true;
gpad_in_use.setRaw(true); gpad_in_use.setRaw(true);
if (Game::Key_IsKeyCatcherActive(gamePadIndex, Game::KEYCATCH_UI)) if (Game::Key_IsCatcherActive(gamePadIndex, Game::KEYCATCH_UI))
CL_GamepadResetMenuScrollTime(gamePadIndex, key, buttonEvent == Game::GPAD_BUTTON_PRESSED, time); CL_GamepadResetMenuScrollTime(gamePadIndex, key, buttonEvent == Game::GPAD_BUTTON_PRESSED, time);

View File

@ -1,102 +0,0 @@
#include <STDInclude.hpp>
namespace Components
{
unsigned int Gametypes::GetGametypeCount()
{
return *Game::gameTypeCount;
}
const char* Gametypes::GetGametypeText(unsigned int index, int)
{
if (static_cast<unsigned int>(*Game::gameTypeCount) > index)
{
return Game::SEH_StringEd_GetString(Game::gameTypes[index].uiName);
}
return "";
}
void Gametypes::SelectGametype(unsigned int index)
{
if (!*Game::gameTypeCount) return;
if (static_cast<unsigned int>(*Game::gameTypeCount) <= index) index = 0;
std::string gametype = Game::gameTypes[index].gameType;
Dvar::Var("ui_gametype").set(gametype);
//Dvar::Var("g_gametype").set(gametype);
}
void* Gametypes::BuildGametypeList(const char*, void* buffer, size_t size)
{
std::vector<std::string> gametypes;
auto pushGametype = [&](std::string gametype)
{
auto pos = gametype.find_last_of("/\\");
if (pos != std::string::npos)
{
gametype = gametype.substr(pos + 1);
}
if (Utils::String::EndsWith(gametype, ".txt"))
{
gametype = gametype.substr(0, gametype.size() - 4);
}
// No need for that :)
if (gametype == "_gametypes") return;
if (std::find(gametypes.begin(), gametypes.end(), gametype) == gametypes.end())
{
gametypes.push_back(gametype);
}
};
// Get the gametypes we can find in the filesystem
std::vector<std::string> rawGametypes = FileSystem::GetFileList("maps/mp/gametypes/", "txt");
// Get the gametypes we can find in the database
Game::DB_EnumXAssets(Game::XAssetType::ASSET_TYPE_RAWFILE, [](Game::XAssetHeader header, void* data)
{
std::string name = header.rawfile->name;
std::vector<std::string>* rawGametypes = reinterpret_cast<std::vector<std::string>*>(data);
if (Utils::String::StartsWith(name, "maps/mp/gametypes/") && Utils::String::EndsWith(name, ".txt"))
{
if (std::count(name.begin(), name.end(), '/') == 3 && std::count(name.begin(), name.end(), '\\') == 0)
{
rawGametypes->push_back(name);
}
}
}, &rawGametypes, false);
std::for_each(rawGametypes.begin(), rawGametypes.end(), pushGametype);
std::string data;
for (auto& gametype : gametypes)
{
if (Game::Scr_AddSourceBuffer(nullptr, Utils::String::VA("maps/mp/gametypes/%s.txt", gametype.data()), nullptr, false))
{
data.append(gametype);
data.append("\r\n");
}
}
// Copy to the actual buffer
std::memcpy(buffer, data.data(), std::min(size, data.size() + 1));
return (data.empty() ? nullptr : buffer);
}
Gametypes::Gametypes()
{
UIFeeder::Add(29.0f, Gametypes::GetGametypeCount, Gametypes::GetGametypeText, Gametypes::SelectGametype);
// Dynamically grab gametypes
Utils::Hook(0x5FA46C, Gametypes::BuildGametypeList, HOOK_CALL).install()->quick(); // Scr_UpdateGameTypeList
Utils::Hook(0x632155, Gametypes::BuildGametypeList, HOOK_CALL).install()->quick(); // UI_UpdateGameTypesList
}
}

View File

@ -1,17 +0,0 @@
#pragma once
namespace Components
{
class Gametypes : public Component
{
public:
Gametypes();
private:
static unsigned int GetGametypeCount();
static const char* GetGametypeText(unsigned int index, int column);
static void SelectGametype(unsigned int index);
static void* BuildGametypeList(const char* file, void* buffer, size_t size);
};
}

View File

@ -1,4 +1,5 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "Lean.hpp"
namespace Components namespace Components
{ {

View File

@ -1,4 +1,5 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "Console.hpp"
namespace Components namespace Components
{ {
@ -6,7 +7,7 @@ namespace Components
std::vector<std::string> Logger::MessageQueue; std::vector<std::string> Logger::MessageQueue;
std::vector<Network::Address> Logger::LoggingAddresses[2]; std::vector<Network::Address> Logger::LoggingAddresses[2];
std::function<void(const std::string&)> Logger::PipeCallback; void(*Logger::PipeCallback)(const std::string&) = nullptr;;
bool Logger::IsConsoleReady() bool Logger::IsConsoleReady()
{ {
@ -19,10 +20,10 @@ namespace Components
va_list va; va_list va;
va_start(va, message); va_start(va, message);
_vsnprintf_s(buf, _TRUNCATE, message, va); vsnprintf_s(buf, _TRUNCATE, message, va);
va_end(va); va_end(va);
Logger::MessagePrint(channel, {buf}); MessagePrint(channel, {buf});
} }
void Logger::MessagePrint(const int channel, const std::string& msg) void Logger::MessagePrint(const int channel, const std::string& msg)
@ -42,14 +43,14 @@ namespace Components
return; return;
} }
if (!Logger::IsConsoleReady()) if (!IsConsoleReady())
{ {
OutputDebugStringA(out.data()); OutputDebugStringA(out.data());
} }
if (!Game::Sys_IsMainThread()) if (!Game::Sys_IsMainThread())
{ {
Logger::EnqueueMessage(msg); EnqueueMessage(msg);
} }
else else
{ {
@ -57,7 +58,7 @@ namespace Components
} }
} }
void Logger::DebugInternal(std::string_view fmt, std::format_args&& args, [[maybe_unused]] const std::source_location& loc) void Logger::DebugInternal(const std::string_view& fmt, std::format_args&& args, [[maybe_unused]] const std::source_location& loc)
{ {
#ifdef LOGGER_TRACE #ifdef LOGGER_TRACE
const auto msg = std::vformat(fmt, args); const auto msg = std::vformat(fmt, args);
@ -67,17 +68,17 @@ namespace Components
const auto out = std::format("^2{}\n", msg); const auto out = std::format("^2{}\n", msg);
#endif #endif
Logger::MessagePrint(Game::CON_CHANNEL_DONT_FILTER, out); MessagePrint(Game::CON_CHANNEL_DONT_FILTER, out);
} }
void Logger::PrintInternal(int channel, std::string_view fmt, std::format_args&& args) void Logger::PrintInternal(Game::conChannel_t channel, const std::string_view& fmt, std::format_args&& args)
{ {
const auto msg = std::vformat(fmt, args); const auto msg = std::vformat(fmt, args);
Logger::MessagePrint(channel, msg); MessagePrint(channel, msg);
} }
void Logger::ErrorInternal(const Game::errorParm_t error, const std::string_view fmt, std::format_args&& args) void Logger::ErrorInternal(const Game::errorParm_t error, const std::string_view& fmt, std::format_args&& args)
{ {
#ifdef _DEBUG #ifdef _DEBUG
if (IsDebuggerPresent()) __debugbreak(); if (IsDebuggerPresent()) __debugbreak();
@ -87,53 +88,53 @@ namespace Components
Game::Com_Error(error, "%s", msg.data()); Game::Com_Error(error, "%s", msg.data());
} }
void Logger::PrintErrorInternal(int channel, std::string_view fmt, std::format_args&& args) void Logger::PrintErrorInternal(Game::conChannel_t channel, const std::string_view& fmt, std::format_args&& args)
{ {
const auto msg = "^1Error: " + std::vformat(fmt, args); const auto msg = "^1Error: " + std::vformat(fmt, args);
++(*Game::com_errorPrintsCount); ++(*Game::com_errorPrintsCount);
Logger::MessagePrint(channel, msg); MessagePrint(channel, msg);
if (*Game::cls_uiStarted != 0 && (*Game::com_fixedConsolePosition == 0)) if (Game::cls->uiStarted != 0 && (*Game::com_fixedConsolePosition == 0))
{ {
Game::CL_ConsoleFixPosition(); Game::CL_ConsoleFixPosition();
} }
} }
void Logger::WarningInternal(int channel, std::string_view fmt, std::format_args&& args) void Logger::WarningInternal(Game::conChannel_t channel, const std::string_view& fmt, std::format_args&& args)
{ {
const auto msg = "^3" + std::vformat(fmt, args); const auto msg = "^3" + std::vformat(fmt, args);
Logger::MessagePrint(channel, msg); MessagePrint(channel, msg);
} }
void Logger::Frame() void Logger::Frame()
{ {
std::unique_lock _(Logger::MessageMutex); std::unique_lock _(MessageMutex);
for (auto i = Logger::MessageQueue.begin(); i != Logger::MessageQueue.end();) for (auto i = MessageQueue.begin(); i != MessageQueue.end();)
{ {
Game::Com_PrintMessage(Game::CON_CHANNEL_DONT_FILTER, i->data(), 0); Game::Com_PrintMessage(Game::CON_CHANNEL_DONT_FILTER, i->data(), 0);
if (!Logger::IsConsoleReady()) if (!IsConsoleReady())
{ {
OutputDebugStringA(i->data()); OutputDebugStringA(i->data());
} }
i = Logger::MessageQueue.erase(i); i = MessageQueue.erase(i);
} }
} }
void Logger::PipeOutput(const std::function<void(const std::string&)>& callback) void Logger::PipeOutput(void(*callback)(const std::string&))
{ {
Logger::PipeCallback = callback; PipeCallback = callback;
} }
void Logger::PrintMessagePipe(const char* data) void Logger::PrintMessagePipe(const char* data)
{ {
if (Logger::PipeCallback) if (PipeCallback)
{ {
Logger::PipeCallback(data); PipeCallback(data);
} }
} }
@ -144,7 +145,7 @@ namespace Components
return; return;
} }
for (const auto& addr : Logger::LoggingAddresses[gLog & 1]) for (const auto& addr : LoggingAddresses[gLog & 1])
{ {
Network::SendCommand(addr, "print", data); Network::SendCommand(addr, "print", data);
} }
@ -169,20 +170,20 @@ namespace Components
} }
// Allow the network log to run even if logFile was not opened // Allow the network log to run even if logFile was not opened
Logger::NetworkLog(string, true); NetworkLog(string, true);
} }
__declspec(naked) void Logger::PrintMessage_Stub() __declspec(naked) void Logger::PrintMessage_Stub()
{ {
__asm __asm
{ {
mov eax, Logger::PipeCallback mov eax, PipeCallback
test eax, eax test eax, eax
jz returnPrint jz returnPrint
pushad pushad
push [esp + 28h] push [esp + 28h]
call Logger::PrintMessagePipe call PrintMessagePipe
add esp, 4h add esp, 4h
popad popad
retn retn
@ -191,7 +192,7 @@ namespace Components
pushad pushad
push 0 push 0
push [esp + 2Ch] push [esp + 2Ch]
call Logger::NetworkLog call NetworkLog
add esp, 8h add esp, 8h
popad popad
@ -205,8 +206,8 @@ namespace Components
void Logger::EnqueueMessage(const std::string& message) void Logger::EnqueueMessage(const std::string& message)
{ {
std::unique_lock _(Logger::MessageMutex); std::unique_lock _(MessageMutex);
Logger::MessageQueue.push_back(message); MessageQueue.push_back(message);
} }
void Logger::RedirectOSPath(const char* file, char* folder) void Logger::RedirectOSPath(const char* file, char* folder)
@ -232,7 +233,7 @@ namespace Components
push [esp + 28h] push [esp + 28h]
push [esp + 30h] push [esp + 30h]
call Logger::RedirectOSPath call RedirectOSPath
add esp, 8h add esp, 8h
@ -256,9 +257,9 @@ namespace Components
Network::Address addr(params->get(1)); Network::Address addr(params->get(1));
if (std::find(Logger::LoggingAddresses[0].begin(), Logger::LoggingAddresses[0].end(), addr) == Logger::LoggingAddresses[0].end()) if (std::find(LoggingAddresses[0].begin(), LoggingAddresses[0].end(), addr) == LoggingAddresses[0].end())
{ {
Logger::LoggingAddresses[0].push_back(addr); LoggingAddresses[0].push_back(addr);
} }
}); });
@ -266,38 +267,38 @@ namespace Components
{ {
if (params->size() < 2) return; if (params->size() < 2) return;
const auto num = atoi(params->get(1)); const auto num = std::atoi(params->get(1));
if (Utils::String::VA("%i", num) == std::string(params->get(1)) && static_cast<unsigned int>(num) < Logger::LoggingAddresses[0].size()) if (!std::strcmp(Utils::String::VA("%i", num), params->get(1)) && static_cast<unsigned int>(num) < LoggingAddresses[0].size())
{ {
auto addr = Logger::LoggingAddresses[0].begin() + num; auto addr = Logger::LoggingAddresses[0].begin() + num;
Logger::Print("Address {} removed\n", addr->getString()); Print("Address {} removed\n", addr->getString());
Logger::LoggingAddresses[0].erase(addr); LoggingAddresses[0].erase(addr);
} }
else else
{ {
Network::Address addr(params->get(1)); Network::Address addr(params->get(1));
const auto i = std::find(Logger::LoggingAddresses[0].begin(), Logger::LoggingAddresses[0].end(), addr); const auto i = std::find(LoggingAddresses[0].begin(), LoggingAddresses[0].end(), addr);
if (i != Logger::LoggingAddresses[0].end()) if (i != LoggingAddresses[0].end())
{ {
Logger::LoggingAddresses[0].erase(i); LoggingAddresses[0].erase(i);
Logger::Print("Address {} removed\n", addr.getString()); Print("Address {} removed\n", addr.getString());
} }
else else
{ {
Logger::Print("Address {} not found!\n", addr.getString()); Print("Address {} not found!\n", addr.getString());
} }
} }
}); });
Command::AddSV("log_list", [](Command::Params*) Command::AddSV("log_list", []([[maybe_unused]] Command::Params* params)
{ {
Logger::Print("# ID: Address\n"); Print("# ID: Address\n");
Logger::Print("-------------\n"); Print("-------------\n");
for (unsigned int i = 0; i < Logger::LoggingAddresses[0].size(); ++i) for (unsigned int i = 0; i < LoggingAddresses[0].size(); ++i)
{ {
Logger::Print("#{:03d}: {}\n", i, Logger::LoggingAddresses[0][i].getString()); Print("#{:03d}: {}\n", i, LoggingAddresses[0][i].getString());
} }
}); });
@ -307,9 +308,9 @@ namespace Components
const Network::Address addr(params->get(1)); const Network::Address addr(params->get(1));
if (std::find(Logger::LoggingAddresses[1].begin(), Logger::LoggingAddresses[1].end(), addr) == Logger::LoggingAddresses[1].end()) if (std::find(LoggingAddresses[1].begin(), LoggingAddresses[1].end(), addr) == LoggingAddresses[1].end())
{ {
Logger::LoggingAddresses[1].push_back(addr); LoggingAddresses[1].push_back(addr);
} }
}); });
@ -318,37 +319,37 @@ namespace Components
if (params->size() < 2) return; if (params->size() < 2) return;
const auto num = std::atoi(params->get(1)); const auto num = std::atoi(params->get(1));
if (Utils::String::VA("%i", num) == std::string(params->get(1)) && static_cast<unsigned int>(num) < Logger::LoggingAddresses[1].size()) if (!std::strcmp(Utils::String::VA("%i", num), params->get(1)) && static_cast<unsigned int>(num) < LoggingAddresses[1].size())
{ {
const auto addr = Logger::LoggingAddresses[1].begin() + num; const auto addr = LoggingAddresses[1].begin() + num;
Logger::Print("Address {} removed\n", addr->getString()); Print("Address {} removed\n", addr->getString());
Logger::LoggingAddresses[1].erase(addr); LoggingAddresses[1].erase(addr);
} }
else else
{ {
const Network::Address addr(params->get(1)); const Network::Address addr(params->get(1));
const auto i = std::find(Logger::LoggingAddresses[1].begin(), Logger::LoggingAddresses[1].end(), addr); const auto i = std::ranges::find(LoggingAddresses[1].begin(), LoggingAddresses[1].end(), addr);
if (i != Logger::LoggingAddresses[1].end()) if (i != LoggingAddresses[1].end())
{ {
Logger::LoggingAddresses[1].erase(i); LoggingAddresses[1].erase(i);
Logger::Print("Address {} removed\n", addr.getString()); Print("Address {} removed\n", addr.getString());
} }
else else
{ {
Logger::Print("Address {} not found!\n", addr.getString()); Print("Address {} not found!\n", addr.getString());
} }
} }
}); });
Command::AddSV("g_log_list", [](Command::Params*) Command::AddSV("g_log_list", [](Command::Params*)
{ {
Logger::Print("# ID: Address\n"); Print("# ID: Address\n");
Logger::Print("-------------\n"); Print("-------------\n");
for (std::size_t i = 0; i < Logger::LoggingAddresses[1].size(); ++i) for (std::size_t i = 0; i < LoggingAddresses[1].size(); ++i)
{ {
Logger::Print("#{:03d}: {}\n", i, Logger::LoggingAddresses[1][i].getString()); Print("#{:03d}: {}\n", i, LoggingAddresses[1][i].getString());
} }
}); });
} }
@ -356,30 +357,28 @@ namespace Components
Logger::Logger() Logger::Logger()
{ {
Dvar::Register<bool>("iw4x_onelog", false, Game::DVAR_LATCH | Game::DVAR_ARCHIVE, "Only write the game log to the 'userraw' OS folder"); Dvar::Register<bool>("iw4x_onelog", false, Game::DVAR_LATCH | Game::DVAR_ARCHIVE, "Only write the game log to the 'userraw' OS folder");
Utils::Hook(0x642139, Logger::BuildOSPath_Stub, HOOK_JUMP).install()->quick(); Utils::Hook(0x642139, BuildOSPath_Stub, HOOK_JUMP).install()->quick();
Logger::PipeOutput(nullptr); Scheduler::Loop(Frame, Scheduler::Pipeline::SERVER);
Scheduler::Loop(Logger::Frame, Scheduler::Pipeline::SERVER); Utils::Hook(Game::G_LogPrintf, G_LogPrintf_Hk, HOOK_JUMP).install()->quick();
Utils::Hook(Game::Com_PrintMessage, PrintMessage_Stub, HOOK_JUMP).install()->quick();
Utils::Hook(Game::G_LogPrintf, Logger::G_LogPrintf_Hk, HOOK_JUMP).install()->quick();
Utils::Hook(Game::Com_PrintMessage, Logger::PrintMessage_Stub, HOOK_JUMP).install()->quick();
if (Loader::IsPerformingUnitTests()) if (Loader::IsPerformingUnitTests())
{ {
Utils::Hook(Game::Com_Printf, Logger::Print_Stub, HOOK_JUMP).install()->quick(); Utils::Hook(Game::Com_Printf, Print_Stub, HOOK_JUMP).install()->quick();
} }
Events::OnSVInit(Logger::AddServerCommands); Events::OnSVInit(AddServerCommands);
} }
Logger::~Logger() Logger::~Logger()
{ {
Logger::LoggingAddresses[0].clear(); LoggingAddresses[0].clear();
Logger::LoggingAddresses[1].clear(); LoggingAddresses[1].clear();
std::unique_lock lock(Logger::MessageMutex); std::unique_lock lock(MessageMutex);
Logger::MessageQueue.clear(); MessageQueue.clear();
lock.unlock(); lock.unlock();
// Flush the console log // Flush the console log

View File

@ -12,90 +12,107 @@ namespace Components
static void Print_Stub(int channel, const char* message, ...); static void Print_Stub(int channel, const char* message, ...);
static void PipeOutput(const std::function<void(const std::string&)>& callback); static void PipeOutput(void(*callback)(const std::string&));
static void PrintInternal(int channel, std::string_view fmt, std::format_args&& args); static void PrintInternal(Game::conChannel_t channel, const std::string_view& fmt, std::format_args&& args);
static void ErrorInternal(Game::errorParm_t error, std::string_view fmt, std::format_args&& args); static void ErrorInternal(Game::errorParm_t error, const std::string_view& fmt, std::format_args&& args);
static void PrintErrorInternal(int channel, std::string_view fmt, std::format_args&& args); static void PrintErrorInternal(Game::conChannel_t channel, const std::string_view& fmt, std::format_args&& args);
static void WarningInternal(int channel, std::string_view fmt, std::format_args&& args); static void WarningInternal(Game::conChannel_t channel, const std::string_view& fmt, std::format_args&& args);
static void DebugInternal(std::string_view fmt, std::format_args&& args, const std::source_location& loc); static void DebugInternal(const std::string_view& fmt, std::format_args&& args, const std::source_location& loc);
static void Print(std::string_view fmt) static void Print(const std::string_view& fmt)
{ {
PrintInternal(Game::CON_CHANNEL_DONT_FILTER, fmt, std::make_format_args(0)); PrintInternal(Game::CON_CHANNEL_DONT_FILTER, fmt, std::make_format_args(0));
} }
static void Print(int channel, std::string_view fmt) static void Print(Game::conChannel_t channel, const std::string_view& fmt)
{ {
PrintInternal(channel, fmt, std::make_format_args(0)); PrintInternal(channel, fmt, std::make_format_args(0));
} }
template <typename... Args> template <typename... Args>
static void Print(std::string_view fmt, Args&&... args) static void Print(const std::string_view& fmt, Args&&... args)
{ {
(Utils::String::SanitizeFormatArgs(args), ...);
PrintInternal(Game::CON_CHANNEL_DONT_FILTER, fmt, std::make_format_args(args...)); PrintInternal(Game::CON_CHANNEL_DONT_FILTER, fmt, std::make_format_args(args...));
} }
template <typename... Args> template <typename... Args>
static void Print(int channel, std::string_view fmt, Args&&... args) static void Print(Game::conChannel_t channel, const std::string_view& fmt, Args&&... args)
{ {
(Utils::String::SanitizeFormatArgs(args), ...);
PrintInternal(channel, fmt, std::make_format_args(args...)); PrintInternal(channel, fmt, std::make_format_args(args...));
} }
static void Error(Game::errorParm_t error, std::string_view fmt) static void Error(Game::errorParm_t error, const std::string_view& fmt)
{ {
ErrorInternal(error, fmt, std::make_format_args(0)); ErrorInternal(error, fmt, std::make_format_args(0));
} }
template <typename... Args> template <typename... Args>
static void Error(Game::errorParm_t error, std::string_view fmt, Args&&... args) static void Error(Game::errorParm_t error, const std::string_view& fmt, Args&&... args)
{ {
(Utils::String::SanitizeFormatArgs(args), ...);
ErrorInternal(error, fmt, std::make_format_args(args...)); ErrorInternal(error, fmt, std::make_format_args(args...));
} }
static void Warning(int channel, std::string_view fmt) static void Warning(Game::conChannel_t channel, const std::string_view& fmt)
{ {
WarningInternal(channel, fmt, std::make_format_args(0)); WarningInternal(channel, fmt, std::make_format_args(0));
} }
template <typename... Args> template <typename... Args>
static void Warning(int channel, std::string_view fmt, Args&&... args) static void Warning(Game::conChannel_t channel, const std::string_view& fmt, Args&&... args)
{ {
(Utils::String::SanitizeFormatArgs(args), ...);
WarningInternal(channel, fmt, std::make_format_args(args...)); WarningInternal(channel, fmt, std::make_format_args(args...));
} }
static void PrintError(int channel, std::string_view fmt) static void PrintError(Game::conChannel_t channel, const std::string_view& fmt)
{ {
PrintErrorInternal(channel, fmt, std::make_format_args(0)); PrintErrorInternal(channel, fmt, std::make_format_args(0));
} }
template <typename... Args> template <typename... Args>
static void PrintError(int channel, std::string_view fmt, Args&&... args) static void PrintError(Game::conChannel_t channel, const std::string_view& fmt, Args&&... args)
{ {
(Utils::String::SanitizeFormatArgs(args), ...);
PrintErrorInternal(channel, fmt, std::make_format_args(args...)); PrintErrorInternal(channel, fmt, std::make_format_args(args...));
} }
template <typename... Args> struct FormatWithLocation
class Debug
{ {
public: std::string_view format;
Debug([[maybe_unused]] std::string_view fmt, [[maybe_unused]] const Args&... args, [[maybe_unused]] const std::source_location& loc = std::source_location::current()) std::source_location location;
FormatWithLocation(const std::string_view& fmt, std::source_location loc = std::source_location::current())
: format(fmt)
, location(std::move(loc))
{
}
FormatWithLocation(const char* fmt, std::source_location loc = std::source_location::current())
: format(fmt)
, location(std::move(loc))
{ {
#ifdef _DEBUG
DebugInternal(fmt, std::make_format_args(args...), loc);
#endif
} }
}; };
template <typename... Args> template <typename... Args>
Debug(std::string_view fmt, const Args&... args) -> Debug<Args...>; static void Debug([[maybe_unused]] const FormatWithLocation& f, [[maybe_unused]] const Args&... args)
{
#ifdef _DEBUG
(Utils::String::SanitizeFormatArgs(args), ...);
DebugInternal(f.format, std::make_format_args(args...), f.location);
#endif
}
private: private:
static std::mutex MessageMutex; static std::mutex MessageMutex;
static std::vector<std::string> MessageQueue; static std::vector<std::string> MessageQueue;
static std::vector<Network::Address> LoggingAddresses[2]; static std::vector<Network::Address> LoggingAddresses[2];
static std::function<void(const std::string&)> PipeCallback; static void(*PipeCallback)(const std::string&);
static void MessagePrint(int channel, const std::string& msg); static void MessagePrint(int channel, const std::string& msg);

View File

@ -1,4 +1,5 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "MapDump.hpp"
namespace Components namespace Components
{ {
@ -362,27 +363,8 @@ namespace Components
return image; return image;
} }
// TODO: This is still wrong.
if (image->mapType == 5 && false)
{
for (auto i = 0; i < 6; ++i)
{
IDirect3DSurface9* surface = nullptr;
image->texture.cubemap->GetCubeMapSurface(D3DCUBEMAP_FACES(i), 0, &surface);
if (surface)
{
std::string _name = Utils::String::VA("raw/mapdump/%s/textures/%s_%i.png", this->world_->baseName, image->name, i);
D3DXSaveSurfaceToFileA(_name.data(), D3DXIFF_PNG, surface, nullptr, nullptr);
surface->Release();
}
}
}
else
{
std::string _name = Utils::String::VA("raw/mapdump/%s/textures/%s.png", this->world_->baseName, image->name); std::string _name = Utils::String::VA("raw/mapdump/%s/textures/%s.png", this->world_->baseName, image->name);
D3DXSaveTextureToFileA(_name.data(), D3DXIFF_PNG, image->texture.map, nullptr); D3DXSaveTextureToFileA(_name.data(), D3DXIFF_PNG, image->texture.map, nullptr);
}
return image; return image;
} }

View File

@ -1,4 +1,5 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "MapRotation.hpp"
namespace Components namespace Components
{ {
@ -26,7 +27,7 @@ namespace Components
this->rotationEntries_.emplace_back(std::make_pair(key, value)); this->rotationEntries_.emplace_back(std::make_pair(key, value));
} }
std::size_t MapRotation::RotationData::getEntriesSize() const std::size_t MapRotation::RotationData::getEntriesSize() const noexcept
{ {
return this->rotationEntries_.size(); return this->rotationEntries_.size();
} }
@ -47,7 +48,7 @@ namespace Components
const auto& key = tokens[i]; const auto& key = tokens[i];
const auto& value = tokens[i + 1]; const auto& value = tokens[i + 1];
if (key == "map" || key == "gametype") if (key == "map"s || key == "gametype"s)
{ {
this->addEntry(key, value); this->addEntry(key, value);
} }
@ -58,10 +59,14 @@ namespace Components
} }
} }
bool MapRotation::RotationData::empty() const noexcept
{
return this->rotationEntries_.empty();
}
bool MapRotation::RotationData::contains(const std::string& key, const std::string& value) const bool MapRotation::RotationData::contains(const std::string& key, const std::string& value) const
{ {
return std::ranges::any_of(this->rotationEntries_, return std::ranges::any_of(this->rotationEntries_, [&](const auto& entry)
[&](const auto& entry)
{ {
return entry.first == key && entry.second == value; return entry.first == key && entry.second == value;
}); });
@ -74,18 +79,18 @@ namespace Components
for (const auto& [key, val] : this->rotationEntries_) for (const auto& [key, val] : this->rotationEntries_)
{ {
if (key == "map") if (key == "map"s)
{ {
mapVector.emplace_back(val); mapVector.emplace_back(val);
} }
else if (key == "gametype") else if (key == "gametype"s)
{ {
gametypeVector.emplace_back(val); gametypeVector.emplace_back(val);
} }
} }
nlohmann::json mapRotationJson = nlohmann::json auto mapRotationJson = nlohmann::json
{ {
{"maps", mapVector}, {"maps", mapVector},
{"gametypes", gametypeVector}, {"gametypes", gametypeVector},
@ -194,7 +199,7 @@ namespace Components
void MapRotation::ApplyGametype(const std::string& gametype) void MapRotation::ApplyGametype(const std::string& gametype)
{ {
assert(!gametype.empty()); assert(!gametype.empty());
Dvar::Var("g_gametype").set(gametype.data()); Dvar::Var("g_gametype").set(gametype);
} }
void MapRotation::RestartCurrentMap() void MapRotation::RestartCurrentMap()
@ -204,7 +209,7 @@ namespace Components
if (svMapname.empty()) if (svMapname.empty())
{ {
Logger::Print(Game::CON_CHANNEL_SERVER, "mapname dvar is empty! Defaulting to mp_afghan\n"); Logger::Print(Game::CON_CHANNEL_SERVER, "mapname dvar is empty! Defaulting to mp_afghan\n");
svMapname = "mp_afghan"; svMapname = "mp_afghan"s;
} }
ApplyMap(svMapname); ApplyMap(svMapname);
@ -212,7 +217,7 @@ namespace Components
void MapRotation::ApplyRotation(RotationData& rotation) void MapRotation::ApplyRotation(RotationData& rotation)
{ {
assert(rotation.getEntriesSize() != 0); assert(!rotation.empty());
// Continue to apply gametype until a map is found // Continue to apply gametype until a map is found
auto foundMap = false; auto foundMap = false;
@ -222,7 +227,7 @@ namespace Components
{ {
const auto& entry = rotation.getNextEntry(); const auto& entry = rotation.getNextEntry();
if (entry.first == "map") if (entry.first == "map"s)
{ {
Logger::Debug("Loading new map: '{}'", entry.second); Logger::Debug("Loading new map: '{}'", entry.second);
ApplyMap(entry.second); ApplyMap(entry.second);
@ -230,7 +235,7 @@ namespace Components
// Map was found so we exit the loop // Map was found so we exit the loop
foundMap = true; foundMap = true;
} }
else if (entry.first == "gametype") else if (entry.first == "gametype"s)
{ {
Logger::Debug("Applying new gametype: '{}'", entry.second); Logger::Debug("Applying new gametype: '{}'", entry.second);
ApplyGametype(entry.second); ApplyGametype(entry.second);
@ -260,7 +265,7 @@ namespace Components
Game::Dvar_SetString(*Game::sv_mapRotationCurrent, ""); Game::Dvar_SetString(*Game::sv_mapRotationCurrent, "");
if (rotationCurrent.getEntriesSize() == 0) if (rotationCurrent.empty())
{ {
Logger::Print(Game::CON_CHANNEL_SERVER, "{} is empty or contains invalid data. Restarting map\n", (*Game::sv_mapRotationCurrent)->name); Logger::Print(Game::CON_CHANNEL_SERVER, "{} is empty or contains invalid data. Restarting map\n", (*Game::sv_mapRotationCurrent)->name);
RestartCurrentMap(); RestartCurrentMap();
@ -302,7 +307,7 @@ namespace Components
} }
LoadMapRotation(); LoadMapRotation();
if (DedicatedRotation.getEntriesSize() == 0) if (DedicatedRotation.empty())
{ {
Logger::Print(Game::CON_CHANNEL_SERVER, "{} is empty or contains invalid data. Restarting map\n", (*Game::sv_mapRotation)->name); Logger::Print(Game::CON_CHANNEL_SERVER, "{} is empty or contains invalid data. Restarting map\n", (*Game::sv_mapRotation)->name);
RestartCurrentMap(); RestartCurrentMap();
@ -339,7 +344,7 @@ namespace Components
} }
catch (const std::exception& ex) catch (const std::exception& ex)
{ {
Logger::PrintError(Game::CON_CHANNEL_ERROR, "{}: parsing of 'normal' failed", ex.what()); Logger::PrintError(Game::CON_CHANNEL_ERROR, "{}: parsing of 'normal' failed\n", ex.what());
return false; return false;
} }

View File

@ -30,11 +30,12 @@ namespace Components
// this method should be called to add a new entry (gamemode/map & value) // this method should be called to add a new entry (gamemode/map & value)
void addEntry(const std::string& key, const std::string& value); void addEntry(const std::string& key, const std::string& value);
[[nodiscard]] std::size_t getEntriesSize() const; [[nodiscard]] std::size_t getEntriesSize() const noexcept;
rotationEntry& getNextEntry(); rotationEntry& getNextEntry();
void parse(const std::string& data); void parse(const std::string& data);
[[nodiscard]] bool empty() const noexcept;
[[nodiscard]] bool contains(const std::string& key, const std::string& value) const; [[nodiscard]] bool contains(const std::string& key, const std::string& value) const;
[[nodiscard]] nlohmann::json to_json() const; [[nodiscard]] nlohmann::json to_json() const;

View File

@ -1,4 +1,8 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "RawFiles.hpp"
#include "StartupMessages.hpp"
#include "Theatre.hpp"
#include "UIFeeder.hpp"
namespace Components namespace Components
{ {
@ -29,8 +33,8 @@ namespace Components
{ {
if (this->isValid() && !this->searchPath.iwd) if (this->isValid() && !this->searchPath.iwd)
{ {
std::string iwdName = Utils::String::VA("%s.iwd", this->mapname.data()); auto iwdName = std::format("{}.iwd", this->mapname);
std::string path = Utils::String::VA("%s\\usermaps\\%s\\%s", Dvar::Var("fs_basepath").get<const char*>(), this->mapname.data(), iwdName.data()); auto path = std::format("{}\\usermaps\\{}\\{}", Dvar::Var("fs_basepath").get<std::string>(), this->mapname, iwdName);
if (Utils::IO::FileExists(path)) if (Utils::IO::FileExists(path))
{ {
@ -146,10 +150,10 @@ namespace Components
team.allocFlags = zoneInfo->allocFlags; team.allocFlags = zoneInfo->allocFlags;
team.freeFlags = zoneInfo->freeFlags; team.freeFlags = zoneInfo->freeFlags;
team.name = allocator.duplicateString(Utils::String::VA("iw4x_team_%s", teams.first.data())); team.name = allocator.duplicateString(std::format("iw4x_team_{}", teams.first));
data.push_back(team); data.push_back(team);
team.name = allocator.duplicateString(Utils::String::VA("iw4x_team_%s", teams.second.data())); team.name = allocator.duplicateString(std::format("iw4x_team_{}", teams.second));
data.push_back(team); data.push_back(team);
} }
@ -166,7 +170,7 @@ namespace Components
} }
// Load patch files // Load patch files
std::string patchZone = Utils::String::VA("patch_%s", zoneInfo->name); auto patchZone = std::format("patch_{}", zoneInfo->name);
if (FastFiles::Exists(patchZone)) if (FastFiles::Exists(patchZone))
{ {
data.push_back({patchZone.data(), zoneInfo->allocFlags, zoneInfo->freeFlags}); data.push_back({patchZone.data(), zoneInfo->allocFlags, zoneInfo->freeFlags});
@ -301,7 +305,7 @@ namespace Components
Game::GfxWorld* world = *reinterpret_cast<Game::GfxWorld**>(0x66DEE94); Game::GfxWorld* world = *reinterpret_cast<Game::GfxWorld**>(0x66DEE94);
if (FileSystem::File(Utils::String::VA("sun/%s.sun", Maps::CurrentMainZone.data())).exists()) if (FileSystem::File(std::format("sun/{}.sun", Maps::CurrentMainZone)))
{ {
Game::R_LoadSunThroughDvars(Maps::CurrentMainZone.data(), &world->sun); Game::R_LoadSunThroughDvars(Maps::CurrentMainZone.data(), &world->sun);
} }
@ -404,13 +408,13 @@ namespace Components
unsigned int Maps::GetUsermapHash(const std::string& map) unsigned int Maps::GetUsermapHash(const std::string& map)
{ {
if (Utils::IO::DirectoryExists(Utils::String::VA("usermaps/%s", map.data()))) if (Utils::IO::DirectoryExists(std::format("usermaps/{}", map)))
{ {
std::string hash; std::string hash;
for (std::size_t i = 0; i < ARRAYSIZE(Maps::UserMapFiles); ++i) for (std::size_t i = 0; i < ARRAYSIZE(Maps::UserMapFiles); ++i)
{ {
std::string filePath = Utils::String::VA("usermaps/%s/%s%s", map.data(), map.data(), Maps::UserMapFiles[i]); auto filePath = std::format("usermaps/{}/{}{}", map, map, Maps::UserMapFiles[i]);
if (Utils::IO::FileExists(filePath)) if (Utils::IO::FileExists(filePath))
{ {
hash.append(Utils::Cryptography::SHA256::Compute(Utils::IO::ReadFile(filePath))); hash.append(Utils::Cryptography::SHA256::Compute(Utils::IO::ReadFile(filePath)));
@ -564,13 +568,13 @@ namespace Components
bool Maps::IsCustomMap() bool Maps::IsCustomMap()
{ {
Game::GfxWorld*& gameWorld = *reinterpret_cast<Game::GfxWorld**>(0x66DEE94); Game::GfxWorld*& gameWorld = *reinterpret_cast<Game::GfxWorld**>(0x66DEE94);
if(gameWorld) return gameWorld->checksum == 0xDEADBEEF; if (gameWorld) return (gameWorld->checksum & 0xFFFF0000) == 0xC0D40000;
return false; return false;
} }
bool Maps::IsUserMap(const std::string& mapname) bool Maps::IsUserMap(const std::string& mapname)
{ {
return Utils::IO::DirectoryExists(Utils::String::VA("usermaps/%s", mapname.data())) && Utils::IO::FileExists(Utils::String::VA("usermaps/%s/%s.ff", mapname.data(), mapname.data())); return Utils::IO::DirectoryExists(std::format("usermaps/{}", mapname)) && Utils::IO::FileExists(std::format("usermaps/{}/{}.ff", mapname, mapname));
} }
Game::XAssetEntry* Maps::GetAssetEntryPool() Game::XAssetEntry* Maps::GetAssetEntryPool()
@ -619,7 +623,7 @@ namespace Components
for (unsigned int i = 0; i < gameWorld->dpvs.smodelCount; ++i) for (unsigned int i = 0; i < gameWorld->dpvs.smodelCount; ++i)
{ {
if (gameWorld->dpvs.smodelDrawInsts[i].model->name == model) if (model == "all"s || gameWorld->dpvs.smodelDrawInsts[i].model->name == model)
{ {
gameWorld->dpvs.smodelVisData[0][i] = 0; gameWorld->dpvs.smodelVisData[0][i] = 0;
gameWorld->dpvs.smodelVisData[1][i] = 0; gameWorld->dpvs.smodelVisData[1][i] = 0;
@ -641,86 +645,6 @@ namespace Components
} }
} }
Game::dvar_t* Maps::GetDistortionDvar()
{
Game::dvar_t*& r_distortion = *reinterpret_cast<Game::dvar_t**>(0x69F0DCC);
if(Maps::IsCustomMap())
{
static Game::dvar_t noDistortion;
ZeroMemory(&noDistortion, sizeof noDistortion);
return &noDistortion;
}
return r_distortion;
}
__declspec(naked) void Maps::SetDistortionStub()
{
__asm
{
push eax
pushad
call Maps::GetDistortionDvar
mov [esp + 20h], eax
popad
pop eax
retn
}
}
Game::dvar_t* Maps::GetSpecularDvar()
{
Game::dvar_t*& r_specular = *reinterpret_cast<Game::dvar_t**>(0x69F0D94);
static Game::dvar_t* r_specularCustomMaps = Game::Dvar_RegisterBool("r_specularCustomMaps", false, Game::DVAR_ARCHIVE, "Allows shaders to use phong specular lighting on custom maps");
if (Maps::IsCustomMap())
{
if (!r_specularCustomMaps->current.enabled)
{
static Game::dvar_t noSpecular;
ZeroMemory(&noSpecular, sizeof noSpecular);
return &noSpecular;
}
}
return r_specular;
}
__declspec(naked) void Maps::SetSpecularStub1()
{
__asm
{
push eax
pushad
call Maps::GetSpecularDvar
mov [esp + 20h], eax
popad
pop eax
retn
}
}
__declspec(naked) void Maps::SetSpecularStub2()
{
__asm
{
push eax
pushad
call Maps::GetSpecularDvar
mov [esp + 20h], eax
popad
pop edx
retn
}
}
void Maps::G_SpawnTurretHook(Game::gentity_s* ent, int unk, int unk2) void Maps::G_SpawnTurretHook(Game::gentity_s* ent, int unk, int unk2)
{ {
if (Maps::CurrentMainZone == "mp_backlot_sh"s || Maps::CurrentMainZone == "mp_con_spring"s || if (Maps::CurrentMainZone == "mp_backlot_sh"s || Maps::CurrentMainZone == "mp_con_spring"s ||
@ -764,7 +688,7 @@ namespace Components
Maps::AddDlc({ 6, "Freighter", {"mp_cargoship_sh"} }); Maps::AddDlc({ 6, "Freighter", {"mp_cargoship_sh"} });
Maps::AddDlc({ 7, "Resurrection Pack", {"mp_shipment_long", "mp_rust_long", "mp_firingrange"} }); Maps::AddDlc({ 7, "Resurrection Pack", {"mp_shipment_long", "mp_rust_long", "mp_firingrange"} });
Maps::AddDlc({ 8, "Recycled Pack", {"mp_bloc_sh", "mp_crash_tropical", "mp_estate_tropical", "mp_fav_tropical", "mp_storm_spring"} }); Maps::AddDlc({ 8, "Recycled Pack", {"mp_bloc_sh", "mp_crash_tropical", "mp_estate_tropical", "mp_fav_tropical", "mp_storm_spring"} });
Maps::AddDlc({ 9, "Classics Pack #3", {"mp_farm", "mp_backlot", "mp_pipeline", "mp_countdown", "mp_crash_snow", "mp_carentan"}}); Maps::AddDlc({ 9, "Classics Pack #3", {"mp_farm", "mp_backlot", "mp_pipeline", "mp_countdown", "mp_crash_snow", "mp_carentan", "mp_broadcast", "mp_showdown", "mp_convoy"} });
Maps::UpdateDlcStatus(); Maps::UpdateDlcStatus();
@ -842,16 +766,6 @@ namespace Components
// Allow loading raw suns // Allow loading raw suns
Utils::Hook(0x51B46A, Maps::LoadRawSun, HOOK_CALL).install()->quick(); Utils::Hook(0x51B46A, Maps::LoadRawSun, HOOK_CALL).install()->quick();
// Disable distortion on custom maps
//Utils::Hook(0x50AA47, Maps::SetDistortionStub, HOOK_CALL).install()->quick();
// Disable speculars on custom maps
Utils::Hook(0x525EA6, Maps::SetSpecularStub1, HOOK_CALL).install()->quick();
Utils::Hook(0x51FBC7, Maps::SetSpecularStub2, HOOK_CALL).install()->quick();
Utils::Hook(0x522A2E, Maps::SetSpecularStub2, HOOK_CALL).install()->quick();
Utils::Hook::Nop(0x51FBCC, 1);
Utils::Hook::Nop(0x522A33, 1);
// Intercept map loading for usermap initialization // Intercept map loading for usermap initialization
Utils::Hook(0x6245E3, Maps::SpawnServerStub, HOOK_CALL).install()->quick(); Utils::Hook(0x6245E3, Maps::SpawnServerStub, HOOK_CALL).install()->quick();
Utils::Hook(0x62493E, Maps::SpawnServerStub, HOOK_CALL).install()->quick(); Utils::Hook(0x62493E, Maps::SpawnServerStub, HOOK_CALL).install()->quick();
@ -916,7 +830,7 @@ namespace Components
unsigned int i = 0; unsigned int i = 0;
for (auto& model : models) for (auto& model : models)
{ {
Game::R_AddCmdDrawText(Utils::String::VA("%d %s", model.second, model.first.data()), 0x7FFFFFFF, font, 15.0f, (height * scale + 1) * (i++ + 1) + 15.0f, scale, scale, 0.0f, color, Game::ITEM_TEXTSTYLE_NORMAL); Game::R_AddCmdDrawText(Utils::String::VA("%d %s", model.second, model.first.data()), std::numeric_limits<int>::max(), font, 15.0f, (height * scale + 1) * (i++ + 1) + 15.0f, scale, scale, 0.0f, color, Game::ITEM_TEXTSTYLE_NORMAL);
} }
}, Scheduler::Pipeline::RENDERER); }, Scheduler::Pipeline::RENDERER);
} }

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