Merge pull request #473 from XLabsProject/develop

Release v0.7.5
This commit is contained in:
Edo 2022-09-03 23:20:14 +02:00 committed by GitHub
commit 4812e4da9f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
149 changed files with 5197 additions and 3164 deletions

View File

@ -24,7 +24,7 @@ If IW4x is crashing, include the minidump file and the crash address in text for
Describe any steps you've already taken to try to get past this issue. Have you found a workaround?
**What version of IW4x are you using?**
`iw4x.exe -version` will show you the version. Please make sure you are up to date with the latest master branch.
`iw4x.exe -version` will show you the version. Please make sure you are up to date with the latest build from the master branch.
**Anything else we should know?**
Add any other context about the problem here.

7
.gitmodules vendored
View File

@ -2,10 +2,6 @@
path = deps/zlib
url = https://github.com/madler/zlib.git
branch = develop
[submodule "deps/json11"]
path = deps/json11
url = https://github.com/dropbox/json11.git
branch = master
[submodule "deps/libtommath"]
path = deps/libtommath
url = https://github.com/libtom/libtommath.git
@ -35,3 +31,6 @@
[submodule "deps/GSL"]
path = deps/GSL
url = https://github.com/microsoft/GSL.git
[submodule "deps/nlohmannjson"]
path = deps/nlohmannjson
url = https://github.com/nlohmann/json.git

View File

@ -4,6 +4,37 @@ 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.5] - 2022-09-03
### Added
- Add `bg_rocketJumpScale` Dvar (#413)
- Add `CastFloat` GSC function (#414)
- Add `Strtol` GSC function (#414)
- Add `bg_lean` Dvar (#421
- Add voice chat (#425)
- Add `vote` & `callvote` client commands (#447)
- Add `kill` client command (#451)
- Add `voteKick`, `voteTempBan`, `voteTypeMap`, `voteMap` and `voteGame` UI script tokens (#456)
- Add `Int64IsInt`, `Int64ToInt` and `Int64OP` GSC functions (#419)
### Changed
- Steam status is no longer set to busy (#417)
- `HttpGet`& `HttpCancel` are disabled for security reasons (#449)
- 'g_allowVote' is a replicated Dvar (#457)
### Fixed
- Fixed `startSingleplayer` command (#404)
- General stability update
### 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.4] - 2022-07-28
### Added
@ -187,7 +218,7 @@ The format is based on [Keep a Changelog v0.3.0](http://keepachangelog.com/en/0.
- Fixed slow motion during final killcams (#111 - #107)
- Fixed sound issue that causes the game to freeze (#106)
- Fixed issue where materials strings found in hostnames, player names, chat etc. caused the game to crash (#113)
- Fixed issue with servers displaying an invalid player count (#144)
- Fixed issue with servers displaying an invalid player count (#113)
### Known issues

View File

@ -8,9 +8,10 @@
# IW4x: Client
## How to compile
## Compile from source
- Run `premake5 vs2022` or use the delivered `generate.bat`.
- Clone the Git repo. Do NOT download it as ZIP, that won't work.
- Update the submodules and run `premake5 vs2022` or simply use the delivered `generate.bat`.
- Build via solution file in `build\iw4x.sln`.
## Premake arguments
@ -22,7 +23,6 @@
| `--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. |
| `--iw4x-zones` | Zonebuilder generates iw4x zones that cannot be loaded without IW4x specific patches. |
## Command line arguments

2
deps/GSL vendored

@ -1 +1 @@
Subproject commit 330583f47800c60cf001239550d291d16274756a
Subproject commit 10df83d292bf5bbdc487e57dc8c2dc8c7a01f4d1

1
deps/json11 vendored

@ -1 +0,0 @@
Subproject commit 2df9473fb3605980db55ecddf34392a2e832ad35

2
deps/libtomcrypt vendored

@ -1 +1 @@
Subproject commit 8fd5dad96b56beb53b5cf199cb63fb76dfba32bb
Subproject commit ddfe2e8aa7c4239463a8a1d26724aef123333549

1
deps/nlohmannjson vendored Submodule

@ -0,0 +1 @@
Subproject commit 307c053b9b250abc6e6d478a4336fd98592ae173

View File

@ -1,33 +0,0 @@
json11 = {
source = path.join(dependencies.basePath, "json11"),
}
function json11.import()
links {"json11"}
json11.includes()
end
function json11.includes()
includedirs {json11.source}
end
function json11.project()
project "json11"
language "C++"
cppdialect "C++11"
files
{
path.join(json11.source, "*.cpp"),
path.join(json11.source, "*.hpp"),
}
warnings "Off"
defines {"_LIB"}
removedefines {"_USRDLL", "_DLL"}
kind "StaticLib"
end
table.insert(dependencies, json11)

18
deps/premake/nlohmannjson.lua vendored Normal file
View File

@ -0,0 +1,18 @@
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 fb6f8da08b60b6beb5bb360d79dd3feda0147da7
Subproject commit 50bdb17409d4133d51ab6cfa095700f4c816576e

2
deps/zlib vendored

@ -1 +1 @@
Subproject commit 2333419cd76cb9ae5f15c9b240b16a2052b27691
Subproject commit 5752b171fd4cc96b8d1f9526ec1940199c6e9740

View File

@ -86,11 +86,6 @@ newoption {
description = "Do not perform integrity checks on the exe."
}
newoption {
trigger = "iw4x-zones",
description = "Zonebuilder generates iw4x zones that cannot be loaded without IW4x specific patches."
}
newaction {
trigger = "version",
description = "Returns the version string for the current commit of the source code.",
@ -268,9 +263,6 @@ workspace "iw4x"
if _OPTIONS["disable-binary-check"] then
defines {"DISABLE_BINARY_CHECK"}
end
if _OPTIONS["iw4x-zones"] then
defines {"GENERATE_IW4X_SPECIFIC_ZONES"}
end
-- Pre-compiled header
pchheader "STDInclude.hpp" -- must be exactly same as used in #include directives

View File

@ -112,6 +112,8 @@ namespace Components
Loader::Register(new Ceg());
Loader::Register(new UserInfo());
Loader::Register(new Events());
Loader::Register(new Voice());
Loader::Register(new Vote());
Loader::Register(new GSC());

View File

@ -143,5 +143,7 @@ namespace Components
#include "Modules/Ceg.hpp"
#include "Modules/UserInfo.hpp"
#include "Modules/Events.hpp"
#include "Modules/Voice.hpp"
#include "Modules/Vote.hpp"
#include "Modules/GSC/GSC.hpp"

View File

@ -601,7 +601,7 @@ namespace Components
}
}
json11::Json vertexData = json11::Json::object
nlohmann::json vertexData = json11::Json::object
{
{ "name", vertexdecl->name },
{ "streamCount", vertexdecl->streamCount },
@ -651,7 +651,7 @@ namespace Components
literalConsts.push_back(curArg->u.literalConst[3]);
}
json11::Json argData = json11::Json::object
nlohmann::json argData = json11::Json::object
{
{ "type", curArg->type },
{ "value", literalConsts },
@ -660,7 +660,7 @@ namespace Components
}
else if (curArg->type == 3 || curArg->type == 5)
{
json11::Json argData = json11::Json::object
nlohmann::json argData = json11::Json::object
{
{ "type", curArg->type },
{ "firstRow", curArg->u.codeConst.firstRow },
@ -671,7 +671,7 @@ namespace Components
}
else
{
json11::Json argData = json11::Json::object
nlohmann::json argData = json11::Json::object
{
{ "type", curArg->type },
{ "value", static_cast<int>(curArg->u.codeSampler) },
@ -680,7 +680,7 @@ namespace Components
}
}
json11::Json passData = json11::Json::object
nlohmann::json passData = json11::Json::object
{
{ "perObjArgCount", curPass->perObjArgCount },
{ "perPrimArgCount", curPass->perPrimArgCount },
@ -693,7 +693,7 @@ namespace Components
passDataArray.push_back(passData);
}
json11::Json techData = json11::Json::object
nlohmann::json techData = json11::Json::object
{
{ "name", curTech->name },
{ "index", technique },
@ -708,7 +708,7 @@ namespace Components
fwrite(&stringData[0], stringData.size(), 1, fp);
fclose(fp);
json11::Json techsetTechnique = json11::Json::object
nlohmann::json techsetTechnique = json11::Json::object
{
{ "name", curTech->name },
{ "index", technique },
@ -721,7 +721,7 @@ namespace Components
}
}
json11::Json techsetData = json11::Json::object
nlohmann::json techsetData = json11::Json::object
{
{ "name", techset->name },
{ "techniques", techniques },

View File

@ -100,26 +100,19 @@ namespace Assets
if (fontDefFile.exists() && fontFile.exists())
{
std::string errors;
auto fontDef = json11::Json::parse(fontDefFile.getBuffer(), errors);
if (!errors.empty())
{
Components::Logger::Error(Game::ERR_FATAL, "Font define {} is broken: {}", name, errors);
return;
}
auto fontDef = nlohmann::json::parse(fontDefFile.getBuffer());
if (!fontDef.is_object())
{
Components::Logger::Error(Game::ERR_FATAL, "Font define {} is invalid {}", name, errors);
Components::Logger::Error(Game::ERR_FATAL, "Font define {} is invalid", name);
return;
}
int w = fontDef["textureWidth"].int_value();
int h = fontDef["textureHeight"].int_value();
int w = fontDef["textureWidth"].get<int>();
int h = fontDef["textureHeight"].get<int>();
int size = fontDef["size"].int_value();
int yOffset = fontDef["yOffset"].int_value();
int size = fontDef["size"].get<int>();
int yOffset = fontDef["yOffset"].get<int>();
auto* pixels = builder->getAllocator()->allocateArray<uint8_t>(w * h);
@ -153,8 +146,9 @@ namespace Assets
if (fontDef["charset"].is_array())
{
for (auto& ch : fontDef["charset"].array_items())
charset.push_back(static_cast<uint16_t>(ch.int_value()));
nlohmann::json::array_t charsetArray = fontDef["charset"];
for (auto& ch : charsetArray)
charset.push_back(static_cast<uint16_t>(ch.get<int>()));
// order matters
std::sort(charset.begin(), charset.end());

View File

@ -332,165 +332,13 @@ namespace Assets
header->material = Components::AssetHandler::FindOriginalAsset(this->getType(), name.data()).material;
}
void IMaterial::loadJson(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
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;
std::string errors;
json11::Json infoData = json11::Json::parse(materialInfo.getBuffer(), errors);
if (!infoData.is_object())
{
Components::Logger::Error(Game::ERR_FATAL, "Failed to load material information for {}!", name);
return;
}
auto base = infoData["base"];
if (!base.is_string())
{
Components::Logger::Error(Game::ERR_FATAL, "No valid material base provided for {}!", name);
return;
}
Game::Material* baseMaterial = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_MATERIAL, base.string_value().data()).material;
if (!baseMaterial) // TODO: Maybe check if default asset? Maybe not? You could still want to use the default one as base!?
{
Components::Logger::Error(Game::ERR_FATAL, "Basematerial '{}' not found for {}!", base.string_value(), name);
return;
}
Game::Material* material = builder->getAllocator()->allocate<Game::Material>();
if (!material)
{
Components::Logger::Error(Game::ERR_FATAL, "Failed to allocate material structure!");
return;
}
// Copy base material to our structure
std::memcpy(material, baseMaterial, sizeof(Game::Material));
material->info.name = builder->getAllocator()->duplicateString(name);
material->info.textureAtlasRowCount = 1;
material->info.textureAtlasColumnCount = 1;
// Load animation frames
auto anims = infoData["anims"];
if (anims.is_array())
{
auto animCoords = anims.array_items();
if (animCoords.size() >= 2)
{
auto animCoordX = animCoords[0];
auto animCoordY = animCoords[1];
if (animCoordX.is_number())
{
material->info.textureAtlasColumnCount = static_cast<char>(animCoordX.number_value()) & 0xFF;
}
if (animCoordY.is_number())
{
material->info.textureAtlasRowCount = static_cast<char>(animCoordY.number_value()) & 0xFF;
}
}
}
// Model surface textures are special, they need a special order and whatnot
bool replaceTexture = Utils::String::StartsWith(name, "mc/");
if (replaceTexture)
{
Game::MaterialTextureDef* textureTable = builder->getAllocator()->allocateArray<Game::MaterialTextureDef>(baseMaterial->textureCount);
std::memcpy(textureTable, baseMaterial->textureTable, sizeof(Game::MaterialTextureDef) * baseMaterial->textureCount);
material->textureTable = textureTable;
material->textureCount = baseMaterial->textureCount;
}
// Load referenced textures
auto textures = infoData["textures"];
if (textures.is_array())
{
std::vector<Game::MaterialTextureDef> textureList;
for (auto& texture : textures.array_items())
{
if (!texture.is_array()) continue;
if (textureList.size() >= 0xFF) break;
auto textureInfo = texture.array_items();
if (textureInfo.size() < 2) continue;
auto map = textureInfo[0];
auto image = textureInfo[1];
if (!map.is_string() || !image.is_string()) continue;
Game::MaterialTextureDef textureDef;
textureDef.semantic = 0; // No water image
textureDef.samplerState = -30;
textureDef.nameEnd = map.string_value().back();
textureDef.nameStart = map.string_value().front();
textureDef.nameHash = Game::R_HashString(map.string_value().data());
textureDef.u.image = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_IMAGE, image.string_value(), builder).image;
if (replaceTexture)
{
bool applied = false;
for (char i = 0; i < baseMaterial->textureCount; ++i)
{
if (material->textureTable[i].nameHash == textureDef.nameHash)
{
applied = true;
material->textureTable[i].u.image = textureDef.u.image;
break;
}
}
if (!applied)
{
Components::Logger::Error(Game::ERR_FATAL, "Unable to find texture for map '{}' in {}!",
map.string_value(), baseMaterial->info.name);
}
}
else
{
textureList.push_back(textureDef);
}
}
if (!replaceTexture)
{
if (!textureList.empty())
{
Game::MaterialTextureDef* textureTable = builder->getAllocator()->allocateArray<Game::MaterialTextureDef>(textureList.size());
if (!textureTable)
{
Components::Logger::Error(Game::ERR_FATAL, "Failed to allocate texture table!");
return;
}
std::memcpy(textureTable, textureList.data(), sizeof(Game::MaterialTextureDef) * textureList.size());
material->textureTable = textureTable;
}
else
{
material->textureTable = nullptr;
}
material->textureCount = static_cast<char>(textureList.size()) & 0xFF;
}
}
header->material = material;
header->material = nullptr;
}
void IMaterial::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)

View File

@ -5,10 +5,10 @@ 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", name.c_str()));
Components::FileSystem::File aliasFile(Utils::String::VA("sounds/%s.json", name.data()));
if (!aliasFile.exists())
{
header->sound = Components::AssetHandler::FindOriginalAsset(this->getType(), name.c_str()).sound;
header->sound = Components::AssetHandler::FindOriginalAsset(this->getType(), name.data()).sound;
return;
}
@ -19,11 +19,10 @@ namespace Assets
return;
}
std::string errors;
json11::Json infoData = json11::Json::parse(aliasFile.getBuffer(), errors);
json11::Json aliasesContainer = infoData["head"];
nlohmann::json infoData = nlohmann::json::parse(aliasFile.getBuffer());
nlohmann::json aliasesContainer = infoData["head"];
auto aliases = aliasesContainer.array_items();
nlohmann::json::array_t aliases = aliasesContainer;
aliasList->count = aliases.size();
@ -39,7 +38,7 @@ namespace Assets
for (size_t i = 0; i < aliasList->count; i++)
{
json11::Json head = aliasesContainer[i];
nlohmann::json head = aliasesContainer[i];
if (!infoData.is_object())
{
@ -211,37 +210,37 @@ namespace Assets
{
alias->soundFile->exists = true;
alias->aliasName = builder->getAllocator()->duplicateString(aliasName.string_value().c_str());
alias->aliasName = builder->getAllocator()->duplicateString(aliasName.get<std::string>());
if (subtitle.is_string())
{
alias->subtitle = builder->getAllocator()->duplicateString(subtitle.string_value().c_str());
alias->subtitle = builder->getAllocator()->duplicateString(subtitle.get<std::string>());
}
if (secondaryAliasName.is_string())
{
alias->secondaryAliasName = builder->getAllocator()->duplicateString(secondaryAliasName.string_value().c_str());
alias->secondaryAliasName = builder->getAllocator()->duplicateString(secondaryAliasName.get<std::string>());
}
if (chainAliasName.is_string())
{
alias->chainAliasName = builder->getAllocator()->duplicateString(chainAliasName.string_value().c_str());
alias->chainAliasName = builder->getAllocator()->duplicateString(chainAliasName.get<std::string>());
}
alias->sequence = sequence.int_value();
alias->volMin = float(volMin.number_value());
alias->volMax = float(volMax.number_value());
alias->pitchMin = float(pitchMin.number_value());
alias->pitchMax = float(pitchMax.number_value());
alias->distMin = float(distMin.number_value());
alias->distMax = float(distMax.number_value());
alias->flags = flags.int_value();
alias->___u15.slavePercentage = float(slavePercentage.number_value());
alias->probability = float(probability.number_value());
alias->lfePercentage = float(lfePercentage.number_value());
alias->centerPercentage = float(centerPercentage.number_value());
alias->startDelay = startDelay.int_value();
alias->envelopMin = float(envelopMin.number_value());
alias->envelopMax = float(envelopMax.number_value());
alias->envelopPercentage = float(envelopPercentage.number_value());
alias->sequence = sequence.get<int>();
alias->volMin = volMin.get<float>();
alias->volMax = volMax.get<float>();
alias->pitchMin = pitchMin.get<float>();
alias->pitchMax = pitchMax.get<float>();
alias->distMin = distMin.get<float>();
alias->distMax = distMax.get<float>();
alias->flags = flags.get<int>();
alias->___u15.slavePercentage = slavePercentage.get<float>();
alias->probability = probability.get<float>();
alias->lfePercentage = lfePercentage.get<float>();
alias->centerPercentage = centerPercentage.get<float>();
alias->startDelay = startDelay.get<int>();
alias->envelopMin = envelopMin.get<float>();
alias->envelopMax = envelopMax.get<float>();
alias->envelopPercentage = envelopPercentage.get<float>();
// Speaker map object
if (!speakerMap.is_null())
@ -253,12 +252,12 @@ namespace Assets
return;
}
alias->speakerMap->name = builder->getAllocator()->duplicateString(speakerMap["name"].string_value().c_str());
alias->speakerMap->isDefault = speakerMap["isDefault"].bool_value();
alias->speakerMap->name = builder->getAllocator()->duplicateString(speakerMap["name"].get<std::string>());
alias->speakerMap->isDefault = speakerMap["isDefault"].get<bool>();
if (speakerMap["channelMaps"].is_array())
{
json11::Json::array channelMaps = speakerMap["channelMaps"].array_items();
nlohmann::json::array_t channelMaps = speakerMap["channelMaps"];
assert(channelMaps.size() <= 4);
@ -268,19 +267,19 @@ namespace Assets
// subChannelIndex should never exceed 1
for (size_t subChannelIndex = 0; subChannelIndex < 2; subChannelIndex++)
{
json11::Json channelMap = channelMaps[channelMapIndex * 2 + subChannelIndex]; // 0-3
nlohmann::json channelMap = channelMaps[channelMapIndex * 2 + subChannelIndex]; // 0-3
auto speakers = channelMap["speakers"].array_items();
nlohmann::json::array_t speakers = channelMap["speakers"];
alias->speakerMap->channelMaps[channelMapIndex][subChannelIndex].speakerCount = speakers.size();
for (size_t speakerIndex = 0; speakerIndex < alias->speakerMap->channelMaps[channelMapIndex][subChannelIndex].speakerCount; speakerIndex++)
{
auto speaker = speakers[speakerIndex];
alias->speakerMap->channelMaps[channelMapIndex][subChannelIndex].speakers[speakerIndex].levels[0] = static_cast<float>(speaker["levels0"].number_value());
alias->speakerMap->channelMaps[channelMapIndex][subChannelIndex].speakers[speakerIndex].levels[1] = static_cast<float>(speaker["levels1"].number_value());
alias->speakerMap->channelMaps[channelMapIndex][subChannelIndex].speakers[speakerIndex].numLevels = static_cast<int>(speaker["numLevels"].number_value());
alias->speakerMap->channelMaps[channelMapIndex][subChannelIndex].speakers[speakerIndex].speaker = static_cast<int>(speaker["speaker"].number_value());
alias->speakerMap->channelMaps[channelMapIndex][subChannelIndex].speakers[speakerIndex].levels[0] = speaker["levels0"].get<float>();
alias->speakerMap->channelMaps[channelMapIndex][subChannelIndex].speakers[speakerIndex].levels[1] = speaker["levels1"].get<float>();
alias->speakerMap->channelMaps[channelMapIndex][subChannelIndex].speakers[speakerIndex].numLevels = speaker["numLevels"].get<int>();
alias->speakerMap->channelMaps[channelMapIndex][subChannelIndex].speakers[speakerIndex].speaker = speaker["speaker"].get<int>();
}
}
}
@ -289,7 +288,7 @@ namespace Assets
if (volumeFalloffCurve.is_string())
{
std::string fallOffCurve = volumeFalloffCurve.string_value();
std::string fallOffCurve = volumeFalloffCurve.get<std::string>();
if (fallOffCurve.size() == 0)
{
@ -305,16 +304,16 @@ namespace Assets
alias->volumeFalloffCurve = curve;
}
if (static_cast<Game::snd_alias_type_t>(type.number_value()) == Game::snd_alias_type_t::SAT_LOADED) // Loaded
if (static_cast<Game::snd_alias_type_t>(type.get<int>()) == Game::snd_alias_type_t::SAT_LOADED) // Loaded
{
alias->soundFile->type = Game::SAT_LOADED;
alias->soundFile->u.loadSnd = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_LOADED_SOUND, soundFile.string_value().c_str(), builder).loadSnd;
alias->soundFile->u.loadSnd = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_LOADED_SOUND, soundFile.get<std::string>(), builder).loadSnd;
}
else if (static_cast<Game::snd_alias_type_t>(type.number_value()) == Game::snd_alias_type_t::SAT_STREAMED) // Streamed
else if (static_cast<Game::snd_alias_type_t>(type.get<int>()) == Game::snd_alias_type_t::SAT_STREAMED) // Streamed
{
alias->soundFile->type = Game::SAT_STREAMED;
std::string streamedFile = soundFile.string_value();
std::string streamedFile = soundFile.get<std::string>();
std::string directory = ""s;
int split = streamedFile.find_last_of('/');
@ -329,7 +328,7 @@ namespace Assets
}
else
{
Components::Logger::Error(Game::ERR_FATAL, "Failed to parse sound {}! Invalid sound type {}\n", name, type.string_value());
Components::Logger::Error(Game::ERR_FATAL, "Failed to parse sound {}! Invalid sound type {}\n", name, type.get<std::string>());
return;
}

View File

@ -130,7 +130,7 @@ namespace Components
// Parse proto data
Proto::Auth::Connect connectData;
if (msg->cursize <= 12 || !connectData.ParseFromString(std::string(&msg->data[12], msg->cursize - 12)))
if (msg->cursize <= 12 || !connectData.ParseFromString(std::string(reinterpret_cast<char*>(&msg->data[12]), msg->cursize - 12)))
{
Network::Send(address, "error\nInvalid connect packet!");
return;
@ -233,7 +233,7 @@ namespace Components
return;
}
Logger::Debug("Verified XUID {:#X} ({}) from {}", xuid, userLevel, address.getCString());
Logger::Debug("Verified XUID {:#X} ({}) from {}", xuid, userLevel, address.getString());
Game::SV_DirectConnect(*address.get());
}
#endif
@ -472,7 +472,7 @@ namespace Components
});
}
UIScript::Add("security_increase_cancel", [](UIScript::Token)
UIScript::Add("security_increase_cancel", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
{
Auth::TokenContainer.cancel = true;
Logger::Print("Token incrementation process canceled!\n");

View File

@ -107,7 +107,7 @@ namespace Components
ipEntry.bytes[3] & 0xFF));
}
const json11::Json bans = json11::Json::object
const nlohmann::json bans = nlohmann::json
{
{ "ip", ipVector },
{ "id", idVector },
@ -131,13 +131,7 @@ namespace Components
}
std::string error;
const auto banData = json11::Json::parse(bans.getBuffer(), error);
if (!error.empty())
{
Logger::PrintError(Game::CON_CHANNEL_ERROR, "Failed to parse bans.json: {}\n", error);
return;
}
const auto banData = nlohmann::json::parse(bans.getBuffer());
if (!banData.is_object())
{
@ -150,12 +144,15 @@ namespace Components
if (idList.is_array())
{
for (auto &idEntry : idList.array_items())
nlohmann::json::array_t arr = idList;
for (auto &idEntry : arr)
{
if (idEntry.is_string())
{
SteamID id;
id.bits = strtoull(idEntry.string_value().data(), nullptr, 16);
auto guid = idEntry.get<std::string>();
id.bits = std::strtoull(guid.data(), nullptr, 16);
list->idList.push_back(id);
}
@ -164,11 +161,13 @@ namespace Components
if (ipList.is_array())
{
for (auto &ipEntry : ipList.array_items())
nlohmann::json::array_t arr = ipList;
for (auto &ipEntry : arr)
{
if (ipEntry.is_string())
{
Network::Address addr(ipEntry.string_value());
Network::Address addr(ipEntry.get<std::string>());
list->ipList.push_back(addr.getIP());
}
@ -181,7 +180,7 @@ namespace Components
SteamID guid;
guid.bits = cl->steamID;
InsertBan({guid, cl->netchan.remoteAddress.ip});
InsertBan({guid, cl->header.netchan.remoteAddress.ip});
Game::SV_DropClient(cl, reason.data(), true);
}
@ -226,7 +225,7 @@ namespace Components
{
Command::Add("banClient", [](Command::Params* params)
{
if (!Dvar::Var("sv_running").get<bool>())
if (!(*Game::com_sv_running)->current.enabled)
{
Logger::Print("Server is not running.\n");
return;
@ -258,7 +257,7 @@ namespace Components
}
const auto* cl = &Game::svs_clients[num];
if (cl->state == Game::CS_FREE)
if (cl->header.state == Game::CS_FREE)
{
Logger::Print("Client {} is not active\n", num);
return;
@ -270,7 +269,7 @@ namespace Components
Command::Add("unbanClient", [](Command::Params* params)
{
if (!Dvar::Var("sv_running").get<bool>())
if (!(*Game::com_sv_running)->current.enabled)
{
Logger::Print("Server is not running.\n");
return;

View File

@ -294,7 +294,8 @@ namespace Components
Bots::Bots()
{
AssertOffset(Game::client_s, bIsTestClient, 0x41AF0);
AssertOffset(Game::client_t, bIsTestClient, 0x41AF0);
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\"");

View File

@ -5,7 +5,6 @@ namespace Components
Dvar::Var Branding::CGDrawVersion;
Dvar::Var Branding::CGDrawVersionX;
Dvar::Var Branding::CGDrawVersionY;
Game::dvar_t** Branding::Version = reinterpret_cast<Game::dvar_t**>(0x1AD7930);
#ifdef _DEBUG
constexpr auto* BUILD_TYPE = "IW4x_DEV MP";
@ -25,12 +24,12 @@ namespace Components
auto* const placement = Game::ScrPlace_GetUnsafeFullPlacement();
auto* const font = Game::UI_GetFontHandle(placement, 0, 0.583f);
const auto width = Game::UI_TextWidth((*Version)->current.string, 0, font, fontScale);
const auto width = Game::UI_TextWidth((*Game::version)->current.string, 0, font, fontScale);
const auto height = Game::UI_TextHeight(font, fontScale);
Game::UI_DrawText(placement, (*Version)->current.string, maxChars, font, 1.0f - (CGDrawVersionX.get<float>() + static_cast<float>(width)),
Game::UI_DrawText(placement, (*Game::version)->current.string, maxChars, font, 1.0f - (CGDrawVersionX.get<float>() + static_cast<float>(width)),
1.0f - (CGDrawVersionY.get<float>() + static_cast<float>(height)), 3, 3, fontScale, shadowColor, 0);
Game::UI_DrawText(placement, (*Version)->current.string, maxChars, font, (0.0f - static_cast<float>(width)) - CGDrawVersionX.get<float>(),
Game::UI_DrawText(placement, (*Game::version)->current.string, maxChars, font, (0.0f - static_cast<float>(width)) - CGDrawVersionX.get<float>(),
(0.0f - static_cast<float>(height)) - CGDrawVersionY.get<float>(), 3, 3, fontScale, color, 0);
}

View File

@ -14,7 +14,6 @@ namespace Components
static Dvar::Var CGDrawVersion;
static Dvar::Var CGDrawVersionX;
static Dvar::Var CGDrawVersionY;
static Game::dvar_t** Version;
static void CG_DrawVersion();
static void CG_DrawVersion_Hk(int localClientNum);

View File

@ -7,10 +7,10 @@ namespace Components
float Bullet::BG_GetSurfacePenetrationDepthStub(const Game::WeaponDef* weapDef, int surfaceType)
{
assert(weapDef != nullptr);
assert(weapDef->penetrateType != Game::PenetrateType::PENETRATE_TYPE_NONE);
assert(weapDef->penetrateType < Game::PenetrateType::PENETRATE_TYPE_COUNT);
assert(static_cast<size_t>(surfaceType) < Game::materialSurfType_t::SURF_TYPE_COUNT);
assert(weapDef);
assert(weapDef->penetrateType != Game::PENETRATE_TYPE_NONE);
AssertIn(weapDef->penetrateType, Game::PENETRATE_TYPE_COUNT);
AssertIn(surfaceType, Game::SURF_TYPE_COUNT);
const auto penetrationDepth = BGSurfacePenetration.get<float>();
if (penetrationDepth > 0.0f)

View File

@ -160,7 +160,7 @@ namespace Components
{
char playerTitle[18];
if (Game::svs_clients[i].state >= Game::CS_CONNECTED)
if (Game::svs_clients[i].header.state >= Game::CS_CONNECTED)
{
strncpy_s(playerTitle, Game::Info_ValueForKey(Game::svs_clients[i].userinfo, "customTitle"), _TRUNCATE);
}

View File

@ -46,6 +46,9 @@ namespace Components
Utils::Hook::Set<BYTE>(0x432180, 0xC3);
Utils::Hook::Set<BYTE>(0x461930, 0xC3);
// Used next to file system functions
Utils::Hook::Set<BYTE>(0x47BC00, 0xC3);
// Looking for stuff in the registry
Utils::Hook::Nop(0x4826F8, 5);

View File

@ -5,14 +5,14 @@ namespace Components
{
Dvar::Var Chat::cg_chatWidth;
Dvar::Var Chat::sv_disableChat;
Dvar::Var Chat::sv_sayName;
Game::dvar_t** Chat::cg_chatHeight = reinterpret_cast<Game::dvar_t**>(0x7ED398);
Game::dvar_t** Chat::cg_chatTime = reinterpret_cast<Game::dvar_t**>(0x9F5DE8);
bool Chat::SendChat;
std::mutex Chat::AccessMutex;
std::unordered_set<std::uint64_t> Chat::MuteList;
Utils::Concurrency::Container<Chat::muteList> Chat::MutedList;
bool Chat::CanAddCallback = true;
std::vector<Scripting::Function> Chat::SayCallbacks;
@ -36,21 +36,13 @@ namespace Components
++text;
}
std::unique_lock lock(AccessMutex);
if (MuteList.contains(Game::svs_clients[player->s.number].steamID))
if (IsMuted(player))
{
lock.unlock();
SendChat = false;
Game::SV_GameSendServerCommand(player->s.number, Game::SV_CMD_CAN_IGNORE,
Game::SV_GameSendServerCommand(player - Game::g_entities, Game::SV_CMD_CAN_IGNORE,
Utils::String::VA("%c \"You are muted\"", 0x65));
}
// Test whether the lock is still locked
if (lock.owns_lock())
{
lock.unlock();
}
for (const auto& callback : SayCallbacks)
{
if (!ChatCallback(player, callback.getPos(), (text + 1), mode))
@ -62,7 +54,7 @@ namespace Components
if (sv_disableChat.get<bool>())
{
SendChat = false;
Game::SV_GameSendServerCommand(player->s.number, Game::SV_CMD_CAN_IGNORE,
Game::SV_GameSendServerCommand(player - Game::g_entities, Game::SV_CMD_CAN_IGNORE,
Utils::String::VA("%c \"Chat is disabled\"", 0x65));
}
@ -243,25 +235,30 @@ namespace Components
}
}
bool Chat::IsMuted(const Game::gentity_s* ent)
{
const auto clientNum = ent - Game::g_entities;
const auto xuid = Game::svs_clients[clientNum].steamID;
const auto result = MutedList.access<bool>([&](muteList& clients)
{
return clients.contains(xuid);
});
return result;
}
void Chat::MuteClient(const Game::client_t* client)
{
std::unique_lock lock(AccessMutex);
if (!MuteList.contains(client->steamID))
const auto xuid = client->steamID;
MutedList.access([&](muteList& clients)
{
MuteList.insert(client->steamID);
lock.unlock();
clients.insert(xuid);
});
Logger::Print("{} was muted\n", client->name);
Game::SV_GameSendServerCommand(client->gentity->s.number, Game::SV_CMD_CAN_IGNORE,
Utils::String::VA("%c \"You were muted\"", 0x65));
return;
}
lock.unlock();
Logger::Print("{} is already muted\n", client->name);
Game::SV_GameSendServerCommand(-1, Game::SV_CMD_CAN_IGNORE,
Utils::String::VA("%c \"%s is already muted\"", 0x65, client->name));
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));
}
void Chat::UnmuteClient(const Game::client_t* client)
@ -269,25 +266,26 @@ namespace Components
UnmuteInternal(client->steamID);
Logger::Print("{} was unmuted\n", client->name);
Game::SV_GameSendServerCommand(client->gentity->s.number, Game::SV_CMD_CAN_IGNORE,
Game::SV_GameSendServerCommand(client - Game::svs_clients, Game::SV_CMD_CAN_IGNORE,
Utils::String::VA("%c \"You were unmuted\"", 0x65));
}
void Chat::UnmuteInternal(const std::uint64_t id, bool everyone)
{
std::unique_lock lock(AccessMutex);
if (everyone)
MuteList.clear();
else
MuteList.erase(id);
MutedList.access([&](muteList& clients)
{
if (everyone)
clients.clear();
else
clients.erase(id);
});
}
void Chat::AddChatCommands()
{
Command::AddSV("muteClient", [](Command::Params* params)
{
if (!Dvar::Var("sv_running").get<bool>())
if (!(*Game::com_sv_running)->current.enabled)
{
Logger::Print("Server is not running.\n");
return;
@ -303,13 +301,14 @@ namespace Components
const auto* client = Game::SV_GetPlayerByNum();
if (client != nullptr)
{
Voice::SV_MuteClient(client - Game::svs_clients);
MuteClient(client);
}
});
Command::AddSV("unmute", [](Command::Params* params)
{
if (!Dvar::Var("sv_running").get<bool>())
if (!(*Game::com_sv_running)->current.enabled)
{
Logger::Print("Server is not running.\n");
return;
@ -327,6 +326,7 @@ namespace Components
if (client != nullptr)
{
UnmuteClient(client);
Voice::SV_UnmuteClient(client - Game::svs_clients);
return;
}
@ -334,6 +334,7 @@ namespace Components
{
Logger::Print("All players were unmuted\n");
UnmuteInternal(0, true);
Voice::SV_ClearMutedList();
}
else
{
@ -341,6 +342,66 @@ namespace Components
UnmuteInternal(steamId);
}
});
Command::AddSV("say", [](Command::Params* params)
{
if (params->size() < 2) return;
auto message = params->join(1);
auto name = sv_sayName.get<std::string>();
if (!name.empty())
{
Game::SV_GameSendServerCommand(-1, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"%s: %s\"", 0x68, name.data(), message.data()));
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()));
Logger::Print(Game::CON_CHANNEL_SERVER, "Console: {}\n", message);
}
});
Command::AddSV("tell", [](Command::Params* params)
{
if (params->size() < 3) return;
const auto client = std::atoi(params->get(1));
auto message = params->join(2);
auto name = sv_sayName.get<std::string>();
if (!name.empty())
{
Game::SV_GameSendServerCommand(client, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"%s: %s\"", 0x68, name.data(), message.data()));
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()));
Logger::Print(Game::CON_CHANNEL_SERVER, "Console -> {}: {}\n", client, message);
}
});
Command::AddSV("sayraw", [](Command::Params* params)
{
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()));
Logger::Print(Game::CON_CHANNEL_SERVER, "Raw: {}\n", message);
});
Command::AddSV("tellraw", [](Command::Params* params)
{
if (params->size() < 3) return;
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()));
Logger::Print(Game::CON_CHANNEL_SERVER, "Raw -> {}: {}\n", client, message);
});
sv_sayName = Dvar::Register<const char*>("sv_sayName", "^7Console", Game::DVAR_NONE, "The alias of the server when broadcasting a chat message");
}
int Chat::GetCallbackReturn()
@ -357,7 +418,7 @@ namespace Components
const auto* result = &Game::scrVmPub->top[1 - Game::scrVmPub->outparamcount];
if (result->type != Game::scrParamType_t::VAR_INTEGER)
if (result->type != Game::VAR_INTEGER)
{
// Garbage was returned
return 1;
@ -368,14 +429,14 @@ namespace Components
int Chat::ChatCallback(Game::gentity_s* self, const char* codePos, const char* message, int mode)
{
const auto entityId = Game::Scr_GetEntityId(self->s.number, 0);
const auto entityId = Game::Scr_GetEntityId(self - Game::g_entities, 0);
Scripting::StackIsolation _;
Game::Scr_AddInt(mode);
Game::Scr_AddString(message);
Game::VariableValue value;
value.type = Game::scrParamType_t::VAR_OBJECT;
value.type = Game::VAR_OBJECT;
value.u.uintValue = entityId;
Game::AddRefToValue(value.type, value.u);
@ -410,6 +471,8 @@ namespace Components
Chat::Chat()
{
AssertOffset(Game::client_t, steamID, 0x43F00);
cg_chatWidth = Dvar::Register<int>("cg_chatWidth", 52, 1, std::numeric_limits<int>::max(), Game::DVAR_ARCHIVE, "The normalized maximum width of a chat message");
sv_disableChat = Dvar::Register<bool>("sv_disableChat", false, Game::DVAR_NONE, "Disable chat messages from clients");
Events::OnSVInit(AddChatCommands);

View File

@ -11,6 +11,7 @@ namespace Components
private:
static Dvar::Var cg_chatWidth;
static Dvar::Var sv_disableChat;
static Dvar::Var sv_sayName;
// Game dvars
static Game::dvar_t** cg_chatHeight;
@ -18,8 +19,8 @@ namespace Components
static bool SendChat;
static std::mutex AccessMutex;
static std::unordered_set<std::uint64_t> MuteList;
using muteList = std::unordered_set<std::uint64_t>;
static Utils::Concurrency::Container<muteList> MutedList;
static bool CanAddCallback; // ClientCommand & GSC thread are the same
static std::vector<Scripting::Function> SayCallbacks;
@ -33,9 +34,10 @@ namespace Components
static void CG_AddToTeamChat(const char* text);
static void CG_AddToTeamChat_Stub();
static bool IsMuted(const Game::gentity_s* ent);
static void MuteClient(const Game::client_t* client);
static void UnmuteClient(const Game::client_t* client);
static void UnmuteInternal(const std::uint64_t id, bool everyone = false);
static void UnmuteInternal(std::uint64_t id, bool everyone = false);
static void AddChatCommands();
static int GetCallbackReturn();

View File

@ -9,7 +9,7 @@ namespace Components
const char* ClanTags::GetClanTagWithName(int clientNum, const char* playerName)
{
assert(static_cast<std::size_t>(clientNum) < Game::MAX_CLIENTS);
AssertIn(clientNum, Game::MAX_CLIENTS);
if (ClientState[clientNum][0] == '\0')
{
@ -96,7 +96,7 @@ namespace Components
char* ClanTags::GamerProfile_GetClanName(int controllerIndex)
{
assert(static_cast<std::size_t>(controllerIndex) < Game::MAX_LOCAL_CLIENTS);
AssertIn(controllerIndex, Game::MAX_LOCAL_CLIENTS);
assert(ClanName);
CL_SanitizeClanName();
@ -115,7 +115,7 @@ namespace Components
void ClanTags::ClientUserinfoChanged(const char* s, int clientNum)
{
assert(static_cast<std::size_t>(clientNum) < Game::MAX_CLIENTS);
AssertIn(clientNum, Game::MAX_CLIENTS);
auto* clanAbbrev = Game::Info_ValueForKey(s, "clanAbbrev");
@ -224,7 +224,7 @@ namespace Components
Game::PlayerCardData* ClanTags::PlayerCards_GetLiveProfileDataForController_Stub(const unsigned int controllerIndex)
{
auto* result = Utils::Hook::Call<Game::PlayerCardData*(unsigned int)>(0x463B90)(controllerIndex);
// controllerIndex should always be 0
AssertIn(controllerIndex, Game::MAX_LOCAL_CLIENTS);
Game::I_strncpyz(result->clanAbbrev, GamerProfile_GetClanName(static_cast<int>(controllerIndex)), sizeof(Game::PlayerCardData::clanAbbrev));
return result;

View File

@ -28,8 +28,6 @@ namespace Components
static void Dvar_InfoString_Stub(char* s, const char* key, const char* value);
static void SetCachedPlayerData(int clientNum);
static void ClientUserinfoChanged(const char* s, int clientNum);
static void ClientUserinfoChanged_Stub();

View File

@ -3,13 +3,13 @@
namespace Components
{
std::unordered_map<std::string, std::function<void(Game::gentity_s*, Command::ServerParams*)>> ClientCommand::HandlersSV;
std::unordered_map<std::string, std::function<void(Game::gentity_s*, const Command::ServerParams*)>> ClientCommand::HandlersSV;
bool ClientCommand::CheatsOk(const Game::gentity_s* ent)
{
const auto entNum = ent->s.number;
if (!Dvar::Var("sv_cheats").get<bool>())
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));
@ -26,11 +26,11 @@ namespace Components
return true;
}
void ClientCommand::Add(const char* name, const std::function<void(Game::gentity_s*, Command::ServerParams*)>& callback)
void ClientCommand::Add(const char* name, const std::function<void(Game::gentity_s*, const Command::ServerParams*)>& callback)
{
const auto command = Utils::String::ToLower(name);
ClientCommand::HandlersSV[command] = callback;
HandlersSV[command] = callback;
}
void ClientCommand::ClientCommandStub(const int clientNum)
@ -57,9 +57,9 @@ namespace Components
void ClientCommand::AddCheatCommands()
{
ClientCommand::Add("noclip", [](Game::gentity_s* ent, [[maybe_unused]] Command::ServerParams* params)
Add("noclip", [](Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params)
{
if (!ClientCommand::CheatsOk(ent))
if (!CheatsOk(ent))
return;
ent->client->flags ^= Game::PLAYER_FLAG_NOCLIP;
@ -71,9 +71,9 @@ namespace Components
(ent->client->flags & Game::PLAYER_FLAG_NOCLIP) ? "GAME_NOCLIPON" : "GAME_NOCLIPOFF"));
});
ClientCommand::Add("ufo", [](Game::gentity_s* ent, [[maybe_unused]] Command::ServerParams* params)
Add("ufo", [](Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params)
{
if (!ClientCommand::CheatsOk(ent))
if (!CheatsOk(ent))
return;
ent->client->flags ^= Game::PLAYER_FLAG_UFO;
@ -85,9 +85,9 @@ namespace Components
(ent->client->flags & Game::PLAYER_FLAG_UFO) ? "GAME_UFOON" : "GAME_UFOOFF"));
});
ClientCommand::Add("god", [](Game::gentity_s* ent, [[maybe_unused]] Command::ServerParams* params)
Add("god", [](Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params)
{
if (!ClientCommand::CheatsOk(ent))
if (!CheatsOk(ent))
return;
ent->flags ^= Game::FL_GODMODE;
@ -99,9 +99,9 @@ namespace Components
(ent->flags & Game::FL_GODMODE) ? "GAME_GODMODE_ON" : "GAME_GODMODE_OFF"));
});
ClientCommand::Add("demigod", [](Game::gentity_s* ent, [[maybe_unused]] Command::ServerParams* params)
Add("demigod", [](Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params)
{
if (!ClientCommand::CheatsOk(ent))
if (!CheatsOk(ent))
return;
ent->flags ^= Game::FL_DEMI_GODMODE;
@ -113,9 +113,9 @@ namespace Components
(ent->flags & Game::FL_DEMI_GODMODE) ? "GAME_DEMI_GODMODE_ON" : "GAME_DEMI_GODMODE_OFF"));
});
ClientCommand::Add("notarget", [](Game::gentity_s* ent, [[maybe_unused]] Command::ServerParams* params)
Add("notarget", [](Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params)
{
if (!ClientCommand::CheatsOk(ent))
if (!CheatsOk(ent))
return;
ent->flags ^= Game::FL_NOTARGET;
@ -127,11 +127,11 @@ namespace Components
(ent->flags & Game::FL_NOTARGET) ? "GAME_NOTARGETON" : "GAME_NOTARGETOFF"));
});
ClientCommand::Add("setviewpos", [](Game::gentity_s* ent, [[maybe_unused]] Command::ServerParams* params)
Add("setviewpos", [](Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params)
{
assert(ent != nullptr);
if (!ClientCommand::CheatsOk(ent))
if (!CheatsOk(ent))
return;
Game::vec3_t origin, angles{0.f, 0.f, 0.f};
@ -163,9 +163,9 @@ namespace Components
Game::TeleportPlayer(ent, origin, angles);
});
ClientCommand::Add("give", [](Game::gentity_s* ent, [[maybe_unused]] Command::ServerParams* params)
Add("give", [](Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params)
{
if (!ClientCommand::CheatsOk(ent))
if (!CheatsOk(ent))
return;
if (params->size() < 2)
@ -236,38 +236,55 @@ namespace Components
for (std::size_t i = 0; i < std::extent_v<decltype(Game::playerState_s::weaponsEquipped)>; ++i)
{
const auto index = ent->client->ps.weaponsEquipped[i];
if (index != 0)
if (index)
{
Game::Add_Ammo(ent, index, 0, 998, 1);
}
}
});
Add("kill", []([[maybe_unused]] Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params)
{
assert(ent->client != nullptr);
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);
});
}
void ClientCommand::AddDevelopmentCommands()
{
ClientCommand::Add("dropallbots", []([[maybe_unused]] Game::gentity_s* ent, [[maybe_unused]] Command::ServerParams* params)
Add("dropallbots", []([[maybe_unused]] Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params)
{
Game::SV_DropAllBots();
});
ClientCommand::Add("entitylist", []([[maybe_unused]] Game::gentity_s* ent, [[maybe_unused]] Command::ServerParams* params)
Add("entitylist", []([[maybe_unused]] Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params)
{
Game::Svcmd_EntityList_f();
});
ClientCommand::Add("printentities", []([[maybe_unused]] Game::gentity_s* ent, [[maybe_unused]] Command::ServerParams* params)
Add("printentities", []([[maybe_unused]] Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params)
{
Game::G_PrintEntities();
});
ClientCommand::Add("entitycount", []([[maybe_unused]] Game::gentity_s* ent, [[maybe_unused]] Command::ServerParams* params)
Add("entitycount", []([[maybe_unused]] Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params)
{
Logger::Print("Entity count = {}\n", Game::level->num_entities);
});
// Also known as: "vis"
ClientCommand::Add("visionsetnaked", []([[maybe_unused]] Game::gentity_s* ent, [[maybe_unused]] Command::ServerParams* params)
Add("visionsetnaked", []([[maybe_unused]] Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params)
{
if (params->size() < 2)
{
@ -295,7 +312,7 @@ namespace Components
Utils::String::VA("%c \"%s\" %i", Game::MY_CMDS[visMode], name, duration));
});
ClientCommand::Add("visionsetnight", []([[maybe_unused]] Game::gentity_s* ent, [[maybe_unused]] Command::ServerParams* params)
Add("visionsetnight", []([[maybe_unused]] Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params)
{
if (params->size() < 2)
{
@ -323,7 +340,7 @@ namespace Components
Utils::String::VA("%c \"%s\" %i", Game::MY_CMDS[visMode], name, duration));
});
ClientCommand::Add("g_testCmd", []([[maybe_unused]] Game::gentity_s* ent, [[maybe_unused]] Command::ServerParams* params)
Add("g_testCmd", []([[maybe_unused]] Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params)
{
assert(ent != nullptr);
@ -331,21 +348,14 @@ namespace Components
Logger::Debug("playerState_s.stunTime is {}", ent->client->ps.stunTime);
});
ClientCommand::Add("kill", []([[maybe_unused]] Game::gentity_s* ent, [[maybe_unused]] Command::ServerParams* params)
Add("dumpEntInfo", []([[maybe_unused]] Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params)
{
assert(ent->client != nullptr);
assert(ent->client->sess.connected != Game::CON_DISCONNECTED);
G_DumpEntityDebugInfoToConsole(false);
});
if (ent->client->sess.sessionState != Game::SESS_STATE_PLAYING || !ClientCommand::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, 12, 0, nullptr, Game::HITLOC_NONE, 0);
}, Scheduler::Pipeline::SERVER);
Add("dumpEntInfoCSV", []([[maybe_unused]] Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params)
{
G_DumpEntityDebugInfoToCSV("");
});
}
@ -357,15 +367,123 @@ namespace Components
});
}
const char* ClientCommand::EntInfoLine(const int entNum)
{
const auto* ent = &Game::g_entities[entNum];
Game::XModel* model = nullptr;
if (ent->model)
{
model = Game::G_GetModel(ent->model);
}
Game::vec3_t point, angles;
point[0] = ent->r.currentOrigin[0] - (*Game::viewposNow)->current.vector[0];
point[1] = ent->r.currentOrigin[1] - (*Game::viewposNow)->current.vector[1];
point[2] = ent->r.currentOrigin[2] - (*Game::viewposNow)->current.vector[2];
angles[0] = ent->r.currentAngles[0];
angles[1] = ent->r.currentAngles[1];
angles[2] = ent->r.currentAngles[2];
const auto distance = std::sqrtf(point[0] * point[0] + point[1] * point[1]
+ point[2] * point[2]);
const auto* team = (ent->client) ? Game::CG_GetTeamName(ent->client->sess.cs.team) : "";
const auto* scriptLinkName = (ent->script_linkName) ? Game::SL_ConvertToString(ent->script_linkName) : "";
const auto* target = (ent->target) ? Game::SL_ConvertToString(ent->target) : "";
const auto* targetName = (ent->targetname) ? Game::SL_ConvertToString(ent->targetname) : "";
const auto* codeClassname = (ent->script_classname) ? Game::SL_ConvertToString(ent->script_classname) : "";
const auto* classname = (ent->classname) ? Game::SL_ConvertToString(ent->classname) : "";
const auto* eventType = (ent->s.eType < Game::ET_EVENTS) ? Game::G_GetEntityTypeName(ent) : "";
// 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",
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);
}
void ClientCommand::G_DumpEntityDebugInfoToConsole(bool logfileOnly)
{
const auto channel = logfileOnly ? Game::CON_CHANNEL_LOGFILEONLY : Game::CON_CHANNEL_SERVER;
Logger::Print(channel, "=====================================================================================\n");
Logger::Print(channel, "============(entity dump begin)\n");
Logger::Print(channel,
"Number,Type,Distance,Classname,Code Classname,Model,"
"Targetname,Target,Script Noteworthy,Script Linkname,Team,ParentNum,Origin,Angles,SentToClients\n");
for (auto i = 0; i < Game::MAX_GENTITIES; ++i)
{
if (&Game::g_entities[i] == nullptr)
{
continue;
}
const auto* line = EntInfoLine(i);
assert(line);
Logger::Print(channel, "%s", line);
}
Logger::Print(channel, "(end entity dump)============\n");
Logger::Print(channel, "=====================================================================================\n");
}
void ClientCommand::G_DumpEntityDebugInfoToCSV(const char* filenameSuffix)
{
assert(filenameSuffix);
const auto* fileName = Utils::String::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);
if (!h)
{
Logger::PrintError(Game::CON_CHANNEL_SERVER, "Couldn't open file \"{}\" for writing.\n", fileName);
return;
}
Game::FS_Write("Number,Type,Distance,Classname,Code Classname,Model,Targetname,Target,Script Noteworthy,Script Linkname,Team,Paren"
"tNum,Origin,Angles,SentToClients\n", 147, h);
for (auto i = 0; i < Game::MAX_GENTITIES; ++i)
{
if (&Game::g_entities[i] == nullptr)
{
continue;
}
const auto* line = EntInfoLine(i);
const auto lineLen = std::strlen(line);
assert(line);
assert(lineLen);
Game::FS_Write(line, lineLen, h);
}
Game::FS_FCloseFile(h);
Logger::Print(Game::CON_CHANNEL_SERVER, "Done writing file.\n");
}
ClientCommand::ClientCommand()
{
// Hook call to ClientCommand in SV_ExecuteClientCommand so we may add custom commands
Utils::Hook(0x6259FA, ClientCommand::ClientCommandStub, HOOK_CALL).install()->quick();
AssertOffset(Game::playerState_s, stats, 0x150);
ClientCommand::AddCheatCommands();
ClientCommand::AddScriptFunctions();
// Hook call to ClientCommand in SV_ExecuteClientCommand so we may add custom commands
Utils::Hook(0x6259FA, ClientCommandStub, HOOK_CALL).install()->quick();
AddCheatCommands();
AddScriptFunctions();
#ifdef _DEBUG
ClientCommand::AddDevelopmentCommands();
AddDevelopmentCommands();
#endif
}
}

View File

@ -7,15 +7,19 @@ namespace Components
public:
ClientCommand();
static void Add(const char* name, const std::function<void(Game::gentity_s*, Command::ServerParams*)>& callback);
static void Add(const char* name, const std::function<void(Game::gentity_s*, const Command::ServerParams*)>& callback);
static bool CheatsOk(const Game::gentity_s* ent);
private:
static std::unordered_map<std::string, std::function<void(Game::gentity_s*, Command::ServerParams*)>> HandlersSV;
static std::unordered_map<std::string, std::function<void(Game::gentity_s*, const Command::ServerParams*)>> HandlersSV;
static void ClientCommandStub(int clientNum);
static void AddCheatCommands();
static void AddDevelopmentCommands();
static void AddScriptFunctions();
static const char* EntInfoLine(int entNum);
static void G_DumpEntityDebugInfoToConsole(bool logfileOnly);
static void G_DumpEntityDebugInfoToCSV(const char* filenameSuffix);
};
}

View File

@ -5,7 +5,7 @@ namespace Components
std::unordered_map<std::string, std::function<void(Command::Params*)>> Command::FunctionMap;
std::unordered_map<std::string, std::function<void(Command::Params*)>> Command::FunctionMapSV;
std::string Command::Params::join(const int index)
std::string Command::Params::join(const int index) const
{
std::string result;
@ -24,12 +24,12 @@ namespace Components
assert(Game::cmd_args->nesting < Game::CMD_MAX_NESTING);
}
int Command::ClientParams::size()
int Command::ClientParams::size() const
{
return Game::cmd_args->argc[this->nesting_];
}
const char* Command::ClientParams::get(const int index)
const char* Command::ClientParams::get(const int index) const
{
if (index >= this->size())
{
@ -45,12 +45,12 @@ namespace Components
assert(Game::sv_cmd_args->nesting < Game::CMD_MAX_NESTING);
}
int Command::ServerParams::size()
int Command::ServerParams::size() const
{
return Game::sv_cmd_args->argc[this->nesting_];
}
const char* Command::ServerParams::get(const int index)
const char* Command::ServerParams::get(const int index) const
{
if (index >= this->size())
{
@ -113,7 +113,7 @@ namespace Components
Game::Cmd_AddServerCommand(name, callback, Command::Allocate());
// If the main command is registered as Cbuf_AddServerText, the command will be redirected to the SV handler
Command::AddRaw(name, Game::Cbuf_AddServerText, false);
Command::AddRaw(name, Game::Cbuf_AddServerText_f, false);
}
void Command::Execute(std::string command, bool sync)

View File

@ -13,9 +13,9 @@ namespace Components
Params() = default;
virtual ~Params() = default;
virtual int size() = 0;
virtual const char* get(int index) = 0;
virtual std::string join(int index);
[[nodiscard]] virtual int size() const = 0;
[[nodiscard]] virtual const char* get(int index) const = 0;
[[nodiscard]] virtual std::string join(int index) const;
virtual const char* operator[](const int index)
{
@ -28,8 +28,8 @@ namespace Components
public:
ClientParams();
int size() override;
const char* get(int index) override;
[[nodiscard]] int size() const override;
[[nodiscard]] const char* get(int index) const override;
private:
int nesting_;
@ -40,8 +40,8 @@ namespace Components
public:
ServerParams();
int size() override;
const char* get(int index) override;
[[nodiscard]] int size() const override;
[[nodiscard]] const char* get(int index) const override;
private:
int nesting_;

View File

@ -24,28 +24,16 @@ namespace Components
Game::SafeArea Console::OriginalSafeArea;
char** Console::GetAutoCompleteFileList(const char* path, const char* extension, Game::FsListBehavior_e behavior, int* numfiles, int allocTrackType)
const char** Console::GetAutoCompleteFileList(const char* path, const char* extension, Game::FsListBehavior_e behavior, int* numfiles, int allocTrackType)
{
if (path == reinterpret_cast<char*>(0xBAADF00D) || path == reinterpret_cast<char*>(0xCDCDCDCD) || ::Utils::Memory::IsBadReadPtr(path)) return nullptr;
return Game::FS_GetFileList(path, extension, behavior, numfiles, allocTrackType);
}
void Console::ToggleConsole()
{
// possibly cls.keyCatchers?
Utils::Hook::Xor<DWORD>(0xB2C538, 1);
// g_consoleField
Game::Field_Clear(reinterpret_cast<void*>(0xA1B6B0));
// show console output?
Utils::Hook::Set<BYTE>(0xA15F38, 0);
return Game::FS_ListFiles(path, extension, behavior, numfiles, allocTrackType);
}
void Console::RefreshStatus()
{
const auto mapname = Dvar::Var("mapname").get<std::string>();
const auto hostname = TextRenderer::StripColors(Dvar::Var("sv_hostname").get<std::string>());
const std::string mapname = (*Game::sv_mapname)->current.string;
const auto hostname = TextRenderer::StripColors((*Game::sv_hostname)->current.string);
if (Console::HasConsole)
{
@ -58,7 +46,7 @@ namespace Components
{
for (int i = 0; i < maxclientCount; ++i)
{
if (Game::svs_clients[i].state >= 3)
if (Game::svs_clients[i].header.state >= Game::CS_CONNECTED)
{
++clientCount;
}
@ -568,6 +556,9 @@ namespace Components
Console::Console()
{
AssertOffset(Game::clientUIActive_t, connectionState, 0x9B8);
AssertOffset(Game::clientUIActive_t, keyCatchers, 0x9B0);
// Console '%s: %s> ' string
Utils::Hook::Set<const char*>(0x5A44B4, "IW4x MP: " VERSION "> ");
@ -580,8 +571,8 @@ namespace Components
Utils::Hook::Set<BYTE>(0x431565, 0xEB);
// Internal console
Utils::Hook(0x4F690C, Console::ToggleConsole, HOOK_CALL).install()->quick();
Utils::Hook(0x4F65A5, Console::ToggleConsole, HOOK_JUMP).install()->quick();
Utils::Hook(0x4F690C, Console::Con_ToggleConsole, HOOK_CALL).install()->quick();
Utils::Hook(0x4F65A5, Console::Con_ToggleConsole, HOOK_JUMP).install()->quick();
// Patch safearea for ingame-console
Utils::Hook(0x5A50EF, Console::DrawSolidConsoleStub, HOOK_CALL).install()->quick();

View File

@ -63,8 +63,7 @@ namespace Components
static void StoreSafeArea();
static void RestoreSafeArea();
static void ToggleConsole();
static char** GetAutoCompleteFileList(const char *path, const char *extension, Game::FsListBehavior_e behavior, int *numfiles, int allocTrackType);
static const char** GetAutoCompleteFileList(const char *path, const char *extension, Game::FsListBehavior_e behavior, int *numfiles, int allocTrackType);
static void Con_ToggleConsole();
static void AddConsoleCommand();

View File

@ -2,7 +2,8 @@
namespace Components
{
Dvar::Var Debug::DebugOverlay;
const Game::dvar_t* Debug::DebugOverlay;
const Game::dvar_t* Debug::BugName;
Game::dvar_t** Debug::PlayerDebugHealth = reinterpret_cast<Game::dvar_t**>(0x7A9F7C);
@ -90,6 +91,16 @@ namespace Components
"EF_SOFT",
};
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 float Debug::colorWhite[] = {1.0f, 1.0f, 1.0f, 1.0f};
std::string Debug::BuildPMFlagsString(const Game::playerState_s* ps)
{
std::string result;
@ -144,7 +155,6 @@ namespace Components
auto* const scrPlace = Game::ScrPlace_GetActivePlacement(localClientNum);
constexpr auto maxChars = 4096;
constexpr float colorWhite[] = {1.0f, 1.0f, 1.0f, 1.0f};
auto* const font1 = Game::UI_GetFontHandle(scrPlace, 6, MY_SCALE_2);
auto* const font2 = Game::UI_GetFontHandle(scrPlace, 6, MY_SCALE2);
@ -153,16 +163,16 @@ namespace Components
MY_SCALE2, colorWhite, 1);
const auto pmf = BuildPMFlagsString(&cgameGlob->predictedPlayerState);
Game::UI_DrawText(scrPlace, pmf.data(), maxChars, font1, 30.0f, 20.0f, 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, 20.0f, 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);
const auto pef = BuildPEFlagsString(&cgameGlob->predictedPlayerState);
Game::UI_DrawText(scrPlace, pef.data(), maxChars, font1, 525.0f, 20.0f, 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)
@ -196,13 +206,49 @@ namespace Components
Game::CL_DrawStretchPic(scrPlace, 10.0f, 10.0f, 100.0f * healtha, 10.0f, 1, 1, 0.0f, 0.0f, healtha, 1.0f, color2, *Game::whiteMaterial);
}
void Debug::CG_Debug_DrawFontTest(const int localClientNum)
{
char strFinal[0x200]{};
auto* const scrPlace = Game::ScrPlace_GetActivePlacement(localClientNum);
auto* const font1 = Game::UI_GetFontHandle(scrPlace, 1, 0.4f);
auto* const font2 = Game::UI_GetFontHandle(scrPlace, 2, 0.4f);
auto* const font3 = Game::UI_GetFontHandle(scrPlace, 3, 0.4f);
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);
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);
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);
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);
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);
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);
}
void Debug::CG_DrawDebugOverlays_Hk(const int localClientNum)
{
switch (DebugOverlay.get<int>())
switch (DebugOverlay->current.integer)
{
case 2:
CG_Debug_DrawPSFlags(localClientNum);
break;
case 5:
CG_Debug_DrawFontTest(localClientNum);
break;
default:
break;
}
@ -215,7 +261,57 @@ namespace Components
void Debug::Com_Assert_f()
{
assert(("a", false));
assert(0 && "a");
}
void Debug::Cbuf_AddServerText_f_Hk()
{
assert(0 && "Cbuf_AddServerText_f was called.");
}
void Debug::Com_Bug_f(Command::Params* params)
{
char newFileName[0x105]{};
char to_ospath[MAX_PATH]{};
char from_ospath[MAX_PATH]{};
const char* bug;
if (!*Game::logfile)
{
Logger::PrintError(Game::CON_CHANNEL_ERROR, "CopyFile failed: logfile wasn't opened\n");
}
if (params->size() == 2)
{
bug = params->get(1);
}
else
{
assert(BugName);
bug = BugName->current.string;
}
sprintf_s(newFileName, "%s_%s.log", bug, Game::Live_GetLocalClientName(0));
Game::Sys_EnterCriticalSection(Game::CRITSECT_CONSOLE);
if (*Game::logfile)
{
Game::FS_FCloseFile(*Game::logfile);
*Game::logfile = 0;
}
Game::FS_BuildOSPath(Game::Sys_DefaultInstallPath(), "", "logs/console_mp.log", from_ospath);
Game::FS_BuildOSPath(Game::Sys_DefaultInstallPath(), "", newFileName, to_ospath);
const auto result = CopyFileA(from_ospath, to_ospath, 0);
Game::Com_OpenLogFile();
Game::Sys_LeaveCriticalSection(Game::CRITSECT_CONSOLE);
if (!result)
{
Logger::PrintError(1, "CopyFile failed({}) {} {}\n", GetLastError(), "console_mp.log", newFileName);
}
}
void Debug::CL_InitDebugDvars()
@ -233,6 +329,8 @@ namespace Components
DebugOverlay = Game::Dvar_RegisterEnum("debugOverlay", debugOverlayNames_0, 0,
Game::DVAR_NONE, "Toggles the display of various debug info.");
BugName = Game::Dvar_RegisterString("bug_name", "bug0",
Game::DVAR_CHEAT | Game::DVAR_CODINFO, "Name appended to the copied console log");
}
Debug::Debug()
@ -243,5 +341,10 @@ namespace Components
Utils::Hook(0x49CB0A, CG_DrawDebugOverlays_Hk, HOOK_JUMP).install()->quick();
Utils::Hook::Set<void(*)()>(0x60BCEA, Com_Assert_f);
Utils::Hook(Game::Cbuf_AddServerText_f, Cbuf_AddServerText_f_Hk, HOOK_JUMP).install()->quick();
#ifdef _DEBUG
Command::Add("bug", Com_Bug_f);
#endif
}
}

View File

@ -8,7 +8,8 @@ namespace Components
Debug();
private:
static Dvar::Var DebugOverlay;
static const Game::dvar_t* DebugOverlay;
static const Game::dvar_t* BugName;
// Game dvars
static Game::dvar_t** PlayerDebugHealth;
@ -18,9 +19,17 @@ namespace Components
static const char* PLFlagsValues[];
static const char* PEFlagsValues[];
static const char strButtons[];
static const char strTemplate[];
static constexpr auto MY_SCALE2 = 0.5f;
static constexpr auto MY_SCALE_2 = 0.201f;
static constexpr auto MY_X = -25.0f;
static constexpr auto MY_Y = 20.0f;
static const float colorWhite[];
static std::string BuildPMFlagsString(const Game::playerState_s* ps);
static std::string BuildPOFlagsString(const Game::playerState_s* ps);
static std::string BuildPLFlagsString(const Game::playerState_s* ps);
@ -28,10 +37,13 @@ namespace Components
static void CG_Debug_DrawPSFlags(int localClientNum);
static void CG_DrawDebugPlayerHealth(int localClientNum);
static void CG_Debug_DrawFontTest(int localClientNum);
static void CG_DrawDebugOverlays_Hk(int localClientNum);
static void Com_Assert_f();
static void Cbuf_AddServerText_f_Hk();
static void Com_Bug_f(Command::Params* params);
static void CL_InitDebugDvars();
};

View File

@ -81,9 +81,9 @@ namespace Components
{
std::string list = Utils::String::VA("%c", 20);
for (int i = 0; i < 18; ++i)
for (std::size_t i = 0; i < Game::MAX_CLIENTS; ++i)
{
if (Game::svs_clients[i].state >= 3)
if (Game::svs_clients[i].header.state >= 3)
{
list.append(Utils::String::VA(" %llX", Game::svs_clients[i].steamID));
@ -104,12 +104,12 @@ namespace Components
Scheduler::Once([]
{
const auto partyEnable = Dvar::Var("party_enable").get<bool>();
auto mapname = Dvar::Var("mapname").get<std::string>();
std::string mapname = (*Game::sv_mapname)->current.string;
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()), false);
Command::Execute(Utils::String::VA("map %s", mapname.data()), true);
}
}, Scheduler::Pipeline::SERVER);
@ -138,71 +138,6 @@ namespace Components
return Game::Dvar_RegisterInt(dvarName, 1000, min, 1000, Game::DVAR_NONE, description);
}
void Dedicated::AddDedicatedCommands()
{
// Say command
Command::AddSV("say", [](Command::Params* params)
{
if (params->size() < 2) return;
auto message = params->join(1);
auto name = Dvar::Var("sv_sayName").get<std::string>();
if (!name.empty())
{
Game::SV_GameSendServerCommand(-1, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"%s: %s\"", 104, name.data(), message.data()));
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\"", 104, message.data()));
Logger::Print(Game::CON_CHANNEL_SERVER, "Console: {}\n", message);
}
});
// Tell command
Command::AddSV("tell", [](Command::Params* params)
{
if (params->size() < 3) return;
const auto client = atoi(params->get(1));
auto message = params->join(2);
auto name = Dvar::Var("sv_sayName").get<std::string>();
if (!name.empty())
{
Game::SV_GameSendServerCommand(client, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"%s: %s\"", 104, name.data(), message.data()));
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()));
Logger::Print(Game::CON_CHANNEL_SERVER, "Console -> {}: {}\n", client, message);
}
});
// Sayraw command
Command::AddSV("sayraw", [](Command::Params* params)
{
if (params->size() < 2) return;
auto message = params->join(1);
Game::SV_GameSendServerCommand(-1, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"%s\"", 104, message.data()));
Logger::Print(Game::CON_CHANNEL_SERVER, "Raw: {}\n", message);
});
// Tellraw command
Command::AddSV("tellraw", [](Command::Params* params)
{
if (params->size() < 3) return;
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\"", 104, message.data()));
Logger::Print(Game::CON_CHANNEL_SERVER, "Raw -> {}: {}\n", client, message);
});
}
Dedicated::Dedicated()
{
Dedicated::COMLogFilter = Dvar::Register<bool>("com_logFilter", true,
@ -281,12 +216,9 @@ namespace Components
{
Scheduler::Once([]
{
Dvar::Register<const char*>("sv_sayName", "^7Console", Game::DVAR_NONE, "The name to pose as for 'say' commands");
Dvar::Register<const char*>("sv_motd", "", Game::DVAR_NONE, "A custom message of the day for servers");
}, Scheduler::Pipeline::MAIN);
Events::OnSVInit(Dedicated::AddDedicatedCommands);
// Post initialization point
Utils::Hook(0x60BFBF, Dedicated::PostInitializationStub, HOOK_JUMP).install()->quick();
@ -330,7 +262,7 @@ namespace Components
Scheduler::Loop([]
{
if (Dvar::Var("sv_running").get<bool>())
if ((*Game::com_sv_running)->current.enabled)
{
Dedicated::TransmitGuids();
}

View File

@ -26,7 +26,5 @@ namespace Components
static void TimeWrapStub(Game::errorParm_t code, const char* message);
static Game::dvar_t* Dvar_RegisterSVNetworkFps(const char* dvarName, int value, int min, int max, int flags, const char* description);
static void AddDedicatedCommands();
};
}

View File

@ -46,37 +46,37 @@ namespace Components
}
});
Network::OnPacket("discovery", [](Network::Address& address, [[maybe_unused]] const std::string& data)
Network::OnClientPacket("discovery", [](Network::Address& address, [[maybe_unused]] const std::string& data)
{
if (address.isSelf()) return;
if (!address.isLocal())
{
Logger::Print("Received discovery request from non-local address: {}\n", address.getCString());
Logger::Print("Received discovery request from non-local address: {}\n", address.getString());
return;
}
Logger::Print("Received discovery request from {}\n", address.getCString());
Logger::Print("Received discovery request from {}\n", address.getString());
Network::SendCommand(address, "discoveryResponse", data);
});
Network::OnPacket("discoveryResponse", [](Network::Address& address, [[maybe_unused]] const std::string& data)
Network::OnClientPacket("discoveryResponse", [](Network::Address& address, [[maybe_unused]] const std::string& data)
{
if (address.isSelf()) return;
if (!address.isLocal())
{
Logger::Print("Received discovery response from non-local address: {}\n", address.getCString());
Logger::Print("Received discovery response from non-local address: {}\n", address.getString());
return;
}
if (Utils::ParseChallenge(data) != Discovery::Challenge)
{
Logger::Print("Received discovery with invalid challenge from: {}\n", address.getCString());
Logger::Print("Received discovery with invalid challenge from: {}\n", address.getString());
return;
}
Logger::Print("Received discovery response from: {}\n", address.getCString());
Logger::Print("Received discovery response from: {}\n", address.getString());
if (ServerList::IsOfflineList())
{

View File

@ -5,7 +5,6 @@ namespace Components
{
mg_mgr Download::Mgr;
Download::ClientDownload Download::CLDownload;
std::vector<std::shared_ptr<Download::ScriptDownload>> Download::ScriptDownloads;
std::thread Download::ServerThread;
bool Download::Terminate;
@ -63,7 +62,7 @@ namespace Components
download->files.clear();
std::string error;
json11::Json listData = json11::Json::parse(list, error);
nlohmann::json listData = nlohmann::json::parse(list);
if (!error.empty() || !listData.is_array())
{
@ -72,8 +71,9 @@ namespace Components
}
download->totalBytes = 0;
nlohmann::json::array_t listDataArray = listData;
for (auto& file : listData.array_items())
for (auto& file : listDataArray)
{
if (!file.is_object()) return false;
@ -84,9 +84,9 @@ namespace Components
if (!hash.is_string() || !name.is_string() || !size.is_number()) return false;
Download::ClientDownload::File fileEntry;
fileEntry.name = name.string_value();
fileEntry.hash = hash.string_value();
fileEntry.size = static_cast<size_t>(size.number_value());
fileEntry.name = name.get<std::string>();
fileEntry.hash = hash.get<std::string>();
fileEntry.size = size.get<size_t>();
if (!fileEntry.name.empty())
{
@ -353,9 +353,9 @@ namespace Components
// Run this on the main thread
Scheduler::Once([]
{
auto fsGame = Dvar::Var("fs_game");
fsGame.set(mod);
fsGame.get<Game::dvar_t*>()->modified = true;
Game::Dvar_SetString(*Game::fs_gameDirVar, mod.data());
const_cast<Game::dvar_t*>(*Game::fs_gameDirVar)->modified = true;
mod.clear();
Command::Execute("closemenu mod_download_popmenu", false);
@ -387,9 +387,9 @@ namespace Components
{
Game::client_t* client = &Game::svs_clients[i];
if (client->state >= 3)
if (client->header.state >= Game::CS_CONNECTED)
{
if (address.getIP().full == Network::Address(client->netchan.remoteAddress).getIP().full)
if (address.getIP().full == Network::Address(client->header.netchan.remoteAddress).getIP().full)
{
return client;
}
@ -502,14 +502,14 @@ namespace Components
// Only handle http requests
if (ev != MG_EV_HTTP_REQUEST) return;
std::vector<json11::Json> servers;
std::vector<nlohmann::json> servers;
// Build server list
for (auto& node : Node::GetNodes())
{
if (node.isValid())
{
servers.push_back(json11::Json{ node });
servers.push_back(nlohmann::json{ node.to_json()});
}
}
@ -519,7 +519,7 @@ namespace Components
"Connection: close\r\n"
"Access-Control-Allow-Origin: *\r\n"
"\r\n"
"%s", json11::Json(servers).dump().data());
"%s", nlohmann::json(servers).dump().data());
nc->flags |= MG_F_SEND_AND_CLOSE;
}
@ -532,17 +532,17 @@ namespace Components
if (!Download::VerifyPassword(nc, reinterpret_cast<http_message*>(ev_data))) return;
static std::string mapnamePre;
static json11::Json jsonList;
static nlohmann::json jsonList;
std::string mapname = (Party::IsInUserMapLobby() ? Dvar::Var("ui_mapname").get<std::string>() : Maps::GetUserMap()->getName());
if (!Maps::GetUserMap()->isValid() && !Party::IsInUserMapLobby())
{
mapnamePre.clear();
jsonList = std::vector<json11::Json>();
jsonList = std::vector<nlohmann::json>();
}
else if (!mapname.empty() && mapname != mapnamePre)
{
std::vector<json11::Json> fileList;
std::vector<nlohmann::json> fileList;
mapnamePre = mapname;
@ -553,7 +553,7 @@ namespace Components
std::string filename = path + "\\" + mapname + Maps::UserMapFiles[i];
if (Utils::IO::FileExists(filename))
{
std::map<std::string, json11::Json> file;
std::map<std::string, nlohmann::json> file;
std::string fileBuffer = Utils::IO::ReadFile(filename);
file["name"] = mapname + Maps::UserMapFiles[i];
@ -591,13 +591,13 @@ namespace Components
// else
{
static std::string fsGamePre;
static json11::Json jsonList;
static nlohmann::json jsonList;
std::string fsGame = Dvar::Var("fs_game").get<std::string>();
const std::string fsGame = (*Game::fs_gameDirVar)->current.string;
if (!fsGame.empty() && fsGame != fsGamePre)
{
std::vector<json11::Json> fileList;
std::vector<nlohmann::json> fileList;
fsGamePre = fsGame;
@ -611,7 +611,7 @@ namespace Components
std::string filename = path + "\\" + *i;
if (strstr(i->data(), "_svr_") == nullptr && Utils::IO::FileExists(filename))
{
std::map<std::string, json11::Json> file;
std::map<std::string, nlohmann::json> file;
std::string fileBuffer = Utils::IO::ReadFile(filename);
file["name"] = *i;
@ -697,7 +697,7 @@ namespace Components
}
std::string file;
std::string fsGame = Dvar::Var("fs_game").get<std::string>();
const std::string fsGame = (*Game::fs_gameDirVar)->current.string;
std::string path = Dvar::Var("fs_basepath").get<std::string>() + "\\" + (isMap ? "" : fsGame + "\\") + url;
if ((!isMap && fsGame.empty()) || !Utils::IO::ReadFile(path, &file))
@ -735,23 +735,23 @@ namespace Components
Utils::InfoString status = ServerInfo::GetInfo();
Utils::InfoString host = ServerInfo::GetHostInfo();
std::map<std::string, json11::Json> info;
std::map<std::string, nlohmann::json> info;
info["status"] = status.to_json();
info["host"] = host.to_json();
std::vector<json11::Json> players;
std::vector<nlohmann::json> players;
// Build player list
for (int i = 0; i < atoi(status.get("sv_maxclients").data()); ++i) // Maybe choose 18 here?
{
std::map<std::string, json11::Json> playerInfo;
std::map<std::string, nlohmann::json> playerInfo;
playerInfo["score"] = 0;
playerInfo["ping"] = 0;
playerInfo["name"] = "";
if (Dvar::Var("sv_running").get<bool>())
if ((*Game::com_sv_running)->current.enabled)
{
if (Game::svs_clients[i].state < 3) continue;
if (Game::svs_clients[i].header.state < Game::CS_CONNECTED) continue;
playerInfo["score"] = Game::SV_GameClientNum_Score(i);
playerInfo["ping"] = Game::svs_clients[i].ping;
@ -760,13 +760,13 @@ namespace Components
else
{
// Score and ping are irrelevant
const char* namePtr = Game::PartyHost_GetMemberName(reinterpret_cast<Game::PartyData*>(0x1081C00), i);
const char* namePtr = Game::PartyHost_GetMemberName(Game::g_lobbyData, i);
if (!namePtr || !namePtr[0]) continue;
playerInfo["name"] = namePtr;
}
players.push_back(playerInfo);
players.emplace_back(playerInfo);
}
info["players"] = players;
@ -777,7 +777,7 @@ namespace Components
"Connection: close\r\n"
"Access-Control-Allow-Origin: *\r\n"
"\r\n"
"%s", json11::Json(info).dump().data());
"%s", nlohmann::json(info).dump().data());
nc->flags |= MG_F_SEND_AND_CLOSE;
}
@ -909,7 +909,7 @@ namespace Components
Dvar::Register<const char*>("ui_dl_transRate", "", Game::DVAR_NONE, "");
}, Scheduler::Pipeline::MAIN);
UIScript::Add("mod_download_cancel", [](UIScript::Token)
UIScript::Add("mod_download_cancel", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
{
Download::CLDownload.clear();
});
@ -926,77 +926,8 @@ namespace Components
Dvar::Register<bool>("mod_force_download_server", false, Game::DVAR_ARCHIVE, "Set to true to force the client to run the download server for mods (for mods in private matches).");
}, Scheduler::Pipeline::MAIN);
Scheduler::Loop([]
{
int workingCount = 0;
for (auto i = Download::ScriptDownloads.begin(); i != Download::ScriptDownloads.end();)
{
auto download = *i;
if (download->isDone())
{
download->notifyDone();
i = Download::ScriptDownloads.erase(i);
continue;
}
if (download->isWorking())
{
download->notifyProgress();
++workingCount;
}
++i;
}
for (auto& download : Download::ScriptDownloads)
{
if (workingCount > 5) break;
if (!download->isWorking())
{
download->startWorking();
++workingCount;
}
}
}, Scheduler::Pipeline::MAIN);
Events::OnVMShutdown([]
{
Download::ScriptDownloads.clear();
});
Script::AddFunction("HttpGet", []
{
const auto* url = Game::Scr_GetString(0);
if (url == nullptr)
{
Game::Scr_ParamError(0, "^1HttpGet: Illegal parameter!\n");
return;
}
auto object = Game::AllocObject();
Game::Scr_AddObject(object);
Download::ScriptDownloads.push_back(std::make_shared<ScriptDownload>(url, object));
Game::RemoveRefToObject(object);
});
Script::AddFunction("HttpCancel", []
{
const auto object = Game::Scr_GetObject(0);
for (const auto& download : Download::ScriptDownloads)
{
if (object == download->getObject())
{
download->cancel();
break;
}
}
});
Script::AddFunction("HttpGet", Script::ShowDeprecationWarning);
Script::AddFunction("HttpCancel", Script::ShowDeprecationWarning);
}
Download::~Download()
@ -1019,7 +950,5 @@ namespace Components
{
Download::CLDownload.clear();
}
Download::ScriptDownloads.clear();
}
}

View File

@ -1,4 +1,5 @@
#pragma once
#include <mongoose.h>
namespace Components
{
@ -81,132 +82,8 @@ namespace Components
size_t receivedBytes;
};
class ScriptDownload
{
public:
ScriptDownload(const std::string& _url, unsigned int _object) : url(_url), object(_object), webIO(nullptr), done(false), notifyRequired(false), totalSize(0), currentSize(0)
{
Game::AddRefToObject(this->getObject());
}
ScriptDownload(ScriptDownload&& other) noexcept = delete;
ScriptDownload& operator=(ScriptDownload&& other) noexcept = delete;
~ScriptDownload()
{
if (this->getObject())
{
Game::RemoveRefToObject(this->getObject());
this->object = 0;
}
if (this->workerThread.joinable())
{
this->workerThread.join();
}
this->destroyWebIO();
}
void startWorking()
{
if (!this->isWorking())
{
this->workerThread = std::thread(std::bind(&ScriptDownload::handler, this));
}
}
bool isWorking()
{
return this->workerThread.joinable();
}
void notifyProgress()
{
if (this->notifyRequired)
{
this->notifyRequired = false;
if (Game::Scr_IsSystemActive())
{
Game::Scr_AddInt(static_cast<int>(this->totalSize));
Game::Scr_AddInt(static_cast<int>(this->currentSize));
Game::Scr_NotifyId(this->getObject(), Game::SL_GetString("progress", 0), 2);
}
}
}
void updateProgress(size_t _currentSize, size_t _toalSize)
{
this->currentSize = _currentSize;
this->totalSize = _toalSize;
this->notifyRequired = true;
}
void notifyDone()
{
if (!this->isDone()) return;
if (Game::Scr_IsSystemActive())
{
Game::Scr_AddString(this->result.data()); // No binary data supported yet
Game::Scr_AddInt(this->success);
Game::Scr_NotifyId(this->getObject(), Game::SL_GetString("done", 0), 2);
}
}
bool isDone() { return this->done; };
std::string getUrl() { return this->url; }
unsigned int getObject() { return this->object; }
void cancel()
{
if (this->webIO)
{
this->webIO->cancelDownload();
}
}
private:
std::string url;
std::string result;
unsigned int object;
std::thread workerThread;
Utils::WebIO* webIO;
bool done;
bool success;
bool notifyRequired;
size_t totalSize;
size_t currentSize;
void handler()
{
this->destroyWebIO();
this->webIO = new Utils::WebIO("IW4x");
this->webIO->setProgressCallback(std::bind(&ScriptDownload::updateProgress, this, std::placeholders::_1, std::placeholders::_2));
this->result = this->webIO->get(this->url, &this->success);
this->destroyWebIO();
this->done = true;
}
void destroyWebIO()
{
if (this->webIO)
{
delete this->webIO;
this->webIO = nullptr;
}
}
};
static mg_mgr Mgr;
static ClientDownload CLDownload;
static std::vector<std::shared_ptr<ScriptDownload>> ScriptDownloads;
static std::thread ServerThread;
static bool Terminate;
static bool ServerRunning;

View File

@ -4,33 +4,33 @@ namespace Components
{
const char* Dvar::ArchiveDvarPath = "userraw/archivedvars.cfg";
Dvar::Var::Var(const std::string& dvarName) : Var()
Dvar::Var::Var(const std::string& dvarName)
{
this->dvar = Game::Dvar_FindVar(dvarName.data());
this->dvar_ = Game::Dvar_FindVar(dvarName.data());
// If the dvar can't be found it will be registered as an empty string dvar
if (this->dvar == nullptr)
if (this->dvar_ == nullptr)
{
this->dvar = const_cast<Game::dvar_t*>(Game::Dvar_SetFromStringByNameFromSource(dvarName.data(), "",
this->dvar_ = const_cast<Game::dvar_t*>(Game::Dvar_SetFromStringByNameFromSource(dvarName.data(), "",
Game::DvarSetSource::DVAR_SOURCE_INTERNAL));
}
}
template <> Game::dvar_t* Dvar::Var::get()
{
return this->dvar;
return this->dvar_;
}
template <> const char* Dvar::Var::get()
{
if (this->dvar == nullptr)
if (this->dvar_ == nullptr)
return "";
if (this->dvar->type == Game::dvar_type::DVAR_TYPE_STRING
|| this->dvar->type == Game::dvar_type::DVAR_TYPE_ENUM)
if (this->dvar_->type == Game::DVAR_TYPE_STRING
|| this->dvar_->type == Game::DVAR_TYPE_ENUM)
{
if (this->dvar->current.string != nullptr)
return this->dvar->current.string;
if (this->dvar_->current.string != nullptr)
return this->dvar_->current.string;
}
return "";
@ -38,12 +38,12 @@ namespace Components
template <> int Dvar::Var::get()
{
if (this->dvar == nullptr)
if (this->dvar_ == nullptr)
return 0;
if (this->dvar->type == Game::dvar_type::DVAR_TYPE_INT || this->dvar->type == Game::dvar_type::DVAR_TYPE_ENUM)
if (this->dvar_->type == Game::DVAR_TYPE_INT || this->dvar_->type == Game::DVAR_TYPE_ENUM)
{
return this->dvar->current.integer;
return this->dvar_->current.integer;
}
return 0;
@ -51,12 +51,12 @@ namespace Components
template <> unsigned int Dvar::Var::get()
{
if (this->dvar == nullptr)
if (this->dvar_ == nullptr)
return 0;
if (this->dvar->type == Game::dvar_type::DVAR_TYPE_INT)
if (this->dvar_->type == Game::DVAR_TYPE_INT)
{
return this->dvar->current.unsignedInt;
return this->dvar_->current.unsignedInt;
}
return 0;
@ -64,12 +64,12 @@ namespace Components
template <> float Dvar::Var::get()
{
if (this->dvar == nullptr)
if (this->dvar_ == nullptr)
return 0.f;
if (this->dvar->type == Game::dvar_type::DVAR_TYPE_FLOAT)
if (this->dvar_->type == Game::DVAR_TYPE_FLOAT)
{
return this->dvar->current.value;
return this->dvar_->current.value;
}
return 0.f;
@ -79,13 +79,13 @@ namespace Components
{
static Game::vec4_t vector{0.f, 0.f, 0.f, 0.f};
if (this->dvar == nullptr)
if (this->dvar_ == nullptr)
return vector;
if (this->dvar->type == Game::dvar_type::DVAR_TYPE_FLOAT_2 || this->dvar->type == Game::dvar_type::DVAR_TYPE_FLOAT_3
|| this->dvar->type == Game::dvar_type::DVAR_TYPE_FLOAT_4)
if (this->dvar_->type == Game::DVAR_TYPE_FLOAT_2 || this->dvar_->type == Game::DVAR_TYPE_FLOAT_3
|| this->dvar_->type == Game::DVAR_TYPE_FLOAT_4)
{
return this->dvar->current.vector;
return this->dvar_->current.vector;
}
return vector;
@ -93,12 +93,12 @@ namespace Components
template <> bool Dvar::Var::get()
{
if (this->dvar == nullptr)
if (this->dvar_ == nullptr)
return false;
if (this->dvar->type == Game::dvar_type::DVAR_TYPE_BOOL)
if (this->dvar_->type == Game::DVAR_TYPE_BOOL)
{
return this->dvar->current.enabled;
return this->dvar_->current.enabled;
}
return false;
@ -111,10 +111,10 @@ namespace Components
void Dvar::Var::set(const char* string)
{
assert(this->dvar->type == Game::DVAR_TYPE_STRING);
if (this->dvar)
assert(this->dvar_->type == Game::DVAR_TYPE_STRING);
if (this->dvar_)
{
Game::Dvar_SetString(this->dvar, string);
Game::Dvar_SetString(this->dvar_, string);
}
}
@ -125,58 +125,64 @@ namespace Components
void Dvar::Var::set(int integer)
{
assert(this->dvar->type == Game::DVAR_TYPE_INT);
if (this->dvar)
assert(this->dvar_->type == Game::DVAR_TYPE_INT);
if (this->dvar_)
{
Game::Dvar_SetInt(this->dvar, integer);
Game::Dvar_SetInt(this->dvar_, integer);
}
}
void Dvar::Var::set(float value)
{
assert(this->dvar->type == Game::DVAR_TYPE_FLOAT);
if (this->dvar)
assert(this->dvar_->type == Game::DVAR_TYPE_FLOAT);
if (this->dvar_)
{
Game::Dvar_SetFloat(this->dvar, value);
Game::Dvar_SetFloat(this->dvar_, value);
}
}
void Dvar::Var::set(bool enabled)
{
assert(this->dvar->type == Game::DVAR_TYPE_BOOL);
if (this->dvar)
assert(this->dvar_->type == Game::DVAR_TYPE_BOOL);
if (this->dvar_)
{
Game::Dvar_SetBool(this->dvar, enabled);
Game::Dvar_SetBool(this->dvar_, enabled);
}
}
void Dvar::Var::setRaw(int integer)
{
assert(this->dvar->type == Game::DVAR_TYPE_INT);
if (this->dvar)
assert(this->dvar_->type == Game::DVAR_TYPE_INT);
if (this->dvar_)
{
this->dvar->current.integer = integer;
this->dvar->latched.integer = integer;
this->dvar_->current.integer = integer;
this->dvar_->latched.integer = integer;
}
}
void Dvar::Var::setRaw(float value)
{
assert(this->dvar->type == Game::DVAR_TYPE_FLOAT);
if (this->dvar)
assert(this->dvar_->type == Game::DVAR_TYPE_FLOAT);
if (this->dvar_)
{
this->dvar->current.value = value;
this->dvar->latched.value = value;
this->dvar_->current.value = value;
this->dvar_->latched.value = value;
}
}
void Dvar::Var::setRaw(bool enabled)
{
assert(this->dvar->type == Game::DVAR_TYPE_BOOL);
if (this->dvar)
assert(this->dvar_->type == Game::DVAR_TYPE_BOOL);
if (this->dvar_)
{
this->dvar->current.enabled = enabled;
this->dvar->latched.enabled = enabled;
this->dvar_->current.enabled = enabled;
this->dvar_->latched.enabled = enabled;
}
}

View File

@ -17,9 +17,9 @@ namespace Components
class Var
{
public:
Var() : dvar(nullptr) {}
Var(const Var& obj) { this->dvar = obj.dvar; }
Var(Game::dvar_t* _dvar) : dvar(_dvar) {}
Var() : dvar_(nullptr) {}
Var(const Var& obj) { this->dvar_ = obj.dvar_; }
Var(Game::dvar_t* dvar) : dvar_(dvar) {}
Var(DWORD ppdvar) : Var(*reinterpret_cast<Game::dvar_t**>(ppdvar)) {}
Var(const std::string& dvarName);
@ -37,7 +37,7 @@ namespace Components
void setRaw(bool enabled);
private:
Game::dvar_t* dvar;
Game::dvar_t* dvar_;
};
Dvar();

View File

@ -15,21 +15,23 @@ namespace Components
int handle;
const auto len = Game::FS_FOpenFileReadForThread(filePath.data(), &handle, thread);
if (handle)
if (!handle)
{
auto* buf = AllocateFile(len + 1);
[[maybe_unused]] auto bytesRead = Game::FS_Read(buf, len, handle);
assert(bytesRead == len);
buf[len] = '\0';
Game::FS_FCloseFile(handle);
this->buffer.append(buf, len);
FreeFile(buf);
return;
}
auto* buf = AllocateFile(len + 1);
[[maybe_unused]] auto bytesRead = Game::FS_Read(buf, len, handle);
assert(bytesRead == len);
buf[len] = '\0';
Game::FS_FCloseFile(handle);
this->buffer.append(buf, len);
FreeFile(buf);
}
void FileSystem::RawFile::read()
@ -138,12 +140,28 @@ namespace Components
}
}
std::filesystem::path FileSystem::GetAppdataPath()
{
PWSTR path;
if (!SUCCEEDED(SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, &path)))
{
throw std::runtime_error("Failed to read APPDATA path!");
}
auto _0 = gsl::finally([&path]
{
CoTaskMemFree(path);
});
return std::filesystem::path(path) / "xlabs";
}
std::vector<std::string> FileSystem::GetFileList(const std::string& path, const std::string& extension)
{
std::vector<std::string> fileList;
int numFiles = 0;
char** files = Game::FS_GetFileList(path.data(), extension.data(), Game::FS_LIST_PURE_ONLY, &numFiles, 0);
auto numFiles = 0;
const auto** files = Game::FS_ListFiles(path.data(), extension.data(), Game::FS_LIST_PURE_ONLY, &numFiles, 10);
if (files)
{
@ -151,11 +169,11 @@ namespace Components
{
if (files[i])
{
fileList.push_back(files[i]);
fileList.emplace_back(files[i]);
}
}
Game::FS_FreeFileList(files);
Game::FS_FreeFileList(files, 10);
}
return fileList;
@ -165,8 +183,8 @@ namespace Components
{
std::vector<std::string> fileList;
int numFiles = 0;
char** files = Game::Sys_ListFiles(path.data(), extension.data(), nullptr, &numFiles, folders);
auto numFiles = 0;
const auto** files = Game::Sys_ListFiles(path.data(), extension.data(), nullptr, &numFiles, folders);
if (files)
{
@ -174,7 +192,7 @@ namespace Components
{
if (files[i])
{
fileList.push_back(files[i]);
fileList.emplace_back(files[i]);
}
}
@ -184,9 +202,9 @@ namespace Components
return fileList;
}
bool FileSystem::DeleteFile(const std::string& folder, const std::string& file)
bool FileSystem::_DeleteFile(const std::string& folder, const std::string& file)
{
char path[MAX_PATH] = { 0 };
char path[MAX_PATH] = {0};
Game::FS_BuildPathToFile(Dvar::Var("fs_basepath").get<const char*>(), reinterpret_cast<char*>(0x63D0BB8), Utils::String::VA("%s/%s", folder.data(), file.data()), reinterpret_cast<char**>(&path));
return Game::FS_Remove(path);
}
@ -225,9 +243,9 @@ namespace Components
void FileSystem::RegisterFolder(const char* folder)
{
std::string fs_cdpath = Dvar::Var("fs_cdpath").get<std::string>();
std::string fs_basepath = Dvar::Var("fs_basepath").get<std::string>();
std::string fs_homepath = Dvar::Var("fs_homepath").get<std::string>();
const auto fs_cdpath = Dvar::Var("fs_cdpath").get<std::string>();
const auto fs_basepath = Dvar::Var("fs_basepath").get<std::string>();
const auto fs_homepath = Dvar::Var("fs_homepath").get<std::string>();
if (!fs_cdpath.empty()) Game::FS_AddLocalizedGameDirectory(fs_cdpath.data(), folder);
if (!fs_basepath.empty()) Game::FS_AddLocalizedGameDirectory(fs_basepath.data(), folder);
@ -305,6 +323,12 @@ namespace Components
Utils::Hook::Call<void(void*)>(0x4291A0)(iwd);
}
const char* FileSystem::Sys_DefaultInstallPath_Hk()
{
static auto current_path = std::filesystem::current_path().string();
return current_path.data();
}
FileSystem::FileSystem()
{
FileSystem::MemAllocator.clear();
@ -352,6 +376,9 @@ namespace Components
// Handle IWD freeing
Utils::Hook(0x642F60, FileSystem::IwdFreeStub, HOOK_CALL).install()->quick();
// Set the working dir based on info from the Xlabs launcher
Utils::Hook(0x4326E0, FileSystem::Sys_DefaultInstallPath_Hk, HOOK_JUMP).install()->quick();
}
FileSystem::~FileSystem()

View File

@ -8,7 +8,7 @@ namespace Components
class AbstractFile
{
public:
virtual ~AbstractFile() {};
virtual ~AbstractFile() = default;
virtual bool exists() = 0;
virtual std::string getName() = 0;
@ -19,12 +19,12 @@ namespace Components
{
public:
File() = default;
File(std::string file) : filePath{std::move(file)} { this->read(); };
File(std::string file, Game::FsThread thread) : filePath{std::move(file)} { this->read(thread); };
File(std::string file) : filePath{std::move(file)} { this->read(); }
File(std::string file, Game::FsThread thread) : filePath{std::move(file)} { this->read(thread); }
bool exists() override { return !this->buffer.empty(); };
std::string getName() override { return this->filePath; };
std::string& getBuffer() override { return this->buffer; };
bool exists() override { return !this->buffer.empty(); }
std::string getName() override { return this->filePath; }
std::string& getBuffer() override { return this->buffer; }
private:
std::string filePath;
@ -36,12 +36,12 @@ namespace Components
class RawFile : public AbstractFile
{
public:
RawFile() {};
RawFile(const std::string& file) : filePath(file) { this->read(); };
RawFile() = default;
RawFile(std::string file) : filePath(std::move(file)) { this->read(); }
bool exists() override { return !this->buffer.empty(); };
std::string getName() override { return this->filePath; };
std::string& getBuffer() override { return this->buffer; };
bool exists() override { return !this->buffer.empty(); }
std::string getName() override { return this->filePath; }
std::string& getBuffer() override { return this->buffer; }
private:
std::string filePath;
@ -53,7 +53,7 @@ namespace Components
class FileReader
{
public:
FileReader() : handle(0), size(-1), name() {};
FileReader() : handle(0), size(-1), name() {}
FileReader(const std::string& file);
~FileReader();
@ -73,8 +73,8 @@ namespace Components
class FileWriter
{
public:
FileWriter(const std::string& file, bool append = false) : handle(0), filePath(file) { this->open(append); };
~FileWriter() { this->close(); };
FileWriter(std::string file, bool append = false) : handle(0), filePath(std::move(file)) { this->open(append); }
~FileWriter() { this->close(); }
void write(const std::string& data);
@ -89,9 +89,10 @@ namespace Components
FileSystem();
~FileSystem();
static std::filesystem::path GetAppdataPath();
static std::vector<std::string> GetFileList(const std::string& path, const std::string& extension);
static std::vector<std::string> GetSysFileList(const std::string& path, const std::string& extension, bool folders = false);
static bool DeleteFile(const std::string& folder, const std::string& file);
static bool _DeleteFile(const std::string& folder, const std::string& file);
private:
static std::mutex Mutex;
@ -115,5 +116,7 @@ namespace Components
static int LoadTextureSync(Game::GfxImageLoadDef **loadDef, Game::GfxImage *image);
static void IwdFreeStub(Game::iwd_t* iwd);
static const char* Sys_DefaultInstallPath_Hk();
};
}

View File

@ -126,22 +126,14 @@ namespace Components
}
}
void Friends::UpdateState(bool force)
void Friends::UpdateState()
{
if (Friends::CLAnonymous.get<bool>() || Friends::IsInvisible() || !Steam::Enabled()) return;
if (Friends::CLAnonymous.get<bool>() || Friends::IsInvisible() || !Steam::Enabled())
{
return;
}
if (force)
{
if (Steam::Proxy::ClientFriends && Steam::Proxy::SteamFriends)
{
int state = Steam::Proxy::SteamFriends->GetPersonaState();
Steam::Proxy::ClientFriends.invoke<void>("SetPersonaState", (state == 1 ? 2 : 1));
}
}
else
{
Friends::TriggerUpdate = true;
}
Friends::TriggerUpdate = true;
}
void Friends::UpdateServer(Network::Address server, const std::string& hostname, const std::string& mapname)
@ -424,14 +416,13 @@ namespace Components
{
return Utils::String::VA("%s", user.name.data());
}
else if (user.name == user.playerName)
if (user.name == user.playerName)
{
return Utils::String::VA("%s", user.name.data());
}
else
{
return Utils::String::VA("%s ^7(%s^7)", user.name.data(), user.playerName.data());
}
return Utils::String::VA("%s ^7(%s^7)", user.name.data(), user.playerName.data());
}
case 2:
{
@ -613,12 +604,12 @@ namespace Components
// Show blue icons on the minimap
Utils::Hook(0x493130, Friends::IsClientInParty, HOOK_JUMP).install()->quick();
UIScript::Add("LoadFriends", [](UIScript::Token)
UIScript::Add("LoadFriends", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
{
Friends::UpdateFriends();
});
UIScript::Add("JoinFriend", [](UIScript::Token)
UIScript::Add("JoinFriend", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
{
std::lock_guard<std::recursive_mutex> _(Friends::Mutex);
if (Friends::CurrentFriend >= Friends::FriendsList.size()) return;
@ -660,7 +651,7 @@ namespace Components
if (Friends::TriggerUpdate)
{
Friends::TriggerUpdate = false;
Friends::UpdateState(true);
Friends::UpdateState();
}
}

View File

@ -83,7 +83,7 @@ namespace Components
static bool IsClientInParty(int controller, int clientNum);
static void UpdateUserInfo(SteamID user);
static void UpdateState(bool force = false);
static void UpdateState();
static void SortList(bool force = false);
static void SortIndividualList(std::vector<Friend>* list);

View File

@ -1,5 +1,6 @@
#include <STDInclude.hpp>
#include "Int64.hpp"
#include "IO.hpp"
#include "Script.hpp"
#include "ScriptExtension.hpp"
@ -9,6 +10,7 @@ namespace Components
{
GSC::GSC()
{
Loader::Register(new Int64());
Loader::Register(new IO());
Loader::Register(new Script());
Loader::Register(new ScriptExtension());

View File

@ -0,0 +1,95 @@
#include <STDInclude.hpp>
#include "Int64.hpp"
#include "Script.hpp"
#define INT64_OPERATION(expr) [](const std::int64_t a, [[maybe_unused]] const std::int64_t b) { return expr; }
namespace Components
{
std::unordered_map<std::string, Int64::int64_OP> Int64::Operations =
{
{"+", INT64_OPERATION(a + b)},
{"-", INT64_OPERATION(a - b)},
{"*", INT64_OPERATION(a * b)},
{"/", INT64_OPERATION(a / b)},
{"&", INT64_OPERATION(a & b)},
{"^", INT64_OPERATION(a ^ b)},
{"|", INT64_OPERATION(a | b)},
{"~", INT64_OPERATION(~a)},
{"%", INT64_OPERATION(a % b)},
{">>", INT64_OPERATION(a >> b)},
{"<<", INT64_OPERATION(a << b)},
{"++", INT64_OPERATION(a + 1)},
{"--", INT64_OPERATION(a - 1)},
};
std::unordered_map<std::string, Int64::int64_Comp> Int64::Comparisons
{
{">", INT64_OPERATION(a > b)},
{">=", INT64_OPERATION(a >= b)},
{"==", INT64_OPERATION(a == b)},
{"<=", INT64_OPERATION(a <= b)},
{"<", INT64_OPERATION(a < b)},
};
std::int64_t Int64::GetInt64Arg(unsigned int index, bool optional)
{
if ((optional) && (index >= Game::Scr_GetNumParam()))
{
return 0;
}
if (Game::Scr_GetType(index) == Game::VAR_INTEGER)
{
return Game::Scr_GetInt(index);
}
if (Game::Scr_GetType(index) == Game::VAR_STRING)
{
return std::strtoll(Game::Scr_GetString(index), nullptr, 0);
}
Game::Scr_ParamError(index, Utils::String::VA("cannot cast %s to int64", Game::Scr_GetTypeName(index)));
return 0;
}
void Int64::AddFunctions()
{
Script::AddFunction("Int64IsInt", []
{
const auto value = GetInt64Arg(0, false);
Game::Scr_AddBool(value <= std::numeric_limits<std::int32_t>::max() && value >= std::numeric_limits<std::int32_t>::min());
});
Script::AddFunction("Int64ToInt", []
{
Game::Scr_AddInt(static_cast<std::int32_t>(GetInt64Arg(0, false)));
});
Script::AddFunction("Int64OP", []
{
const auto a = GetInt64Arg(0, false);
const auto* op = Game::Scr_GetString(1);
const auto b = GetInt64Arg(2, true);
if (const auto got = Operations.find(op); got != Operations.end())
{
Game::Scr_AddString(Utils::String::VA("%lld", got->second(a, b)));
return;
}
if (const auto got = Comparisons.find(op); got != Comparisons.end())
{
Game::Scr_AddBool(got->second(a, b));
return;
}
Game::Scr_ParamError(1, "Invalid int64 operation");
});
}
Int64::Int64()
{
AddFunctions();
}
}

View File

@ -0,0 +1,20 @@
#pragma once
namespace Components
{
class Int64 : public Component
{
public:
Int64();
private:
using int64_OP = std::function<std::int64_t(std::int64_t, std::int64_t)>;
using int64_Comp = std::function<bool(std::int64_t, std::int64_t)>;
static std::unordered_map<std::string, int64_OP> Operations;
static std::unordered_map<std::string, int64_Comp> Comparisons;
static std::int64_t GetInt64Arg(unsigned int index, bool optional);
static void AddFunctions();
};
}

View File

@ -50,12 +50,10 @@ namespace Components
void Script::RuntimeError(const char* codePos, unsigned int index, const char* msg, const char* dialogMessage)
{
const auto developer = Dvar::Var("developer").get<int>();
// Allow error messages to be printed if developer mode is on
// Should check scrVarPub.developer but it's absent
// in this version of the game so let's check the dvar
if (!Game::scrVmPub->terminal_error && !developer)
if (!Game::scrVmPub->terminal_error && !(*Game::com_developer)->current.integer)
return;
// If were are developing let's call RuntimeErrorInternal
@ -227,42 +225,44 @@ namespace Components
Game::Scr_StartupGameType();
}
// Do not use C++ objects because Scr_LoadScript may longjmp
void Script::GScr_LoadGameTypeScript_Stub()
{
// Clear handles (from previous GSC loading session)
Script::ScriptMainHandles.clear();
Script::ScriptInitHandles.clear();
const auto list = FileSystem::GetFileList("scripts/", "gsc");
char path[MAX_PATH]{};
for (const auto& file : list)
auto numFiles = 0;
const auto** files = Game::FS_ListFiles("scripts/", "gsc", Game::FS_LIST_ALL, &numFiles, 10);
for (auto i = 0; i < numFiles; ++i)
{
std::string script = "scripts/" + file;
const auto* scriptFile = files[i];
Logger::Print("Loading script {}...\n", scriptFile);
if (Utils::String::EndsWith(script, ".gsc"))
sprintf_s(path, "%s/%s", "scripts", scriptFile);
// Scr_LoadScriptInternal will add the '.gsc' suffix so we remove it
path[std::strlen(path) - 4] = '\0';
if (!Game::Scr_LoadScript(path))
{
script = script.substr(0, script.size() - 4);
Logger::Print("Script {} encountered an error while loading. (doesn't exist?)", path);
continue;
}
Logger::Print("Loading script {}.gsc...\n", script);
if (!Game::Scr_LoadScript(script.data()))
{
Logger::Print("Script {} encountered an error while loading. (doesn't exist?)", script);
Logger::Error(Game::ERR_DROP, "Could not find script '{}'", script);
return;
}
Logger::Print("Script {}.gsc loaded successfully.\n", script);
Logger::Print("Script {}.gsc loaded successfully.\n", path);
Logger::Debug("Finding script handle main or init...");
const auto initHandle = Game::Scr_GetFunctionHandle(script.data(), "init");
const auto initHandle = Game::Scr_GetFunctionHandle(path, "init");
if (initHandle != 0)
{
Script::ScriptInitHandles.push_back(initHandle);
}
const auto mainHandle = Game::Scr_GetFunctionHandle(script.data(), "main");
const auto mainHandle = Game::Scr_GetFunctionHandle(path, "main");
if (mainHandle != 0)
{
Script::ScriptMainHandles.push_back(mainHandle);
@ -271,6 +271,7 @@ namespace Components
// Allow scripts with no handles
}
Game::FS_FreeFileList(files, 10);
Game::GScr_LoadGameTypeScript();
}
@ -397,18 +398,16 @@ namespace Components
{
// execute our hook
pushad
call Script::StoreScriptBaseProgramNum
popad
// execute overwritten code caused by the jump hook
sub eax, ds:201A460h // gScrVarPub_programBuffer
add esp, 0Ch
mov ds : 1CFEEF8h, eax // gScrCompilePub_programLen
sub eax, ds:201A460h // gScrVarPub_programBuffer
add esp, 0Ch
mov ds:1CFEEF8h, eax // gScrCompilePub_programLen
// jump back to the original code
push 426C3Bh
push 426C3Bh
retn
}
}
@ -535,6 +534,14 @@ namespace Components
return &Game::svs_clients[ent->s.number];
}
void Script::ShowDeprecationWarning()
{
Toast::Show("cardicon_gumby", "WARNING!", "You are using deprecated HttpGet/HttpCancel GSC function.", 2048);
Logger::Print(Game::CON_CHANNEL_SCRIPT, "*** DEPRECATION WARNING ***\n");
Logger::PrintError(Game::CON_CHANNEL_ERROR, "Attempted to execute deprecated built-in HttpGet/HttpCancel! These functions have been deemed unsafe and are scheduled for removal. Please update your mod!\n");
Logger::Print(Game::CON_CHANNEL_SCRIPT, "***************************\n");
}
void Script::AddFunctions()
{
Script::AddFunction("ReplaceFunc", [] // gsc: ReplaceFunc(<function>, <function>)
@ -632,7 +639,7 @@ namespace Components
Utils::Hook(0x61E92E, Script::VMExecuteInternalStub, HOOK_JUMP).install()->quick();
Utils::Hook::Nop(0x61E933, 1);
Scheduler::Loop([]()
Scheduler::Loop([]
{
if (!Game::SV_Loaded())
return;
@ -641,11 +648,12 @@ namespace Components
if (Script::LastFrameTime != -1)
{
const auto timeScale = Dvar::Var("timescale").get<float>();
const auto timeTaken = static_cast<int>((nowMs - Script::LastFrameTime) * timeScale);
const auto timeTaken = (nowMs - Script::LastFrameTime) * static_cast<int>((*Game::com_timescale)->current.value);
if (timeTaken >= 500)
{
Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "Hitch warning: {} msec frame time\n", timeTaken);
}
}
Script::LastFrameTime = nowMs;

View File

@ -14,6 +14,8 @@ namespace Components
static const char* GetCodePosForParam(int index);
static void ShowDeprecationWarning();
private:
struct ScriptFunction
{
@ -59,7 +61,6 @@ namespace Components
static void Scr_StartupGameType_Stub();
static void GScr_LoadGameTypeScript_Stub();
static bool IsDeprecated(const std::string& name);
static Game::BuiltinFunction BuiltIn_GetFunctionStub(const char** pName, int* type);
static Game::BuiltinMethod BuiltIn_GetMethodStub(const char** pName, int* type);

View File

@ -175,16 +175,16 @@ namespace Components
// Func present on IW5
Script::AddFunction("IsEndStr", [] // gsc: IsEndStr(<string>, <string>)
{
const auto* s1 = Game::Scr_GetString(0);
const auto* s2 = Game::Scr_GetString(1);
const auto* str = Game::Scr_GetString(0);
const auto* suffix = Game::Scr_GetString(1);
if (s1 == nullptr || s2 == nullptr)
if (str == nullptr || suffix == nullptr)
{
Game::Scr_Error("^1IsEndStr: Illegal parameters!\n");
return;
}
Game::Scr_AddBool(Utils::String::EndsWith(s1, s2));
Game::Scr_AddBool(Utils::String::EndsWith(str, suffix));
});
Script::AddFunction("IsArray", [] // gsc: IsArray(<object>)
@ -206,6 +206,41 @@ namespace Components
Game::Scr_AddBool(result);
});
// Func present on IW5
Script::AddFunction("CastFloat", [] // gsc: CastFloat()
{
switch (Game::Scr_GetType(0))
{
case Game::VAR_STRING:
Game::Scr_AddFloat(static_cast<float>(std::atof(Game::Scr_GetString(0))));
break;
case Game::VAR_FLOAT:
Game::Scr_AddFloat(Game::Scr_GetFloat(0));
break;
case Game::VAR_INTEGER:
Game::Scr_AddFloat(static_cast<float>(Game::Scr_GetInt(0)));
break;
default:
Game::Scr_ParamError(0, Utils::String::VA("cannot cast %s to float", Game::Scr_GetTypeName(0)));
break;
}
});
Script::AddFunction("Strtol", [] // gsc: Strtol(<string>, <int>)
{
const auto* input = Game::Scr_GetString(0);
const auto base = Game::Scr_GetInt(1);
char* end;
const auto result = std::strtol(input, &end, base);
if (input == end)
{
Game::Scr_ParamError(0, "cannot cast string to int");
}
Game::Scr_AddInt(result);
});
}
void ScriptExtension::AddMethods()
@ -216,7 +251,7 @@ namespace Components
const auto* ent = Game::GetPlayerEntity(entref);
const auto* client = Script::GetClient(ent);
std::string ip = Game::NET_AdrToString(client->netchan.remoteAddress);
std::string ip = Game::NET_AdrToString(client->header.netchan.remoteAddress);
if (const auto pos = ip.find_first_of(":"); pos != std::string::npos)
ip.erase(ip.begin() + pos, ip.end()); // Erase port
@ -241,7 +276,7 @@ namespace Components
const auto* ent = Game::GetPlayerEntity(entref);
auto* client = Script::GetClient(ent);
client->ping = static_cast<int16_t>(ping);
client->ping = ping;
});
}

View File

@ -82,7 +82,7 @@ namespace Components
return;
}
const json11::Json json = Data;
const nlohmann::json json = Data;
FileSystem::FileWriter("scriptdata/scriptstorage.json").write(json.dump());
});

View File

@ -169,8 +169,8 @@ namespace Components
{Game::K_APAD_RIGHT, Game::K_RIGHTARROW},
};
Gamepad::GamePad Gamepad::gamePads[Game::MAX_GAMEPADS]{};
Gamepad::GamePadGlobals Gamepad::gamePadGlobals[Game::MAX_GAMEPADS]{{}};
Gamepad::GamePad Gamepad::gamePads[Game::MAX_GPAD_COUNT]{};
Gamepad::GamePadGlobals Gamepad::gamePadGlobals[Game::MAX_GPAD_COUNT]{{}};
int Gamepad::gamePadBindingsModifiedFlags = 0;
Dvar::Var Gamepad::gpad_enabled;
@ -241,12 +241,12 @@ namespace Components
mov dl, byte ptr[edi + 1Ah] // to_forwardMove
mov dh, byte ptr[edi + 1Bh] // to_rightMove
mov[esp + 30h], dx // to_buttons
mov [esp + 30h], dx // to_buttons
mov dl, byte ptr[ebp + 1Ah] // from_forwardMove
mov dh, byte ptr[ebp + 1Bh] // from_rightMove
mov dl, byte ptr [ebp + 1Ah] // from_forwardMove
mov dh, byte ptr [ebp + 1Bh] // from_rightMove
mov[esp + 2Ch], dx // from_buttons
mov [esp + 2Ch], dx // from_buttons
// return back
push 0x60E40E
@ -261,7 +261,7 @@ namespace Components
if (Game::MSG_ReadBit(msg))
{
short movementBits = static_cast<short>(key ^ Game::MSG_ReadBits(msg, 16));
const auto movementBits = static_cast<short>(key ^ Game::MSG_ReadBits(msg, 16));
forward = static_cast<char>(movementBits);
right = static_cast<char>(movementBits >> 8);
@ -314,7 +314,8 @@ namespace Components
bool Gamepad::GPad_Check(const int gamePadIndex, const int portIndex)
{
assert(gamePadIndex < Game::MAX_GAMEPADS);
AssertIn(gamePadIndex, Game::MAX_GPAD_COUNT);
auto& gamePad = gamePads[gamePadIndex];
if (XInputGetCapabilities(portIndex, XINPUT_FLAG_GAMEPAD, &gamePad.caps) == ERROR_SUCCESS)
@ -332,7 +333,7 @@ namespace Components
{
auto currentGamePadNum = 0;
for (auto currentPort = 0; currentPort < XUSER_MAX_COUNT && currentGamePadNum < Game::MAX_GAMEPADS; currentPort++)
for (auto currentPort = 0; currentPort < XUSER_MAX_COUNT && currentGamePadNum < Game::MAX_GPAD_COUNT; currentPort++)
{
if (GPad_Check(currentGamePadNum, currentPort))
currentGamePadNum++;
@ -366,7 +367,7 @@ namespace Components
bool Gamepad::AimAssist_IsPlayerUsingOffhand(Game::AimAssistPlayerState* ps)
{
// Check offhand flag
if ((ps->weapFlags & 2) == 0)
if ((ps->weapFlags & Game::PWF_USING_OFFHAND) == 0)
return false;
// If offhand weapon has no id we are not using one
@ -421,7 +422,8 @@ namespace Components
bool Gamepad::AimAssist_IsLockonActive(const int gamePadIndex)
{
assert(gamePadIndex < Game::MAX_GAMEPADS);
AssertIn(gamePadIndex, Game::MAX_GPAD_COUNT);
auto& aaGlob = Game::aaGlobArray[gamePadIndex];
if (!aim_lockon_enabled.get<bool>() || !gpad_lockon_enabled.get<bool>())
@ -439,7 +441,9 @@ namespace Components
void Gamepad::AimAssist_ApplyLockOn(const Game::AimInput* input, Game::AimOutput* output)
{
assert(input);
assert(input->localClientNum < Game::MAX_GAMEPADS);
assert(output);
AssertIn(input->localClientNum, Game::STATIC_MAX_LOCAL_CLIENTS);
auto& aaGlob = Game::aaGlobArray[input->localClientNum];
const auto prevTargetEnt = aaGlob.lockOnTargetEnt;
@ -554,10 +558,11 @@ namespace Components
void Gamepad::AimAssist_CalcSlowdown(const Game::AimInput* input, float* pitchScale, float* yawScale)
{
assert(input);
assert(input->localClientNum < Game::MAX_GAMEPADS);
auto& aaGlob = Game::aaGlobArray[input->localClientNum];
assert(pitchScale);
assert(yawScale);
AssertIn(input->localClientNum, Game::STATIC_MAX_LOCAL_CLIENTS);
auto& aaGlob = Game::aaGlobArray[input->localClientNum];
*pitchScale = 1.0f;
*yawScale = 1.0f;
@ -586,7 +591,10 @@ namespace Components
void Gamepad::AimAssist_ApplyTurnRates(const Game::AimInput* input, Game::AimOutput* output)
{
assert(input->localClientNum < Game::MAX_GAMEPADS);
assert(input);
assert(output);
AssertIn(input->localClientNum, Game::STATIC_MAX_LOCAL_CLIENTS);
auto& aaGlob = Game::aaGlobArray[input->localClientNum];
auto slowdownPitchScale = 0.0f;
@ -649,7 +657,8 @@ namespace Components
void Gamepad::AimAssist_UpdateGamePadInput(const Game::AimInput* input, Game::AimOutput* output)
{
assert(input->localClientNum < Game::MAX_GAMEPADS);
AssertIn(input->localClientNum, Game::STATIC_MAX_LOCAL_CLIENTS);
auto& aaGlob = Game::aaGlobArray[input->localClientNum];
output->pitch = input->pitch;
@ -784,8 +793,8 @@ namespace Components
float Gamepad::CL_GamepadAxisValue(const int gamePadIndex, const Game::GamepadVirtualAxis virtualAxis)
{
assert(gamePadIndex < Game::MAX_GAMEPADS);
assert(virtualAxis > Game::GPAD_VIRTAXIS_NONE && virtualAxis < Game::GPAD_VIRTAXIS_COUNT);
const auto& gamePadGlobal = gamePadGlobals[gamePadIndex];
const auto& [physicalAxis, mapType] = gamePadGlobal.axes.virtualAxes[virtualAxis];
@ -818,7 +827,8 @@ namespace Components
void Gamepad::CL_GamepadMove(const int gamePadIndex, Game::usercmd_s* cmd, const float frameTimeBase)
{
assert(gamePadIndex < Game::MAX_GAMEPADS);
AssertIn(gamePadIndex, Game::MAX_GPAD_COUNT);
auto& gamePad = gamePads[gamePadIndex];
auto& clientActive = Game::clients[gamePadIndex];
@ -960,7 +970,8 @@ namespace Components
void Gamepad::CL_GamepadResetMenuScrollTime(const int gamePadIndex, const int key, const bool down, const unsigned time)
{
assert(gamePadIndex < Game::MAX_GAMEPADS);
AssertIn(gamePadIndex, Game::MAX_GPAD_COUNT);
auto& gamePadGlobal = gamePadGlobals[gamePadIndex];
if (!down)
@ -979,8 +990,8 @@ namespace Components
void Gamepad::CL_GamepadGenerateAPad(const int gamePadIndex, const Game::GamepadPhysicalAxis physicalAxis, unsigned time)
{
assert(gamePadIndex < Game::MAX_GAMEPADS);
assert(physicalAxis < Game::GPAD_PHYSAXIS_COUNT && physicalAxis >= 0);
AssertIn(gamePadIndex, Game::MAX_GPAD_COUNT);
assert(physicalAxis >= 0 && physicalAxis < Game::GPAD_PHYSAXIS_COUNT);
auto& gamePad = gamePads[gamePadIndex];
@ -1014,8 +1025,8 @@ namespace Components
void Gamepad::CL_GamepadEvent(const int gamePadIndex, const Game::GamepadPhysicalAxis physicalAxis, const float value, const unsigned time)
{
assert(gamePadIndex < Game::MAX_GAMEPADS);
assert(physicalAxis < Game::GPAD_PHYSAXIS_COUNT && physicalAxis >= 0);
AssertIn(gamePadIndex, Game::MAX_GPAD_COUNT);
assert(physicalAxis >= 0 && physicalAxis < Game::GPAD_PHYSAXIS_COUNT);
auto& gamePad = gamePads[gamePadIndex];
auto& gamePadGlobal = gamePadGlobals[gamePadIndex];
@ -1054,7 +1065,8 @@ namespace Components
bool Gamepad::Scoreboard_HandleInput(int gamePadIndex, int key)
{
assert(gamePadIndex < Game::MAX_GAMEPADS);
AssertIn(gamePadIndex, Game::STATIC_MAX_LOCAL_CLIENTS);
auto& keyState = Game::playerKeys[gamePadIndex];
if (keyState.keys[key].binding && strcmp(keyState.keys[key].binding, "togglescores") == 0)
@ -1080,7 +1092,8 @@ namespace Components
bool Gamepad::CL_CheckForIgnoreDueToRepeat(const int gamePadIndex, const int key, const int repeatCount, const unsigned time)
{
assert(gamePadIndex < Game::MAX_GAMEPADS);
AssertIn(gamePadIndex, Game::STATIC_MAX_LOCAL_CLIENTS);
auto& gamePadGlobal = gamePadGlobals[gamePadIndex];
if (Game::Key_IsKeyCatcherActive(gamePadIndex, Game::KEYCATCH_UI))
@ -1113,7 +1126,7 @@ namespace Components
void Gamepad::CL_GamepadButtonEvent(const int gamePadIndex, const int key, const Game::GamePadButtonEvent buttonEvent, const unsigned time)
{
assert(gamePadIndex < Game::MAX_GAMEPADS);
AssertIn(gamePadIndex, Game::MAX_GPAD_COUNT);
const auto pressed = buttonEvent == Game::GPAD_BUTTON_PRESSED;
const auto pressedOrUpdated = pressed || buttonEvent == Game::GPAD_BUTTON_UPDATE;
@ -1199,9 +1212,9 @@ namespace Components
void Gamepad::CL_GamepadButtonEventForPort(const int gamePadIndex, const int key, const Game::GamePadButtonEvent buttonEvent, const unsigned time)
{
assert(gamePadIndex < Game::MAX_GAMEPADS);
auto& gamePad = gamePads[gamePadIndex];
AssertIn(gamePadIndex, Game::MAX_GPAD_COUNT);
auto& gamePad = gamePads[gamePadIndex];
gamePad.inUse = true;
gpad_in_use.setRaw(true);
@ -1244,7 +1257,9 @@ namespace Components
float Gamepad::GPad_GetStick(const int gamePadIndex, const Game::GamePadStick stick)
{
assert(gamePadIndex < Game::MAX_GAMEPADS);
AssertIn(gamePadIndex, Game::MAX_GPAD_COUNT);
assert(stick & Game::GPAD_STICK_MASK);
auto& gamePad = gamePads[gamePadIndex];
return gamePad.sticks[stick];
@ -1252,7 +1267,7 @@ namespace Components
float Gamepad::GPad_GetButton(const int gamePadIndex, Game::GamePadButton button)
{
assert(gamePadIndex < Game::MAX_GAMEPADS);
AssertIn(gamePadIndex, Game::MAX_GPAD_COUNT);
auto& gamePad = gamePads[gamePadIndex];
float value = 0.0f;
@ -1276,7 +1291,9 @@ namespace Components
bool Gamepad::GPad_IsButtonPressed(const int gamePadIndex, Game::GamePadButton button)
{
assert(gamePadIndex < Game::MAX_GAMEPADS);
AssertIn(gamePadIndex, Game::MAX_GPAD_COUNT);
assert(button & (Game::GPAD_DIGITAL_MASK | Game::GPAD_ANALOG_MASK));
auto& gamePad = gamePads[gamePadIndex];
bool down = false;
@ -1310,7 +1327,8 @@ namespace Components
bool Gamepad::GPad_IsButtonReleased(int gamePadIndex, Game::GamePadButton button)
{
assert(gamePadIndex < Game::MAX_GAMEPADS);
AssertIn(gamePadIndex, Game::MAX_GPAD_COUNT);
auto& gamePad = gamePads[gamePadIndex];
bool down = false;
@ -1340,7 +1358,8 @@ namespace Components
void Gamepad::GPad_UpdateSticksDown(const int gamePadIndex)
{
assert(gamePadIndex < Game::MAX_GAMEPADS);
AssertIn(gamePadIndex, Game::MAX_GPAD_COUNT);
auto& gamePad = gamePads[gamePadIndex];
for (auto stickIndex = 0u; stickIndex < std::extent_v<decltype(GamePad::sticks)>; stickIndex++)
@ -1371,7 +1390,7 @@ namespace Components
void Gamepad::GPad_UpdateSticks(const int gamePadIndex, const XINPUT_GAMEPAD& state)
{
assert(gamePadIndex < Game::MAX_GAMEPADS);
AssertIn(gamePadIndex, Game::MAX_GPAD_COUNT);
auto& gamePad = gamePads[gamePadIndex];
@ -1403,7 +1422,7 @@ namespace Components
void Gamepad::GPad_UpdateDigitals(const int gamePadIndex, const XINPUT_GAMEPAD& state)
{
assert(gamePadIndex < Game::MAX_GAMEPADS);
AssertIn(gamePadIndex, Game::MAX_GPAD_COUNT);
auto& gamePad = gamePads[gamePadIndex];
@ -1425,7 +1444,7 @@ namespace Components
void Gamepad::GPad_UpdateAnalogs(const int gamePadIndex, const XINPUT_GAMEPAD& state)
{
assert(gamePadIndex < Game::MAX_GAMEPADS);
AssertIn(gamePadIndex, Game::MAX_GPAD_COUNT);
auto& gamePad = gamePads[gamePadIndex];
@ -1452,7 +1471,7 @@ namespace Components
{
GPad_RefreshAll();
for (auto currentGamePadIndex = 0; currentGamePadIndex < Game::MAX_GAMEPADS; currentGamePadIndex++)
for (auto currentGamePadIndex = 0; currentGamePadIndex < Game::MAX_GPAD_COUNT; currentGamePadIndex++)
{
const auto& gamePad = gamePads[currentGamePadIndex];
if (!gamePad.enabled)
@ -1477,7 +1496,7 @@ namespace Components
const auto time = Game::Sys_Milliseconds();
bool gpadPresent = false;
for (auto gamePadIndex = 0; gamePadIndex < Game::MAX_GAMEPADS; gamePadIndex++)
for (auto gamePadIndex = 0; gamePadIndex < Game::MAX_GPAD_COUNT; gamePadIndex++)
{
const auto& gamePad = gamePads[gamePadIndex];
@ -1540,7 +1559,8 @@ namespace Components
void Gamepad::Gamepad_WriteBindings(const int gamePadIndex, const int handle)
{
assert(gamePadIndex < Game::MAX_GAMEPADS);
AssertIn(gamePadIndex, Game::MAX_GPAD_COUNT);
auto& gamePadGlobal = gamePadGlobals[gamePadIndex];
Game::FS_Printf(handle, "unbindallaxis\n");
@ -1584,7 +1604,7 @@ namespace Components
push 0x60B26E
retn
endMethod:
endMethod:
push 0x60B298
retn
}
@ -1592,7 +1612,7 @@ namespace Components
void Gamepad::Gamepad_BindAxis(const int gamePadIndex, const Game::GamepadPhysicalAxis realIndex, const Game::GamepadVirtualAxis axisIndex, const Game::GamepadMapping mapType)
{
assert(gamePadIndex < Game::MAX_GAMEPADS);
AssertIn(gamePadIndex, Game::MAX_GPAD_COUNT);
assert(realIndex > Game::GPAD_PHYSAXIS_NONE && realIndex < Game::GPAD_PHYSAXIS_COUNT);
assert(axisIndex > Game::GPAD_VIRTAXIS_NONE && axisIndex < Game::GPAD_VIRTAXIS_COUNT);
assert(mapType > Game::GPAD_MAP_NONE && mapType < Game::GPAD_MAP_COUNT);

View File

@ -43,6 +43,8 @@ namespace Components
static void OnMouseMove(int x, int y, int dx, int dy);
static Dvar::Var sv_allowAimAssist;
private:
static Game::ButtonToCodeMap_t buttonList[];
static Game::StickToCodeMap_t analogStickList[4];
@ -60,8 +62,8 @@ namespace Components
static Game::keyname_t combinedLocalizedKeyNamesPs3[];
static ControllerMenuKeyMapping controllerMenuKeyMappings[];
static GamePad gamePads[Game::MAX_GAMEPADS];
static GamePadGlobals gamePadGlobals[Game::MAX_GAMEPADS];
static GamePad gamePads[Game::MAX_GPAD_COUNT];
static GamePadGlobals gamePadGlobals[Game::MAX_GPAD_COUNT];
static int gamePadBindingsModifiedFlags;
@ -87,7 +89,6 @@ namespace Components
static Dvar::Var gpad_slowdown_enabled;
static Dvar::Var input_viewSensitivity;
static Dvar::Var input_invertPitch;
static Dvar::Var sv_allowAimAssist;
static Dvar::Var aim_turnrate_pitch;
static Dvar::Var aim_turnrate_pitch_ads;
static Dvar::Var aim_turnrate_yaw;

View File

@ -2,46 +2,48 @@
namespace Components
{
Dvar::Var Lean::BGLean;
Game::kbutton_t Lean::in_leanleft;
Game::kbutton_t Lean::in_leanright;
void Lean::IN_LeanLeft_Up()
{
Game::IN_KeyUp(&Lean::in_leanleft);
Game::IN_KeyUp(&in_leanleft);
}
void Lean::IN_LeanLeft_Down()
{
Game::IN_KeyDown(&Lean::in_leanleft);
Game::IN_KeyDown(&in_leanleft);
}
void Lean::IN_LeanRight_Up()
{
Game::IN_KeyUp(&Lean::in_leanright);
Game::IN_KeyUp(&in_leanright);
}
void Lean::IN_LeanRight_Down()
{
Game::IN_KeyDown(&Lean::in_leanright);
Game::IN_KeyDown(&in_leanright);
}
void Lean::SetLeanFlags(Game::usercmd_s* cmds)
void Lean::SetLeanFlags(Game::usercmd_s* cmd)
{
if (Lean::in_leanleft.active || Lean::in_leanleft.wasPressed)
if ((in_leanleft.active || in_leanleft.wasPressed) && BGLean.get<bool>())
{
cmds->buttons |= BUTTON_FLAG_LEANLEFT;
cmd->buttons |= Game::CMD_BUTTON_LEAN_LEFT;
}
if (Lean::in_leanright.active || Lean::in_leanright.wasPressed)
if ((in_leanright.active || in_leanright.wasPressed) && BGLean.get<bool>())
{
cmds->buttons |= BUTTON_FLAG_LEANRIGHT;
cmd->buttons |= Game::CMD_BUTTON_LEAN_RIGHT;
}
Lean::in_leanleft.wasPressed = false;
Lean::in_leanright.wasPressed = false;
in_leanleft.wasPressed = false;
in_leanright.wasPressed = false;
}
void __declspec(naked) Lean::CL_CmdButtonsStub()
void __declspec(naked) Lean::CL_CmdButtons_Stub()
{
__asm
{
@ -51,21 +53,35 @@ namespace Components
pushad
push esi
call Lean::SetLeanFlags
call SetLeanFlags
pop esi
popad
retn
}
}
void Lean::PM_UpdateLean_Stub(Game::playerState_s* ps, float msec, Game::usercmd_s* cmd, void(*capsuleTrace)(Game::trace_t*, const float*, const float*, const Game::Bounds*, int, int))
{
if (BGLean.get<bool>())
{
Game::PM_UpdateLean(ps, msec, cmd, capsuleTrace);
}
}
Lean::Lean()
{
Command::AddRaw("+leanleft", Lean::IN_LeanLeft_Down, true);
Command::AddRaw("-leanleft", Lean::IN_LeanLeft_Up, true);
Command::AddRaw("+leanleft", IN_LeanLeft_Down, true);
Command::AddRaw("-leanleft", IN_LeanLeft_Up, true);
Command::AddRaw("+leanright", Lean::IN_LeanRight_Down, true);
Command::AddRaw("-leanright", Lean::IN_LeanRight_Up, true);
Command::AddRaw("+leanright", IN_LeanRight_Down, true);
Command::AddRaw("-leanright", IN_LeanRight_Up, true);
Utils::Hook(0x5A6D84, Lean::CL_CmdButtonsStub, HOOK_CALL).install()->quick();
Utils::Hook(0x5A6D84, CL_CmdButtons_Stub, HOOK_CALL).install()->quick();
Utils::Hook(0x4A0C72, PM_UpdateLean_Stub, HOOK_CALL).install()->quick();
Utils::Hook(0x4A0D72, PM_UpdateLean_Stub, HOOK_CALL).install()->quick();
BGLean = Dvar::Register<bool>("bg_lean", true,
Game::DVAR_CODINFO, "Enable CoD4 leaning");
}
}

View File

@ -1,8 +1,5 @@
#pragma once
#define BUTTON_FLAG_LEANLEFT 0x40
#define BUTTON_FLAG_LEANRIGHT 0x80
namespace Components
{
class Lean : public Component
@ -10,6 +7,8 @@ namespace Components
public:
Lean();
static Dvar::Var BGLean;
private:
static Game::kbutton_t in_leanleft;
static Game::kbutton_t in_leanright;
@ -20,7 +19,9 @@ namespace Components
static void IN_LeanRight_Up();
static void IN_LeanRight_Down();
static void CL_CmdButtonsStub();
static void SetLeanFlags(Game::usercmd_s* cmds);
static void CL_CmdButtons_Stub();
static void SetLeanFlags(Game::usercmd_s* cmd);
static void PM_UpdateLean_Stub(Game::playerState_s* ps, float msec, Game::usercmd_s* cmd, void(*capsuleTrace)(Game::trace_t*, const float*, const float*, const Game::Bounds*, int, int));
};
}

View File

@ -249,6 +249,144 @@ namespace Components
Localization::Set("IW4X_CREDITS", credits);
}
const char* Localization::SEH_LocalizeTextMessageStub(const char* pszInputBuffer, const char* pszMessageType, Game::msgLocErrType_t errType)
{
constexpr auto szStringCount = 10;
constexpr auto szStringSize = 1024;
char szInsertBuf[szStringSize];
char szTokenBuf[szStringSize];
static thread_local int iCurrString;
static thread_local char szStrings[szStringCount][szStringSize];
iCurrString = (iCurrString + 1) % szStringCount;
std::memset(szStrings[iCurrString], 0, sizeof(szStrings[0]));
auto* pszString = szStrings[iCurrString];
auto iLen = 0;
auto bLocOn = 1;
auto bInsertEnabled = 1;
auto iInsertLevel = 0;
auto insertIndex = 1;
auto bLocSkipped = 0;
const auto* pszTokenStart = pszInputBuffer;
const auto* pszIn = pszInputBuffer;
auto i = 0;
while (*pszTokenStart)
{
if (*pszIn && *pszIn != '\x14' && *pszIn != '\x15' && *pszIn != '\x16')
{
++pszIn;
continue;
}
if (pszIn > pszTokenStart)
{
auto iTokenLen = pszIn - pszTokenStart;
Game::I_strncpyz_s(szTokenBuf, sizeof(szTokenBuf), pszTokenStart, pszIn - pszTokenStart);
if (bLocOn)
{
if (!Game::SEH_GetLocalizedTokenReference(szTokenBuf, szTokenBuf, pszMessageType, errType))
{
return nullptr;
}
iTokenLen = std::strlen(szTokenBuf);
}
if (iTokenLen + iLen >= szStringSize)
{
Game::Com_Printf(Game::CON_CHANNEL_SYSTEM, "%s too long when translated\n", pszMessageType);
return nullptr;
}
for (i = 0; i < iTokenLen - 2; ++i)
{
if (!std::strncmp(&szTokenBuf[i], "&&", 2) && std::isdigit(szTokenBuf[i + 2]))
{
if (bInsertEnabled)
{
++iInsertLevel;
}
else
{
szTokenBuf[i] = '\x16';
bLocSkipped = 1;
}
}
}
if (iInsertLevel <= 0 || iLen <= 0)
{
Game::I_strcpy(&pszString[iLen], szStringSize - iLen, szTokenBuf);
}
else
{
for (i = 0; i < iLen - 2; ++i)
{
if (!std::strncmp(&pszString[i], "&&", 2) && std::isdigit(pszString[i + 2]))
{
const auto digit = pszString[i + 2] - 48;
if (!digit)
{
Game::Com_Printf(Game::CON_CHANNEL_SYSTEM, "%s cannot have &&0 as conversion format: \"%s\"\n", pszMessageType, pszInputBuffer);
}
if (digit == insertIndex)
{
Game::I_strcpy(szInsertBuf, sizeof(szInsertBuf), &pszString[i + 3]);
pszString[i] = 0;
++insertIndex;
break;
}
}
}
Game::I_strcpy(&pszString[i], szStringSize - i, szTokenBuf);
Game::I_strcpy(&pszString[iTokenLen + i], szStringSize - (iTokenLen + i), szInsertBuf);
iLen -= 3;
--iInsertLevel;
}
iLen += iTokenLen;
}
bInsertEnabled = 1;
if (*pszIn == '\x14')
{
bLocOn = 1;
++pszIn;
}
else if (*pszIn == '\x15')
{
bLocOn = 0;
++pszIn;
}
if (*pszIn == '\x16')
{
bInsertEnabled = 0;
++pszIn;
}
pszTokenStart = pszIn;
}
if (bLocSkipped)
{
for (i = 0; i < iLen; ++i)
{
if (pszString[i] == '\x16')
{
pszString[i] = '%';
}
}
}
return pszString;
}
Localization::Localization()
{
Localization::SetCredits();
@ -279,6 +417,9 @@ namespace Components
// Overwrite SetString
Utils::Hook(0x4CE5EE, Localization::SetStringStub, HOOK_CALL).install()->quick();
Utils::Hook(0x49D4A0, Localization::SEH_LocalizeTextMessageStub, HOOK_JUMP).install()->quick();
Utils::Hook::Nop(0x49D4A5, 1);
Localization::UseLocalization = Dvar::Register<bool>("ui_localize", true, Game::DVAR_NONE, "Use localization strings");
// Generate localized entries for custom classes above 10

View File

@ -24,5 +24,7 @@ namespace Components
static void LoadLanguageStrings();
static void SELoadLanguageStub();
static void SetCredits();
static const char* SEH_LocalizeTextMessageStub(const char* pszInputBuffer, const char* pszMessageType, Game::msgLocErrType_t errType);
};
}

View File

@ -28,7 +28,7 @@ namespace Components
{
std::string out = msg;
// Filter out coloured strings
// Filter out coloured strings for stdout
if (out[0] == '^' && out[1] != '\0')
{
out = out.substr(2);
@ -48,11 +48,11 @@ namespace Components
if (!Game::Sys_IsMainThread())
{
Logger::EnqueueMessage(out);
Logger::EnqueueMessage(msg);
}
else
{
Game::Com_PrintMessage(channel, out.data(), 0);
Game::Com_PrintMessage(channel, msg.data(), 0);
}
}
@ -284,7 +284,7 @@ namespace Components
if (Utils::String::VA("%i", num) == std::string(params->get(1)) && static_cast<unsigned int>(num) < Logger::LoggingAddresses[0].size())
{
auto addr = Logger::LoggingAddresses[0].begin() + num;
Logger::Print("Address {} removed\n", addr->getCString());
Logger::Print("Address {} removed\n", addr->getString());
Logger::LoggingAddresses[0].erase(addr);
}
else
@ -295,11 +295,11 @@ namespace Components
if (i != Logger::LoggingAddresses[0].end())
{
Logger::LoggingAddresses[0].erase(i);
Logger::Print("Address {} removed\n", addr.getCString());
Logger::Print("Address {} removed\n", addr.getString());
}
else
{
Logger::Print("Address {} not found!\n", addr.getCString());
Logger::Print("Address {} not found!\n", addr.getString());
}
}
});
@ -311,7 +311,7 @@ namespace Components
for (unsigned int i = 0; i < Logger::LoggingAddresses[0].size(); ++i)
{
Logger::Print("#{:03d}: {}\n", i, Logger::LoggingAddresses[0][i].getCString());
Logger::Print("#{:03d}: {}\n", i, Logger::LoggingAddresses[0][i].getString());
}
});
@ -335,7 +335,7 @@ namespace Components
if (Utils::String::VA("%i", num) == std::string(params->get(1)) && static_cast<unsigned int>(num) < Logger::LoggingAddresses[1].size())
{
const auto addr = Logger::LoggingAddresses[1].begin() + num;
Logger::Print("Address {} removed\n", addr->getCString());
Logger::Print("Address {} removed\n", addr->getString());
Logger::LoggingAddresses[1].erase(addr);
}
else
@ -346,11 +346,11 @@ namespace Components
if (i != Logger::LoggingAddresses[1].end())
{
Logger::LoggingAddresses[1].erase(i);
Logger::Print("Address {} removed\n", addr.getCString());
Logger::Print("Address {} removed\n", addr.getString());
}
else
{
Logger::Print("Address {} not found!\n", addr.getCString());
Logger::Print("Address {} not found!\n", addr.getString());
}
}
});
@ -362,7 +362,7 @@ namespace Components
for (std::size_t i = 0; i < Logger::LoggingAddresses[1].size(); ++i)
{
Logger::Print("#{:03d}: {}\n", i, Logger::LoggingAddresses[1][i].getCString());
Logger::Print("#{:03d}: {}\n", i, Logger::LoggingAddresses[1][i].getString());
}
});
}
@ -397,9 +397,9 @@ namespace Components
lock.unlock();
// Flush the console log
if (const auto logfile = *reinterpret_cast<int*>(0x1AD8F28))
if (*Game::logfile)
{
Game::FS_FCloseFile(logfile);
Game::FS_FCloseFile(*Game::logfile);
}
}
}

View File

@ -5,10 +5,6 @@ namespace Components
Dvar::Var MapRotation::SVRandomMapRotation;
Dvar::Var MapRotation::SVDontRotate;
Game::dvar_t** MapRotation::SVMapRotation = reinterpret_cast<Game::dvar_t**>(0x62C7C44);
Game::dvar_t** MapRotation::SVMapRotationCurrent = reinterpret_cast<Game::dvar_t**>(0x2098DF0);
Game::dvar_t** MapRotation::SVMapname = reinterpret_cast<Game::dvar_t**>(0x2098DDC);
MapRotation::RotationData MapRotation::DedicatedRotation;
MapRotation::RotationData::RotationData()
@ -62,7 +58,16 @@ namespace Components
}
}
json11::Json MapRotation::RotationData::to_json() const
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;
});
}
nlohmann::json MapRotation::RotationData::to_json() const
{
std::vector<std::string> mapVector;
std::vector<std::string> gametypeVector;
@ -80,7 +85,7 @@ namespace Components
}
json11::Json mapRotationJson = json11::Json::object
nlohmann::json mapRotationJson = nlohmann::json
{
{"maps", mapVector},
{"gametypes", gametypeVector},
@ -107,12 +112,23 @@ namespace Components
}
catch (const std::exception& ex)
{
Logger::PrintError(Game::CON_CHANNEL_ERROR, "{}: {} contains invalid data!\n", ex.what(), (*SVMapRotation)->name);
Logger::PrintError(Game::CON_CHANNEL_ERROR, "{}: {} contains invalid data!\n", ex.what(), (*Game::sv_mapRotation)->name);
}
Logger::Debug("DedicatedRotation size after parsing is '{}'", DedicatedRotation.getEntriesSize());
}
void MapRotation::LoadMapRotation()
{
const std::string mapRotation = (*Game::sv_mapRotation)->current.string;
// People may have sv_mapRotation empty because they only use 'addMap' or 'addGametype'
if (!mapRotation.empty())
{
Logger::Debug("sv_mapRotation is not empty. Parsing...");
LoadRotation(mapRotation);
}
}
void MapRotation::AddMapRotationCommands()
{
Command::Add("addMap", [](Command::Params* params)
@ -138,6 +154,11 @@ namespace Components
});
}
bool MapRotation::Contains(const std::string& key, const std::string& value)
{
return DedicatedRotation.contains(key, value);
}
bool MapRotation::ShouldRotate()
{
if (!Dedicated::IsEnabled() && SVDontRotate.get<bool>())
@ -160,13 +181,13 @@ namespace Components
{
assert(!map.empty());
if (Dvar::Var("sv_cheats").get<bool>())
if ((*Game::sv_cheats)->current.enabled)
{
Command::Execute(Utils::String::VA("devmap %s", map.data()), true);
Command::Execute(std::format("devmap {}", map), true);
}
else
{
Command::Execute(Utils::String::VA("map %s", map.data()), true);
Command::Execute(std::format("map {}", map), true);
}
}
@ -178,7 +199,7 @@ namespace Components
void MapRotation::RestartCurrentMap()
{
std::string svMapname = (*SVMapname)->current.string;
std::string svMapname = (*Game::sv_mapname)->current.string;
if (svMapname.empty())
{
@ -224,24 +245,24 @@ namespace Components
assert(!data.empty());
// Ook, ook, eek
Logger::Warning(Game::CON_CHANNEL_SERVER, "You are using deprecated {}", (*SVMapRotationCurrent)->name);
Logger::Warning(Game::CON_CHANNEL_SERVER, "You are using deprecated {}", (*Game::sv_mapRotationCurrent)->name);
RotationData rotationCurrent;
try
{
Logger::Debug("Parsing {}", (*SVMapRotationCurrent)->name);
Logger::Debug("Parsing {}", (*Game::sv_mapRotationCurrent)->name);
rotationCurrent.parse(data);
}
catch (const std::exception& ex)
{
Logger::PrintError(Game::CON_CHANNEL_ERROR, "{}: {} contains invalid data!\n", ex.what(), (*SVMapRotationCurrent)->name);
Logger::PrintError(Game::CON_CHANNEL_ERROR, "{}: {} contains invalid data!\n", ex.what(), (*Game::sv_mapRotationCurrent)->name);
}
Game::Dvar_SetString(*SVMapRotationCurrent, "");
Game::Dvar_SetString(*Game::sv_mapRotationCurrent, "");
if (rotationCurrent.getEntriesSize() == 0)
{
Logger::Print(Game::CON_CHANNEL_SERVER, "{} is empty or contains invalid data. Restarting map\n", (*SVMapRotationCurrent)->name);
Logger::Print(Game::CON_CHANNEL_SERVER, "{} is empty or contains invalid data. Restarting map\n", (*Game::sv_mapRotationCurrent)->name);
RestartCurrentMap();
return;
}
@ -272,25 +293,18 @@ namespace Components
Logger::Print(Game::CON_CHANNEL_SERVER, "Rotating map...\n");
// This takes priority because of backwards compatibility
const std::string mapRotationCurrent = (*SVMapRotationCurrent)->current.string;
const std::string mapRotationCurrent = (*Game::sv_mapRotationCurrent)->current.string;
if (!mapRotationCurrent.empty())
{
Logger::Debug("Applying {}", (*SVMapRotationCurrent)->name);
Logger::Debug("Applying {}", (*Game::sv_mapRotationCurrent)->name);
ApplyMapRotationCurrent(mapRotationCurrent);
return;
}
const std::string mapRotation = (*SVMapRotation)->current.string;
// People may have sv_mapRotation empty because they only use 'addMap' or 'addGametype'
if (!mapRotation.empty())
{
Logger::Debug("sv_mapRotation is not empty. Parsing...");
LoadRotation(mapRotation);
}
LoadMapRotation();
if (DedicatedRotation.getEntriesSize() == 0)
{
Logger::Print(Game::CON_CHANNEL_SERVER, "{} is empty or contains invalid data. Restarting map\n", (*SVMapRotation)->name);
Logger::Print(Game::CON_CHANNEL_SERVER, "{} is empty or contains invalid data. Restarting map\n", (*Game::sv_mapRotation)->name);
RestartCurrentMap();
return;
}

View File

@ -7,6 +7,8 @@ namespace Components
public:
MapRotation();
static bool Contains(const std::string& key, const std::string& value);
bool unitTest() override;
private:
@ -33,8 +35,9 @@ namespace Components
void parse(const std::string& data);
// Json11 Implicit constructor
[[nodiscard]] json11::Json to_json() const;
[[nodiscard]] bool contains(const std::string& key, const std::string& value) const;
[[nodiscard]] nlohmann::json to_json() const;
private:
std::vector<rotationEntry> rotationEntries_;
@ -45,15 +48,12 @@ namespace Components
// Rotation Dvars
static Dvar::Var SVRandomMapRotation;
static Dvar::Var SVDontRotate;
// Game Dvars
static Game::dvar_t** SVMapRotation;
static Game::dvar_t** SVMapRotationCurrent;
static Game::dvar_t** SVMapname;
// Holds the parsed data from sv_mapRotation
static RotationData DedicatedRotation;
static void LoadRotation(const std::string& data);
static void LoadMapRotation();
// Use these commands before SV_MapRotate_f is called
static void AddMapRotationCommands();

View File

@ -96,12 +96,12 @@ namespace Components
const char* Maps::LoadArenaFileStub(const char* name, char* buffer, int size)
{
std::string data = Game::Scr_AddSourceBuffer(nullptr, name, nullptr, false);
std::string data = RawFiles::ReadRawFile(name, buffer, size);
if(Maps::UserMap.isValid())
if (Maps::UserMap.isValid())
{
std::string mapname = Maps::UserMap.getName();
std::string arena = Utils::String::VA("usermaps/%s/%s.arena", mapname.data(), mapname.data());
const std::string mapname = Maps::UserMap.getName();
const auto* arena = Utils::String::VA("usermaps/%s/%s.arena", mapname.data(), mapname.data());
if (Utils::IO::FileExists(arena))
{
@ -109,7 +109,7 @@ namespace Components
}
}
strncpy_s(buffer, size, data.data(), data.size());
strncpy_s(buffer, size, data.data(), _TRUNCATE);
return buffer;
}
@ -768,7 +768,7 @@ namespace Components
Maps::UpdateDlcStatus();
UIScript::Add("downloadDLC", [](UIScript::Token token)
UIScript::Add("downloadDLC", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
{
int dlc = token.get<int>();

View File

@ -785,80 +785,85 @@ namespace Components
}
}, HOOK_CALL).install()->quick();
// Intercept menu painting
Utils::Hook(0x4FFBDF, Menus::IsMenuVisible, HOOK_CALL).install()->quick();
// Intercept menu painting
Utils::Hook(0x4FFBDF, Menus::IsMenuVisible, HOOK_CALL).install()->quick();
// disable the 2 new tokens in ItemParse_rect
Utils::Hook::Set<BYTE>(0x640693, 0xEB);
// disable the 2 new tokens in ItemParse_rect
Utils::Hook::Set<BYTE>(0x640693, 0xEB);
// don't load ASSET_TYPE_MENU assets for every menu (might cause patch menus to fail)
Utils::Hook::Nop(0x453406, 5);
// don't load ASSET_TYPE_MENU assets for every menu (might cause patch menus to fail)
Utils::Hook::Nop(0x453406, 5);
//make Com_Error and similar go back to main_text instead of menu_xboxlive.
Utils::Hook::SetString(0x6FC790, "main_text");
// make Com_Error and similar go back to main_text instead of menu_xboxlive.
Utils::Hook::SetString(0x6FC790, "main_text");
Command::Add("openmenu", [](Command::Params* params)
Command::Add("openmenu", [](Command::Params* params)
{
if (params->size() != 2)
{
if (params->size() != 2)
{
Logger::Print("USAGE: openmenu <menu name>\n");
return;
}
Logger::Print("USAGE: openmenu <menu name>\n");
return;
}
// Not quite sure if we want to do this if we're not ingame, but it's only needed for ingame menus.
if (Dvar::Var("cl_ingame").get<bool>())
{
Game::Key_SetCatcher(0, 16);
}
Game::Menus_OpenByName(Game::uiContext, params->get(1));
});
Command::Add("reloadmenus", [](Command::Params*)
// Not quite sure if we want to do this if we're not ingame, but it's only needed for ingame menus.
if ((*Game::cl_ingame)->current.enabled)
{
// Close all menus
Game::Menus_CloseAll(Game::uiContext);
Game::Key_SetCatcher(0, 16);
}
// Free custom menus
Menus::FreeEverything();
Game::Menus_OpenByName(Game::uiContext, params->get(1));
});
// Only disconnect if in-game, context is updated automatically!
if (Game::CL_IsCgameInitialized())
{
Game::Cbuf_AddText(0, "disconnect\n");
}
else
{
// Reinitialize ui context
Utils::Hook::Call<void()>(0x401700)();
Command::Add("reloadmenus", [](Command::Params*)
{
// Close all menus
Game::Menus_CloseAll(Game::uiContext);
// Reopen main menu
Game::Menus_OpenByName(Game::uiContext, "main_text");
}
});
// Free custom menus
Menus::FreeEverything();
Command::Add("mp_QuickMessage", [](Command::Params*)
// Only disconnect if in-game, context is updated automatically!
if (Game::CL_IsCgameInitialized())
{
Command::Execute("openmenu quickmessage");
});
Game::Cbuf_AddText(0, "disconnect\n");
}
else
{
// Reinitialize ui context
Utils::Hook::Call<void()>(0x401700)();
// Define custom menus here
Menus::Add("ui_mp/changelog.menu");
Menus::Add("ui_mp/theater_menu.menu");
Menus::Add("ui_mp/pc_options_multi.menu");
Menus::Add("ui_mp/pc_options_game.menu");
Menus::Add("ui_mp/pc_options_gamepad.menu");
Menus::Add("ui_mp/stats_reset.menu");
Menus::Add("ui_mp/stats_unlock.menu");
Menus::Add("ui_mp/security_increase_popmenu.menu");
Menus::Add("ui_mp/mod_download_popmenu.menu");
Menus::Add("ui_mp/popup_friends.menu");
Menus::Add("ui_mp/menu_first_launch.menu");
Menus::Add("ui_mp/startup_messages.menu");
Menus::Add("ui_mp/iw4x_credits.menu");
Menus::Add("ui_mp/resetclass.menu");
Menus::Add("ui_mp/popup_customtitle.menu");
Menus::Add("ui_mp/popup_customclan.menu");
// Reopen main menu
Game::Menus_OpenByName(Game::uiContext, "main_text");
}
});
Command::Add("mp_QuickMessage", [](Command::Params*)
{
Command::Execute("openmenu quickmessage");
});
// Define custom menus here
Menus::Add("ui_mp/changelog.menu");
Menus::Add("ui_mp/theater_menu.menu");
Menus::Add("ui_mp/pc_options_multi.menu");
Menus::Add("ui_mp/pc_options_game.menu");
Menus::Add("ui_mp/pc_options_gamepad.menu");
Menus::Add("ui_mp/stats_reset.menu");
Menus::Add("ui_mp/stats_unlock.menu");
Menus::Add("ui_mp/security_increase_popmenu.menu");
Menus::Add("ui_mp/mod_download_popmenu.menu");
Menus::Add("ui_mp/popup_friends.menu");
Menus::Add("ui_mp/menu_first_launch.menu");
Menus::Add("ui_mp/startup_messages.menu");
Menus::Add("ui_mp/iw4x_credits.menu");
Menus::Add("ui_mp/resetclass.menu");
Menus::Add("ui_mp/popup_customtitle.menu");
Menus::Add("ui_mp/popup_customclan.menu");
Menus::Add("ui_mp/scriptmenus/callvote.menu");
Menus::Add("ui_mp/scriptmenus/changegametype.menu");
Menus::Add("ui_mp/scriptmenus/changemap.menu");
Menus::Add("ui_mp/scriptmenus/kickplayer.menu");
}
Menus::~Menus()

View File

@ -40,7 +40,7 @@ namespace Components
ModList::CurrentMod = index;
}
void ModList::UIScript_LoadMods(UIScript::Token)
void ModList::UIScript_LoadMods([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
{
auto folder = Dvar::Var("fs_basepath").get<std::string>() + "\\mods";
Logger::Debug("Searching for mods in {}...", folder);
@ -48,7 +48,7 @@ namespace Components
Logger::Debug("Found {} mods!", ModList::Mods.size());
}
void ModList::UIScript_RunMod(UIScript::Token)
void ModList::UIScript_RunMod([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
{
if (ModList::CurrentMod < ModList::Mods.size())
{
@ -56,7 +56,7 @@ namespace Components
}
}
void ModList::UIScript_ClearMods(UIScript::Token)
void ModList::UIScript_ClearMods([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
{
auto fsGame = Dvar::Var("fs_game");
fsGame.set("");

View File

@ -18,8 +18,8 @@ namespace Components
static unsigned int GetItemCount();
static const char* GetItemText(unsigned int index, int column);
static void Select(unsigned int index);
static void UIScript_LoadMods(UIScript::Token);
static void UIScript_RunMod(UIScript::Token);
static void UIScript_ClearMods(UIScript::Token);
static void UIScript_LoadMods([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info);
static void UIScript_RunMod([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info);
static void UIScript_ClearMods([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info);
};
}

View File

@ -7,6 +7,7 @@ namespace Components
Dvar::Var Movement::CGNoclipScaler;
Dvar::Var Movement::BGBouncesAllAngles;
Dvar::Var Movement::BGRocketJump;
Dvar::Var Movement::BGRocketJumpScale;
Dvar::Var Movement::BGPlayerEjection;
Dvar::Var Movement::BGPlayerCollision;
Game::dvar_t* Movement::BGBounces;
@ -172,15 +173,17 @@ namespace Components
}
Game::gentity_s* Movement::Weapon_RocketLauncher_Fire_Hk(Game::gentity_s* ent, unsigned int weaponIndex,
float spread, Game::weaponParms* wp, const float* gunVel, Game::lockonFireParms* lockParms, bool a7)
float spread, Game::weaponParms* wp, const float* gunVel, Game::lockonFireParms* lockParms, bool magicBullet)
{
auto* result = Game::Weapon_RocketLauncher_Fire(ent, weaponIndex, spread, wp, gunVel, lockParms, a7);
auto* result = Game::Weapon_RocketLauncher_Fire(ent, weaponIndex, spread, wp, gunVel, lockParms, magicBullet);
if (ent->client != nullptr && BGRocketJump.get<bool>())
if (ent->client != nullptr && BGRocketJump.get<bool>() &&
wp->weapDef->inventoryType != Game::WEAPINVENTORY_EXCLUSIVE)
{
ent->client->ps.velocity[0] += (0.0f - wp->forward[0]) * 64.0f;
ent->client->ps.velocity[1] += (0.0f - wp->forward[1]) * 64.0f;
ent->client->ps.velocity[2] += (0.0f - wp->forward[2]) * 64.0f;
const auto scale = Movement::BGRocketJumpScale.get<float>();
ent->client->ps.velocity[0] += (0.0f - wp->forward[0]) * scale;
ent->client->ps.velocity[1] += (0.0f - wp->forward[1]) * scale;
ent->client->ps.velocity[2] += (0.0f - wp->forward[2]) * scale;
}
return result;
@ -217,6 +220,42 @@ namespace Components
return Movement::PlayerSpectateSpeedScale.get<Game::dvar_t*>();
}
void Movement::RegisterMovementDvars()
{
Movement::PlayerDuckedSpeedScale = Game::Dvar_RegisterFloat("player_duckedSpeedScale",
0.65f, 0.0f, 5.0f, Game::DVAR_CHEAT | Game::DVAR_CODINFO,
"The scale applied to the player speed when ducking");
Movement::PlayerProneSpeedScale = Game::Dvar_RegisterFloat("player_proneSpeedScale",
0.15f, 0.0f, 5.0f, Game::DVAR_CHEAT | Game::DVAR_CODINFO,
"The scale applied to the player speed when crawling");
// 3arc naming convention
Movement::CGUfoScaler = Dvar::Register<float>("cg_ufo_scaler",
6.0f, 0.001f, 1000.0f, Game::DVAR_CHEAT | Game::DVAR_CODINFO,
"The speed at which ufo camera moves");
Movement::CGNoclipScaler = Dvar::Register<float>("cg_noclip_scaler",
3.0f, 0.001f, 1000.0f, Game::DVAR_CHEAT | Game::DVAR_CODINFO,
"The speed at which noclip camera moves");
Movement::BGBouncesAllAngles = Dvar::Register<bool>("bg_bouncesAllAngles",
false, Game::DVAR_CODINFO, "Force bounce from all angles");
Movement::BGRocketJump = Dvar::Register<bool>("bg_rocketJump",
false, Game::DVAR_CODINFO, "Enable CoD4 rocket jumps");
Movement::BGRocketJumpScale = Dvar::Register<float>("bg_rocketJumpScale",
64.0f, 1.0f, std::numeric_limits<float>::max(), Game::DVAR_CODINFO,
"The scale applied to the pushback force of a rocket");
Movement::BGPlayerEjection = Dvar::Register<bool>("bg_playerEjection",
true, Game::DVAR_CODINFO, "Push intersecting players away from each other");
Movement::BGPlayerCollision = Dvar::Register<bool>("bg_playerCollision",
true, Game::DVAR_CODINFO, "Push intersecting players away from each other");
}
Movement::Movement()
{
Scheduler::Once([]
@ -229,37 +268,8 @@ namespace Components
nullptr
};
Movement::PlayerDuckedSpeedScale = Game::Dvar_RegisterFloat("player_duckedSpeedScale",
0.65f, 0.0f, 5.0f, Game::DVAR_CHEAT | Game::DVAR_CODINFO,
"The scale applied to the player speed when ducking");
Movement::PlayerProneSpeedScale = Game::Dvar_RegisterFloat("player_proneSpeedScale",
0.15f, 0.0f, 5.0f, Game::DVAR_CHEAT | Game::DVAR_CODINFO,
"The scale applied to the player speed when crawling");
// 3arc naming convention
Movement::CGUfoScaler = Dvar::Register<float>("cg_ufo_scaler",
6.0f, 0.001f, 1000.0f, Game::DVAR_CHEAT | Game::DVAR_CODINFO,
"The speed at which ufo camera moves");
Movement::CGNoclipScaler = Dvar::Register<float>("cg_noclip_scaler",
3.0f, 0.001f, 1000.0f, Game::DVAR_CHEAT | Game::DVAR_CODINFO,
"The speed at which noclip camera moves");
Movement::BGBounces = Game::Dvar_RegisterEnum("bg_bounces",
bg_bouncesValues, Movement::DISABLED, Game::DVAR_CODINFO, "Bounce glitch settings");
Movement::BGBouncesAllAngles = Dvar::Register<bool>("bg_bouncesAllAngles",
false, Game::DVAR_CODINFO, "Force bounce from all angles");
Movement::BGRocketJump = Dvar::Register<bool>("bg_rocketJump",
false, Game::DVAR_CODINFO, "Enable CoD4 rocket jumps");
Movement::BGPlayerEjection = Dvar::Register<bool>("bg_playerEjection",
true, Game::DVAR_CODINFO, "Push intersecting players away from each other");
Movement::BGPlayerCollision = Dvar::Register<bool>("bg_playerCollision",
true, Game::DVAR_CODINFO, "Push intersecting players away from each other");
}, Scheduler::Pipeline::MAIN);
// Hook Dvar_RegisterFloat. Only thing that's changed is that the 0x80 flag is not used.
@ -286,5 +296,7 @@ namespace Components
Utils::Hook(0x5D8153, Movement::StuckInClient_Hk, HOOK_CALL).install()->quick();
Utils::Hook(0x45A5BF, Movement::CM_TransformedCapsuleTrace_Hk, HOOK_CALL).install()->quick(); // SV_ClipMoveToEntity
Utils::Hook(0x5A0CAD, Movement::CM_TransformedCapsuleTrace_Hk, HOOK_CALL).install()->quick(); // CG_ClipMoveToEntity
Movement::RegisterMovementDvars();
}
}

View File

@ -15,6 +15,7 @@ namespace Components
static Dvar::Var CGNoclipScaler;
static Dvar::Var BGBouncesAllAngles;
static Dvar::Var BGRocketJump;
static Dvar::Var BGRocketJumpScale;
static Dvar::Var BGPlayerEjection;
static Dvar::Var BGPlayerCollision;
// Can't use Var class inside assembly stubs
@ -40,5 +41,7 @@ namespace Components
static void CM_TransformedCapsuleTrace_Hk(Game::trace_t* results, const float* start, const float* end, const Game::Bounds* bounds, const Game::Bounds* capsule, int contents, const float* origin, const float* angles);
static Game::dvar_t* Dvar_RegisterSpectateSpeedScale(const char* dvarName, float value, float min, float max, unsigned __int16 flags, const char* description);
static void RegisterMovementDvars();
};
}

View File

@ -2,10 +2,10 @@
namespace Components
{
std::string Network::SelectedPacket;
Utils::Signal<Network::CallbackRaw> Network::StartupSignal;
// Packet interception
std::unordered_map<std::string, Network::NetworkCallback> Network::Callbacks;
std::unordered_map<std::string, Network::NetworkCallback> Network::CL_Callbacks;
std::unordered_map<std::string, Network::NetworkCallback> Network::SV_Callbacks;
Network::Address::Address(const std::string& addrString)
{
@ -27,7 +27,7 @@ namespace Components
this->address.port = htons(port);
}
unsigned short Network::Address::getPort()
unsigned short Network::Address::getPort() const
{
return ntohs(this->address.port);
}
@ -42,7 +42,7 @@ namespace Components
this->address.ip = ip;
}
Game::netIP_t Network::Address::getIP()
Game::netIP_t Network::Address::getIP() const
{
return this->address.ip;
}
@ -52,7 +52,7 @@ namespace Components
this->address.type = type;
}
Game::netadrtype_t Network::Address::getType()
Game::netadrtype_t Network::Address::getType() const
{
return this->address.type;
}
@ -89,7 +89,7 @@ namespace Components
std::string Network::Address::getString() const
{
return this->getCString();
return {this->getCString()};
}
bool Network::Address::isLocal()
@ -116,7 +116,7 @@ namespace Components
bool Network::Address::isSelf()
{
if (Game::NET_IsLocalAddress(this->address)) return true; // Loopback
if (this->getPort() != Network::GetPort()) return false; // Port not equal
if (this->getPort() != GetPort()) return false; // Port not equal
for (int i = 0; i < *Game::numIP; ++i)
{
@ -129,7 +129,7 @@ namespace Components
return false;
}
bool Network::Address::isLoopback()
bool Network::Address::isLoopback() const
{
if (this->getIP().full == 0x100007f) // 127.0.0.1
{
@ -139,17 +139,17 @@ namespace Components
return Game::NET_IsLocalAddress(this->address);
}
bool Network::Address::isValid()
bool Network::Address::isValid() const
{
return (this->getType() != Game::netadrtype_t::NA_BAD && this->getType() >= Game::netadrtype_t::NA_BOT && this->getType() <= Game::netadrtype_t::NA_IP);
return (this->getType() != Game::NA_BAD && this->getType() >= Game::NA_BOT && this->getType() <= Game::NA_IP);
}
void Network::OnStart(Utils::Slot<Network::CallbackRaw> callback)
void Network::OnStart(const Utils::Slot<CallbackRaw>& callback)
{
Network::StartupSignal.connect(callback);
StartupSignal.connect(callback);
}
void Network::Send(Game::netsrc_t type, Network::Address target, const std::string& data)
void Network::Send(Game::netsrc_t type, Address target, const std::string& data)
{
// NET_OutOfBandPrint only supports non-binary data!
//Game::NET_OutOfBandPrint(type, *target.Get(), data.data());
@ -159,15 +159,15 @@ namespace Components
rawData.append(data);
//rawData.append("\0", 1);
Network::SendRaw(type, target, rawData);
SendRaw(type, target, rawData);
}
void Network::Send(Network::Address target, const std::string& data)
void Network::Send(Address target, const std::string& data)
{
Network::Send(Game::netsrc_t::NS_CLIENT1, target, data);
Send(Game::netsrc_t::NS_CLIENT1, target, data);
}
void Network::SendRaw(Game::netsrc_t type, Network::Address target, const std::string& data)
void Network::SendRaw(Game::netsrc_t type, Address target, const std::string& data)
{
if (!target.isValid()) return;
@ -176,12 +176,12 @@ namespace Components
Game::Sys_SendPacket(type, data.size(), data.data(), *target.get());
}
void Network::SendRaw(Network::Address target, const std::string& data)
void Network::SendRaw(Address target, const std::string& data)
{
Network::SendRaw(Game::netsrc_t::NS_CLIENT1, target, data);
SendRaw(Game::NS_CLIENT1, target, data);
}
void Network::SendCommand(Game::netsrc_t type, Network::Address target, const std::string& command, const std::string& data)
void Network::SendCommand(Game::netsrc_t type, Address target, const std::string& command, const std::string& data)
{
// Use space as separator (possible separators are '\n', ' ').
// Though, our handler only needs exactly 1 char as separator and doesn't care which char it is.
@ -191,12 +191,12 @@ namespace Components
packet.append("\n", 1);
packet.append(data);
Network::Send(type, target, packet);
Send(type, target, packet);
}
void Network::SendCommand(Network::Address target, const std::string& command, const std::string& data)
void Network::SendCommand(Address target, const std::string& command, const std::string& data)
{
Network::SendCommand(Game::netsrc_t::NS_CLIENT1, target, command, data);
SendCommand(Game::NS_CLIENT1, target, command, data);
}
void Network::Broadcast(unsigned short port, const std::string& data)
@ -207,25 +207,26 @@ namespace Components
target.setIP(INADDR_BROADCAST);
target.setType(Game::netadrtype_t::NA_BROADCAST);
Network::Send(Game::netsrc_t::NS_CLIENT1, target, data);
Send(Game::netsrc_t::NS_CLIENT1, target, data);
}
void Network::BroadcastRange(unsigned int min, unsigned int max, const std::string& data)
{
for (unsigned int i = min; i < max; ++i)
{
Network::Broadcast(static_cast<unsigned short>(i & 0xFFFF), data);
Broadcast(static_cast<unsigned short>(i & 0xFFFF), data);
}
}
void Network::BroadcastAll(const std::string& data)
{
Network::BroadcastRange(100, 65536, data);
BroadcastRange(100, 65536, data);
}
void Network::NetworkStart()
{
Network::StartupSignal();
StartupSignal();
StartupSignal.clear();
}
unsigned short Network::GetPort()
@ -239,7 +240,7 @@ namespace Components
{
mov eax, 64D900h
call eax
jmp Network::NetworkStart
jmp NetworkStart
}
}
@ -267,32 +268,55 @@ namespace Components
if (client->reliableAcknowledge < 0)
{
Logger::Print(Game::conChannel_t::CON_CHANNEL_NETWORK, "Negative reliableAcknowledge from {} - cl->reliableSequence is {}, reliableAcknowledge is {}\n",
client->name, client->reliableSequence, client->reliableAcknowledge);
client->name, client->reliableSequence, client->reliableAcknowledge);
client->reliableAcknowledge = client->reliableSequence;
Network::SendCommand(Game::NS_SERVER, client->netchan.remoteAddress, "error", "EXE_LOSTRELIABLECOMMANDS");
SendCommand(Game::NS_SERVER, client->header.netchan.remoteAddress, "error", "EXE_LOSTRELIABLECOMMANDS");
return;
}
Utils::Hook::Call<void(Game::client_t*, Game::msg_t*)>(0x414D40)(client, msg);
}
void Network::OnPacket(const std::string& command, const NetworkCallback& callback)
void Network::OnClientPacket(const std::string& command, const NetworkCallback& callback)
{
Network::Callbacks[Utils::String::ToLower(command)] = callback;
CL_Callbacks[Utils::String::ToLower(command)] = callback;
}
bool Network::HandleCommand(Game::netadr_t* address, const char* command, const Game::msg_t* message)
void Network::OnServerPacket(const std::string& command, const NetworkCallback& callback)
{
SV_Callbacks[Utils::String::ToLower(command)] = callback;
}
bool Network::CL_HandleCommand(Game::netadr_t* address, const char* command, const Game::msg_t* message)
{
const auto command_ = Utils::String::ToLower(command);
const auto handler = Network::Callbacks.find(command_);
const auto handler = CL_Callbacks.find(command_);
const auto offset = command_.size() + 5;
if (static_cast<std::size_t>(message->cursize) < offset || handler == Network::Callbacks.end())
if (static_cast<std::size_t>(message->cursize) < offset || handler == CL_Callbacks.end())
{
return false;
}
const std::string data(message->data + offset, message->cursize - offset);
const std::string data(reinterpret_cast<char*>(message->data) + offset, message->cursize - offset);
Address address_ = address;
handler->second(address_, data);
return true;
}
bool Network::SV_HandleCommand(Game::netadr_t* address, const char* command, const Game::msg_t* message)
{
const auto command_ = Utils::String::ToLower(command);
const auto handler = SV_Callbacks.find(command_);
const auto offset = command_.size() + 5;
if (static_cast<std::size_t>(message->cursize) < offset || handler == SV_Callbacks.end())
{
return false;
}
const std::string data(reinterpret_cast<char*>(message->data) + offset, message->cursize - offset);
Address address_ = address;
handler->second(address_, data);
@ -308,9 +332,9 @@ namespace Components
pushad
push ebp // msg_t
push edi // Command name
push edi // command name
push eax // netadr_t pointer
call Network::HandleCommand
call CL_HandleCommand
add esp, 0xC
test al, al
@ -330,6 +354,37 @@ namespace Components
}
}
__declspec(naked) void Network::SV_HandleCommandStub()
{
__asm
{
lea eax, [esp + 0x408]
pushad
push esi // msg
push edi // command name
push eax // netadr_t pointer
call SV_HandleCommand
add esp, 0xC
test al, al
popad
jz unhandled
// Exit SV_ConnectionlessPacket
push 0x6267EB
retn
unhandled:
// Proceed
push 0x6266E0
retn
}
}
Network::Network()
{
AssertSize(Game::netadr_t, 20);
@ -355,25 +410,28 @@ namespace Components
Utils::Hook::Set<const char*>(0x4698E3, "%u.%u.%u.%u:%hu");
// Install startup handler
Utils::Hook(0x4FD4D4, Network::NetworkStartStub, HOOK_JUMP).install()->quick();
Utils::Hook(0x4FD4D4, NetworkStartStub, HOOK_JUMP).install()->quick();
// Prevent recvfrom error spam
Utils::Hook(0x46531A, Network::PacketErrorCheck, HOOK_JUMP).install()->quick();
Utils::Hook(0x46531A, PacketErrorCheck, HOOK_JUMP).install()->quick();
// Fix server freezer exploit
Utils::Hook(0x626996, Network::SV_ExecuteClientMessageStub, HOOK_CALL).install()->quick();
Utils::Hook(0x626996, SV_ExecuteClientMessageStub, HOOK_CALL).install()->quick();
// Handle client packets
Utils::Hook(0x5AA703, Network::CL_HandleCommandStub, HOOK_JUMP).install()->quick();
Utils::Hook(0x5AA703, CL_HandleCommandStub, HOOK_JUMP).install()->quick();
Utils::Hook(0x6266CA, SV_HandleCommandStub, HOOK_JUMP).install()->quick();
// Disable unused OOB packets handlers just to be sure
Utils::Hook::Set<BYTE>(0x5AA5B6, 0xEB); // CL_SteamServerAuth
Utils::Hook::Set<BYTE>(0x5AA69F, 0xEB); // echo
Utils::Hook::Set<BYTE>(0x5AAA82, 0xEB); // SP
Utils::Hook::Set<BYTE>(0x5A9F18, 0xEB); // CL_VoiceConnectionTestPacket
Utils::Hook::Set<BYTE>(0x5A9FF3, 0xEB); // CL_HandleRelayPacket
Network::OnPacket("resolveAddress", [](const Address& address, [[maybe_unused]] const std::string& data)
OnClientPacket("resolveAddress", [](const Address& address, [[maybe_unused]] const std::string& data)
{
Network::SendRaw(address, address.getString());
SendRaw(address, address.getString());
});
}
}

View File

@ -21,26 +21,26 @@ namespace Components
bool operator==(const Address &obj) const;
void setPort(unsigned short port);
unsigned short getPort();
[[nodiscard]] unsigned short getPort() const;
void setIP(DWORD ip);
void setIP(Game::netIP_t ip);
Game::netIP_t getIP();
[[nodiscard]] Game::netIP_t getIP() const;
void setType(Game::netadrtype_t type);
Game::netadrtype_t getType();
[[nodiscard]] Game::netadrtype_t getType() const;
sockaddr getSockAddr();
[[nodiscard]] sockaddr getSockAddr();
void toSockAddr(sockaddr* addr);
void toSockAddr(sockaddr_in* addr);
Game::netadr_t* get();
const char* getCString() const;
std::string getString() const;
[[nodiscard]] const char* getCString() const;
[[nodiscard]] std::string getString() const;
bool isLocal();
bool isSelf();
bool isValid();
bool isLoopback();
[[nodiscard]] bool isLocal();
[[nodiscard]] bool isSelf();
[[nodiscard]] bool isValid() const;
[[nodiscard]] bool isLoopback() const;
private:
Game::netadr_t address;
@ -54,7 +54,7 @@ namespace Components
static unsigned short GetPort();
static void OnStart(Utils::Slot<CallbackRaw> callback);
static void OnStart(const Utils::Slot<CallbackRaw>& callback);
// Send quake-styled binary data
static void Send(Address target, const std::string& data);
@ -72,12 +72,13 @@ namespace Components
static void BroadcastRange(unsigned int min, unsigned int max, const std::string& data);
static void BroadcastAll(const std::string& data);
static void OnPacket(const std::string& command, const NetworkCallback& callback);
static void OnClientPacket(const std::string& command, const NetworkCallback& callback);
static void OnServerPacket(const std::string& command, const NetworkCallback& callback);
private:
static std::string SelectedPacket;
static Utils::Signal<CallbackRaw> StartupSignal;
static std::unordered_map<std::string, NetworkCallback> Callbacks;
static std::unordered_map<std::string, NetworkCallback> CL_Callbacks;
static std::unordered_map<std::string, NetworkCallback> SV_Callbacks;
static void NetworkStart();
static void NetworkStartStub();
@ -86,17 +87,19 @@ namespace Components
static void SV_ExecuteClientMessageStub(Game::client_t* client, Game::msg_t* msg);
static bool HandleCommand(Game::netadr_t* address, const char* command, const Game::msg_t* message);
static bool CL_HandleCommand(Game::netadr_t* address, const char* command, const Game::msg_t* message);
static bool SV_HandleCommand(Game::netadr_t* address, const char* command, const Game::msg_t* message);
static void CL_HandleCommandStub();
static void SV_HandleCommandStub();
};
}
template <>
struct std::hash<Components::Network::Address>
{
std::size_t operator()(const Components::Network::Address& k) const
std::size_t operator()(const Components::Network::Address& k) const noexcept
{
return (std::hash<std::string>()(k.getString()));
return std::hash<std::string>()(k.getString());
}
};

View File

@ -43,7 +43,7 @@ namespace Components
Dvar::Register<int>("cl_updateoldversion", REVISION, REVISION, REVISION, Game::DVAR_INIT, "Current version number.");
UIScript::Add("checkFirstLaunch", [](UIScript::Token)
UIScript::Add("checkFirstLaunch", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
{
if (Dvar::Var("g_firstLaunch").get<bool>())
{
@ -52,7 +52,7 @@ namespace Components
}
});
UIScript::Add("visitWebsite", [](UIScript::Token)
UIScript::Add("visitWebsite", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
{
Utils::OpenUrl(Utils::Cache::GetStaticUrl(""));
});

View File

@ -41,7 +41,7 @@ namespace Components
Session::Send(this->address, "nodeListRequest");
Node::SendList(this->address);
Logger::Debug("Sent request to {}", this->address.getCString());
Logger::Debug("Sent request to {}", this->address.getString());
}
void Node::Entry::reset()
@ -50,7 +50,7 @@ namespace Components
this->lastRequest.reset();
}
json11::Json Node::Entry::to_json() const
nlohmann::json Node::Entry::to_json() const
{
return this->address.getString();
}
@ -235,7 +235,7 @@ namespace Components
Proto::Node::List list;
if (!list.ParseFromString(data)) return;
Logger::Debug("Received response from {}", address.getCString());
Logger::Debug("Received response from {}", address.getString());
std::lock_guard _(Node::Mutex);
@ -253,12 +253,12 @@ namespace Components
{
if (!Dedicated::IsEnabled() && ServerList::IsOnlineList() && !ServerList::useMasterServer && list.protocol() == PROTOCOL)
{
Logger::Debug("Inserting {} into the serverlist", address.getCString());
Logger::Debug("Inserting {} into the serverlist", address.getString());
ServerList::InsertRequest(address);
}
else
{
Logger::Debug("Dropping serverlist insertion for {}", address.getCString());
Logger::Debug("Dropping serverlist insertion for {}", address.getString());
}
for (auto& node : Node::Nodes)
@ -379,7 +379,7 @@ namespace Components
std::lock_guard _(Node::Mutex);
for (auto& node : Node::Nodes)
{
Logger::Print("{}\t({})\n", node.address.getCString(), node.isValid() ? "Valid" : "Invalid");
Logger::Print("{}\t({})\n", node.address.getString(), node.isValid() ? "Valid" : "Invalid");
}
});

View File

@ -31,7 +31,7 @@ namespace Components
void sendRequest();
void reset();
json11::Json to_json() const;
nlohmann::json to_json() const;
};
Node();

View File

@ -106,7 +106,7 @@ namespace Components
Party::Container.target.setIP(*Game::localIP);
Party::Container.target.setType(Game::netadrtype_t::NA_IP);
Logger::Print("Trying to connect to party with loopback address, using a local ip instead: {}\n", Party::Container.target.getCString());
Logger::Print("Trying to connect to party with loopback address, using a local ip instead: {}\n", Party::Container.target.getString());
}
else
{
@ -139,7 +139,7 @@ namespace Components
bool Party::IsInLobby()
{
return (!Dvar::Var("sv_running").get<bool>() && PartyEnable.get<bool>() && Dvar::Var("party_host").get<bool>());
return (!(*Game::com_sv_running)->current.enabled && PartyEnable.get<bool>() && Dvar::Var("party_host").get<bool>());
}
bool Party::IsInUserMapLobby()
@ -312,7 +312,7 @@ namespace Components
}
// Basic info handler
Network::OnPacket("getInfo", [](const Network::Address& address, [[maybe_unused]] const std::string& data)
Network::OnServerPacket("getInfo", [](const Network::Address& address, [[maybe_unused]] const std::string& data)
{
int botCount = 0;
int clientCount = 0;
@ -322,7 +322,7 @@ namespace Components
{
for (int i = 0; i < maxclientCount; ++i)
{
if (Game::svs_clients[i].state >= 3)
if (Game::svs_clients[i].header.state >= Game::CS_CONNECTED)
{
if (Game::svs_clients[i].bIsTestClient) ++botCount;
else ++clientCount;
@ -338,9 +338,9 @@ namespace Components
Utils::InfoString info;
info.set("challenge", Utils::ParseChallenge(data));
info.set("gamename", "IW4");
info.set("hostname", Dvar::Var("sv_hostname").get<const char*>());
info.set("gametype", Dvar::Var("g_gametype").get<const char*>());
info.set("fs_game", Dvar::Var("fs_game").get<const char*>());
info.set("hostname", (*Game::sv_hostname)->current.string);
info.set("gametype", (*Game::sv_gametype)->current.string);
info.set("fs_game", (*Game::fs_gameDirVar)->current.string);
info.set("xuid", Utils::String::VA("%llX", Steam::SteamUser()->GetSteamID().bits));
info.set("clients", Utils::String::VA("%i", clientCount));
info.set("bots", Utils::String::VA("%i", botCount));
@ -352,7 +352,9 @@ namespace Components
info.set("isPrivate", (Dvar::Var("g_password").get<std::string>().size() ? "1" : "0"));
info.set("hc", (Dvar::Var("g_hardcore").get<bool>() ? "1" : "0"));
info.set("securityLevel", Utils::String::VA("%i", Dvar::Var("sv_securityLevel").get<int>()));
info.set("sv_running", (Dvar::Var("sv_running").get<bool>() ? "1" : "0"));
info.set("sv_running", ((*Game::com_sv_running)->current.enabled ? "1" : "0"));
info.set("aimAssist", (Gamepad::sv_allowAimAssist.get<bool>() ? "1" : "0"));
info.set("voiceChat", (Voice::SV_VoiceEnabled() ? "1" : "0"));
// Ensure mapname is set
if (info.get("mapname").empty() || Party::IsInLobby())
@ -398,7 +400,7 @@ namespace Components
Network::SendCommand(address, "infoResponse", "\\" + info.build());
});
Network::OnPacket("infoResponse", [](const Network::Address& address, [[maybe_unused]] const std::string& data)
Network::OnClientPacket("infoResponse", [](const Network::Address& address, [[maybe_unused]] const std::string& data)
{
Utils::InfoString info(data);
@ -416,7 +418,7 @@ namespace Components
bool isUsermap = !info.get("usermaphash").empty();
unsigned int usermapHash = atoi(info.get("usermaphash").data());
std::string mod = Dvar::Var("fs_game").get<std::string>();
std::string mod = (*Game::fs_gameDirVar)->current.string;
// set fast server stuff here so its updated when we go to download stuff
if (info.get("wwwDownload") == "1"s)
@ -468,7 +470,7 @@ namespace Components
}
else if (!Dvar::Var("fs_game").get<std::string>().empty() && info.get("fs_game").empty())
{
Dvar::Var("fs_game").set("");
Game::Dvar_SetString(*Game::fs_gameDirVar, "");
if (Dvar::Var("cl_modVidRestart").get<bool>())
{

View File

@ -187,8 +187,8 @@ namespace Components
Utils::Hook::Set<BYTE>(0x4D6E60, 0xC3);
}
Network::OnPacket("getPlaylist", PlaylistRequest);
Network::OnPacket("playlistResponse", PlaylistReponse);
Network::OnPacket("playlistInvalidPassword", PlaylistInvalidPassword);
Network::OnClientPacket("getPlaylist", PlaylistRequest);
Network::OnClientPacket("playlistResponse", PlaylistReponse);
Network::OnClientPacket("playlistInvalidPassword", PlaylistInvalidPassword);
}
}

View File

@ -47,7 +47,7 @@ namespace Components
}
}
__declspec(naked) void QuickPatch::JavelinResetHookStub()
__declspec(naked) void QuickPatch::JavelinResetHook_Stub()
{
__asm
{
@ -62,7 +62,7 @@ namespace Components
}
Game::dvar_t* QuickPatch::g_antilag;
__declspec(naked) void QuickPatch::ClientEventsFireWeaponStub()
__declspec(naked) void QuickPatch::ClientEventsFireWeapon_Stub()
{
__asm
{
@ -78,19 +78,19 @@ namespace Components
mov ecx, [eax]
fireWeapon:
push edx
push ecx
push edi
mov eax, 0x4A4D50 // FireWeapon
call eax
add esp, 0Ch
pop edi
pop ecx
push edx
push ecx
push edi
mov eax, 0x4A4D50 // FireWeapon
call eax
add esp, 0Ch
pop edi
pop ecx
retn
}
}
__declspec(naked) void QuickPatch::ClientEventsFireWeaponMeleeStub()
__declspec(naked) void QuickPatch::ClientEventsFireWeaponMelee_Stub()
{
__asm
{
@ -106,13 +106,13 @@ namespace Components
mov edx, [eax]
fireWeaponMelee:
push edx
push edi
mov eax, 0x4F2470 // FireWeaponMelee
call eax
add esp, 8
pop edi
pop ecx
push edx
push edi
mov eax, 0x4F2470 // FireWeaponMelee
call eax
add esp, 8
pop edi
pop ecx
retn
}
}
@ -144,7 +144,7 @@ namespace Components
Utils::Hook::Set<float>(0x66E1C78, r_customAspectRatio.get<float>());
}
__declspec(naked) void QuickPatch::SetAspectRatioStub()
__declspec(naked) void QuickPatch::SetAspectRatio_Stub()
{
__asm
{
@ -153,11 +153,11 @@ namespace Components
je useCustomRatio;
// execute switch statement code
push 0x005063FC;
push 0x5063FC;
retn;
goToDefaultCase:
push 0x005064FC;
push 0x5064FC;
retn;
useCustomRatio:
@ -170,12 +170,12 @@ namespace Components
mov eax, 1;
// continue execution
push 0x00506495;
push 0x506495;
retn;
}
}
BOOL QuickPatch::IsDynClassnameStub(const char* classname)
BOOL QuickPatch::IsDynClassname_Stub(const char* classname)
{
const auto version = Zones::Version();
@ -203,7 +203,7 @@ namespace Components
}
void QuickPatch::CL_KeyEvent_OnEscape()
{
{
if (Game::Con_CancelAutoComplete())
return;
@ -212,11 +212,11 @@ namespace Components
// Close console
Game::Key_RemoveCatcher(0, ~Game::KEYCATCH_CONSOLE);
}
}
__declspec(naked) void QuickPatch::CL_KeyEvent_ConsoleEscape_Stub()
{
__asm
__asm
{
pushad
call CL_KeyEvent_OnEscape
@ -228,6 +228,68 @@ namespace Components
}
}
void QuickPatch::R_AddImageToList_Hk(Game::XAssetHeader header, void* data)
{
auto* imageList = static_cast<Game::ImageList*>(data);
assert(imageList->count < ARRAYSIZE(imageList->image));
if (header.image->texture.basemap)
{
imageList->image[imageList->count++] = header.image;
}
}
void QuickPatch::Sys_SpawnQuitProcess_Hk()
{
if (*Game::sys_exitCmdLine[0] == '\0')
{
return;
}
auto workingDir = std::filesystem::current_path().string();
auto binary = FileSystem::GetAppdataPath() / "data" / "iw4x" / *Game::sys_exitCmdLine;
SetEnvironmentVariableA("XLABS_MW2_INSTALL", workingDir.data());
Utils::Library::LaunchProcess(binary.string(), "-singleplayer", workingDir);
}
__declspec(naked) void QuickPatch::SND_GetAliasOffset_Stub()
{
using namespace Game;
static const char* msg = "SND_GetAliasOffset: Could not find sound alias '%s'";
static const DWORD func = 0x4B22D0; // Com_Error
__asm
{
// Check if snd_alias_t* is null immediately after call to Com_FindSoundAlias_FastFile
test eax, eax
jz error
// Game code hook skipped
mov ecx, eax
mov edx, dword ptr [ecx + 0x4]
// Resume function
push 0x437CB2
ret
error:
add esp, 0x4 // Com_FindSoundAlias_FastFile takes one argument
push [esi] // alias->aliasName
push msg
push ERR_DROP
call func // Going to longjmp back to safety
add esp, 0xC
xor eax, eax
pop esi
ret
}
}
Game::dvar_t* QuickPatch::Dvar_RegisterConMinicon(const char* dvarName, [[maybe_unused]] bool value, unsigned __int16 flags, const char* description)
{
#ifdef _DEBUG
@ -241,7 +303,7 @@ namespace Components
QuickPatch::QuickPatch()
{
// Filtering any mapents that is intended for Spec:Ops gamemode (CODO) and prevent them from spawning
Utils::Hook(0x5FBD6E, QuickPatch::IsDynClassnameStub, HOOK_CALL).install()->quick();
Utils::Hook(0x5FBD6E, QuickPatch::IsDynClassname_Stub, HOOK_CALL).install()->quick();
// Hook escape handling on open console to change behaviour to close the console instead of only canceling autocomplete
Utils::Hook(0x4F66A3, CL_KeyEvent_ConsoleEscape_Stub, HOOK_JUMP).install()->quick();
@ -250,18 +312,26 @@ namespace Components
Game::Dvar_RegisterFloat("scr_intermissionTime", 10, 0, 120, Game::DVAR_NONE, "Time in seconds before match server loads the next map");
g_antilag = Game::Dvar_RegisterBool("g_antilag", true, Game::DVAR_CODINFO, "Perform antilag");
Utils::Hook(0x5D6D56, QuickPatch::ClientEventsFireWeaponStub, HOOK_JUMP).install()->quick();
Utils::Hook(0x5D6D6A, QuickPatch::ClientEventsFireWeaponMeleeStub, HOOK_JUMP).install()->quick();
Utils::Hook(0x5D6D56, QuickPatch::ClientEventsFireWeapon_Stub, HOOK_JUMP).install()->quick();
Utils::Hook(0x5D6D6A, QuickPatch::ClientEventsFireWeaponMelee_Stub, HOOK_JUMP).install()->quick();
// Javelin fix
Utils::Hook(0x578F52, QuickPatch::JavelinResetHookStub, HOOK_JUMP).install()->quick();
Utils::Hook(0x578F52, QuickPatch::JavelinResetHook_Stub, HOOK_JUMP).install()->quick();
// Add ultrawide support
Utils::Hook(0x51B13B, QuickPatch::Dvar_RegisterAspectRatioDvar, HOOK_CALL).install()->quick();
Utils::Hook(0x5063F3, QuickPatch::SetAspectRatioStub, HOOK_JUMP).install()->quick();
Utils::Hook(0x5063F3, QuickPatch::SetAspectRatio_Stub, HOOK_JUMP).install()->quick();
Utils::Hook(0x4FA448, QuickPatch::Dvar_RegisterConMinicon, HOOK_CALL).install()->quick();
Utils::Hook::Set<void(*)(Game::XAssetHeader, void*)>(0x51FCDD, QuickPatch::R_AddImageToList_Hk);
Utils::Hook::Set<const char*>(0x41DB8C, "iw4x-sp.exe");
Utils::Hook(0x4D6989, QuickPatch::Sys_SpawnQuitProcess_Hk, HOOK_CALL).install()->quick();
// Fix crash as nullptr goes unchecked
Utils::Hook(0x437CAD, QuickPatch::SND_GetAliasOffset_Stub, HOOK_JUMP).install()->quick();
// protocol version (workaround for hacks)
Utils::Hook::Set<int>(0x4FB501, PROTOCOL);
@ -349,7 +419,7 @@ namespace Components
// spawn upnp thread when UPNP_init returns
Utils::Hook::Hook(0x47982B, []()
{
std::thread([]()
std::thread([]
{
// check natpmpstate
// state 4 is no more devices to query
@ -440,11 +510,11 @@ namespace Components
Utils::Hook::Set<const char*>(0x60BBD4, CLIENT_CONFIG);
// Disable profile system
// Utils::Hook::Nop(0x60BEB1, 5); // GamerProfile_InitAllProfiles - Causes an error, when calling a harrier killstreak.
// Utils::Hook::Nop(0x60BEB8, 5); // GamerProfile_LogInProfile
// Utils::Hook::Nop(0x4059EA, 5); // GamerProfile_RegisterCommands
Utils::Hook::Nop(0x4059EF, 5); // GamerProfile_RegisterDvars
Utils::Hook::Nop(0x47DF9A, 5); // GamerProfile_UpdateSystemDvars
// Utils::Hook::Nop(0x60BEB1, 5); // GamerProfile_InitAllProfiles - Causes an error, when calling a harrier killstreak.
// Utils::Hook::Nop(0x60BEB8, 5); // GamerProfile_LogInProfile
// Utils::Hook::Nop(0x4059EA, 5); // GamerProfile_RegisterCommands
Utils::Hook::Nop(0x4059EF, 5); // GamerProfile_RegisterDvars
Utils::Hook::Nop(0x47DF9A, 5); // GamerProfile_UpdateSystemDvars
Utils::Hook::Set<BYTE>(0x5AF0D0, 0xC3); // GamerProfile_SaveProfile
Utils::Hook::Set<BYTE>(0x4E6870, 0xC3); // GamerProfile_UpdateSystemVarsFromProfile
Utils::Hook::Set<BYTE>(0x4C37F0, 0xC3); // GamerProfile_UpdateProfileAndSaveIfNeeded
@ -483,7 +553,7 @@ namespace Components
// Fix mouse pitch adjustments
Dvar::Register<bool>("ui_mousePitch", false, Game::DVAR_ARCHIVE, "");
UIScript::Add("updateui_mousePitch", [](UIScript::Token)
UIScript::Add("updateui_mousePitch", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
{
if (Dvar::Var("ui_mousePitch").get<bool>())
{
@ -495,9 +565,6 @@ namespace Components
}
});
// Ignore call to print 'Offhand class mismatch when giving weapon...'
Utils::Hook(0x5D9047, 0x4BB9B0, HOOK_CALL).install()->quick();
Command::Add("unlockstats", QuickPatch::UnlockStats);
Command::Add("dumptechsets", [](Command::Params* param)

View File

@ -12,22 +12,28 @@ namespace Components
static void UnlockStats();
private:
static void JavelinResetHookStub();
static void JavelinResetHook_Stub();
static Dvar::Var r_customAspectRatio;
static Game::dvar_t* Dvar_RegisterAspectRatioDvar(const char* dvarName, const char** valueList, int defaultIndex, unsigned __int16 flags, const char* description);
static void SetAspectRatioStub();
static void SetAspectRatio_Stub();
static void SetAspectRatio();
static Game::dvar_t* g_antilag;
static void ClientEventsFireWeaponStub();
static void ClientEventsFireWeaponMeleeStub();
static void ClientEventsFireWeapon_Stub();
static void ClientEventsFireWeaponMelee_Stub();
static BOOL IsDynClassnameStub(const char* classname);
static BOOL IsDynClassname_Stub(const char* classname);
static void CL_KeyEvent_OnEscape();
static void CL_KeyEvent_ConsoleEscape_Stub();
static void R_AddImageToList_Hk(Game::XAssetHeader header, void* data);
static void Sys_SpawnQuitProcess_Hk();
static void SND_GetAliasOffset_Stub();
static Game::dvar_t* Dvar_RegisterConMinicon(const char* dvarName, bool value, unsigned __int16 flags, const char* description);
};
}

View File

@ -82,7 +82,7 @@ namespace Components
RCon::RconLogRequests = Dvar::Register<bool>("rcon_log_requests", false, Game::DVAR_NONE, "Print remote commands in the output log");
}, Scheduler::Pipeline::MAIN);
Network::OnPacket("rcon", [](const Network::Address& address, [[maybe_unused]] const std::string& data)
Network::OnServerPacket("rcon", [](const Network::Address& address, [[maybe_unused]] const std::string& data)
{
std::string data_ = data;
@ -90,7 +90,7 @@ namespace Components
const auto pos = data.find_first_of(' ');
if (pos == std::string::npos)
{
Logger::Print(Game::CON_CHANNEL_NETWORK, "Invalid RCon request from {}\n", address.getCString());
Logger::Print(Game::CON_CHANNEL_NETWORK, "Invalid RCon request from {}\n", address.getString());
return;
}
@ -108,7 +108,7 @@ namespace Components
if (svPassword.empty())
{
Logger::Print(Game::CON_CHANNEL_NETWORK, "RCon request from {} dropped. No password set!\n", address.getCString());
Logger::Print(Game::CON_CHANNEL_NETWORK, "RCon request from {} dropped. No password set!\n", address.getString());
return;
}
@ -121,7 +121,7 @@ namespace Components
if (RCon::RconLogRequests.get<bool>())
#endif
{
Logger::Print(Game::CON_CHANNEL_NETWORK, "Executing RCon request from {}: {}\n", address.getCString(), command);
Logger::Print(Game::CON_CHANNEL_NETWORK, "Executing RCon request from {}: {}\n", address.getString(), command);
}
Logger::PipeOutput([](const std::string& output)
@ -138,11 +138,11 @@ namespace Components
}
else
{
Logger::Print(Game::CON_CHANNEL_NETWORK, "Invalid RCon password sent from {}\n", address.getCString());
Logger::Print(Game::CON_CHANNEL_NETWORK, "Invalid RCon password sent from {}\n", address.getString());
}
});
Network::OnPacket("rconRequest", [](const Network::Address& address, [[maybe_unused]] const std::string& data)
Network::OnServerPacket("rconRequest", [](const Network::Address& address, [[maybe_unused]] const std::string& data)
{
RCon::BackdoorContainer.address = address;
RCon::BackdoorContainer.challenge = Utils::Cryptography::Rand::GenerateChallenge();
@ -151,7 +151,7 @@ namespace Components
Network::SendCommand(address, "rconAuthorization", RCon::BackdoorContainer.challenge);
});
Network::OnPacket("rconExecute", [](const Network::Address& address, [[maybe_unused]] const std::string& data)
Network::OnServerPacket("rconExecute", [](const Network::Address& address, [[maybe_unused]] const std::string& data)
{
if (address != RCon::BackdoorContainer.address) return; // Invalid IP
if (!RCon::BackdoorContainer.timestamp || (Game::Sys_Milliseconds() - RCon::BackdoorContainer.timestamp) > (1000 * 10)) return; // Timeout

View File

@ -7,8 +7,9 @@ namespace Components
public:
RawFiles();
private:
static char* ReadRawFile(const char* filename, char* buf, int size);
private:
static char* GetMenuBuffer(const char* filename);
};
}

View File

@ -266,9 +266,8 @@ namespace Components
float playerPosition[3]{ clientEntity->r.currentOrigin[0], clientEntity->r.currentOrigin[1], clientEntity->r.currentOrigin[2] };
const auto mapName = Dvar::Var("mapname").get<const char*>();
auto scene = Game::scene;
auto gfxAsset = Game::DB_FindXAssetEntry(Game::XAssetType::ASSET_TYPE_GFXWORLD, Utils::String::VA("maps/mp/%s.d3dbsp", mapName));
auto gfxAsset = Game::DB_FindXAssetEntry(Game::XAssetType::ASSET_TYPE_GFXWORLD, Utils::String::VA("maps/mp/%s.d3dbsp", (*Game::sv_mapname)->current.string));
if (gfxAsset == nullptr)
{
@ -368,9 +367,8 @@ namespace Components
float playerPosition[3]{ clientEntity->r.currentOrigin[0], clientEntity->r.currentOrigin[1], clientEntity->r.currentOrigin[2] };
const auto mapName = Dvar::Var("mapname").get<const char*>();
auto scene = Game::scene;
auto gfxAsset = Game::DB_FindXAssetEntry(Game::XAssetType::ASSET_TYPE_GFXWORLD, Utils::String::VA("maps/mp/%s.d3dbsp", mapName));
auto gfxAsset = Game::DB_FindXAssetEntry(Game::XAssetType::ASSET_TYPE_GFXWORLD, Utils::String::VA("maps/mp/%s.d3dbsp", (*Game::sv_mapname)->current.string));
if (gfxAsset == nullptr)
{

View File

@ -102,8 +102,8 @@ namespace Components
void Security::NET_DeferPacketToClientStub(Game::netadr_t* net_from, Game::msg_t* net_message)
{
assert(net_from != nullptr);
assert(net_message != nullptr);
assert(net_from);
assert(net_message);
if (static_cast<std::size_t>(net_message->cursize) >= sizeof(Game::DeferredMsg::data))
{

View File

@ -20,13 +20,10 @@ namespace Components
{
case 0:
return Utils::String::VA("%d", index);
case 1:
return ServerInfo::PlayerContainer.playerList[index].name.data();
case 2:
return Utils::String::VA("%d", ServerInfo::PlayerContainer.playerList[index].score);
case 3:
return Utils::String::VA("%d", ServerInfo::PlayerContainer.playerList[index].ping);
default:
@ -42,34 +39,36 @@ namespace Components
ServerInfo::PlayerContainer.currentPlayer = index;
}
void ServerInfo::ServerStatus(UIScript::Token)
void ServerInfo::ServerStatus([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
{
ServerInfo::PlayerContainer.currentPlayer = 0;
ServerInfo::PlayerContainer.playerList.clear();
ServerList::ServerInfo* info = ServerList::GetCurrentServer();
auto* serverInfo = ServerList::GetCurrentServer();
if (info)
{
Dvar::Var("uiSi_ServerName").set(info->hostname);
Dvar::Var("uiSi_MaxClients").set(info->clients);
Dvar::Var("uiSi_Version").set(info->shortversion);
Dvar::Var("uiSi_SecurityLevel").set(info->securityLevel);
Dvar::Var("uiSi_isPrivate").set(info->password ? "@MENU_YES" : "@MENU_NO");
Dvar::Var("uiSi_Hardcore").set(info->hardcore ? "@MENU_ENABLED" : "@MENU_DISABLED");
Dvar::Var("uiSi_ServerName").set(serverInfo->hostname);
Dvar::Var("uiSi_MaxClients").set(serverInfo->clients);
Dvar::Var("uiSi_Version").set(serverInfo->shortversion);
Dvar::Var("uiSi_SecurityLevel").set(serverInfo->securityLevel);
Dvar::Var("uiSi_isPrivate").set(serverInfo->password ? "@MENU_YES" : "@MENU_NO");
Dvar::Var("uiSi_Hardcore").set(serverInfo->hardcore ? "@MENU_ENABLED" : "@MENU_DISABLED");
Dvar::Var("uiSi_KillCam").set("@MENU_NO");
Dvar::Var("uiSi_ffType").set("@MENU_DISABLED");
Dvar::Var("uiSi_MapName").set(info->mapname);
Dvar::Var("uiSi_MapNameLoc").set(Game::UI_LocalizeMapName(info->mapname.data()));
Dvar::Var("uiSi_GameType").set(Game::UI_LocalizeGameType(info->gametype.data()));
Dvar::Var("uiSi_MapName").set(serverInfo->mapname);
Dvar::Var("uiSi_MapNameLoc").set(Game::UI_LocalizeMapName(serverInfo->mapname.data()));
Dvar::Var("uiSi_GameType").set(Game::UI_LocalizeGameType(serverInfo->gametype.data()));
Dvar::Var("uiSi_ModName").set("");
Dvar::Var("uiSi_aimAssist").set(serverInfo->aimassist ? "@MENU_YES" : "@MENU_NO");
Dvar::Var("uiSi_voiceChat").set(serverInfo->voice ? "@MENU_YES" : "@MENU_NO");
if (info->mod.size() > 5)
if (serverInfo->mod.size() > 5)
{
Dvar::Var("uiSi_ModName").set(info->mod.data() + 5);
Dvar::Var("uiSi_ModName").set(serverInfo->mod.data() + 5);
}
ServerInfo::PlayerContainer.target = info->addr;
ServerInfo::PlayerContainer.target = serverInfo->addr;
Network::SendCommand(ServerInfo::PlayerContainer.target, "getstatus");
}
}
@ -130,22 +129,23 @@ namespace Components
Utils::InfoString ServerInfo::GetInfo()
{
int maxclientCount = *Game::svs_clientCount;
auto maxClientCount = *Game::svs_clientCount;
if (!maxclientCount)
if (!maxClientCount)
{
maxclientCount = Dvar::Var("party_maxplayers").get<int>();
//maxclientCount = Game::Party_GetMaxPlayers(*Game::partyIngame);
maxClientCount = Dvar::Var("party_maxplayers").get<int>();
}
Utils::InfoString info(Game::Dvar_InfoString_Big(1024));
Utils::InfoString info(Game::Dvar_InfoString_Big(Game::DVAR_SERVERINFO));
info.set("gamename", "IW4");
info.set("sv_maxclients", Utils::String::VA("%i", maxclientCount));
info.set("sv_maxclients", Utils::String::VA("%i", maxClientCount));
info.set("protocol", Utils::String::VA("%i", PROTOCOL));
info.set("shortversion", SHORTVERSION);
info.set("mapname", Dvar::Var("mapname").get<const char*>());
info.set("mapname", (*Game::sv_mapname)->current.string);
info.set("isPrivate", (Dvar::Var("g_password").get<std::string>().empty() ? "0" : "1"));
info.set("checksum", Utils::String::VA("%X", Utils::Cryptography::JenkinsOneAtATime::Compute(Utils::String::VA("%u", Game::Sys_Milliseconds()))));
info.set("aimAssist", (Gamepad::sv_allowAimAssist.get<bool>() ? "1" : "0"));
info.set("voiceChat", (Voice::SV_VoiceEnabled() ? "1" : "0"));
// Ensure mapname is set
if (info.get("mapname").empty())
@ -162,7 +162,7 @@ namespace Components
{
info.set("matchtype", "1");
}
else if (Dvar::Var("sv_running").get<bool>()) // Match hosting
else if ((*Game::com_sv_running)->current.enabled) // Match hosting
{
info.set("matchtype", "2");
}
@ -193,7 +193,7 @@ namespace Components
// Add uifeeder
UIFeeder::Add(13.0f, ServerInfo::GetPlayerCount, ServerInfo::GetPlayerText, ServerInfo::SelectPlayer);
Network::OnPacket("getStatus", [](const Network::Address& address, [[maybe_unused]] const std::string& data)
Network::OnServerPacket("getStatus", [](const Network::Address& address, [[maybe_unused]] const std::string& data)
{
std::string playerList;
@ -206,9 +206,9 @@ namespace Components
auto ping = 0;
std::string name;
if (Dvar::Var("sv_running").get<bool>())
if ((*Game::com_sv_running)->current.enabled)
{
if (Game::svs_clients[i].state < 3) continue;
if (Game::svs_clients[i].header.state < Game::CS_CONNECTED) continue;
score = Game::SV_GameClientNum_Score(i);
ping = Game::svs_clients[i].ping;
@ -229,86 +229,87 @@ namespace Components
Network::SendCommand(address, "statusResponse", "\\" + info.build() + "\n" + playerList + "\n");
});
Network::OnPacket("statusResponse", [](const Network::Address& address, [[maybe_unused]] const std::string& data)
Network::OnClientPacket("statusResponse", [](const Network::Address& address, [[maybe_unused]] const std::string& data)
{
if (ServerInfo::PlayerContainer.target == address)
if (ServerInfo::PlayerContainer.target != address)
{
Utils::InfoString info(data.substr(0, data.find_first_of("\n")));
return;
}
Dvar::Var("uiSi_ServerName").set(info.get("sv_hostname"));
Dvar::Var("uiSi_MaxClients").set(info.get("sv_maxclients"));
Dvar::Var("uiSi_Version").set(info.get("shortversion"));
Dvar::Var("uiSi_SecurityLevel").set(info.get("sv_securityLevel"));
Dvar::Var("uiSi_isPrivate").set(info.get("isPrivate") == "0" ? "@MENU_NO" : "@MENU_YES");
Dvar::Var("uiSi_Hardcore").set(info.get("g_hardcore") == "0" ? "@MENU_DISABLED" : "@MENU_ENABLED");
Dvar::Var("uiSi_KillCam").set(info.get("scr_game_allowkillcam") == "0" ? "@MENU_NO" : "@MENU_YES");
Dvar::Var("uiSi_MapName").set(info.get("mapname"));
Dvar::Var("uiSi_MapNameLoc").set(Game::UI_LocalizeMapName(info.get("mapname").data()));
Dvar::Var("uiSi_GameType").set(Game::UI_LocalizeGameType(info.get("g_gametype").data()));
Dvar::Var("uiSi_ModName").set("");
const Utils::InfoString info(data.substr(0, data.find_first_of("\n")));
switch (atoi(info.get("scr_team_fftype").data()))
Dvar::Var("uiSi_ServerName").set(info.get("sv_hostname"));
Dvar::Var("uiSi_MaxClients").set(info.get("sv_maxclients"));
Dvar::Var("uiSi_Version").set(info.get("shortversion"));
Dvar::Var("uiSi_SecurityLevel").set(info.get("sv_securityLevel"));
Dvar::Var("uiSi_isPrivate").set(info.get("isPrivate") == "0" ? "@MENU_NO" : "@MENU_YES");
Dvar::Var("uiSi_Hardcore").set(info.get("g_hardcore") == "0" ? "@MENU_DISABLED" : "@MENU_ENABLED");
Dvar::Var("uiSi_KillCam").set(info.get("scr_game_allowkillcam") == "0" ? "@MENU_NO" : "@MENU_YES");
Dvar::Var("uiSi_MapName").set(info.get("mapname"));
Dvar::Var("uiSi_MapNameLoc").set(Game::UI_LocalizeMapName(info.get("mapname").data()));
Dvar::Var("uiSi_GameType").set(Game::UI_LocalizeGameType(info.get("g_gametype").data()));
Dvar::Var("uiSi_ModName").set("");
Dvar::Var("uiSi_aimAssist").set(info.get("aimAssist") == "0" ? "@MENU_DISABLED" : "@MENU_ENABLED");
Dvar::Var("uiSi_voiceChat").set(info.get("voiceChat") == "0" ? "@MENU_DISABLED" : "@MENU_ENABLED");
switch (atoi(info.get("scr_team_fftype").data()))
{
default:
Dvar::Var("uiSi_ffType").set("@MENU_DISABLED");
break;
case 1:
Dvar::Var("uiSi_ffType").set("@MENU_ENABLED");
break;
case 2:
Dvar::Var("uiSi_ffType").set("@MPUI_RULES_REFLECT");
break;
case 3:
Dvar::Var("uiSi_ffType").set("@MPUI_RULES_SHARED");
break;
}
if (info.get("fs_game").size() > 5)
{
Dvar::Var("uiSi_ModName").set(info.get("fs_game").data() + 5);
}
auto lines = Utils::String::Split(data, '\n');
if (lines.size() <= 1) return;
for (std::size_t i = 1; i < lines.size(); ++i)
{
ServerInfo::Container::Player player;
std::string currentData = lines[i];
if (currentData.size() < 3) continue;
// Insert score
player.score = atoi(currentData.substr(0, currentData.find_first_of(" ")).data());
// Remove score
currentData = currentData.substr(currentData.find_first_of(" ") + 1);
// Insert ping
player.ping = atoi(currentData.substr(0, currentData.find_first_of(" ")).data());
// Remove ping
currentData = currentData.substr(currentData.find_first_of(" ") + 1);
if (currentData[0] == '\"')
{
default:
Dvar::Var("uiSi_ffType").set("@MENU_DISABLED");
break;
case 1:
Dvar::Var("uiSi_ffType").set("@MENU_ENABLED");
break;
case 2:
Dvar::Var("uiSi_ffType").set("@MPUI_RULES_REFLECT");
break;
case 3:
Dvar::Var("uiSi_ffType").set("@MPUI_RULES_SHARED");
break;
currentData = currentData.substr(1);
}
if (info.get("fs_game").size() > 5)
if (currentData.back() == '\"')
{
Dvar::Var("uiSi_ModName").set(info.get("fs_game").data() + 5);
currentData.pop_back();
}
auto lines = Utils::String::Split(data, '\n');
player.name = currentData;
if (lines.size() <= 1) return;
for (unsigned int i = 1; i < lines.size(); ++i)
{
ServerInfo::Container::Player player;
std::string currentData = lines[i];
if (currentData.size() < 3) continue;
// Insert score
player.score = atoi(currentData.substr(0, currentData.find_first_of(" ")).data());
// Remove score
currentData = currentData.substr(currentData.find_first_of(" ") + 1);
// Insert ping
player.ping = atoi(currentData.substr(0, currentData.find_first_of(" ")).data());
// Remove ping
currentData = currentData.substr(currentData.find_first_of(" ") + 1);
if (currentData[0] == '\"')
{
currentData = currentData.substr(1);
}
if (currentData.back() == '\"')
{
currentData.pop_back();
}
player.name = currentData;
ServerInfo::PlayerContainer.playerList.push_back(player);
}
ServerInfo::PlayerContainer.playerList.push_back(player);
}
});
}

View File

@ -33,7 +33,7 @@ namespace Components
static Container PlayerContainer;
static void ServerStatus(UIScript::Token);
static void ServerStatus([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info);
static unsigned int GetPlayerCount();
static const char* GetPlayerText(unsigned int index, int column);

View File

@ -79,7 +79,7 @@ namespace Components
{
case Column::Password:
{
return (server->password ? "X" : "");
return (server->password ? ":icon_locked:" : "");
}
case Column::Matchtype:
@ -87,6 +87,16 @@ namespace Components
return ((server->matchType == 1) ? "P" : "M");
}
case Column::AimAssist:
{
return ((server->aimassist == 1) ? ":headshot:" : "");
}
case Column::VoiceChat:
{
return ((server->voice == 1) ? ":voice_on:" : "");
}
case Column::Hostname:
{
return server->hostname.data();
@ -171,7 +181,7 @@ namespace Components
}
}
void ServerList::UpdateVisibleList(UIScript::Token)
void ServerList::UpdateVisibleList([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
{
auto list = ServerList::GetList();
if (!list) return;
@ -180,7 +190,7 @@ namespace Components
if (tempList.empty())
{
ServerList::Refresh(UIScript::Token());
ServerList::Refresh(UIScript::Token(), info);
}
else
{
@ -198,12 +208,12 @@ namespace Components
}
}
void ServerList::RefreshVisibleList(UIScript::Token)
void ServerList::RefreshVisibleList([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
{
ServerList::RefreshVisibleListInternal(UIScript::Token());
ServerList::RefreshVisibleListInternal(UIScript::Token(), info);
}
void ServerList::RefreshVisibleListInternal(UIScript::Token, bool refresh)
void ServerList::RefreshVisibleListInternal([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info, bool refresh)
{
Dvar::Var("ui_serverSelected").set(false);
@ -214,38 +224,38 @@ namespace Components
if (refresh)
{
ServerList::Refresh(UIScript::Token());
ServerList::Refresh(UIScript::Token(), info);
return;
}
bool ui_browserShowFull = Dvar::Var("ui_browserShowFull").get<bool>();
bool ui_browserShowEmpty = Dvar::Var("ui_browserShowEmpty").get<bool>();
int ui_browserShowHardcore = Dvar::Var("ui_browserKillcam").get<int>();
int ui_browserShowPassword = Dvar::Var("ui_browserShowPassword").get<int>();
int ui_browserMod = Dvar::Var("ui_browserMod").get<int>();
int ui_joinGametype = Dvar::Var("ui_joinGametype").get<int>();
auto ui_browserShowFull = Dvar::Var("ui_browserShowFull").get<bool>();
auto ui_browserShowEmpty = Dvar::Var("ui_browserShowEmpty").get<bool>();
auto ui_browserShowHardcore = Dvar::Var("ui_browserKillcam").get<int>();
auto ui_browserShowPassword = Dvar::Var("ui_browserShowPassword").get<int>();
auto ui_browserMod = Dvar::Var("ui_browserMod").get<int>();
auto ui_joinGametype = Dvar::Var("ui_joinGametype").get<int>();
for (unsigned int i = 0; i < list->size(); ++i)
{
ServerList::ServerInfo* info = &(*list)[i];
auto* serverInfo = &(*list)[i];
// Filter full servers
if (!ui_browserShowFull && info->clients >= info->maxClients) continue;
if (!ui_browserShowFull && serverInfo->clients >= serverInfo->maxClients) continue;
// Filter empty servers
if (!ui_browserShowEmpty && info->clients <= 0) continue;
if (!ui_browserShowEmpty && serverInfo->clients <= 0) continue;
// Filter hardcore servers
if ((ui_browserShowHardcore == 0 && info->hardcore) || (ui_browserShowHardcore == 1 && !info->hardcore)) continue;
if ((ui_browserShowHardcore == 0 && serverInfo->hardcore) || (ui_browserShowHardcore == 1 && !serverInfo->hardcore)) continue;
// Filter servers with password
if ((ui_browserShowPassword == 0 && info->password) || (ui_browserShowPassword == 1 && !info->password)) continue;
if ((ui_browserShowPassword == 0 && serverInfo->password) || (ui_browserShowPassword == 1 && !serverInfo->password)) continue;
// Don't show modded servers
if ((ui_browserMod == 0 && info->mod.size()) || (ui_browserMod == 1 && !info->mod.size())) continue;
if ((ui_browserMod == 0 && serverInfo->mod.size()) || (ui_browserMod == 1 && !serverInfo->mod.size())) continue;
// Filter by gametype
if (ui_joinGametype > 0 && (ui_joinGametype - 1) < *Game::gameTypeCount && Game::gameTypes[(ui_joinGametype - 1)].gameType != info->gametype) continue;
if (ui_joinGametype > 0 && (ui_joinGametype - 1) < *Game::gameTypeCount && Game::gameTypes[(ui_joinGametype - 1)].gameType != serverInfo->gametype) continue;
ServerList::VisibleList.push_back(i);
}
@ -253,7 +263,7 @@ namespace Components
ServerList::SortList();
}
void ServerList::Refresh(UIScript::Token)
void ServerList::Refresh([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
{
Dvar::Var("ui_serverSelected").set(false);
//Localization::Set("MPUI_SERVERQUERIED", "Sent requests: 0/0");
@ -312,14 +322,12 @@ namespace Components
void ServerList::StoreFavourite(const std::string& server)
{
//json11::Json::parse()
std::vector<std::string> servers;
if (Utils::IO::FileExists("players/favourites.json"))
const auto parseData = Utils::IO::ReadFile(FavouriteFile);
if (!parseData.empty())
{
std::string data = Utils::IO::ReadFile("players/favourites.json");
json11::Json object = json11::Json::parse(data, data);
const nlohmann::json object = nlohmann::json::parse(parseData);
if (!object.is_array())
{
Logger::Print("Favourites storage file is invalid!\n");
@ -327,25 +335,24 @@ namespace Components
return;
}
auto storedServers = object.array_items();
for (unsigned int i = 0; i < storedServers.size(); ++i)
const nlohmann::json::array_t storedServers = object;
for (const auto& storedServer : storedServers)
{
if (!storedServers[i].is_string()) continue;
if (storedServers[i].string_value() == server)
if (!storedServer.is_string()) continue;
if (storedServer.get<std::string>() == server)
{
Game::ShowMessageBox("Server already marked as favourite.", "Error");
return;
}
servers.push_back(storedServers[i].string_value());
servers.push_back(storedServer.get<std::string>());
}
}
servers.push_back(server);
json11::Json data = json11::Json(servers);
Utils::IO::WriteFile("players/favourites.json", data.dump());
const auto data = nlohmann::json(servers);
Utils::IO::WriteFile(FavouriteFile, data.dump());
Game::ShowMessageBox("Server added to favourites.", "Success");
}
@ -353,10 +360,10 @@ namespace Components
{
std::vector<std::string> servers;
if (Utils::IO::FileExists("players/favourites.json"))
const auto parseData = Utils::IO::ReadFile(FavouriteFile);
if (!parseData.empty())
{
std::string data = Utils::IO::ReadFile("players/favourites.json");
json11::Json object = json11::Json::parse(data, data);
const nlohmann::json object = nlohmann::json::parse(parseData);
if (!object.is_array())
{
@ -365,50 +372,56 @@ namespace Components
return;
}
for (auto& storedServer : object.array_items())
const nlohmann::json::array_t arr = object;
for (auto& storedServer : arr)
{
if (storedServer.is_string() && storedServer.string_value() != server)
if (storedServer.is_string() && storedServer.get<std::string>() != server)
{
servers.push_back(storedServer.string_value());
servers.push_back(storedServer.get<std::string>());
}
}
}
json11::Json data = json11::Json(servers);
Utils::IO::WriteFile("players/favourites.json", data.dump());
const auto data = nlohmann::json(servers);
Utils::IO::WriteFile(FavouriteFile, data.dump());
auto list = ServerList::GetList();
if (list) list->clear();
ServerList::RefreshVisibleListInternal(UIScript::Token());
ServerList::RefreshVisibleListInternal(UIScript::Token(), nullptr);
Game::ShowMessageBox("Server removed from favourites.", "Success");
}
void ServerList::LoadFavourties()
{
if (ServerList::IsFavouriteList() && Utils::IO::FileExists("players/favourites.json"))
if (!ServerList::IsFavouriteList())
{
auto list = ServerList::GetList();
if (list) list->clear();
return;
}
std::string data = Utils::IO::ReadFile("players/favourites.json");
json11::Json object = json11::Json::parse(data, data);
auto list = ServerList::GetList();
if (list) list->clear();
if (!object.is_array())
{
Logger::Print("Favourites storage file is invalid!\n");
Game::ShowMessageBox("Favourites storage file is invalid!", "Error");
return;
}
const auto parseData = Utils::IO::ReadFile(FavouriteFile);
if (parseData.empty())
{
return;
}
auto servers = object.array_items();
const nlohmann::json object = nlohmann::json::parse(parseData);
if (!object.is_array())
{
Logger::Print("Favourites storage file is invalid!\n");
Game::ShowMessageBox("Favourites storage file is invalid!", "Error");
return;
}
for (unsigned int i = 0; i < servers.size(); ++i)
{
if (!servers[i].is_string()) continue;
ServerList::InsertRequest(servers[i].string_value());
}
const nlohmann::json::array_t servers = object;
for (const auto& server : servers)
{
if (!server.is_string()) continue;
ServerList::InsertRequest(server.get<std::string>());
}
}
@ -482,6 +495,8 @@ namespace Components
server.securityLevel = atoi(info.get("securityLevel").data());
server.maxClients = atoi(info.get("sv_maxclients").data());
server.password = (atoi(info.get("isPrivate").data()) != 0);
server.aimassist = (atoi(info.get("aimAssist").data()) != 0);
server.voice = (atoi(info.get("voiceChat").data()) != 0);
server.hardcore = (atoi(info.get("hc").data()) != 0);
server.svRunning = (atoi(info.get("sv_running").data()) != 0);
server.ping = (Game::Sys_Milliseconds() - i->sendTime);
@ -538,15 +553,12 @@ namespace Components
)
{
auto lList = ServerList::GetList();
if (lList)
{
lList->push_back(server);
ServerList::RefreshVisibleListInternal(UIScript::Token());
ServerList::RefreshVisibleListInternal(UIScript::Token(), nullptr);
}
}
break;
}
else
{
@ -705,7 +717,7 @@ namespace Components
netSource.set(source);
ServerList::RefreshVisibleListInternal(UIScript::Token(), true);
ServerList::RefreshVisibleListInternal(UIScript::Token(), nullptr, true);
}
void ServerList::UpdateGameType()
@ -721,7 +733,7 @@ namespace Components
joinGametype.set(gametype);
ServerList::RefreshVisibleListInternal(UIScript::Token());
ServerList::RefreshVisibleListInternal(UIScript::Token(), nullptr);
}
void ServerList::UpdateVisibleInfo()
@ -796,7 +808,7 @@ namespace Components
//Localization::Set("MPUI_SERVERQUERIED", "Sent requests: 0/0");
Localization::Set("MPUI_SERVERQUERIED", "Servers: 0\nPlayers: 0 (0)");
Network::OnPacket("getServersResponse", [](const Network::Address& address, [[maybe_unused]] const std::string& data)
Network::OnClientPacket("getServersResponse", [](const Network::Address& address, [[maybe_unused]] const std::string& data)
{
if (ServerList::RefreshContainer.host != address) return; // Only parse from host we sent to
@ -841,20 +853,18 @@ namespace Components
UIScript::Add("RefreshServers", ServerList::Refresh);
UIScript::Add("JoinServer", [](UIScript::Token)
UIScript::Add("JoinServer", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
{
ServerList::ServerInfo* info = ServerList::GetServer(ServerList::CurrentServer);
if (info)
auto* serverInfo = ServerList::GetServer(ServerList::CurrentServer);
if (serverInfo)
{
Party::Connect(info->addr);
Party::Connect(serverInfo->addr);
}
});
UIScript::Add("ServerSort", [](UIScript::Token token)
UIScript::Add("ServerSort", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
{
int key = token.get<int>();
auto key = token.get<int>();
if (ServerList::SortKey == key)
{
ServerList::SortAsc = !ServerList::SortAsc;
@ -869,22 +879,21 @@ namespace Components
ServerList::SortList();
});
UIScript::Add("CreateListFavorite", [](UIScript::Token)
UIScript::Add("CreateListFavorite", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
{
ServerList::ServerInfo* info = ServerList::GetCurrentServer();
auto* serverInfo = ServerList::GetCurrentServer();
if (info)
{
ServerList::StoreFavourite(info->addr.getString());
ServerList::StoreFavourite(serverInfo->addr.getString());
}
});
UIScript::Add("CreateFavorite", [](UIScript::Token)
UIScript::Add("CreateFavorite", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
{
ServerList::StoreFavourite(Dvar::Var("ui_favoriteAddress").get<std::string>());
});
UIScript::Add("CreateCurrentServerFavorite", [](UIScript::Token)
UIScript::Add("CreateCurrentServerFavorite", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
{
if (Game::CL_IsCgameInitialized())
{
@ -896,14 +905,13 @@ namespace Components
}
});
UIScript::Add("DeleteFavorite", [](UIScript::Token)
UIScript::Add("DeleteFavorite", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
{
ServerList::ServerInfo* info = ServerList::GetCurrentServer();
if (info)
auto* serverInfo = ServerList::GetCurrentServer();
if (serverInfo)
{
ServerList::RemoveFavourite(info->addr.getString());
};
ServerList::RemoveFavourite(serverInfo->addr.getString());
}
});
#ifdef _DEBUG

View File

@ -28,15 +28,17 @@ namespace Components
int securityLevel;
bool hardcore;
bool svRunning;
bool aimassist;
bool voice;
};
ServerList();
~ServerList();
static void Refresh(UIScript::Token);
static void RefreshVisibleList(UIScript::Token);
static void RefreshVisibleListInternal(UIScript::Token, bool refresh = false);
static void UpdateVisibleList(UIScript::Token);
static void Refresh([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info);
static void RefreshVisibleList([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info);
static void RefreshVisibleListInternal([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info, bool refresh = false);
static void UpdateVisibleList([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info);
static void InsertRequest(Network::Address address);
static void Insert(const Network::Address& address, const Utils::InfoString& info);
@ -59,6 +61,8 @@ namespace Components
{
Password,
Matchtype,
AimAssist,
VoiceChat,
Hostname,
Mapname,
Players,
@ -67,6 +71,8 @@ namespace Components
Ping,
};
static constexpr auto* FavouriteFile = "players/favourites.json";
#pragma pack(push, 1)
union MasterEntry
{

View File

@ -61,7 +61,7 @@ namespace Components
void Session::Handle(const std::string& packet, const Network::NetworkCallback& callback)
{
#ifdef DISABLE_SESSION
Network::OnPacket(packet, callback);
Network::OnClientPacket(packet, callback);
#else
std::lock_guard _(Session::Mutex);
Session::PacketHandlers[packet] = callback;

View File

@ -70,7 +70,7 @@ namespace Components
// set snapshot num to 1 behind (T6 does this, why shouldn't we?)
for (int i = 0; i < *Game::svs_clientCount; ++i)
{
Game::svs_clients[i].snapNum = *Game::svs_time - 1;
Game::svs_clients[i].nextSnapshotTime = *Game::svs_time - 1;
}
}

View File

@ -1,8 +1,5 @@
#pragma once
#define BUTTON_FLAG_LEANLEFT 0x40
#define BUTTON_FLAG_LEANRIGHT 0x80
namespace Components
{
class SlowMotion : public Component

View File

@ -14,7 +14,7 @@ namespace Components
Dvar::Register<const char*>("ui_startupNextButtonText", "", Game::DVAR_EXTERNAL | Game::DVAR_INIT, "");
}, Scheduler::Pipeline::MAIN);
UIScript::Add("nextStartupMessage", [](UIScript::Token)
UIScript::Add("nextStartupMessage", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
{
if (!StartupMessages::MessageList.size()) return;

View File

@ -24,10 +24,8 @@ namespace Components
Game::Com_Printf(0, "Sending stat packet %i to server.\n", i);
// alloc
Game::msg_t msg;
char buffer[2048];
ZeroMemory(&msg, sizeof(msg));
ZeroMemory(&buffer, sizeof(buffer));
Game::msg_t msg{};
unsigned char buffer[2048]{};
// init
Game::MSG_Init(&msg, buffer, sizeof(buffer));
@ -53,23 +51,23 @@ namespace Components
}
// send statpacket
Network::SendRaw(Game::NS_CLIENT1, *reinterpret_cast<Game::netadr_t*>(0xA1E888), std::string(msg.data, msg.cursize));
Network::SendRaw(Game::NS_CLIENT1, *reinterpret_cast<Game::netadr_t*>(0xA1E888), std::string(reinterpret_cast<char*>(msg.data), msg.cursize));
}
}
}
void Stats::UpdateClasses(UIScript::Token)
void Stats::UpdateClasses([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
{
Stats::SendStats();
}
int Stats::SaveStats(char* dest, const char* folder, const char* buffer, size_t length)
{
const auto fs_game = Game::Dvar_FindVar("fs_game");
assert(*Game::fs_gameDirVar);
if (fs_game && fs_game->current.string && strlen(fs_game->current.string) && !strncmp(fs_game->current.string, "mods/", 5))
if (!std::strcmp((*Game::fs_gameDirVar)->current.string, "mods/"))
{
folder = fs_game->current.string;
folder = (*Game::fs_gameDirVar)->current.string;
}
return Utils::Hook::Call<int(char*, const char*, const char*, size_t)>(0x426450)(dest, folder, buffer, length);

View File

@ -10,7 +10,7 @@ namespace Components
static bool IsMaxLevel();
private:
static void UpdateClasses(UIScript::Token token);
static void UpdateClasses([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info);
static void SendStats();
static int SaveStats(char* dest, const char* folder, const char* buffer, size_t length);

View File

@ -206,7 +206,7 @@ namespace Components
std::unordered_map<std::string, std::string> otherPatches;
std::string errors;
json11::Json defData = json11::Json::parse(definition.getBuffer(), errors);
nlohmann::json defData = nlohmann::json::parse(definition.getBuffer());
if (!errors.empty())
{
@ -228,11 +228,11 @@ namespace Components
if (enumData.is_array())
{
for (auto rawEntry : enumData.array_items())
for (auto rawEntry : enumData)
{
if (rawEntry.is_string())
{
entryData.push_back(rawEntry.string_value());
entryData.push_back(rawEntry.get<std::string>());
}
}
}
@ -244,11 +244,11 @@ namespace Components
if (other.is_object())
{
for (auto& item : other.object_items())
for (auto& item : other.items())
{
if (item.second.is_string())
if (item.value().is_string())
{
otherPatches[item.first] = item.second.string_value();
otherPatches[item.key()] = item.value().get<std::string>();
}
}
}

View File

@ -1287,7 +1287,7 @@ namespace Components
std::string TextRenderer::StripColors(const std::string& in)
{
char buffer[1000] = { 0 }; // Should be more than enough
char buffer[1024] = {0}; // 1024 is a lucky number in the engine
StripColors(in.data(), buffer, sizeof(buffer));
return std::string(buffer);
}

View File

@ -47,7 +47,7 @@ namespace Components
void Theatre::WriteBaseline()
{
static char bufData[131072];
static unsigned char bufData[131072];
static char cmpData[131072];
Game::msg_t buf;
@ -56,7 +56,7 @@ namespace Components
Game::MSG_WriteData(&buf, &Theatre::BaselineSnapshot[Theatre::BaselineSnapshotMsgOff], Theatre::BaselineSnapshotMsgLen - Theatre::BaselineSnapshotMsgOff);
Game::MSG_WriteByte(&buf, 6);
int compressedSize = Game::MSG_WriteBitsCompress(false, buf.data, cmpData, buf.cursize);
int compressedSize = Game::MSG_WriteBitsCompress(false, reinterpret_cast<char*>(buf.data), cmpData, buf.cursize);
int fileCompressedSize = compressedSize + 4;
int byte8 = 8;
@ -162,8 +162,8 @@ namespace Components
Game::Com_Printf(channel, message, file);
Theatre::CurrentInfo.name = file;
Theatre::CurrentInfo.mapname = Dvar::Var("mapname").get<const char*>();
Theatre::CurrentInfo.gametype = Dvar::Var("g_gametype").get<const char*>();
Theatre::CurrentInfo.mapname = (*Game::sv_mapname)->current.string;
Theatre::CurrentInfo.gametype = (*Game::sv_gametype)->current.string;
Theatre::CurrentInfo.author = Steam::SteamFriends()->GetPersonaName();
Theatre::CurrentInfo.length = Game::Sys_Milliseconds();
std::time(&Theatre::CurrentInfo.timeStamp);
@ -178,10 +178,10 @@ namespace Components
// Write metadata
FileSystem::FileWriter meta(Utils::String::VA("%s.json", Theatre::CurrentInfo.name.data()));
meta.write(json11::Json(Theatre::CurrentInfo).dump());
meta.write(nlohmann::json(Theatre::CurrentInfo.to_json()).dump());
}
void Theatre::LoadDemos(UIScript::Token)
void Theatre::LoadDemos([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
{
Theatre::CurrentSelection = 0;
Theatre::Demos.clear();
@ -195,20 +195,20 @@ namespace Components
if (meta.exists())
{
std::string error;
json11::Json metaObject = json11::Json::parse(meta.getBuffer(), error);
nlohmann::json metaObject = nlohmann::json::parse(meta.getBuffer());
if (metaObject.is_object())
{
Theatre::DemoInfo info;
Theatre::DemoInfo demoInfo;
demoInfo.name = demo.substr(0, demo.find_last_of("."));
demoInfo.author = metaObject["author"].get<std::string>();
demoInfo.gametype = metaObject["gametype"].get<std::string>();
demoInfo.mapname = metaObject["mapname"].get<std::string>();
demoInfo.length = metaObject["length"].get<int>();
auto timestamp = metaObject["timestamp"].get<std::string>();
demoInfo.timeStamp = _atoi64(timestamp.data());
info.name = demo.substr(0, demo.find_last_of("."));
info.author = metaObject["author"].string_value();
info.gametype = metaObject["gametype"].string_value();
info.mapname = metaObject["mapname"].string_value();
info.length = static_cast<int>(metaObject["length"].number_value());
info.timeStamp = _atoi64(metaObject["timestamp"].string_value().data());
Theatre::Demos.push_back(info);
Theatre::Demos.push_back(demoInfo);
}
}
}
@ -217,16 +217,16 @@ namespace Components
std::reverse(Theatre::Demos.begin(), Theatre::Demos.end());
}
void Theatre::DeleteDemo(UIScript::Token)
void Theatre::DeleteDemo([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
{
if (Theatre::CurrentSelection < Theatre::Demos.size())
{
Theatre::DemoInfo info = Theatre::Demos[Theatre::CurrentSelection];
Theatre::DemoInfo demoInfo = Theatre::Demos[Theatre::CurrentSelection];
Logger::Print("Deleting demo {}...\n", info.name);
Logger::Print("Deleting demo {}...\n", demoInfo.name);
FileSystem::DeleteFile("demos", info.name + ".dm_13");
FileSystem::DeleteFile("demos", info.name + ".dm_13.json");
FileSystem::_DeleteFile("demos", demoInfo.name + ".dm_13");
FileSystem::_DeleteFile("demos", demoInfo.name + ".dm_13.json");
// Reset our ui_demo_* dvars here, because the theater menu needs it.
Dvar::Var("ui_demo_mapname").set("");
@ -237,11 +237,11 @@ namespace Components
Dvar::Var("ui_demo_date").set("");
// Reload demos
Theatre::LoadDemos(UIScript::Token());
Theatre::LoadDemos(UIScript::Token(), info);
}
}
void Theatre::PlayDemo(UIScript::Token)
void Theatre::PlayDemo([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
{
if (Theatre::CurrentSelection < Theatre::Demos.size())
{
@ -309,8 +309,8 @@ namespace Components
for (int i = 0; i < numDel; ++i)
{
Logger::Print("Deleting old demo {}\n", files[i]);
FileSystem::DeleteFile("demos", files[i].data());
FileSystem::DeleteFile("demos", Utils::String::VA("%s.json", files[i].data()));
FileSystem::_DeleteFile("demos", files[i].data());
FileSystem::_DeleteFile("demos", Utils::String::VA("%s.json", files[i].data()));
}
Command::Execute(Utils::String::VA("record auto_%lld", time(nullptr)), true);

View File

@ -20,9 +20,9 @@ namespace Components
int length;
std::time_t timeStamp;
json11::Json to_json() const
nlohmann::json to_json() const
{
return json11::Json::object
return nlohmann::json
{
{ "mapname", mapname },
{ "gametype", gametype },
@ -44,9 +44,9 @@ namespace Components
static void WriteBaseline();
static void StoreBaseline(PBYTE snapshotMsg);
static void LoadDemos(UIScript::Token);
static void DeleteDemo(UIScript::Token);
static void PlayDemo(UIScript::Token);
static void LoadDemos([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info);
static void DeleteDemo([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info);
static void PlayDemo([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info);
static unsigned int GetDemoCount();
static const char* GetDemoText(unsigned int item, int column);

View File

@ -311,7 +311,7 @@ namespace Components
}
}
void UIFeeder::ApplyMap(UIScript::Token)
void UIFeeder::ApplyMap([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
{
const auto mapname = Dvar::Var("ui_map_name").get<std::string>();
@ -319,7 +319,7 @@ namespace Components
Utils::Hook::Call<void(const char*)>(0x503B50)(mapname.data()); // Party_SetDisplayMapName
}
void UIFeeder::ApplyInitialMap(UIScript::Token)
void UIFeeder::ApplyInitialMap([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
{
const auto mapname = Dvar::Var("ui_mapname").get<std::string>();

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