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]
concurrency:
group: ${{ github.ref }}
cancel-in-progress: true
jobs:
build:
name: Build binaries
@ -18,15 +22,7 @@ jobs:
configuration:
- Debug
- Release
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 }}
steps:
- name: Check out files
uses: actions/checkout@v3
with:
@ -83,13 +79,6 @@ jobs:
- name: Add 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
# 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
url = https://github.com/microsoft/GSL.git
[submodule "deps/nlohmannjson"]
path = deps/nlohmannjson
path = deps/json
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/).
## [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
### 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-pdb` | Copy debug information for binaries as well to the path given via --copy-to. |
| `--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. |
## 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_PROTOTYPES",
"LTC_NO_RSA_BLINDING",
"LTC_NO_FILE",
"ARGTYPE=4",
}
end
@ -32,6 +34,7 @@ function libtomcrypt.project()
}
removefiles {
path.join(libtomcrypt.source, "src/**/*_test.c"),
path.join(libtomcrypt.source, "src/**/*tab.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."
}
newoption {
trigger = "force-exception-handler",
description = "Install custom unhandled exception handler even for Debug builds."
}
newoption {
trigger = "disable-binary-check",
description = "Do not perform integrity checks on the exe."
@ -257,9 +252,6 @@ workspace "iw4x"
if _OPTIONS["force-unit-tests"] then
defines {"FORCE_UNIT_TESTS"}
end
if _OPTIONS["force-exception-handler"] then
defines {"FORCE_EXCEPTION_HANDLER"}
end
if _OPTIONS["disable-binary-check"] then
defines {"DISABLE_BINARY_CHECK"}
end

View File

@ -1,5 +1,55 @@
#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
{
bool Loader::Pregame = true;
@ -9,126 +59,127 @@ namespace Components
bool Loader::IsPregame()
{
return Loader::Pregame;
return Pregame;
}
bool Loader::IsPostgame()
{
return Loader::Postgame;
return Postgame;
}
bool Loader::IsUninitializing()
{
return Loader::Uninitializing;
return Uninitializing;
}
void Loader::Initialize()
{
Loader::Pregame = true;
Loader::Postgame = false;
Loader::Uninitializing = false;
Pregame = true;
Postgame = false;
Uninitializing = false;
Utils::Memory::GetAllocator()->clear();
Loader::Register(new Flags());
Loader::Register(new Singleton());
// Install our exception handler as early as posssible to get better debug dumps from startup crashes
Loader::Register(new Exception());
Loader::Register(new Auth());
Loader::Register(new Bans());
Loader::Register(new Bots());
Loader::Register(new Dvar());
Loader::Register(new Lean());
Loader::Register(new Maps());
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());
Register(new Auth());
Register(new Command());
Register(new Dvar());
Register(new Exception()); // Install our exception handler as early as posssible to get better debug dumps from startup crashes
Register(new Flags());
Register(new Network());
Register(new Logger());
Register(new Singleton());
Register(new UIScript());
Register(new ZoneBuilder());
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::Register(new GSC());
Register(new GSC());
Loader::Pregame = false;
Pregame = false;
// Make sure preDestroy is called when the game shuts down
Scheduler::OnGameShutdown(Loader::PreDestroy);
Scheduler::OnGameShutdown(PreDestroy);
}
void Loader::Uninitialize()
{
Loader::Uninitializing = true;
Loader::PreDestroyNoPostGame();
Uninitializing = true;
PreDestroyNoPostGame();
std::reverse(Loader::Components.begin(), Loader::Components.end());
for (auto component : Loader::Components)
std::reverse(Components.begin(), Components.end());
for (auto& component : Components)
{
#ifdef DEBUG
if (!Loader::IsPerformingUnitTests())
if (!IsPerformingUnitTests())
{
Logger::Print("Unregister component: {}\n", component->getName());
}
@ -136,21 +187,21 @@ namespace Components
delete component;
}
Loader::Components.clear();
Components.clear();
Utils::Memory::GetAllocator()->clear();
Loader::Uninitializing = false;
Uninitializing = false;
}
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());
for (auto component : components)
for (auto& component : components)
{
component->preDestroy();
}
@ -159,17 +210,17 @@ namespace Components
void Loader::PreDestroyNoPostGame()
{
if (!Loader::Postgame)
if (!Postgame)
{
auto components = Loader::Components;
auto components = Components;
std::reverse(components.begin(), components.end());
for (auto component : components)
for (auto& component : components)
{
component->preDestroy();
}
Loader::Postgame = true;
Postgame = true;
}
}
@ -179,7 +230,7 @@ namespace Components
Logger::Print("Performing unit tests for components:\n");
for (const auto component : Loader::Components)
for (const auto& component : Components)
{
#if defined(FORCE_UNIT_TESTS)
Logger::Debug("Testing '{}'...\n", component->getName());
@ -208,12 +259,12 @@ namespace Components
if (component)
{
#if defined(DEBUG) || defined(FORCE_UNIT_TESTS)
if (!Loader::IsPerformingUnitTests())
if (!IsPerformingUnitTests())
{
Logger::Print("Component registered: {}\n", component->getName());
}
#endif
Loader::Components.push_back(component);
Components.push_back(component);
}
}
}

View File

@ -42,7 +42,7 @@ namespace Components
template <typename T>
static T* GetInstance()
{
for (auto& component : Loader::Components)
for (auto& component : Components)
{
if (typeid(*component) == typeid(T))
{
@ -61,85 +61,39 @@ namespace Components
};
}
#include "Modules/Scheduler.hpp"
// Priority
#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/Console.hpp"
#include "Modules/UIScript.hpp"
#include "Modules/ModList.hpp"
#include "Modules/Dvar.hpp"
#include "Modules/Exception.hpp"
#include "Modules/Flags.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/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/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/ServerCommands.hpp"
#include "Modules/StructuredData.hpp"
#include "Modules/ConnectProtocol.hpp"
#include "Modules/StartupMessages.hpp"
#include "Modules/Stats.hpp"
#include "Modules/SoundMutexFix.hpp"
#include "Modules/Chat.hpp"
#include "Modules/Maps.hpp"
#include "Modules/Materials.hpp"
#include "Modules/Menus.hpp"
#include "Modules/ModList.hpp"
#include "Modules/ModelSurfs.hpp"
#include "Modules/Node.hpp"
#include "Modules/Party.hpp"
#include "Modules/Renderer.hpp"
#include "Modules/Scheduler.hpp"
#include "Modules/TextRenderer.hpp"
#include "Modules/Movement.hpp"
#include "Modules/Elevators.hpp"
#include "Modules/ClientCommand.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/Toast.hpp"
#include "Modules/Window.hpp"
#include "Modules/Zones.hpp"
#include "Modules/GSC/GSC.hpp"

View File

@ -1,4 +1,5 @@
#include <STDInclude.hpp>
#include "Weapon.hpp"
#include "AssetInterfaces/IFont_s.hpp"
#include "AssetInterfaces/IWeapon.hpp"
@ -257,7 +258,7 @@ namespace Components
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)
{

View File

@ -11,7 +11,7 @@ namespace Assets
Utils::String::Replace(name, "maps/mp/", "");
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())
{

View File

@ -95,22 +95,22 @@ namespace Assets
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 fontFile(Utils::String::VA("%s.ttf", name.data()));
Components::FileSystem::File fontDefFile(std::format("{}.json", name));
Components::FileSystem::File fontFile(std::format("{}.ttf", name));
if (!fontDefFile.exists() || !fontFile.exists())
{
return;
}
nlohmann::json fontDef = nlohmann::json::parse(fontDefFile.getBuffer());
nlohmann::json fontDef;
try
{
fontDef = nlohmann::json::parse(fontDefFile.getBuffer());
}
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;
}
@ -254,7 +254,7 @@ namespace Assets
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)

View File

@ -64,7 +64,7 @@ namespace Assets
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())
{
@ -391,7 +391,7 @@ namespace Assets
switch (elemType)
{
case 7:
case Game::FX_ELEM_TYPE_MODEL:
{
if (visuals->model)
{
@ -401,11 +401,11 @@ namespace Assets
break;
}
case 8:
case 9:
case Game::FX_ELEM_TYPE_OMNI_LIGHT:
case Game::FX_ELEM_TYPE_SPOT_LIGHT:
break;
case 0xA:
case Game::FX_ELEM_TYPE_SOUND:
{
if (visuals->soundName)
{
@ -416,7 +416,7 @@ namespace Assets
break;
}
case 0xC:
case Game::FX_ELEM_TYPE_RUNNER:
{
if (visuals->effectDef.handle)
{
@ -491,7 +491,7 @@ namespace Assets
// Save_FxElemDefVisuals
{
if (elemDef->elemType == 11)
if (elemDef->elemType == Game::FX_ELEM_TYPE_DECAL)
{
if (elemDef->visuals.markArray)
{
@ -501,7 +501,7 @@ namespace Assets
Game::FxElemMarkVisuals* destMarkArray = buffer->dest<Game::FxElemMarkVisuals>();
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])
{
@ -563,7 +563,7 @@ namespace Assets
{
AssertSize(Game::FxElemExtendedDefPtr, 4);
if (elemDef->elemType == 3)
if (elemDef->elemType == Game::FX_ELEM_TYPE_TRAIL)
{
// 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)
{

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;
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 "IGameWorldMp.hpp"
#define IW4X_GAMEWORLD_VERSION 1
namespace Assets
{
void IGameWorldMp::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
@ -75,4 +77,118 @@ namespace Assets
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; }
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)
{
AssertSize(Game::GameWorldMp, 8);
AssertSize(Game::GameWorldSp, 0x38);
Utils::Stream* buffer = builder->getBuffer();
Game::GameWorldSp* asset = header.gameWorldSp;
Game::GameWorldSp* dest = buffer->dest<Game::GameWorldSp>();
auto* asset = header.gameWorldSp;
auto* dest = buffer->dest<Game::GameWorldSp>();
buffer->save(asset);
buffer->pushBlock(Game::XFILE_BLOCK_VIRTUAL);
@ -187,7 +187,7 @@ namespace Assets
for (char j = 0; j < 5; ++j)
{
builder->mapScriptString(&(&node->constant.targetname)[j]);
builder->mapScriptString((&node->constant.targetname)[j]);
}
if (node->constant.Links)

View File

@ -18,12 +18,12 @@ namespace Assets
}
image->name = builder->getAllocator()->duplicateString(name);
image->semantic = 2;
image->semantic = Game::TextureSemantic::TS_COLOR_MAP;
const char* tempName = image->name;
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())
{
Utils::Stream::Reader reader(builder->getAllocator(), imageFile.getBuffer());
@ -35,7 +35,7 @@ namespace Assets
}
image->mapType = reader.read<char>();
image->semantic = reader.read<char>();
image->semantic = reader.read<Game::TextureSemantic>();
image->category = reader.read<char>();
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)
{
Components::FileSystem::File mapFile(Utils::String::VA("lights/%s.iw4xLight", name.data()));
Components::FileSystem::File mapFile(std::format("lights/{}.iw4xLight", name));
if (mapFile.exists())
{

View File

@ -3,6 +3,27 @@
#define IW4X_GFXMAP_VERSION 1
// The xmodel vehicle_small_hatch_green_destructible_mp causes EXTREME lag
// when placed in the world, for reasons unknown.
//
// Something happens with the SModelSurfIterator which makes it load garbage
// as an XSurface in the middle of otherwise valid surfaces. This bug is very
// easy to reproduce with an empty map and just this car in the middle
//
// As of know we do not know why the iterator corruption occurs or what causes
// it. It doesn't seem linked to the SModel, nor to the materials or techsets,
// nor to the sortkeys, nor to the tilemode, boneinfo, and so on. So for now
// and to make it work for majority of users, we just swap the car. (no, using
// the identical car from iw4's favela_escape doesn't work either!)
//
// Two other models have this problem: ch_apartment_9story_noentry_02 and
// ch_apartment_5story_noentry_01
// But these exist in mp_vacant in slightly different versions, and can be
// swapped safely by deleting the two .iw4XModel files and requiring mp_vacant
// or a minimal zone containing just these two models.
//
#define SWAP_GREEN_VEHICLE_XMODEL 1
namespace Assets
{
void IGfxWorld::loadGfxWorldDpvsStatic(Game::GfxWorld* world, Game::GfxWorldDpvsStatic* asset, Components::ZoneBuilder::Zone* builder, Utils::Stream::Reader* reader)
@ -47,7 +68,11 @@ namespace Assets
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, ".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())
{

View File

@ -5,7 +5,7 @@ namespace Assets
{
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())
{
header->loadSnd = Components::AssetHandler::FindOriginalAsset(this->getType(), name.data()).loadSnd;
@ -120,17 +120,8 @@ namespace Assets
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);
Utils::Stream::ClearPointer(&dest->sound.data);
}
buffer->saveArray(asset->sound.data, asset->sound.info.data_len);
Utils::Stream::ClearPointer(&dest->sound.data);
}
buffer->popBlock();

View File

@ -81,4 +81,51 @@ namespace Assets
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;
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, ".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())
{
Game::MapEnts* entites = builder->getAllocator()->allocate<Game::MapEnts>();
@ -48,7 +48,7 @@ namespace Assets
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->numEntityChars = entityString.size() + 1;

View File

@ -1,15 +1,439 @@
#include <STDInclude.hpp>
#include "IMaterial.hpp"
#define IW4X_MAT_VERSION "1"
#define IW4X_MAT_BIN_VERSION "1"
#define IW4X_MAT_JSON_VERSION 1
namespace Assets
{
const std::unordered_map<std::string, std::string> techSetCorrespondance =
{
{"effect", "effect_blend"},
{"effect", "effect_blend"},
{"effect_nofog", "effect_blend_nofog"},
{"effect_zfeather", "effect_zfeather_blend"},
{"effect_zfeather_falloff", "effect_zfeather_falloff_add"},
{"effect_zfeather_nofog", "effect_zfeather_add_nofog"},
{"wc_unlit_add", "wc_unlit_add_lin"},
{"wc_unlit_distfalloff", "wc_unlit_distfalloff_replace"},
{"wc_unlit_multiply", "wc_unlit_multiply_lin"},
{"wc_unlit_falloff_add", "wc_unlit_falloff_add_lin"},
{"wc_unlit", "wc_unlit_replace_lin"},
{"wc_unlit_alphatest", "wc_unlit_blend_lin"},
{"wc_unlit_blend", "wc_unlit_blend_lin"},
{"wc_unlit_replace", "wc_unlit_replace_lin"},
{"wc_unlit_nofog", "wc_unlit_replace_lin_nofog_nocast" },
{"mc_unlit_replace", "mc_unlit_replace_lin"},
{"mc_unlit_nofog", "mc_unlit_blend_nofog_ua"},
{"mc_unlit", "mc_unlit_replace_lin_nocast"},
{"mc_unlit_alphatest", "mc_unlit_blend_lin"},
{"mc_effect_nofog", "mc_effect_blend_nofog"},
{"mc_effect_falloff_add_nofog", "mc_effect_falloff_add_nofog_eyeoffset"},
};
void IMaterial::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
{
if (!header->data) this->loadJson(header, name, builder); // Check if we want to 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->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)
@ -30,30 +454,7 @@ namespace Assets
"_add_lin_nofog",
};
static std::unordered_map<std::string, std::string> techSetCorrespondance =
{
{"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()));
Components::FileSystem::File materialFile(std::format("materials/{}.iw4xMaterial", name));
if (!materialFile.exists()) return;
Utils::Stream::Reader reader(builder->getAllocator(), materialFile.getBuffer());
@ -66,9 +467,9 @@ namespace Assets
std::string version;
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>();
@ -127,7 +528,7 @@ namespace Assets
{
Game::MaterialTextureDef* textureDef = &asset->textureTable[i];
if (textureDef->semantic == SEMANTIC_WATER_MAP)
if (textureDef->semantic == Game::TextureSemantic::TS_WATER_MAP)
{
if (textureDef->u.water)
{
@ -175,29 +576,29 @@ namespace Assets
// Find correct sortkey by comparing techsets
Game::DB_EnumXAssetEntries(Game::XAssetType::ASSET_TYPE_MATERIAL, [asset](Game::XAssetEntry* entry)
{
if (!replacementFound)
{
Game::XAssetHeader header = entry->asset.header;
const char* name = asset->techniqueSet->name;
if (name[0] == ',') ++name;
if (std::string(name) == header.material->techniqueSet->name)
if (!replacementFound)
{
asset->info.sortKey = header.material->info.sortKey;
Game::XAssetHeader header = entry->asset.header;
// This is temp, as nobody has time to fix materials
asset->stateBitsCount = header.material->stateBitsCount;
asset->stateBitsTable = header.material->stateBitsTable;
std::memcpy(asset->stateBitsEntry, header.material->stateBitsEntry, 48);
asset->constantCount = header.material->constantCount;
asset->constantTable = header.material->constantTable;
Components::Logger::Print("For {}, copied constants & statebits from {}\n", asset->info.name, header.material->info.name);
replacementFound = true;
const char* name = asset->techniqueSet->name;
if (name[0] == ',') ++name;
if (std::string(name) == header.material->techniqueSet->name)
{
asset->info.sortKey = header.material->info.sortKey;
// This is temp, as nobody has time to fix materials
asset->stateBitsCount = header.material->stateBitsCount;
asset->stateBitsTable = header.material->stateBitsTable;
std::memcpy(asset->stateBitsEntry, header.material->stateBitsEntry, ARRAYSIZE(asset->stateBitsEntry));
asset->constantCount = header.material->constantCount;
asset->constantTable = header.material->constantTable;
Components::Logger::Print("For {}, copied constants & statebits from {}\n", asset->info.name, header.material->info.name);
replacementFound = true;
}
}
}
}, false);
}, false);
if (!replacementFound)
{
@ -222,70 +623,72 @@ namespace Assets
Game::DB_EnumXAssetEntries(Game::XAssetType::ASSET_TYPE_MATERIAL, [asset, techsetMatches](Game::XAssetEntry* entry)
{
if (!replacementFound)
{
Game::XAssetHeader header = entry->asset.header;
if (techsetMatches(header.material, asset))
if (!replacementFound)
{
Components::Logger::Print("Material {} with techset {} has been mapped to {}\n", asset->info.name, asset->techniqueSet->name, header.material->techniqueSet->name);
asset->info.sortKey = header.material->info.sortKey;
replacementFound = true;
Game::XAssetHeader header = entry->asset.header;
if (techsetMatches(header.material, asset))
{
Components::Logger::Print("Material {} with techset {} has been mapped to {}\n", asset->info.name, asset->techniqueSet->name, header.material->techniqueSet->name);
asset->info.sortKey = header.material->info.sortKey;
replacementFound = true;
}
}
}
}, false);
}, false);
}
if (!replacementFound && asset->techniqueSet)
{
Components::Logger::Print("No replacement found for material {} with techset {}\n", asset->info.name, asset->techniqueSet->name);
std::string techName = asset->techniqueSet->name;
if (techSetCorrespondance.contains(techName))
if (const auto itr = techSetCorrespondance.find(techName); itr != techSetCorrespondance.end())
{
auto iw4TechSetName = techSetCorrespondance[techName];
Game::XAssetEntry* iw4TechSet = Game::DB_FindXAssetEntry(Game::XAssetType::ASSET_TYPE_TECHNIQUE_SET, iw4TechSetName.data());
auto& iw4TechSetName = itr->second;
auto* iw4TechSet = Game::DB_FindXAssetEntry(Game::ASSET_TYPE_TECHNIQUE_SET, iw4TechSetName.data());
if (iw4TechSet)
if (iw4TechSet)
{
Game::DB_EnumXAssetEntries(Game::XAssetType::ASSET_TYPE_MATERIAL, [asset, iw4TechSet](Game::XAssetEntry* entry)
{
if (!replacementFound)
{
Game::XAssetHeader header = entry->asset.header;
if (header.material->techniqueSet == iw4TechSet->asset.header.techniqueSet
&& 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 (!replacementFound)
{
Game::XAssetHeader header = entry->asset.header;
Components::Logger::Print("Material {} with techset {} has been mapped to {} (last chance!), taking the sort key of material {}\n",
asset->info.name, asset->techniqueSet->name, header.material->techniqueSet->name, header.material->info.name);
asset->info.sortKey = header.material->info.sortKey;
asset->techniqueSet = iw4TechSet->asset.header.techniqueSet;
// Yeah this has a tendency to fuck up a LOT of transparent materials
if (header.material->techniqueSet == iw4TechSet->asset.header.techniqueSet && std::string(header.material->info.name).find("icon") != std::string::npos)
{
Components::Logger::Print("Material {} with techset {} has been mapped to {} (last chance!), taking the sort key of material {}\n",
asset->info.name, asset->techniqueSet->name, header.material->techniqueSet->name, header.material->info.name);
// this is terrible!
asset->stateBitsCount = header.material->stateBitsCount;
asset->stateBitsTable = header.material->stateBitsTable;
std::memcpy(asset->stateBitsEntry, header.material->stateBitsEntry, 48);
asset->constantCount = header.material->constantCount;
asset->constantTable = header.material->constantTable;
asset->info.sortKey = header.material->info.sortKey;
asset->techniqueSet = iw4TechSet->asset.header.techniqueSet;
replacementFound = true;
// this is terrible!
asset->stateBitsCount = header.material->stateBitsCount;
asset->stateBitsTable = header.material->stateBitsTable;
std::memcpy(asset->stateBitsEntry, header.material->stateBitsEntry, 48);
asset->constantCount = header.material->constantCount;
asset->constantTable = header.material->constantTable;
replacementFound = true;
}
}
}
}, false);
}, false);
if (!replacementFound)
if (!replacementFound)
{
Components::Logger::Print("Could not find any loaded material with techset {} (in replacement of {}), so I cannot set the sortkey for material {}\n", iw4TechSetName, asset->techniqueSet->name, asset->info.name);
}
}
else
else
{
Components::Logger::Print("Could not find any loaded techset with iw4 name {} for iw3 techset {}\n", iw4TechSetName, asset->techniqueSet->name);
}
}
else
else
{
Components::Logger::Print("Could not match iw3 techset {} with any of the techsets I know! This is a critical error, there's a good chance the map will not be playable.\n", techName);
}
@ -327,15 +730,6 @@ namespace Assets
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)
{
Game::Material* asset = header.material;
@ -351,7 +745,7 @@ namespace Assets
{
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)
{
@ -411,7 +805,7 @@ namespace Assets
auto* destTextureDef = &destTextureTable[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);

View File

@ -13,5 +13,8 @@ namespace Assets
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 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 "IMaterialPixelShader.hpp"
#define IW4X_TECHSET_VERSION "0"
#define GFX_RENDERER_SHADER_SM3 0
namespace Assets
{
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->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*/)
@ -19,36 +19,19 @@ namespace Assets
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;
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);
if (std::memcmp(magic, "IW4xPIXL", 8))
{
Components::Logger::Error(Game::ERR_FATAL, "Reading pixel shader '{}' failed, header is invalid!", name);
}
asset->name = builder->getAllocator()->duplicateString(name);
asset->prog.loadDef.loadForRenderer = GFX_RENDERER_SHADER_SM3;
asset->prog.loadDef.programSize = static_cast<unsigned short>(programSize);
asset->prog.loadDef.program = builder->getAllocator()->allocateArray<unsigned int>(programSize);
memcpy_s(asset->prog.loadDef.program, buff.size(), buff.data(), buff.size());
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;
}

View File

@ -1,14 +1,14 @@
#include <STDInclude.hpp>
#include "IMaterialTechniqueSet.hpp"
#define IW4X_TECHSET_VERSION "0"
#define IW4X_TECHSET_VERSION 1
namespace Assets
{
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->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*/)
@ -16,132 +16,202 @@ namespace Assets
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);
Components::FileSystem::File techFile(Utils::String::VA("techniques/%s.iw4xTech", name.data()));
if (!techFile.exists()) {
Components::FileSystem::File techFile(std::format("techniques/{}.iw4x.json", name));
if (!techFile.exists())
{
*tech = nullptr;
Components::Logger::Warning(Game::CON_CHANNEL_DONT_FILTER, "Missing technique '{}'\n", name);
return;
}
Utils::Stream::Reader reader(builder->getAllocator(), techFile.getBuffer());
nlohmann::json technique;
char* magic = reader.readArray<char>(8);
if (std::memcmp(magic, "IW4xTECH", 8))
try
{
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;
version.push_back(reader.read<char>());
int version = technique["version"].get<int>();
if (version != IW4X_TECHSET_VERSION)
{
Components::Logger::Error(Game::ERR_FATAL,
"Reading technique '{}' failed, expected version is {}, but it was {}!", name, IW4X_TECHSET_VERSION, version.data());
"Reading technique '{}' failed, expected version is {}, but it was {}!", name, IW4X_TECHSET_VERSION, version);
}
unsigned short flags = reader.read<unsigned short>();
unsigned short passCount = reader.read<unsigned short>();
unsigned short flags = static_cast<unsigned short>(Utils::Json::ReadFlags(technique["flags"].get<std::string>(), sizeof(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"];
asset->name = builder->getAllocator()->duplicateString(name);
asset->flags = flags;
asset->passCount = passCount;
Game::MaterialTechnique* asset = (Game::MaterialTechnique*)builder->getAllocator()->allocateArray<unsigned char>(sizeof(Game::MaterialTechnique) + (sizeof(Game::MaterialPass) * (passArray.size() - 1)));
Game::MaterialPass* passes = reader.readArray<Game::MaterialPass>(passCount);
std::memcpy(asset->passArray, passes, sizeof(Game::MaterialPass) * passCount);
asset->name = builder->getAllocator()->duplicateString(name);
asset->flags = flags;
asset->passCount = static_cast<unsigned short>(passArray.size());
for (unsigned short i = 0; i < asset->passCount; i++)
{
Game::MaterialPass* pass = &asset->passArray[i];
Game::MaterialPass* passes = builder->getAllocator()->allocateArray<Game::MaterialPass>(asset->passCount);
std::memcpy(asset->passArray, passes, sizeof(Game::MaterialPass) * asset->passCount);
if (pass->vertexDecl)
for (unsigned short i = 0; i < asset->passCount; i++)
{
const char* declName = reader.readCString();
pass->vertexDecl = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_VERTEXDECL, declName, builder).vertexDecl;
}
Game::MaterialPass* pass = &asset->passArray[i];
auto jsonPass = passArray[i];
if (pass->vertexShader)
{
const char* vsName = reader.readCString();
pass->vertexShader = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_VERTEXSHADER, vsName, builder).vertexShader;
}
if (pass->pixelShader)
{
const char* psName = reader.readCString();
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);
for (int j = 0; j < pass->perPrimArgCount + pass->perObjArgCount + pass->stableArgCount; j++)
{
if (pass->args[j].type == 1 || pass->args[j].type == 7)
if (jsonPass["vertexDeclaration"].is_string())
{
pass->args[j].u.literalConst = reader.readArray<float>(4);
auto declName = jsonPass["vertexDeclaration"].get<std::string>();
pass->vertexDecl = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_VERTEXDECL, declName, builder).vertexDecl;
}
if (pass->args[j].type == 3 || pass->args[j].type == 5)
if (jsonPass["vertexShader"].is_string())
{
pass->args[j].u.codeConst.index = *reader.readObject<unsigned short>();
pass->args[j].u.codeConst.firstRow = *reader.readObject<unsigned char>();
pass->args[j].u.codeConst.rowCount = *reader.readObject<unsigned char>();
auto vsName = jsonPass["vertexShader"].get<std::string>();
pass->vertexShader = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_VERTEXSHADER, vsName, builder).vertexShader;
}
if (jsonPass["pixelShader"].is_string())
{
auto psName = jsonPass["pixelShader"].get<std::string>();
pass->pixelShader = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_PIXELSHADER, psName, builder).pixelShader;
}
pass->perPrimArgCount = jsonPass["perPrimArgCount"].get<char>();
pass->perObjArgCount = jsonPass["perObjArgCount"].get<char>();
pass->stableArgCount = jsonPass["stableArgCount"].get<char>();
pass->customSamplerFlags = jsonPass["customSamplerFlags"].get<char>();
if (jsonPass["arguments"].is_array())
{
nlohmann::json::array_t jsonAguments = jsonPass["arguments"];
pass->args = builder->getAllocator()->allocateArray<Game::MaterialShaderArgument>(jsonAguments.size());
for (size_t j = 0; j < jsonAguments.size(); j++)
{
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 ||
argument->type == Game::MaterialShaderArgumentType::MTL_ARG_CODE_PIXEL_CONST)
{
if (jsonArgument["codeConst"].is_object())
{
auto codeConst = jsonArgument["codeConst"];
argument->u.codeConst.index = codeConst["index"].get<unsigned short>();
argument->u.codeConst.firstRow = codeConst["firstRow"].get<unsigned char>();
argument->u.codeConst.rowCount = codeConst["rowCount"].get<unsigned char>();
}
}
else if (argument->type == Game::MaterialShaderArgumentType::MTL_ARG_MATERIAL_PIXEL_SAMPLER ||
argument->type == Game::MaterialShaderArgumentType::MTL_ARG_MATERIAL_VERTEX_CONST ||
argument->type == Game::MaterialShaderArgumentType::MTL_ARG_MATERIAL_PIXEL_CONST)
{
argument->u.nameHash = jsonArgument["nameHash"].get<unsigned int>();
}
else if (argument->type == Game::MaterialShaderArgumentType::MTL_ARG_CODE_PIXEL_SAMPLER)
{
argument->u.codeSampler = jsonArgument["codeSampler"].get<unsigned int>();
}
}
}
}
*tech = asset;
}
*tech = asset;
}
void IMaterialTechniqueSet::loadBinary(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
void IMaterialTechniqueSet::loadFromDisk(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
{
Components::FileSystem::File tsFile(Utils::String::VA("techsets/%s.iw4xTS", name.data()));
Components::FileSystem::File tsFile(std::format("techsets/{}.iw4x.json", name));
if (!tsFile.exists()) return;
Utils::Stream::Reader reader(builder->getAllocator(), tsFile.getBuffer());
nlohmann::json techset;
char* magic = reader.readArray<char>(8);
if (std::memcmp(magic, "IW4xTSET", 8))
try
{
Components::Logger::Error(Game::ERR_FATAL, "Reading techset '{}' failed, header is invalid!", name);
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());
}
std::string version;
version.push_back(reader.read<char>());
auto version = techset["version"].get<int>();
if (version != IW4X_TECHSET_VERSION)
{
Components::Logger::Error(Game::ERR_FATAL, "Reading techset '{}' failed, expected version is {}, but it was {}!",
name, IW4X_TECHSET_VERSION, version);
}
Game::MaterialTechniqueSet* asset = 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())
{
auto remapped = techset["remappedTechniqueSet"].get<std::string>();
if (remapped != asset->name)
{
const char* techName = reader.readCString();
this->loadBinaryTechnique(&asset->techniques[i], techName, builder);
builder->loadAssetByName(Game::XAssetType::ASSET_TYPE_TECHNIQUE_SET, remapped, false);
}
}
if (techset["techniques"].is_object())
{
for (int i = 0; i < Game::TECHNIQUE_COUNT; i++)
{
auto technique = techset["techniques"].at(std::to_string(i));
if (technique.is_string())
{
this->loadTechniqueFromDisk(&asset->techniques[i], technique.get<std::string>(), builder);
}
}
}
header->techniqueSet = asset;
}
void IMaterialTechniqueSet::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
{
{
Game::MaterialTechniqueSet* asset = header.techniqueSet;
for (int i = 0; i < ARRAYSIZE(Game::MaterialTechniqueSet::techniques); ++i)
@ -177,8 +247,10 @@ namespace Assets
AssertSize(Game::MaterialTechniqueSet, 204);
Utils::Stream* buffer = builder->getBuffer();
Game::MaterialTechniqueSet* asset = header.techniqueSet;
Game::MaterialTechniqueSet* dest = buffer->dest<Game::MaterialTechniqueSet>();
buffer->save(asset);
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 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 "IMaterialVertexDeclaration.hpp"
#define IW4X_TECHSET_VERSION "0"
#define IW4X_TECHSET_VERSION 1
namespace Assets
{
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->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*/)
@ -18,7 +18,7 @@ namespace Assets
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;
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);
}
std::string version;
version.push_back(reader.read<char>());
auto version = reader.read<char>();
if (version != IW4X_TECHSET_VERSION)
{
Components::Logger::Error(Game::ERR_FATAL, "Reading vertex declaration '{}' failed, expected version is {}, but it was {}!",
name, IW4X_TECHSET_VERSION, version.data());
Components::Logger::Error(Game::ERR_FATAL, "Reading vertex declaration '{}' failed, expected version is {}, but it was {:d}!",
name, IW4X_TECHSET_VERSION, version);
}
Game::MaterialVertexDeclaration* asset = reader.readObject<Game::MaterialVertexDeclaration>();

View File

@ -1,14 +1,14 @@
#include <STDInclude.hpp>
#include "IMaterialVertexShader.hpp"
#define IW4X_TECHSET_VERSION "0"
#define GFX_RENDERER_SHADER_SM3 0
namespace Assets
{
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->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*/)
@ -18,36 +18,18 @@ namespace Assets
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;
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);
if (std::memcmp(magic, "IW4xVERT", 8))
{
Components::Logger::Error(Game::ERR_FATAL, "Reading vertex shader '{}' failed, header is invalid!", name);
}
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);
}
asset->name = builder->getAllocator()->duplicateString(name);
asset->prog.loadDef.loadForRenderer = GFX_RENDERER_SHADER_SM3;
asset->prog.loadDef.programSize = static_cast<unsigned short>(programSize);
asset->prog.loadDef.program = builder->getAllocator()->allocateArray<unsigned int>(programSize);
memcpy_s(asset->prog.loadDef.program, buff.size(), buff.data(), buff.size());
header->vertexShader = asset;
}

View File

@ -12,7 +12,7 @@ namespace Assets
if (menus.empty()) return;
// Allocate new menu list
Game::MenuList* newList = allocator->allocate<Game::MenuList>();
auto* newList = allocator->allocate<Game::MenuList>();
if (!newList) return;
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)
{
Game::MenuList *asset = header.menuList;
auto* asset = header.menuList;
for (int i = 0; i < asset->menuCount; ++i)
{
@ -51,7 +51,7 @@ namespace Assets
Utils::Stream* buffer = builder->getBuffer();
Game::MenuList* asset = header.menuList;
Game::MenuList* dest = buffer->dest<Game::MenuList>();
auto* dest = buffer->dest<Game::MenuList>();
buffer->save(asset);
@ -67,7 +67,7 @@ namespace Assets
{
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);
for (int i = 0; i < asset->menuCount; ++i)

View File

@ -18,23 +18,24 @@ namespace Assets
return;
}
const auto data = Utils::Compression::ZLib::Compress(rawFile.getBuffer());
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->compressedLen = static_cast<int>(data.size());
asset->buffer = builder->getAllocator()->allocateArray<char>(compressedData.size());
std::memcpy(const_cast<char*>(asset->buffer), compressedData.data(), compressedData.size());
asset->compressedLen = static_cast<int>(compressedData.size());
}
else
{
asset->buffer = builder->getAllocator()->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->len = static_cast<int>(rawFile.getBuffer().size());
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*/)
{
// 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
// otherwise it fails to link things properly
@ -280,7 +280,7 @@ namespace Assets
unsigned short* scriptStringTable = buffer->dest<unsigned short>();
buffer->saveArray(def->notetrackSoundMapKeys, 16);
for (int i = 0; i < 16; i++) {
builder->mapScriptString(&scriptStringTable[i]);
builder->mapScriptString(scriptStringTable[i]);
}
Utils::Stream::ClearPointer(&dest->notetrackSoundMapKeys);
@ -292,7 +292,7 @@ namespace Assets
unsigned short* scriptStringTable = buffer->dest<unsigned short>();
buffer->saveArray(def->notetrackSoundMapValues, 16);
for (int i = 0; i < 16; i++) {
builder->mapScriptString(&scriptStringTable[i]);
builder->mapScriptString(scriptStringTable[i]);
}
Utils::Stream::ClearPointer(&dest->notetrackSoundMapValues);
@ -304,7 +304,7 @@ namespace Assets
unsigned short* scriptStringTable = buffer->dest<unsigned short>();
buffer->saveArray(def->notetrackRumbleMapKeys, 16);
for (int i = 0; i < 16; i++) {
builder->mapScriptString(&scriptStringTable[i]);
builder->mapScriptString(scriptStringTable[i]);
}
Utils::Stream::ClearPointer(&dest->notetrackRumbleMapKeys);
@ -316,7 +316,7 @@ namespace Assets
unsigned short* scriptStringTable = buffer->dest<unsigned short>();
buffer->saveArray(def->notetrackRumbleMapValues, 16);
for (int i = 0; i < 16; i++) {
builder->mapScriptString(&scriptStringTable[i]);
builder->mapScriptString(scriptStringTable[i]);
}
Utils::Stream::ClearPointer(&dest->notetrackRumbleMapValues);
@ -725,7 +725,7 @@ namespace Assets
unsigned short* scriptStringTable = buffer->dest<unsigned short>();
buffer->saveArray(asset->hideTags, 32);
for (int i = 0; i < 32; i++) {
builder->mapScriptString(&scriptStringTable[i]);
builder->mapScriptString(scriptStringTable[i]);
}
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)
{
Components::FileSystem::File animFile(Utils::String::VA("xanim/%s.iw4xAnim", name.data()));
Components::FileSystem::File animFile(std::format("xanim/{}.iw4xAnim", name));
if (animFile.exists())
{
@ -39,7 +39,7 @@ namespace Assets
xanim->names = builder->getAllocator()->allocateArray<unsigned short>(xanim->boneCount[Game::PART_TYPE_ALL]);
for (int i = 0; i < xanim->boneCount[Game::PART_TYPE_ALL]; ++i)
{
xanim->names[i] = 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)
{
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)
{
builder->mapScriptString(&destTagnames[i]);
builder->mapScriptString(destTagnames[i]);
}
Utils::Stream::ClearPointer(&dest->names);
@ -280,7 +280,7 @@ namespace Assets
for (char i = 0; i < asset->notifyCount; ++i)
{
builder->mapScriptString(&destNotetracks[i].name);
builder->mapScriptString(destNotetracks[i].name);
}
Utils::Stream::ClearPointer(&dest->notify);

View File

@ -1,7 +1,7 @@
#include <STDInclude.hpp>
#include "IXModel.hpp"
#define IW4X_MODEL_VERSION 5
#define IW4X_MODEL_VERSION 8
namespace Assets
{
@ -9,32 +9,32 @@ namespace Assets
{
if (entry->nodes)
{
entry->nodes = reader->readArray<Game::XSurfaceCollisionNode>(entry->nodeCount);
entry->nodes = reader->readArrayOnce<Game::XSurfaceCollisionNode>(entry->nodeCount);
}
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)
{
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
if (surf->verts0)
{
surf->verts0 = reader->readArray<Game::GfxPackedVertex>(surf->vertCount);
surf->verts0 = reader->readArrayOnce<Game::GfxPackedVertex>(surf->vertCount);
}
// Save_XRigidVertListArray
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)
{
@ -51,17 +51,7 @@ namespace Assets
// Access index block
if (surf->triIndices)
{
void* oldPtr = surf->triIndices;
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);
}
surf->triIndices = reader->readArrayOnce<unsigned short>(surf->triCount * 3);
}
}
@ -74,7 +64,7 @@ namespace Assets
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)
{
@ -85,7 +75,7 @@ namespace Assets
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()))
{
@ -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);
}
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>();
if (asset->name)
@ -128,33 +113,33 @@ namespace Assets
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)
{
asset->parentList = reader.readArray<char>(asset->numBones - asset->numRootBones);
asset->parentList = reader.readArrayOnce<unsigned char>(asset->numBones - asset->numRootBones);
}
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)
{
asset->trans = reader.readArray<float>((asset->numBones - asset->numRootBones) * 3);
asset->trans = reader.readArrayOnce<float>((asset->numBones - asset->numRootBones) * 3);
}
if (asset->partClassification)
{
asset->partClassification = reader.readArray<char>(asset->numBones);
asset->partClassification = reader.readArrayOnce<unsigned char>(asset->numBones);
}
if (asset->baseMat)
{
asset->baseMat = reader.readArray<Game::DObjAnimMat>(asset->numBones);
asset->baseMat = reader.readArrayOnce<Game::DObjAnimMat>(asset->numBones);
}
if (asset->materialHandles)
@ -172,7 +157,7 @@ namespace Assets
// Save_XModelLodInfoArray
{
for (int i = 0; i < 4; ++i)
for (unsigned int i = 0; i < 4; ++i)
{
if (asset->lodInfo[i].modelSurfs)
{
@ -247,60 +232,53 @@ namespace Assets
if (asset->physCollmap)
{
if (version == 4)
Game::PhysCollmap* collmap = reader.readObject<Game::PhysCollmap>();
asset->physCollmap = collmap;
if (collmap->name)
{
asset->physCollmap = nullptr;
collmap->name = reader.readCString();
}
else
if (collmap->geoms)
{
Game::PhysCollmap* collmap = reader.readObject<Game::PhysCollmap>();
asset->physCollmap = collmap;
collmap->geoms = reader.readArray<Game::PhysGeomInfo>(collmap->count);
if (collmap->name)
for (unsigned int i = 0; i < collmap->count; ++i)
{
collmap->name = reader.readCString();
}
Game::PhysGeomInfo* geom = &collmap->geoms[i];
if (collmap->geoms)
{
collmap->geoms = reader.readArray<Game::PhysGeomInfo>(collmap->count);
for (unsigned int i = 0; i < collmap->count; ++i)
if (geom->brushWrapper)
{
Game::PhysGeomInfo* geom = &collmap->geoms[i];
if (geom->brushWrapper)
Game::BrushWrapper* brush = reader.readObject<Game::BrushWrapper>();
geom->brushWrapper = brush;
{
Game::BrushWrapper* brush = reader.readObject<Game::BrushWrapper>();
geom->brushWrapper = brush;
if (brush->brush.sides)
{
if (brush->brush.sides)
brush->brush.sides = reader.readArray<Game::cbrushside_t>(brush->brush.numsides);
for (unsigned short j = 0; j < brush->brush.numsides; ++j)
{
brush->brush.sides = reader.readArray<Game::cbrushside_t>(brush->brush.numsides);
for (unsigned short j = 0; j < brush->brush.numsides; ++j)
{
Game::cbrushside_t* side = &brush->brush.sides[j];
Game::cbrushside_t* side = &brush->brush.sides[j];
// TODO: Add pointer support
if (side->plane)
{
side->plane = reader.readObject<Game::cplane_s>();
}
// TODO: Add pointer support
if (side->plane)
{
side->plane = reader.readObject<Game::cplane_s>();
}
}
if (brush->brush.baseAdjacentSide)
{
brush->brush.baseAdjacentSide = reader.readArray<char>(brush->totalEdgeCount);
}
}
// TODO: Add pointer support
if (brush->planes)
if (brush->brush.baseAdjacentSide)
{
brush->planes = reader.readArray<Game::cplane_s>(brush->brush.numsides);
brush->brush.baseAdjacentSide = reader.readArray<char>(brush->totalEdgeCount);
}
}
// TODO: Add pointer support
if (brush->planes)
{
brush->planes = reader.readArray<Game::cplane_s>(brush->brush.numsides);
}
}
}
@ -386,7 +364,7 @@ namespace Assets
for (char i = 0; i < asset->numBones; ++i)
{
builder->mapScriptString(&destBoneNames[i]);
builder->mapScriptString(destBoneNames[i]);
}
Utils::Stream::ClearPointer(&dest->boneNames);
@ -394,37 +372,77 @@ namespace Assets
if (asset->parentList)
{
buffer->save(asset->parentList, asset->numBones - asset->numRootBones);
Utils::Stream::ClearPointer(&dest->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);
Utils::Stream::ClearPointer(&dest->parentList);
}
}
if (asset->quats)
{
buffer->align(Utils::Stream::ALIGN_2);
buffer->saveArray(asset->quats, (asset->numBones - asset->numRootBones) * 4);
Utils::Stream::ClearPointer(&dest->quats);
if (builder->hasPointer(asset->quats))
{
dest->quats = builder->getPointer(asset->quats);
}
else
{
buffer->align(Utils::Stream::ALIGN_2);
builder->storePointer(asset->quats);
buffer->saveArray(asset->quats, (asset->numBones - asset->numRootBones) * 4);
Utils::Stream::ClearPointer(&dest->quats);
}
}
if (asset->trans)
{
buffer->align(Utils::Stream::ALIGN_4);
buffer->saveArray(asset->trans, (asset->numBones - asset->numRootBones) * 3);
Utils::Stream::ClearPointer(&dest->trans);
if (builder->hasPointer(asset->trans))
{
dest->trans = builder->getPointer(asset->trans);
}
else
{
buffer->align(Utils::Stream::ALIGN_4);
builder->storePointer(asset->trans);
buffer->saveArray(asset->trans, (asset->numBones - asset->numRootBones) * 3);
Utils::Stream::ClearPointer(&dest->trans);
}
}
if (asset->partClassification)
{
buffer->save(asset->partClassification, asset->numBones);
Utils::Stream::ClearPointer(&dest->partClassification);
if (builder->hasPointer(asset->partClassification))
{
dest->partClassification = builder->getPointer(asset->partClassification);
}
else
{
builder->storePointer(asset->partClassification);
buffer->save(asset->partClassification, asset->numBones);
Utils::Stream::ClearPointer(&dest->partClassification);
}
}
if (asset->baseMat)
{
AssertSize(Game::DObjAnimMat, 32);
if (builder->hasPointer(asset->baseMat))
{
dest->baseMat = builder->getPointer(asset->baseMat);
}
else
{
buffer->align(Utils::Stream::ALIGN_4);
builder->storePointer(asset->baseMat);
buffer->saveArray(asset->baseMat, asset->numBones);
Utils::Stream::ClearPointer(&dest->baseMat);
}
buffer->align(Utils::Stream::ALIGN_4);
buffer->saveArray(asset->baseMat, asset->numBones);
Utils::Stream::ClearPointer(&dest->baseMat);
}
if (asset->materialHandles)

View File

@ -37,9 +37,18 @@ namespace Assets
if (surf->vertInfo.vertsBlend)
{
buffer->align(Utils::Stream::ALIGN_2);
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);
if (builder->hasPointer(surf->vertInfo.vertsBlend))
{
destSurf->vertInfo.vertsBlend = builder->getPointer(surf->vertInfo.vertsBlend);
}
else
{
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));
Utils::Stream::ClearPointer(&destSurf->vertInfo.vertsBlend);
}
}
// Access vertex block
@ -48,9 +57,17 @@ namespace Assets
{
AssertSize(Game::GfxPackedVertex, 32);
buffer->align(Utils::Stream::ALIGN_16);
buffer->saveArray(surf->verts0, surf->vertCount);
Utils::Stream::ClearPointer(&destSurf->verts0);
if (builder->hasPointer(surf->verts0))
{
destSurf->verts0 = builder->getPointer(surf->verts0);
}
else
{
buffer->align(Utils::Stream::ALIGN_16);
builder->storePointer(surf->verts0);
buffer->saveArray(surf->verts0, surf->vertCount);
Utils::Stream::ClearPointer(&destSurf->verts0);
}
}
buffer->popBlock();
@ -59,25 +76,40 @@ namespace Assets
{
AssertSize(Game::XRigidVertList, 12);
buffer->align(Utils::Stream::ALIGN_4);
Game::XRigidVertList* destCt = buffer->dest<Game::XRigidVertList>();
buffer->saveArray(surf->vertList, surf->vertListCount);
for (unsigned int i = 0; i < surf->vertListCount; ++i)
if (builder->hasPointer(surf->vertList))
{
Game::XRigidVertList* destRigidVertList = &destCt[i];
Game::XRigidVertList* rigidVertList = &surf->vertList[i];
if (rigidVertList->collisionTree)
{
buffer->align(Utils::Stream::ALIGN_4);
this->saveXSurfaceCollisionTree(rigidVertList->collisionTree, builder);
Utils::Stream::ClearPointer(&destRigidVertList->collisionTree);
}
destSurf->vertList = builder->getPointer(surf->vertList);
}
else
{
buffer->align(Utils::Stream::ALIGN_4);
builder->storePointer(surf->vertList);
Utils::Stream::ClearPointer(&destSurf->vertList);
Game::XRigidVertList* destCt = buffer->dest<Game::XRigidVertList>();
buffer->saveArray(surf->vertList, surf->vertListCount);
for (unsigned int i = 0; i < surf->vertListCount; ++i)
{
Game::XRigidVertList* destRigidVertList = &destCt[i];
Game::XRigidVertList* rigidVertList = &surf->vertList[i];
if (rigidVertList->collisionTree)
{
if (builder->hasPointer(rigidVertList->collisionTree))
{
destRigidVertList->collisionTree = builder->getPointer(rigidVertList->collisionTree);
}
else {
buffer->align(Utils::Stream::ALIGN_4);
builder->storePointer(rigidVertList->collisionTree);
this->saveXSurfaceCollisionTree(rigidVertList->collisionTree, builder);
Utils::Stream::ClearPointer(&destRigidVertList->collisionTree);
}
}
}
Utils::Stream::ClearPointer(&destSurf->vertList);
}
}
// Access index block
@ -89,6 +121,7 @@ namespace Assets
else
{
buffer->align(Utils::Stream::ALIGN_16);
builder->storePointer(surf->triIndices);
buffer->saveArray(surf->triIndices, surf->triCount * 3);
Utils::Stream::ClearPointer(&destSurf->triIndices);
}

View File

@ -573,7 +573,7 @@ namespace Assets
Utils::String::Replace(name, "maps/mp/", "");
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())
{
return;
@ -882,7 +882,7 @@ namespace Assets
clipMap->smodelNodeCount = reader.read<unsigned short>();
clipMap->smodelNodes = reader.readArray<Game::SModelAabbNode>(clipMap->smodelNodeCount);
clipMap->mapEnts = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_MAP_ENTS, 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
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*/)
{
// 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);
header->menu = menus[0].second;
@ -20,7 +20,7 @@ namespace Assets
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)
{
@ -59,14 +59,14 @@ namespace Assets
buffer->align(Utils::Stream::ALIGN_4);
Game::ExpressionSupportingData *dest = buffer->dest<Game::ExpressionSupportingData>();
auto* dest = buffer->dest<Game::ExpressionSupportingData>();
buffer->save(asset);
if (asset->uifunctions.functions)
{
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);
for (int i = 0; i < asset->uifunctions.totalFunctions; ++i)
@ -87,17 +87,17 @@ namespace Assets
{
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);
for (int i = 0; i < asset->staticDvarList.numStaticDvars; ++i)
for (auto i = 0; i < asset->staticDvarList.numStaticDvars; ++i)
{
if (asset->staticDvarList.staticDvars[i])
{
Utils::Stream::ClearPointer(&destStaticDvars[i]);
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]);
if (asset->staticDvarList.staticDvars[i]->dvarName)
@ -115,7 +115,7 @@ namespace Assets
{
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);
for (int i = 0; i < asset->uiStrings.totalStrings; ++i)
@ -123,7 +123,7 @@ namespace Assets
if (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
// Write header data
Game::Statement_s *dest = buffer->dest<Game::Statement_s>();
auto* dest = buffer->dest<Game::Statement_s>();
buffer->save(asset);
// Write statement entries
@ -155,7 +155,7 @@ namespace Assets
buffer->align(Utils::Stream::ALIGN_4);
// Write entries
Game::expressionEntry *destEntries = buffer->dest<Game::expressionEntry>();
auto* destEntries = buffer->dest<Game::expressionEntry>();
buffer->save(asset->entries, sizeof(Game::expressionEntry), asset->numEntries);
// Loop through entries
@ -222,7 +222,7 @@ namespace Assets
#endif
// Write header data
Game::MenuEventHandlerSet *destset = buffer->dest<Game::MenuEventHandlerSet>();
auto* destset = buffer->dest<Game::MenuEventHandlerSet>();
buffer->save(asset);
// Event handlers
@ -234,7 +234,7 @@ namespace Assets
buffer->save(asset->eventHandlers, sizeof(Game::MenuEventHandler*), asset->eventHandlerCount);
// Loop through eventHandlers
for (int i = 0; i < asset->eventHandlerCount; ++i)
for (auto i = 0; i < asset->eventHandlerCount; ++i)
{
if (asset->eventHandlers[i])
{
@ -244,7 +244,7 @@ namespace Assets
#endif
// Write menu event handler
Game::MenuEventHandler *dest = buffer->dest<Game::MenuEventHandler>();
auto* dest = buffer->dest<Game::MenuEventHandler>();
buffer->save(asset->eventHandlers[i]);
// Write additional data based on type
@ -264,7 +264,7 @@ namespace Assets
if (asset->eventHandlers[i]->eventData.conditionalScript)
{
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);
// eventExpression
@ -307,7 +307,7 @@ namespace Assets
buffer->align(Utils::Stream::ALIGN_4);
// header data
Game::SetLocalVarData *destLocalVarData = buffer->dest<Game::SetLocalVarData>();
auto* destLocalVarData = buffer->dest<Game::SetLocalVarData>();
buffer->save(asset->eventHandlers[i]->eventData.setLocalVarData);
// localVarName
@ -354,7 +354,7 @@ namespace Assets
while (asset)
{
// Write header
Game::ItemKeyHandler* dest = buffer->dest<Game::ItemKeyHandler>();
auto* dest = buffer->dest<Game::ItemKeyHandler>();
buffer->save(asset);
// MenuEventHandlerSet
@ -367,7 +367,7 @@ namespace Assets
if (asset->next)
{
// align every indice, besides the first one?
// align every index, besides the first one?
buffer->align(Utils::Stream::ALIGN_4);
}
@ -379,20 +379,20 @@ namespace Assets
#endif
}
#define EVENTHANDLERSET(__indice) \
if (asset->__indice) \
#define EVENTHANDLERSET(__index) \
if (asset->__index) \
{ \
buffer->align(Utils::Stream::ALIGN_4); \
this->save_MenuEventHandlerSet(asset->__indice, builder); \
Utils::Stream::ClearPointer(&dest->__indice); \
this->save_MenuEventHandlerSet(asset->__index, builder); \
Utils::Stream::ClearPointer(&dest->__index); \
}
#define STATEMENT(__indice) \
if (asset->__indice) \
#define STATEMENT(__index) \
if (asset->__index) \
{ \
buffer->align(Utils::Stream::ALIGN_4); \
this->save_Statement_s(asset->__indice, builder); \
Utils::Stream::ClearPointer(&dest->__indice); \
this->save_Statement_s(asset->__index, builder); \
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)
@ -412,7 +412,7 @@ namespace Assets
if (type == 6)
{
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);
if (asset->listBox->onDoubleClick)
@ -427,17 +427,7 @@ namespace Assets
}
}
// HexRays spaghetti
else if (type != 4
&& type != 9
&& type != 16
&& type != 18
&& type != 11
&& type != 14
&& type != 10
&& type != 17
&& type != 22
&& type != 23
&& type != 0)
else if (type != 4 && type != 9 && type != 16 && type != 18 && type != 11 && type != 14 && type != 10 && type != 17 && type != 22 && type != 23 && type != 0)
{
switch (type)
{
@ -457,7 +447,7 @@ namespace Assets
break;
case 12:
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);
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)
{
AssertSize(Game::itemDef_s, 380);
AssertSize(Game::itemDef_s, 0x17C);
Utils::Stream* buffer = builder->getBuffer();
Game::itemDef_s* dest = buffer->dest<Game::itemDef_s>();
auto* dest = buffer->dest<Game::itemDef_s>();
#ifdef WRITE_LOGS
if (asset->window.name)
@ -587,7 +577,7 @@ namespace Assets
buffer->enterStruct("floatExpressions");
#endif
Game::ItemFloatExpression* destExp = buffer->dest<Game::ItemFloatExpression>();
auto* destExp = buffer->dest<Game::ItemFloatExpression>();
buffer->saveArray(asset->floatExpressions, asset->floatExpressionCount);
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)
{
AssertSize(Game::menuDef_t, 400);
AssertSize(Game::windowDef_t, 0xA4);
#ifdef WRITE_LOGS
buffer->enterStruct("ImenuDef_t");
#endif
Utils::Stream* buffer = builder->getBuffer();
Game::menuDef_t* asset = header.menu;
Game::menuDef_t* dest = buffer->dest<Game::menuDef_t>();
auto* asset = header.menu;
auto* dest = buffer->dest<Game::menuDef_t>();
buffer->save(asset);
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)
{
Components::FileSystem::File aliasFile(Utils::String::VA("sounds/%s.json", name.data()));
Components::FileSystem::File aliasFile(std::format("sounds/{}.json", name));
if (!aliasFile.exists())
{
header->sound = Components::AssetHandler::FindOriginalAsset(this->getType(), name.data()).sound;

View File

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

View File

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

View File

@ -1,16 +1,20 @@
#include <STDInclude.hpp>
#include "Bots.hpp"
#include "GSC/Script.hpp"
namespace Components
{
std::vector<std::string> Bots::BotNames;
std::vector<Bots::botData> Bots::BotNames;
Dvar::Var Bots::SVRandomBotNames;
struct BotMovementInfo
{
int buttons; // Actions
int8_t forward;
int8_t right;
uint16_t weapon;
std::int32_t buttons; // Actions
std::int8_t forward;
std::int8_t right;
std::uint16_t weapon;
bool active;
};
@ -19,67 +23,94 @@ namespace Components
struct BotAction
{
std::string action;
int key;
std::int32_t key;
};
static const BotAction BotActions[] =
{
{ "gostand", Game::usercmdButtonBits::CMD_BUTTON_UP },
{ "gocrouch", Game::usercmdButtonBits::CMD_BUTTON_CROUCH },
{ "goprone", Game::usercmdButtonBits::CMD_BUTTON_PRONE },
{ "fire", Game::usercmdButtonBits::CMD_BUTTON_ATTACK },
{ "melee", Game::usercmdButtonBits::CMD_BUTTON_MELEE },
{ "frag", Game::usercmdButtonBits::CMD_BUTTON_FRAG },
{ "smoke", Game::usercmdButtonBits::CMD_BUTTON_OFFHAND_SECONDARY },
{ "reload", Game::usercmdButtonBits::CMD_BUTTON_RELOAD },
{ "sprint", Game::usercmdButtonBits::CMD_BUTTON_SPRINT },
{ "leanleft", Game::usercmdButtonBits::CMD_BUTTON_LEAN_LEFT },
{ "leanright", Game::usercmdButtonBits::CMD_BUTTON_LEAN_RIGHT },
{ "ads", Game::usercmdButtonBits::CMD_BUTTON_ADS },
{ "holdbreath", Game::usercmdButtonBits::CMD_BUTTON_BREATH },
{ "usereload", Game::usercmdButtonBits::CMD_BUTTON_USE_RELOAD },
{ "activate", Game::usercmdButtonBits::CMD_BUTTON_ACTIVATE },
{ "gostand", Game::CMD_BUTTON_UP },
{ "gocrouch", Game::CMD_BUTTON_CROUCH },
{ "goprone", Game::CMD_BUTTON_PRONE },
{ "fire", Game::CMD_BUTTON_ATTACK },
{ "melee", Game::CMD_BUTTON_MELEE },
{ "frag", Game::CMD_BUTTON_FRAG },
{ "smoke", Game::CMD_BUTTON_OFFHAND_SECONDARY },
{ "reload", Game::CMD_BUTTON_RELOAD },
{ "sprint", Game::CMD_BUTTON_SPRINT },
{ "leanleft", Game::CMD_BUTTON_LEAN_LEFT },
{ "leanright", Game::CMD_BUTTON_LEAN_RIGHT },
{ "ads", Game::CMD_BUTTON_ADS },
{ "holdbreath", Game::CMD_BUTTON_BREATH },
{ "usereload", Game::CMD_BUTTON_USE_RELOAD },
{ "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)
{
static size_t botId = 0;
static size_t botId = 0; // Loop over the BotNames vector
static bool loadedNames = false; // Load file only once
const char* botName;
const char* clanName;
if (Bots::BotNames.empty() && !loadedNames)
if (BotNames.empty() && !loadedNames)
{
FileSystem::File bots("bots.txt");
loadedNames = true;
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", "");
name = Utils::String::Trim(name);
std::random_device rd;
std::mt19937 gen(rd());
std::ranges::shuffle(data, gen);
}
if (!name.empty())
for (auto& entry : data)
{
// Take into account for CR line endings
Utils::String::Replace(entry, "\r", "");
// Remove whitespace
Utils::String::Trim(entry);
if (!entry.empty())
{
Bots::BotNames.push_back(name);
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();
botName = Bots::BotNames[botId++].data();
botId %= BotNames.size();
const auto index = botId++;
botName = BotNames[index].first.data();
clanName = BotNames[index].second.data();
}
else
{
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)
@ -96,13 +127,13 @@ namespace Components
{
Game::Scr_AddString("autoassign");
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]
{
Game::Scr_AddString(Utils::String::VA("class%u", Utils::Cryptography::Rand::GenerateInt() % 5u));
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);
@ -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);
}
void Bots::AddMethods()
{
Script::AddMethod("IsBot", Bots::GScr_isTestClient); // Usage: self IsBot();
Script::AddMethod("IsTestClient", Bots::GScr_isTestClient); // Usage: self IsTestClient();
Script::AddMethMultiple(GScr_isTestClient, false, {"IsTestClient", "IsBot"}); // Usage: self IsTestClient();
Script::AddMethod("BotStop", [](Game::scr_entref_t entref) // Usage: <bot> BotStop();
{
@ -234,7 +270,8 @@ namespace Components
return;
}
Game::usercmd_s userCmd = {0};
Game::usercmd_s userCmd;
ZeroMemory(&userCmd, sizeof(Game::usercmd_s));
userCmd.serverTime = *Game::svs_time;
@ -254,7 +291,7 @@ namespace Components
pushad
push edi
call Bots::BotAiAction
call BotAiAction
add esp, 4
popad
@ -278,7 +315,7 @@ namespace Components
push [esp + 0x20 + 0x8]
push [esp + 0x20 + 0x8]
call Bots::G_SelectWeaponIndex
call G_SelectWeaponIndex
add esp, 0x8
popad
@ -298,15 +335,17 @@ namespace Components
AssertOffset(Game::client_t, ping, 0x212C8);
// 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
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(0x627241, Bots::SV_BotUserMove_Hk, HOOK_CALL).install()->quick();
Utils::Hook(0x627021, 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
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);
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
Events::OnVMShutdown([]

View File

@ -8,7 +8,10 @@ namespace Components
Bots();
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);

View File

@ -1,4 +1,7 @@
#include <STDInclude.hpp>
#include "Branding.hpp"
#include <version.hpp>
namespace Components
{
@ -125,5 +128,19 @@ namespace Components
// Hook CG_DrawFullScreenDebugOverlays so we may render the version when it's appropriate
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 "Bullet.hpp"
namespace Components
{
Dvar::Var Bullet::BGSurfacePenetration;
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)
{
assert(weapDef);
@ -47,6 +56,63 @@ namespace Components
*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()
{
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(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
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 void Bullet_FireStub();
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 "CardTitles.hpp"
#include "ServerCommands.hpp"
namespace Components
{
std::string CardTitles::CustomTitles[18];
char CardTitles::CustomTitles[Game::MAX_CLIENTS][18];
Dvar::Var CardTitles::CustomTitle;
CClient* CardTitles::GetClientByIndex(std::uint32_t index)
@ -10,35 +12,34 @@ namespace Components
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>();
if (data->name == username)
const auto* username = Dvar::Var("name").get<const char*>();
if (std::strcmp(data->name, username) == 0)
{
returnResult += 0xFE000000;
result += 0xFE000000;
}
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 (!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
returnResult += 0xFF000000;
returnResult += clientNum * 0x10000;
result += 0xFF000000;
result += i * 0x10000;
break;
}
}
}
}
return returnResult;
return result;
}
void __declspec(naked) CardTitles::GetPlayerCardClientInfoStub()
@ -71,7 +72,7 @@ namespace Components
std::uint8_t prefix = (request->tableRow >> (8 * 3)) & 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)
{
@ -82,10 +83,10 @@ namespace Components
{
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
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
operand->internals.stringVal.string = title;
@ -96,9 +97,9 @@ namespace Components
}
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
operand->internals.stringVal.string = title;
@ -156,11 +157,11 @@ namespace Components
{
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);
}
@ -169,10 +170,10 @@ namespace Components
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);
}
@ -180,10 +181,17 @@ namespace Components
{
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;
else CardTitles::CustomTitles[i].clear();
if (playerTitle[0] == '\0')
{
CustomTitles[i][0] = '\0';
}
else
{
Game::I_strncpyz(CustomTitles[i], playerTitle, sizeof(CustomTitles[0]) / sizeof(char));
}
}
}
@ -191,16 +199,18 @@ namespace Components
{
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);
std::memset(&CustomTitles, 0, sizeof(char[Game::MAX_CLIENTS][18]));
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)
{
CardTitles::ParseCustomTitles(params->get(2));
ParseCustomTitles(params->get(2));
return true;
}
}
@ -209,10 +219,10 @@ namespace Components
});
Utils::Hook(0x62EB26, CardTitles::GetPlayerCardClientInfoStub).install()->quick();
Utils::Hook(0x62EB26, GetPlayerCardClientInfoStub).install()->quick();
// Table lookup stuff
Utils::Hook(0x62DCC1, CardTitles::TableLookupByRowHookStub).install()->quick();
Utils::Hook(0x62DCC1, TableLookupByRowHookStub).install()->quick();
Utils::Hook::Nop(0x62DCC6, 1);
}
}

View File

@ -14,27 +14,27 @@ namespace Components
struct CClient
{
std::uint32_t IsValid; // 0x0000
std::uint32_t IsValid2; // 0x0004
std::uint32_t ClientNumber; // 0x0008
char Name[16]; // 0x000C
std::uint32_t Team; // 0x001C
std::uint32_t Team2; // 0x0020
std::uint32_t Rank; // 0x0024 (rank - 1)
std::uint32_t Prestige; // 0x0028
std::uint32_t Perks; // 0x002C
std::uint32_t Kills; // 0x0030
std::uint32_t Score; // 0x0034
std::uint32_t IsValid; // 0x0000
std::uint32_t IsValid2; // 0x0004
std::uint32_t ClientNumber; // 0x0008
char Name[16]; // 0x000C
std::uint32_t Team; // 0x001C
std::uint32_t Team2; // 0x0020
std::uint32_t Rank; // 0x0024 (rank - 1)
std::uint32_t Prestige; // 0x0028
std::uint32_t Perks; // 0x002C
std::uint32_t Kills; // 0x0030
std::uint32_t Score; // 0x0034
std::uint8_t _0x0038[968];
std::uint32_t ViewAngles; // 0x0400
std::uint32_t ViewAngles; // 0x0400
std::uint8_t _0x040C[136];
std::uint32_t IsShooting; // 0x0494
std::uint32_t IsShooting; // 0x0494
std::uint8_t _0x0498[4];
std::uint32_t IsZoomed; // 0x049C
std::uint32_t IsZoomed; // 0x049C
std::uint8_t _0x04A0[68];
std::uint32_t weaponID; // 0x04E4
std::uint32_t weaponID; // 0x04E4
std::uint8_t _0x04E8[24];
std::uint32_t weaponID2; // 0x0500
std::uint32_t weaponID2; // 0x0500
std::uint8_t _0x0504[40];
std::uint8_t _padding[8];
};
@ -44,19 +44,20 @@ namespace Components
public:
AssertOffset(Game::PlayerCardData, Game::PlayerCardData::name, 0x1C);
static Dvar::Var CustomTitle;
static std::string CustomTitles[18];
static void SendCustomTitlesToClients();
static void ParseCustomTitles(const char* msg);
CardTitles();
private:
static Dvar::Var CustomTitle;
static char CustomTitles[Game::MAX_CLIENTS][18];
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 const char* TableLookupByRowHook(Game::Operand* operand, tablelookuprequest_s* request);
static void TableLookupByRowHookStub();
static void ParseCustomTitles(const char* msg);
};
}

View File

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

View File

@ -1,4 +1,6 @@
#include <STDInclude.hpp>
#include "Changelog.hpp"
#include "UIFeeder.hpp"
namespace Components
{
@ -7,44 +9,44 @@ namespace Components
void Changelog::LoadChangelog()
{
//if (!Changelog::Lines.empty())
// return;
std::lock_guard _(Mutex);
Lines.clear();
std::lock_guard<std::mutex> _(Changelog::Mutex);
Changelog::Lines.clear();
std::string data = Utils::Cache::GetFile("/develop/CHANGELOG.md");
const auto data = Utils::Cache::GetFile("/develop/CHANGELOG.md");
if (data.empty())
{
data = "^1Unable to get changelog.";
Lines.emplace_back("^1Unable to get changelog.");
return;
}
Changelog::Lines = Utils::String::Split(data, '\n');
for (auto& line : Changelog::Lines)
auto buffer = Utils::String::Split(data, '\n');
for (auto& line : buffer)
{
Utils::String::Replace(line, "\r", "");
}
Lines = buffer;
}
unsigned int Changelog::GetChangelogCount()
{
return Changelog::Lines.size();
return Lines.size();
}
// 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);
if (item < Changelog::Lines.size())
std::lock_guard _(Mutex);
if (item < Lines.size())
{
return Utils::String::VA("%s", Changelog::Lines[item].data());
return Utils::String::Format("{}", Lines[item]);
}
return "";
}
void Changelog::SelectChangelog(unsigned int /*index*/)
void Changelog::SelectChangelog([[maybe_unused]] unsigned int index)
{
// Don't do anything in here
}
@ -54,6 +56,6 @@ namespace Components
if (Dedicated::IsEnabled()) return;
// 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 "Chat.hpp"
#include "PlayerName.hpp"
#include "Voice.hpp"
#include "GSC/Script.hpp"
namespace Components
@ -73,7 +77,7 @@ namespace Components
Game::Scr_AddEntity(player);
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;
}
@ -268,8 +272,7 @@ namespace Components
});
Logger::Print("{} was muted\n", client->name);
Game::SV_GameSendServerCommand(client - Game::svs_clients, Game::SV_CMD_CAN_IGNORE,
Utils::String::VA("%c \"You were muted\"", 0x65));
Game::SV_GameSendServerCommand(client - Game::svs_clients, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"You were muted\"", 0x65));
}
void Chat::UnmuteClient(const Game::client_t* client)
@ -277,8 +280,7 @@ namespace Components
UnmuteInternal(client->steamID);
Logger::Print("{} was unmuted\n", client->name);
Game::SV_GameSendServerCommand(client - Game::svs_clients, Game::SV_CMD_CAN_IGNORE,
Utils::String::VA("%c \"You were unmuted\"", 0x65));
Game::SV_GameSendServerCommand(client - Game::svs_clients, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"You were unmuted\"", 0x65));
}
void Chat::UnmuteInternal(const std::uint64_t id, bool everyone)
@ -369,12 +371,12 @@ namespace Components
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);
}
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);
}
});
@ -395,12 +397,12 @@ namespace Components
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);
}
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);
}
});
@ -416,7 +418,7 @@ namespace Components
if (params->size() < 2) return;
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);
});
@ -432,7 +434,7 @@ namespace Components
const auto client = atoi(params->get(1));
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);
});

View File

@ -1,4 +1,7 @@
#include <STDInclude.hpp>
#include "ClanTags.hpp"
#include "PlayerName.hpp"
#include "ServerCommands.hpp"
namespace Components
{
@ -28,15 +31,15 @@ namespace Components
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, command);
Game::SV_GameSendServerCommand(-1, Game::SV_CMD_CAN_IGNORE, Utils::String::Format("{:c} clanNames \"{}\"", 22, list));
}
void ClanTags::ParseClanTags(const char* infoString)
{
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')
{
@ -70,9 +73,7 @@ namespace Components
void ClanTags::CL_SanitizeClanName()
{
char saneNameBuf[5];
std::memset(saneNameBuf, 0, sizeof(saneNameBuf));
char saneNameBuf[5]{};
auto* saneName = saneNameBuf;
assert(ClanName);

View File

@ -1,6 +1,10 @@
#include <STDInclude.hpp>
#include "ClientCommand.hpp"
#include "GSC/Script.hpp"
using namespace Utils::String;
namespace Components
{
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)
{
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;
}
if (ent->health < 1)
{
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;
}
@ -37,9 +41,9 @@ namespace Components
{
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;
}
@ -67,8 +71,7 @@ namespace Components
const auto entNum = ent->s.number;
Logger::Debug("Noclip toggled for entity {}", entNum);
Game::SV_GameSendServerCommand(entNum, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"%s\"", 0x65,
(ent->client->flags & Game::PLAYER_FLAG_NOCLIP) ? "GAME_NOCLIPON" : "GAME_NOCLIPOFF"));
Game::SV_GameSendServerCommand(entNum, Game::SV_CMD_CAN_IGNORE, VA("%c \"%s\"", 0x65, (ent->client->flags & Game::PLAYER_FLAG_NOCLIP) ? "GAME_NOCLIPON" : "GAME_NOCLIPOFF"));
});
Add("ufo", [](Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params)
@ -81,8 +84,7 @@ namespace Components
const auto entNum = ent->s.number;
Logger::Debug("UFO toggled for entity {}", entNum);
Game::SV_GameSendServerCommand(entNum, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"%s\"", 0x65,
(ent->client->flags & Game::PLAYER_FLAG_UFO) ? "GAME_UFOON" : "GAME_UFOOFF"));
Game::SV_GameSendServerCommand(entNum, Game::SV_CMD_CAN_IGNORE, VA("%c \"%s\"", 0x65, (ent->client->flags & Game::PLAYER_FLAG_UFO) ? "GAME_UFOON" : "GAME_UFOOFF"));
});
Add("god", [](Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params)
@ -95,8 +97,7 @@ namespace Components
const auto entNum = ent->s.number;
Logger::Debug("God toggled for entity {}", entNum);
Game::SV_GameSendServerCommand(entNum, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"%s\"", 0x65,
(ent->flags & Game::FL_GODMODE) ? "GAME_GODMODE_ON" : "GAME_GODMODE_OFF"));
Game::SV_GameSendServerCommand(entNum, Game::SV_CMD_CAN_IGNORE, VA("%c \"%s\"", 0x65, (ent->flags & Game::FL_GODMODE) ? "GAME_GODMODE_ON" : "GAME_GODMODE_OFF"));
});
Add("demigod", [](Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params)
@ -109,8 +110,7 @@ namespace Components
const auto entNum = ent->s.number;
Logger::Debug("Demigod toggled for entity {}", entNum);
Game::SV_GameSendServerCommand(entNum, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"%s\"", 0x65,
(ent->flags & Game::FL_DEMI_GODMODE) ? "GAME_DEMI_GODMODE_ON" : "GAME_DEMI_GODMODE_OFF"));
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"));
});
Add("notarget", [](Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params)
@ -123,13 +123,12 @@ namespace Components
const auto entNum = ent->s.number;
Logger::Debug("Notarget toggled for entity {}", entNum);
Game::SV_GameSendServerCommand(entNum, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"%s\"", 0x65,
(ent->flags & Game::FL_NOTARGET) ? "GAME_NOTARGETON" : "GAME_NOTARGETOFF"));
Game::SV_GameSendServerCommand(entNum, Game::SV_CMD_CAN_IGNORE, VA("%c \"%s\"", 0x65, (ent->flags & Game::FL_NOTARGET) ? "GAME_NOTARGETON" : "GAME_NOTARGETOFF"));
});
Add("setviewpos", [](Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params)
{
assert(ent != nullptr);
assert(ent);
if (!CheatsOk(ent))
return;
@ -138,8 +137,7 @@ namespace Components
if (params->size() < 4 || params->size() > 6)
{
Game::SV_GameSendServerCommand(ent->s.number, Game::SV_CMD_CAN_IGNORE,
Utils::String::VA("%c \"GAME_USAGE\x15: setviewpos x y z [yaw] [pitch]\n\"", 0x65));
Game::SV_GameSendServerCommand(ent->s.number, Game::SV_CMD_CAN_IGNORE, VA("%c \"GAME_USAGE\x15: setviewpos x y z [yaw] [pitch]\n\"", 0x65));
return;
}
@ -170,8 +168,7 @@ namespace Components
if (params->size() < 2)
{
Game::SV_GameSendServerCommand(ent->s.number, Game::SV_CMD_CAN_IGNORE,
Utils::String::VA("%c \"GAME_USAGE\x15: give <weapon name>\"", 0x65));
Game::SV_GameSendServerCommand(ent->s.number, Game::SV_CMD_CAN_IGNORE, VA("%c \"GAME_USAGE\x15: give <weapon name>\"", 0x65));
return;
}
@ -245,19 +242,26 @@ namespace Components
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);
if (ent->client->sess.sessionState != Game::SESS_STATE_PLAYING || !CheatsOk(ent))
return;
Scheduler::Once([ent]
{
ent->flags &= ~(Game::FL_GODMODE | Game::FL_DEMI_GODMODE);
ent->health = 0;
ent->client->ps.stats[0] = 0;
Game::player_die(ent, ent, ent, 100000, Game::MOD_SUICIDE, 0, nullptr, Game::HITLOC_NONE, 0);
}, Scheduler::Pipeline::SERVER);
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->health = 0;
ent->client->ps.stats[0] = 0;
Game::player_die(ent, ent, ent, 100000, Game::MOD_SUICIDE, 0, nullptr, Game::HITLOC_NONE, 0);
assert(*bgs == Game::level_bgs);
*bgs = nullptr;
});
}
@ -299,7 +303,7 @@ namespace Components
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;
const auto* name = params->get(1);
@ -308,8 +312,7 @@ namespace Components
strncpy_s(ent->client->visionName[visMode],
sizeof(Game::gclient_t::visionName[0]) / sizeof(char), name, _TRUNCATE);
Game::SV_GameSendServerCommand(ent->s.number, Game::SV_CMD_RELIABLE,
Utils::String::VA("%c \"%s\" %i", Game::MY_CMDS[visMode], name, duration));
Game::SV_GameSendServerCommand(ent->s.number, Game::SV_CMD_RELIABLE, VA("%c \"%s\" %i", Game::MY_CMDS[visMode], name, duration));
});
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));
}
assert(ent->client != nullptr);
assert(ent->client);
constexpr auto visMode = Game::visionSetMode_t::VISIONSET_NIGHT;
const auto* name = params->get(1);
@ -336,13 +339,12 @@ namespace Components
strncpy_s(ent->client->visionName[visMode],
sizeof(Game::gclient_t::visionName[0]) / sizeof(char), name, _TRUNCATE);
Game::SV_GameSendServerCommand(ent->s.number, Game::SV_CMD_RELIABLE,
Utils::String::VA("%c \"%s\" %i", Game::MY_CMDS[visMode], name, duration));
Game::SV_GameSendServerCommand(ent->s.number, Game::SV_CMD_RELIABLE, 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)
{
assert(ent != nullptr);
assert(ent);
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);
@ -406,7 +408,7 @@ namespace Components
// 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
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 : "",
targetName, target, "", scriptLinkName, team, "",
point[0], point[1], point[2], angles[0], angles[1], angles[2], 0);
@ -442,7 +444,7 @@ namespace Components
{
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);
auto h = Game::FS_FOpenTextFileWrite(fileName);
@ -464,9 +466,11 @@ namespace Components
const auto* line = EntInfoLine(i);
const auto lineLen = std::strlen(line);
assert(line);
assert(lineLen);
Game::FS_Write(line, lineLen, h);
Game::FS_Write(line, static_cast<int>(lineLen), h);
}
Game::FS_FCloseFile(h);

View File

@ -1,4 +1,5 @@
#include <STDInclude.hpp>
#include "ConnectProtocol.hpp"
namespace Components
{
@ -7,17 +8,17 @@ namespace Components
bool ConnectProtocol::IsEvaluated()
{
return ConnectProtocol::Evaluated;
return Evaluated;
}
bool ConnectProtocol::Used()
{
if (!ConnectProtocol::IsEvaluated())
if (!IsEvaluated())
{
ConnectProtocol::EvaluateProtocol();
EvaluateProtocol();
}
return (!ConnectProtocol::ConnectString.empty());
return (!ConnectString.empty());
}
bool ConnectProtocol::InstallProtocol()
@ -25,8 +26,8 @@ namespace Components
HKEY hKey = nullptr;
std::string data;
char ownPth[MAX_PATH] = {0};
char workdir[MAX_PATH] = {0};
char ownPth[MAX_PATH]{};
char workdir[MAX_PATH]{};
DWORD dwsize = MAX_PATH;
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);
if (openRes == ERROR_SUCCESS)
{
char regred[MAX_PATH] = { 0 };
char regred[MAX_PATH]{};
// Check if the game has been moved.
openRes = RegQueryValueExA(hKey, nullptr, nullptr, nullptr, reinterpret_cast<BYTE*>(regred), &dwsize);
if (openRes == ERROR_SUCCESS)
{
char* endPtr = strstr(regred, "\" \"%1\"");
auto* endPtr = std::strstr(regred, "\" \"%1\"");
if (endPtr != nullptr)
{
*endPtr = 0;
@ -82,7 +83,8 @@ namespace Components
}
RegCloseKey(hKey);
if (strcmp(regred + 1, ownPth))
if (std::strcmp(regred + 1, ownPth) != 0)
{
RegDeleteKeyA(HKEY_CURRENT_USER, "SOFTWARE\\Classes\\iw4x");
}
@ -173,8 +175,8 @@ namespace Components
void ConnectProtocol::EvaluateProtocol()
{
if (ConnectProtocol::Evaluated) return;
ConnectProtocol::Evaluated = true;
if (Evaluated) return;
Evaluated = true;
std::string cmdLine = GetCommandLineA();
@ -190,15 +192,15 @@ namespace Components
cmdLine = cmdLine.substr(0, pos);
}
ConnectProtocol::ConnectString = cmdLine;
ConnectString = cmdLine;
}
}
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
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
Scheduler::OnGameInitialized(ConnectProtocol::Invocation, Scheduler::Pipeline::MAIN);
Scheduler::OnGameInitialized(Invocation, Scheduler::Pipeline::MAIN);
ConnectProtocol::InstallProtocol();
ConnectProtocol::EvaluateProtocol();
InstallProtocol();
EvaluateProtocol();
// Fire protocol handlers
// Make sure this happens after the pipe-initialization!
if (ConnectProtocol::Used())
if (Used())
{
if (!Singleton::IsFirstInstance())
{
IPCPipe::Write("connect", ConnectProtocol::ConnectString);
IPCPipe::Write("connect", ConnectString);
ExitProcess(0);
}
else
{
// 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([]
{

View File

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

View File

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

View File

@ -1,4 +1,6 @@
#include <STDInclude.hpp>
#include "Debug.hpp"
#include "Game/Engine/ScopedCriticalSection.hpp"
namespace Components
@ -6,7 +8,7 @@ namespace Components
const Game::dvar_t* Debug::DebugOverlay;
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[] =
{
@ -92,15 +94,15 @@ namespace Components
"EF_SOFT",
};
const char Debug::strButtons[] =
const char Debug::StrButtons[] =
{
'\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x0E', '\x0F', '\x10',
'\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)
{
@ -161,19 +163,19 @@ namespace Components
auto* const font2 = Game::UI_GetFontHandle(scrPlace, 6, MY_SCALE2);
Game::UI_DrawText(scrPlace, "Client View of Flags", maxChars, font2, -60.0f, 0, 1, 1,
MY_SCALE2, colorWhite, 1);
MY_SCALE2, ColorWhite, 1);
const auto pmf = BuildPMFlagsString(&cgameGlob->predictedPlayerState);
Game::UI_DrawText(scrPlace, pmf.data(), maxChars, font1, 30.0f, MY_Y, 1, 1, MY_SCALE_2, colorWhite, 3);
Game::UI_DrawText(scrPlace, pmf.data(), maxChars, font1, 30.0f, MY_Y, 1, 1, MY_SCALE_2, ColorWhite, 3);
const auto pof = BuildPOFlagsString(&cgameGlob->predictedPlayerState);
Game::UI_DrawText(scrPlace, pof.data(), maxChars, font1, 350.0f, MY_Y, 1, 1, MY_SCALE_2, colorWhite, 3);
Game::UI_DrawText(scrPlace, pof.data(), maxChars, font1, 350.0f, MY_Y, 1, 1, MY_SCALE_2, ColorWhite, 3);
const auto plf = BuildPLFlagsString(&cgameGlob->predictedPlayerState);
Game::UI_DrawText(scrPlace, plf.data(), maxChars, font1, 350.0f, 250.0f, 1, 1, MY_SCALE_2, colorWhite, 3);
Game::UI_DrawText(scrPlace, plf.data(), maxChars, font1, 350.0f, 250.0f, 1, 1, MY_SCALE_2, ColorWhite, 3);
const auto pef = BuildPEFlagsString(&cgameGlob->predictedPlayerState);
Game::UI_DrawText(scrPlace, pef.data(), maxChars, font1, 525.0f, MY_Y, 1, 1, MY_SCALE_2, colorWhite, 3);
Game::UI_DrawText(scrPlace, pef.data(), maxChars, font1, 525.0f, MY_Y, 1, 1, MY_SCALE_2, ColorWhite, 3);
}
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 color2[] = {0.0f, 1.0f, 0.0f, 1.0f};
assert((*PlayerDebugHealth)->current.enabled);
assert(PlayerDebugHealth->current.enabled);
const auto* cgameGlob = Game::cgArray;
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 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_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_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_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_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_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)
@ -254,7 +256,7 @@ namespace Components
break;
}
if ((*PlayerDebugHealth)->current.enabled)
if (PlayerDebugHealth->current.enabled)
{
CG_DrawDebugPlayerHealth(localClientNum);
}
@ -304,10 +306,31 @@ namespace Components
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()
{
static const char* debugOverlayNames_0[] =
@ -327,6 +350,12 @@ namespace Components
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()
{
Scheduler::Once(CL_InitDebugDvars, Scheduler::Pipeline::MAIN);
@ -336,8 +365,11 @@ namespace Components
Utils::Hook::Set<void(*)()>(0x60BCEA, Com_Assert_f);
Utils::Hook(0x4487F7, Dvar_Register_PlayerDebugHealth, HOOK_CALL).install()->quick();
#ifdef _DEBUG
Command::Add("bug", Com_Bug_f);
Command::Add("bug_name_inc", Com_BugNameInc_f);
#endif
}
}

View File

@ -12,15 +12,15 @@ namespace Components
static const Game::dvar_t* BugName;
// Game dvars
static Game::dvar_t** PlayerDebugHealth;
static const Game::dvar_t* PlayerDebugHealth;
static const char* PMFlagsValues[];
static const char* POFlagsValues[];
static const char* PLFlagsValues[];
static const char* PEFlagsValues[];
static const char strButtons[];
static const char strTemplate[];
static const char StrButtons[];
static const char StrTemplate[];
static constexpr auto MY_SCALE2 = 0.5f;
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_Y = 20.0f;
static const float colorWhite[];
static const float ColorWhite[];
static std::string BuildPMFlagsString(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_Bug_f(Command::Params* params);
static void Com_BugNameInc_f();
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 "CardTitles.hpp"
#include "ClanTags.hpp"
#include "ServerCommands.hpp"
namespace Components
{
@ -114,7 +117,7 @@ namespace Components
if (!partyEnable) // Time wrapping should not occur in party servers, but yeah...
{
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);

View File

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

View File

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

View File

@ -81,7 +81,7 @@ namespace Components
static ClientDownload CLDownload;
static std::thread ServerThread;
static bool Terminate;
static volatile bool Terminate;
static bool ServerRunning;
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()
@ -229,7 +229,8 @@ namespace Components
// Don't perform any checks if name didn't change
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] == '{'))
{
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;
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";
@ -306,10 +307,10 @@ namespace Components
{
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)
@ -320,7 +321,7 @@ namespace Components
{
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;
}
@ -330,6 +331,12 @@ namespace Components
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);
}
@ -392,15 +399,29 @@ namespace Components
// un-cheat cg_fovscale and add archive flags
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
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);
// remove write protection from fs_game
Utils::Hook::Xor<std::uint32_t>(0x6431EA, Game::DVAR_INIT);
// cheat protect g_hardcore
Utils::Hook::Xor<std::uint32_t>(0x5E374F, Game::DVAR_CHEAT);
Utils::Hook::Xor<std::uint32_t>(0x4D3689, Game::DVAR_CHEAT);
Utils::Hook::Xor<std::uint32_t>(0x4197C3, Game::DVAR_CHEAT);
// set cg_fov max to 160.0
// because that's the max on SP
static float cg_Fov = 160.0f;

View File

@ -5,15 +5,6 @@ namespace Components
class Dvar : public Component
{
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
{
public:
@ -44,8 +35,8 @@ namespace Components
~Dvar();
// 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, T min, T max, 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, std::uint16_t flag, const char* description);
static void ResetDvarsValue();

View File

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

View File

@ -1,24 +1,17 @@
#include <STDInclude.hpp>
#include "Console.hpp"
#include <version.hpp>
namespace Components
{
Utils::Hook Exception::SetFilterHook;
int Exception::MiniDumpType;
__declspec(noreturn) void Exception::ErrorLongJmp(jmp_buf _Buf, int _Value)
{
if (!*reinterpret_cast<DWORD*>(0x1AD7EB4))
{
TerminateProcess(GetCurrentProcess(), 1337);
}
longjmp(_Buf, _Value);
}
__declspec(noreturn) void Exception::LongJmp(jmp_buf _Buf, int _Value)
__declspec(noreturn) void Exception::LongJmp_Internal_Stub(jmp_buf env, int status)
{
AssetHandler::ResetBypassState();
longjmp(_Buf, _Value);
Game::longjmp_internal(env, status);
}
void Exception::SuspendProcess()
@ -74,7 +67,7 @@ namespace Components
return;
}
auto lock = GlobalLock(hMem);
auto* lock = GlobalLock(hMem);
if (lock != nullptr)
{
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);
}
//Exception::SuspendProcess();
// Message should be copied to the keyboard if no button is pressed
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"))
{
Exception::SetMiniDumpType(true, false);
SetMiniDumpType(true, false);
}
// Current executable name
@ -125,30 +116,29 @@ namespace Components
PathRemoveExtensionA(exeFileName);
// Generate filename
char filenameFriendlyTime[MAX_PATH];
char filenameFriendlyTime[MAX_PATH]{};
__time64_t time;
tm ltime;
_time64(&time);
_localtime64_s(&ltime, &time);
strftime(filenameFriendlyTime, sizeof(filenameFriendlyTime) - 1, "%Y%m%d%H%M%S", &ltime);
// Combine with queuedMinidumpsFolder
char filename[MAX_PATH] = { 0 };
Utils::IO::CreateDir("minidumps");
// Combine with queued MinidumpsFolder
char filename[MAX_PATH]{};
CreateDirectoryA("minidumps", nullptr);
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);
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!");
Utils::OutputDebugLastError();
TerminateProcess(GetCurrentProcess(), ExceptionInfo->ExceptionRecord->ExceptionCode);
}
//if (ExceptionInfo->ExceptionRecord->ExceptionFlags == EXCEPTION_NONCONTINUABLE)
{
TerminateProcess(GetCurrentProcess(), ExceptionInfo->ExceptionRecord->ExceptionCode);
}
@ -156,54 +146,50 @@ namespace Components
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)
{
Exception::MiniDumpType = MiniDumpIgnoreInaccessibleMemory;
Exception::MiniDumpType |= MiniDumpWithHandleData;
Exception::MiniDumpType |= MiniDumpScanMemory;
Exception::MiniDumpType |= MiniDumpWithProcessThreadData;
Exception::MiniDumpType |= MiniDumpWithFullMemoryInfo;
Exception::MiniDumpType |= MiniDumpWithThreadInfo;
//Exception::MiniDumpType |= MiniDumpWithModuleHeaders;
MiniDumpType = MiniDumpIgnoreInaccessibleMemory;
MiniDumpType |= MiniDumpWithHandleData;
MiniDumpType |= MiniDumpScanMemory;
MiniDumpType |= MiniDumpWithProcessThreadData;
MiniDumpType |= MiniDumpWithFullMemoryInfo;
MiniDumpType |= MiniDumpWithThreadInfo;
if (codeseg)
{
Exception::MiniDumpType |= MiniDumpWithCodeSegs;
MiniDumpType |= MiniDumpWithCodeSegs;
}
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::SetMiniDumpType(Flags::HasFlag("bigminidumps"), Flags::HasFlag("reallybigminidumps"));
SetMiniDumpType(Flags::HasFlag("bigminidumps"), Flags::HasFlag("reallybigminidumps"));
#if !defined(DEBUG) || defined(FORCE_EXCEPTION_HANDLER)
Exception::SetFilterHook.initialize(SetUnhandledExceptionFilter, Exception::SetUnhandledExceptionFilterStub, HOOK_JUMP);
Exception::SetFilterHook.install();
SetFilterHook.initialize(::SetUnhandledExceptionFilter, SetUnhandledExceptionFilter_Stub, HOOK_JUMP);
SetFilterHook.install();
SetUnhandledExceptionFilter(&Exception::ExceptionFilter);
#endif
::SetUnhandledExceptionFilter(&ExceptionFilter);
//Utils::Hook(0x4B241F, Exception::ErrorLongJmp, HOOK_CALL).install()->quick();
Utils::Hook(0x6B8898, Exception::LongJmp, HOOK_JUMP).install()->quick();
Utils::Hook(0x4B241F, LongJmp_Internal_Stub, HOOK_CALL).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)
{
Game::UI_UpdateArenas();
@ -225,6 +211,6 @@ namespace Components
Exception::~Exception()
{
Exception::SetFilterHook.uninstall();
SetFilterHook.uninstall();
}
}

View File

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

View File

@ -235,10 +235,11 @@ namespace Components
const char* dir = Dvar::Var("fs_basepath").get<const char*>();
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())
{
paths.push_back(Utils::String::VA("%s\\", modDir.data()));
paths.push_back(std::format("{}\\", modDir));
}
if (Utils::String::StartsWith(file, "mp_"))
@ -256,17 +257,17 @@ namespace Components
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);
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
if (!Utils::String::EndsWith(absoluteFile, ".ff"))
@ -277,11 +278,11 @@ namespace Components
// Check if FastFile exists
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)

View File

@ -78,19 +78,19 @@ namespace Components
Utils::Memory::Allocator allocator;
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);
char* buffer = allocator.allocateArray<char>(this->size);
if (!this->read(buffer, this->size))
{
this->seek(position, Game::FS_SEEK_SET);
return std::string();
return {};
}
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)

View File

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

View File

@ -6,9 +6,9 @@ namespace Components
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))
{
@ -40,7 +40,7 @@ namespace Components
if (wFlag[0] == L'-')
{
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
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 "UIFeeder.hpp"
namespace Components
{

View File

@ -4,6 +4,7 @@
#include "IO.hpp"
#include "Script.hpp"
#include "ScriptExtension.hpp"
#include "ScriptPatches.hpp"
#include "ScriptStorage.hpp"
namespace Components
@ -14,6 +15,7 @@ namespace Components
Loader::Register(new IO());
Loader::Register(new Script());
Loader::Register(new ScriptExtension());
Loader::Register(new ScriptPatches());
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 file = p.filename().string();
Game::Scr_AddInt(FileSystem::_DeleteFile(folder, file));

View File

@ -3,8 +3,8 @@
namespace Components
{
std::unordered_map<std::string, Script::ScriptFunction> Script::CustomScrFunctions;
std::unordered_map<std::string, Script::ScriptMethod> Script::CustomScrMethods;
std::vector<Script::ScriptFunction> Script::CustomScrFunctions;
std::vector<Script::ScriptMethod> Script::CustomScrMethods;
std::string Script::ScriptName;
std::vector<std::string> Script::ScriptNameStack;
@ -15,29 +15,21 @@ namespace Components
std::unordered_map<const char*, const char*> Script::ReplacedFunctions;
const char* Script::ReplacedPos = nullptr;
std::vector<int> Script::ScriptMainHandles;
std::vector<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");
}
std::unordered_map<std::string, int> Script::ScriptMainHandles;
std::unordered_map<std::string, int> Script::ScriptInitHandles;
void Script::FunctionError()
{
const auto* funcName = Game::SL_ConvertToString(Script::FunctionName);
const auto* funcName = Game::SL_ConvertToString(FunctionName);
Game::Scr_ShutdownAllocNode();
Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "\n");
Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "******* script compile error *******\n");
Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "Error: unknown function {} in {}\n", funcName, Script::ScriptName);
Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "Error: unknown function {} in {}\n", funcName, ScriptName);
Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "************************************\n");
Logger::Error(Game::ERR_SCRIPT_DROP, "script compile error\nunknown function {}\n{}\n\n", funcName, Script::ScriptName);
Logger::Error(Game::ERR_SCRIPT_DROP, "script compile error\nunknown function {}\n{}\n\n", funcName, ScriptName);
}
__declspec(naked) void Script::StoreFunctionNameStub()
@ -45,7 +37,7 @@ namespace Components
__asm
{
mov eax, [esp - 8h]
mov Script::FunctionName, ax
mov FunctionName, ax
sub esp, 0Ch
push 0
@ -87,12 +79,12 @@ namespace Components
void Script::StoreScriptName(const char* name)
{
Script::ScriptNameStack.push_back(Script::ScriptName);
Script::ScriptName = name;
ScriptNameStack.push_back(ScriptName);
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]
push ecx
call Script::StoreScriptName
call StoreScriptName
add esp, 4h
popad
@ -120,8 +112,8 @@ namespace Components
void Script::RestoreScriptName()
{
Script::ScriptName = Script::ScriptNameStack.back();
Script::ScriptNameStack.pop_back();
ScriptName = ScriptNameStack.back();
ScriptNameStack.pop_back();
}
__declspec(naked) void Script::RestoreScriptNameStub()
@ -129,7 +121,7 @@ namespace Components
__asm
{
pushad
call Script::RestoreScriptName
call RestoreScriptName
popad
mov ds:1CDEAA8h, ebp
@ -205,17 +197,19 @@ namespace Components
Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "\n");
Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "******* script compile error *******\n");
Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "Error: {} ", msgbuf);
Script::PrintSourcePos(Script::ScriptName.data(), offset);
PrintSourcePos(ScriptName.data(), offset);
Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "************************************\n\n");
Logger::Error(Game::ERR_SCRIPT_DROP, "script compile error\n{}\n{}\n(see console for actual details)\n", msgbuf, 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()
{
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));
}
@ -224,9 +218,11 @@ namespace Components
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));
}
@ -237,8 +233,8 @@ namespace Components
void Script::GScr_LoadGameTypeScript_Stub()
{
// Clear handles (from previous GSC loading session)
Script::ScriptMainHandles.clear();
Script::ScriptInitHandles.clear();
ScriptMainHandles.clear();
ScriptInitHandles.clear();
char path[MAX_PATH]{};
@ -262,18 +258,17 @@ namespace Components
}
Logger::Print("Script {}.gsc loaded successfully.\n", path);
Logger::Debug("Finding script handle main or init...");
const auto initHandle = Game::Scr_GetFunctionHandle(path, "init");
if (initHandle != 0)
{
Script::ScriptInitHandles.push_back(initHandle);
ScriptInitHandles.insert_or_assign(path, initHandle);
}
const auto mainHandle = Game::Scr_GetFunctionHandle(path, "main");
if (mainHandle != 0)
{
Script::ScriptMainHandles.push_back(mainHandle);
ScriptMainHandles.insert_or_assign(path, mainHandle);
}
// Allow scripts with no handles
@ -283,43 +278,74 @@ namespace Components
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.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.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)
{
if (pName != nullptr)
{
// If no function was found let's call game's function
if (const auto itr = Script::CustomScrFunctions.find(Utils::String::ToLower(*pName)); itr != Script::CustomScrFunctions.end())
const auto name = Utils::String::ToLower(*pName);
for (const auto& func : CustomScrFunctions)
{
*type = itr->second.type;
return itr->second.actionFunc;
}
if (Utils::Contains(&func.aliases, name))
{
*type = func.type;
return func.actionFunc;
}
}
}
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
}
@ -327,27 +353,32 @@ namespace Components
{
if (pName != nullptr)
{
// If no method was found let's call game's function
if (const auto itr = Script::CustomScrMethods.find(Utils::String::ToLower(*pName)); itr != Script::CustomScrMethods.end())
const auto name = Utils::String::ToLower(*pName);
for (const auto& meth : CustomScrMethods)
{
*type = itr->second.type;
return itr->second.actionFunc;
if (Utils::Contains(&meth.aliases, name))
{
*type = meth.type;
return meth.actionFunc;
}
}
}
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
}
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)
@ -355,7 +386,7 @@ namespace Components
auto bestCodePos = -1, nextCodePos = -1, offset = -1;
std::string file;
for (const auto& [key, value] : Script::ScriptBaseProgramNum)
for (const auto& [key, value] : ScriptBaseProgramNum)
{
const auto codePos = key;
@ -388,7 +419,7 @@ namespace Components
__asm
{
push esi
call Script::Scr_PrintPrevCodePos
call Scr_PrintPrevCodePos
add esp, 4h
pop esi
@ -402,7 +433,7 @@ namespace Components
{
// execute our hook
pushad
call Script::StoreScriptBaseProgramNum
call StoreScriptBaseProgramNum
popad
// execute overwritten code caused by the jump hook
@ -452,9 +483,9 @@ namespace Components
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;
}
if (Script::ReplacedFunctions.contains(what))
if (ReplacedFunctions.contains(what))
{
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()
@ -481,12 +512,12 @@ namespace Components
pushad
push edx
call Script::GetReplacedPos
call GetReplacedPos
pop edx
popad
cmp Script::ReplacedPos, 0
cmp ReplacedPos, 0
jne SetPos
movzx eax, byte ptr [edx]
@ -509,8 +540,8 @@ namespace Components
retn
SetPos:
mov edx, Script::ReplacedPos
mov Script::ReplacedPos, 0
mov edx, ReplacedPos
mov ReplacedPos, 0
movzx eax, byte ptr [edx]
inc edx
@ -521,7 +552,7 @@ namespace Components
Game::client_t* Script::GetClient(const Game::gentity_t* ent)
{
assert(ent != nullptr);
assert(ent);
if (ent->client == nullptr)
{
@ -529,7 +560,7 @@ namespace Components
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));
return nullptr;
@ -540,7 +571,7 @@ namespace Components
void Script::AddFunctions()
{
Script::AddFunction("ReplaceFunc", [] // gsc: ReplaceFunc(<function>, <function>)
AddFunction("ReplaceFunc", [] // gsc: ReplaceFunc(<function>, <function>)
{
if (Game::Scr_GetNumParam() != 2)
{
@ -548,14 +579,14 @@ namespace Components
return;
}
const auto what = Script::GetCodePosForParam(0);
const auto with = Script::GetCodePosForParam(1);
const auto what = GetCodePosForParam(0);
const auto with = GetCodePosForParam(1);
Script::SetReplacedPos(what, with);
SetReplacedPos(what, with);
});
// System time
Script::AddFunction("GetSystemMilliseconds", [] // gsc: GetSystemMilliseconds()
AddFunction("GetSystemMilliseconds", [] // gsc: GetSystemMilliseconds()
{
SYSTEMTIME time;
GetSystemTime(&time);
@ -564,7 +595,7 @@ namespace Components
});
// Executes command to the console
Script::AddFunction("Exec", [] // gsc: Exec(<string>)
AddFunction("Exec", [] // gsc: Exec(<string>)
{
const auto str = Game::Scr_GetString(0);
@ -578,7 +609,7 @@ namespace Components
});
// 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)
{
@ -595,9 +626,9 @@ namespace Components
});
// 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);
});
@ -605,34 +636,34 @@ namespace Components
Script::Script()
{
Utils::Hook(0x612DB0, Script::StoreFunctionNameStub, HOOK_JUMP).install()->quick();
Utils::Hook(0x427E71, Script::RestoreScriptNameStub, HOOK_JUMP).install()->quick();
Utils::Hook(0x427DBC, Script::StoreScriptNameStub, HOOK_JUMP).install()->quick();
Utils::Hook(0x426C2D, Script::StoreScriptBaseProgramNumStub, HOOK_JUMP).install()->quick();
Utils::Hook(0x42281B, Script::Scr_PrintPrevCodePosStub, HOOK_JUMP).install()->quick();
Utils::Hook(0x612DB0, StoreFunctionNameStub, HOOK_JUMP).install()->quick();
Utils::Hook(0x427E71, RestoreScriptNameStub, HOOK_JUMP).install()->quick();
Utils::Hook(0x427DBC, StoreScriptNameStub, HOOK_JUMP).install()->quick();
Utils::Hook(0x426C2D, StoreScriptBaseProgramNumStub, HOOK_JUMP).install()->quick();
Utils::Hook(0x42281B, Scr_PrintPrevCodePosStub, HOOK_JUMP).install()->quick();
Utils::Hook(0x61E3AD, Script::RuntimeError, HOOK_CALL).install()->quick();
Utils::Hook(0x621976, Script::RuntimeError, HOOK_CALL).install()->quick();
Utils::Hook(0x62246E, Script::RuntimeError, HOOK_CALL).install()->quick();
Utils::Hook(0x61E3AD, RuntimeError, HOOK_CALL).install()->quick();
Utils::Hook(0x621976, RuntimeError, HOOK_CALL).install()->quick();
Utils::Hook(0x62246E, RuntimeError, HOOK_CALL).install()->quick();
// Skip check in GScr_CheckAllowedToSetPersistentData to prevent log spam in RuntimeError.
// On IW5 the function is entirely nullsubbed
Utils::Hook::Set<BYTE>(0x5F8DBF, 0xEB);
Utils::Hook::Set<std::uint8_t>(0x5F8DBF, 0xEB);
Utils::Hook(0x612E8D, Script::FunctionError, HOOK_CALL).install()->quick();
Utils::Hook(0x612EA2, Script::FunctionError, HOOK_CALL).install()->quick();
Utils::Hook(0x434260, Script::CompileError, HOOK_JUMP).install()->quick();
Utils::Hook(0x612E8D, FunctionError, HOOK_CALL).install()->quick();
Utils::Hook(0x612EA2, FunctionError, HOOK_CALL).install()->quick();
Utils::Hook(0x434260, CompileError, HOOK_JUMP).install()->quick();
Utils::Hook(0x48EFFE, Script::Scr_LoadGameType_Stub, HOOK_CALL).install()->quick();
Utils::Hook(0x48F008, Script::Scr_StartupGameType_Stub, HOOK_CALL).install()->quick();
Utils::Hook(0x45D44A, Script::GScr_LoadGameTypeScript_Stub, HOOK_CALL).install()->quick();
Utils::Hook(0x48EFFE, Scr_LoadGameType_Stub, HOOK_CALL).install()->quick();
Utils::Hook(0x48F008, Scr_StartupGameType_Stub, HOOK_CALL).install()->quick();
Utils::Hook(0x45D44A, GScr_LoadGameTypeScript_Stub, HOOK_CALL).install()->quick();
// Fetch custom functions
Utils::Hook(0x44E72E, Script::BuiltIn_GetFunctionStub, HOOK_CALL).install()->quick(); // Scr_GetFunction
Utils::Hook(0x4EC8DD, Script::BuiltIn_GetMethodStub, HOOK_CALL).install()->quick(); // Scr_GetMethod
Utils::Hook(0x44E72E, BuiltIn_GetFunctionStub, HOOK_CALL).install()->quick(); // Scr_GetFunction
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);
Scheduler::Loop([]
@ -642,9 +673,9 @@ namespace Components
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)
{
@ -652,11 +683,11 @@ namespace Components
}
}
Script::LastFrameTime = nowMs;
LastFrameTime = nowMs;
}, Scheduler::Pipeline::SERVER);
#ifdef _DEBUG
Script::AddFunction("DebugBox", []
AddFunction("DebugBox", []
{
const auto* message = Game::Scr_GetString(0);
@ -666,14 +697,14 @@ namespace Components
}
MessageBoxA(nullptr, message, "DEBUG", MB_OK);
}, 1);
}, true);
#endif
Script::AddFunctions();
AddFunctions();
Events::OnVMShutdown([]
{
Script::ReplacedFunctions.clear();
ReplacedFunctions.clear();
});
}
}

View File

@ -7,30 +7,55 @@ namespace Components
public:
Script();
using scriptNames = std::vector<std::string>;
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 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 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:
struct ScriptFunction
{
Game::BuiltinFunction actionFunc;
bool type;
scriptNames aliases;
};
struct ScriptMethod
{
Game::BuiltinMethod actionFunc;
bool type;
scriptNames aliases;
};
static std::unordered_map<std::string, ScriptFunction> CustomScrFunctions;
static std::unordered_map<std::string, ScriptMethod> CustomScrMethods;
static std::vector<ScriptFunction> CustomScrFunctions;
static std::vector<ScriptMethod> CustomScrMethods;
static std::string ScriptName;
static std::vector<std::string> ScriptNameStack;
@ -38,8 +63,8 @@ namespace Components
static std::unordered_map<int, std::string> ScriptBaseProgramNum;
static int LastFrameTime;
static std::vector<int> ScriptMainHandles;
static std::vector<int> ScriptInitHandles;
static std::unordered_map<std::string, int> ScriptMainHandles;
static std::unordered_map<std::string, int> ScriptInitHandles;
static std::unordered_map<const char*, const char*> ReplacedFunctions;
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()
{
AddEntityField("entityflags", Game::F_INT,
@ -330,16 +306,10 @@ namespace Components
AddEntityFields();
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(0x41BED2, Scr_SetObjectFieldStub, HOOK_CALL).install()->quick(); // SetEntityFieldValue
Utils::Hook(0x5FBF01, Scr_SetClientFieldStub, HOOK_CALL).install()->quick(); // Scr_SetObjectField
Utils::Hook(0x4FF413, Scr_GetEntityFieldStub, HOOK_CALL).install()->quick(); // Scr_GetObjectField
// 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 AddEntityFields();
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());
});
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();
{
Data.clear();

View File

@ -1,4 +1,6 @@
#include <STDInclude.hpp>
#include "Gamepad.hpp"
#include "RawMouse.hpp"
namespace Components
{
@ -749,7 +751,7 @@ namespace Components
-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[1] = Game::cgArray[0].selectedLocation[1];
}
@ -788,7 +790,7 @@ namespace Components
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)
@ -1096,7 +1098,7 @@ namespace Components
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 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))
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)
{
@ -1176,7 +1178,7 @@ namespace Components
char cmd[1024];
if (pressedOrUpdated)
{
if (Game::Key_IsKeyCatcherActive(gamePadIndex, Game::KEYCATCH_UI))
if (Game::Key_IsCatcherActive(gamePadIndex, Game::KEYCATCH_UI))
{
UI_GamepadKeyEvent(gamePadIndex, key, pressedOrUpdated);
return;
@ -1203,7 +1205,7 @@ namespace Components
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);
}
@ -1218,7 +1220,7 @@ namespace Components
gamePad.inUse = 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);

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 "Lean.hpp"
namespace Components
{

View File

@ -1,4 +1,5 @@
#include <STDInclude.hpp>
#include "Console.hpp"
namespace Components
{
@ -6,7 +7,7 @@ namespace Components
std::vector<std::string> Logger::MessageQueue;
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()
{
@ -19,10 +20,10 @@ namespace Components
va_list va;
va_start(va, message);
_vsnprintf_s(buf, _TRUNCATE, message, va);
vsnprintf_s(buf, _TRUNCATE, message, va);
va_end(va);
Logger::MessagePrint(channel, {buf});
MessagePrint(channel, {buf});
}
void Logger::MessagePrint(const int channel, const std::string& msg)
@ -42,14 +43,14 @@ namespace Components
return;
}
if (!Logger::IsConsoleReady())
if (!IsConsoleReady())
{
OutputDebugStringA(out.data());
}
if (!Game::Sys_IsMainThread())
{
Logger::EnqueueMessage(msg);
EnqueueMessage(msg);
}
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
const auto msg = std::vformat(fmt, args);
@ -67,17 +68,17 @@ namespace Components
const auto out = std::format("^2{}\n", msg);
#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);
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
if (IsDebuggerPresent()) __debugbreak();
@ -87,53 +88,53 @@ namespace Components
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);
++(*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();
}
}
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);
Logger::MessagePrint(channel, msg);
MessagePrint(channel, msg);
}
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);
if (!Logger::IsConsoleReady())
if (!IsConsoleReady())
{
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)
{
if (Logger::PipeCallback)
if (PipeCallback)
{
Logger::PipeCallback(data);
PipeCallback(data);
}
}
@ -144,7 +145,7 @@ namespace Components
return;
}
for (const auto& addr : Logger::LoggingAddresses[gLog & 1])
for (const auto& addr : LoggingAddresses[gLog & 1])
{
Network::SendCommand(addr, "print", data);
}
@ -169,20 +170,20 @@ namespace Components
}
// 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()
{
__asm
{
mov eax, Logger::PipeCallback
mov eax, PipeCallback
test eax, eax
jz returnPrint
pushad
push [esp + 28h]
call Logger::PrintMessagePipe
call PrintMessagePipe
add esp, 4h
popad
retn
@ -191,7 +192,7 @@ namespace Components
pushad
push 0
push [esp + 2Ch]
call Logger::NetworkLog
call NetworkLog
add esp, 8h
popad
@ -205,8 +206,8 @@ namespace Components
void Logger::EnqueueMessage(const std::string& message)
{
std::unique_lock _(Logger::MessageMutex);
Logger::MessageQueue.push_back(message);
std::unique_lock _(MessageMutex);
MessageQueue.push_back(message);
}
void Logger::RedirectOSPath(const char* file, char* folder)
@ -232,7 +233,7 @@ namespace Components
push [esp + 28h]
push [esp + 30h]
call Logger::RedirectOSPath
call RedirectOSPath
add esp, 8h
@ -256,9 +257,9 @@ namespace Components
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;
const auto num = atoi(params->get(1));
if (Utils::String::VA("%i", num) == std::string(params->get(1)) && static_cast<unsigned int>(num) < Logger::LoggingAddresses[0].size())
const auto num = std::atoi(params->get(1));
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;
Logger::Print("Address {} removed\n", addr->getString());
Logger::LoggingAddresses[0].erase(addr);
Print("Address {} removed\n", addr->getString());
LoggingAddresses[0].erase(addr);
}
else
{
Network::Address addr(params->get(1));
const auto i = std::find(Logger::LoggingAddresses[0].begin(), Logger::LoggingAddresses[0].end(), addr);
if (i != Logger::LoggingAddresses[0].end())
const auto i = std::find(LoggingAddresses[0].begin(), LoggingAddresses[0].end(), addr);
if (i != LoggingAddresses[0].end())
{
Logger::LoggingAddresses[0].erase(i);
Logger::Print("Address {} removed\n", addr.getString());
LoggingAddresses[0].erase(i);
Print("Address {} removed\n", addr.getString());
}
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");
Logger::Print("-------------\n");
Print("# ID: Address\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));
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;
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;
Logger::Print("Address {} removed\n", addr->getString());
Logger::LoggingAddresses[1].erase(addr);
const auto addr = LoggingAddresses[1].begin() + num;
Print("Address {} removed\n", addr->getString());
LoggingAddresses[1].erase(addr);
}
else
{
const Network::Address addr(params->get(1));
const auto i = std::find(Logger::LoggingAddresses[1].begin(), Logger::LoggingAddresses[1].end(), addr);
if (i != Logger::LoggingAddresses[1].end())
const auto i = std::ranges::find(LoggingAddresses[1].begin(), LoggingAddresses[1].end(), addr);
if (i != LoggingAddresses[1].end())
{
Logger::LoggingAddresses[1].erase(i);
Logger::Print("Address {} removed\n", addr.getString());
LoggingAddresses[1].erase(i);
Print("Address {} removed\n", addr.getString());
}
else
{
Logger::Print("Address {} not found!\n", addr.getString());
Print("Address {} not found!\n", addr.getString());
}
}
});
Command::AddSV("g_log_list", [](Command::Params*)
{
Logger::Print("# ID: Address\n");
Logger::Print("-------------\n");
Print("# ID: Address\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()
{
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, Logger::G_LogPrintf_Hk, HOOK_JUMP).install()->quick();
Utils::Hook(Game::Com_PrintMessage, Logger::PrintMessage_Stub, HOOK_JUMP).install()->quick();
Utils::Hook(Game::G_LogPrintf, G_LogPrintf_Hk, HOOK_JUMP).install()->quick();
Utils::Hook(Game::Com_PrintMessage, PrintMessage_Stub, HOOK_JUMP).install()->quick();
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::LoggingAddresses[0].clear();
Logger::LoggingAddresses[1].clear();
LoggingAddresses[0].clear();
LoggingAddresses[1].clear();
std::unique_lock lock(Logger::MessageMutex);
Logger::MessageQueue.clear();
std::unique_lock lock(MessageMutex);
MessageQueue.clear();
lock.unlock();
// Flush the console log

View File

@ -12,90 +12,107 @@ namespace Components
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 ErrorInternal(Game::errorParm_t error, std::string_view fmt, std::format_args&& args);
static void PrintErrorInternal(int channel, std::string_view fmt, std::format_args&& args);
static void WarningInternal(int channel, 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 PrintInternal(Game::conChannel_t channel, const 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(Game::conChannel_t channel, const 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(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));
}
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));
}
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...));
}
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...));
}
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));
}
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...));
}
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));
}
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...));
}
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));
}
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...));
}
template <typename... Args>
class Debug
struct FormatWithLocation
{
public:
Debug([[maybe_unused]] std::string_view fmt, [[maybe_unused]] const Args&... args, [[maybe_unused]] const std::source_location& loc = std::source_location::current())
std::string_view format;
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>
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:
static std::mutex MessageMutex;
static std::vector<std::string> MessageQueue;
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);

View File

@ -1,4 +1,5 @@
#include <STDInclude.hpp>
#include "MapDump.hpp"
namespace Components
{
@ -362,27 +363,8 @@ namespace Components
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);
D3DXSaveTextureToFileA(_name.data(), D3DXIFF_PNG, image->texture.map, nullptr);
}
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);
return image;
}

View File

@ -1,4 +1,5 @@
#include <STDInclude.hpp>
#include "MapRotation.hpp"
namespace Components
{
@ -26,7 +27,7 @@ namespace Components
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();
}
@ -47,7 +48,7 @@ namespace Components
const auto& key = tokens[i];
const auto& value = tokens[i + 1];
if (key == "map" || key == "gametype")
if (key == "map"s || key == "gametype"s)
{
this->addEntry(key, value);
}
@ -58,13 +59,17 @@ 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
{
return std::ranges::any_of(this->rotationEntries_,
[&](const auto& entry)
{
return entry.first == key && entry.second == value;
});
return std::ranges::any_of(this->rotationEntries_, [&](const auto& entry)
{
return entry.first == key && entry.second == value;
});
}
nlohmann::json MapRotation::RotationData::to_json() const
@ -74,18 +79,18 @@ namespace Components
for (const auto& [key, val] : this->rotationEntries_)
{
if (key == "map")
if (key == "map"s)
{
mapVector.emplace_back(val);
}
else if (key == "gametype")
else if (key == "gametype"s)
{
gametypeVector.emplace_back(val);
}
}
nlohmann::json mapRotationJson = nlohmann::json
auto mapRotationJson = nlohmann::json
{
{"maps", mapVector},
{"gametypes", gametypeVector},
@ -194,7 +199,7 @@ namespace Components
void MapRotation::ApplyGametype(const std::string& gametype)
{
assert(!gametype.empty());
Dvar::Var("g_gametype").set(gametype.data());
Dvar::Var("g_gametype").set(gametype);
}
void MapRotation::RestartCurrentMap()
@ -204,7 +209,7 @@ namespace Components
if (svMapname.empty())
{
Logger::Print(Game::CON_CHANNEL_SERVER, "mapname dvar is empty! Defaulting to mp_afghan\n");
svMapname = "mp_afghan";
svMapname = "mp_afghan"s;
}
ApplyMap(svMapname);
@ -212,7 +217,7 @@ namespace Components
void MapRotation::ApplyRotation(RotationData& rotation)
{
assert(rotation.getEntriesSize() != 0);
assert(!rotation.empty());
// Continue to apply gametype until a map is found
auto foundMap = false;
@ -222,7 +227,7 @@ namespace Components
{
const auto& entry = rotation.getNextEntry();
if (entry.first == "map")
if (entry.first == "map"s)
{
Logger::Debug("Loading new map: '{}'", entry.second);
ApplyMap(entry.second);
@ -230,7 +235,7 @@ namespace Components
// Map was found so we exit the loop
foundMap = true;
}
else if (entry.first == "gametype")
else if (entry.first == "gametype"s)
{
Logger::Debug("Applying new gametype: '{}'", entry.second);
ApplyGametype(entry.second);
@ -260,7 +265,7 @@ namespace Components
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);
RestartCurrentMap();
@ -302,7 +307,7 @@ namespace Components
}
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);
RestartCurrentMap();
@ -339,7 +344,7 @@ namespace Components
}
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;
}

View File

@ -30,11 +30,12 @@ namespace Components
// this method should be called to add a new entry (gamemode/map & 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();
void parse(const std::string& data);
[[nodiscard]] bool empty() const noexcept;
[[nodiscard]] bool contains(const std::string& key, const std::string& value) const;
[[nodiscard]] nlohmann::json to_json() const;

View File

@ -1,4 +1,8 @@
#include <STDInclude.hpp>
#include "RawFiles.hpp"
#include "StartupMessages.hpp"
#include "Theatre.hpp"
#include "UIFeeder.hpp"
namespace Components
{
@ -29,8 +33,8 @@ namespace Components
{
if (this->isValid() && !this->searchPath.iwd)
{
std::string iwdName = Utils::String::VA("%s.iwd", this->mapname.data());
std::string path = Utils::String::VA("%s\\usermaps\\%s\\%s", Dvar::Var("fs_basepath").get<const char*>(), this->mapname.data(), iwdName.data());
auto iwdName = std::format("{}.iwd", this->mapname);
auto path = std::format("{}\\usermaps\\{}\\{}", Dvar::Var("fs_basepath").get<std::string>(), this->mapname, iwdName);
if (Utils::IO::FileExists(path))
{
@ -146,10 +150,10 @@ namespace Components
team.allocFlags = zoneInfo->allocFlags;
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);
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);
}
@ -166,7 +170,7 @@ namespace Components
}
// Load patch files
std::string patchZone = Utils::String::VA("patch_%s", zoneInfo->name);
auto patchZone = std::format("patch_{}", zoneInfo->name);
if (FastFiles::Exists(patchZone))
{
data.push_back({patchZone.data(), zoneInfo->allocFlags, zoneInfo->freeFlags});
@ -301,7 +305,7 @@ namespace Components
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);
}
@ -404,13 +408,13 @@ namespace Components
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;
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))
{
hash.append(Utils::Cryptography::SHA256::Compute(Utils::IO::ReadFile(filePath)));
@ -564,13 +568,13 @@ namespace Components
bool Maps::IsCustomMap()
{
Game::GfxWorld*& gameWorld = *reinterpret_cast<Game::GfxWorld**>(0x66DEE94);
if(gameWorld) return gameWorld->checksum == 0xDEADBEEF;
if (gameWorld) return (gameWorld->checksum & 0xFFFF0000) == 0xC0D40000;
return false;
}
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()
@ -619,7 +623,7 @@ namespace Components
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[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)
{
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({ 7, "Resurrection Pack", {"mp_shipment_long", "mp_rust_long", "mp_firingrange"} });
Maps::AddDlc({ 8, "Recycled Pack", {"mp_bloc_sh", "mp_crash_tropical", "mp_estate_tropical", "mp_fav_tropical", "mp_storm_spring"} });
Maps::AddDlc({ 9, "Classics Pack #3", {"mp_farm", "mp_backlot", "mp_pipeline", "mp_countdown", "mp_crash_snow", "mp_carentan"}});
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();
@ -842,16 +766,6 @@ namespace Components
// Allow loading raw suns
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
Utils::Hook(0x6245E3, 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;
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);
}

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