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? 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?** **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?** **Anything else we should know?**
Add any other context about the problem here. Add any other context about the problem here.

7
.gitmodules vendored
View File

@ -2,10 +2,6 @@
path = deps/zlib path = deps/zlib
url = https://github.com/madler/zlib.git url = https://github.com/madler/zlib.git
branch = develop branch = develop
[submodule "deps/json11"]
path = deps/json11
url = https://github.com/dropbox/json11.git
branch = master
[submodule "deps/libtommath"] [submodule "deps/libtommath"]
path = deps/libtommath path = deps/libtommath
url = https://github.com/libtom/libtommath.git url = https://github.com/libtom/libtommath.git
@ -35,3 +31,6 @@
[submodule "deps/GSL"] [submodule "deps/GSL"]
path = deps/GSL path = deps/GSL
url = https://github.com/microsoft/GSL.git 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/). 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 ## [0.7.4] - 2022-07-28
### Added ### 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 slow motion during final killcams (#111 - #107)
- Fixed sound issue that causes the game to freeze (#106) - 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 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 ### Known issues

View File

@ -8,9 +8,10 @@
# IW4x: Client # 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`. - Build via solution file in `build\iw4x.sln`.
## Premake arguments ## Premake arguments
@ -22,7 +23,6 @@
| `--force-unit-tests` | Always compile unit tests. | | `--force-unit-tests` | Always compile unit tests. |
| `--force-exception-handler` | Install custom unhandled exception handler even for Debug builds. | | `--force-exception-handler` | Install custom unhandled exception handler even for Debug builds. |
| `--disable-binary-check` | Do not perform integrity checks on the exe. | | `--disable-binary-check` | Do not perform integrity checks on the exe. |
| `--iw4x-zones` | Zonebuilder generates iw4x zones that cannot be loaded without IW4x specific patches. |
## Command line arguments ## 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." 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 { newaction {
trigger = "version", trigger = "version",
description = "Returns the version string for the current commit of the source code.", 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 if _OPTIONS["disable-binary-check"] then
defines {"DISABLE_BINARY_CHECK"} defines {"DISABLE_BINARY_CHECK"}
end end
if _OPTIONS["iw4x-zones"] then
defines {"GENERATE_IW4X_SPECIFIC_ZONES"}
end
-- Pre-compiled header -- Pre-compiled header
pchheader "STDInclude.hpp" -- must be exactly same as used in #include directives 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 Ceg());
Loader::Register(new UserInfo()); Loader::Register(new UserInfo());
Loader::Register(new Events()); Loader::Register(new Events());
Loader::Register(new Voice());
Loader::Register(new Vote());
Loader::Register(new GSC()); Loader::Register(new GSC());

View File

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

View File

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

View File

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

View File

@ -332,165 +332,13 @@ namespace Assets
header->material = Components::AssetHandler::FindOriginalAsset(this->getType(), name.data()).material; header->material = Components::AssetHandler::FindOriginalAsset(this->getType(), name.data()).material;
} }
void IMaterial::loadJson(Game::XAssetHeader* header, const std::string& name, 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())); Components::FileSystem::File materialInfo(Utils::String::VA("materials/%s.json", name.data()));
if (!materialInfo.exists()) return; if (!materialInfo.exists()) return;
std::string errors; header->material = nullptr;
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;
} }
void IMaterial::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) 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) 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()) 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; return;
} }
@ -19,11 +19,10 @@ namespace Assets
return; return;
} }
std::string errors; nlohmann::json infoData = nlohmann::json::parse(aliasFile.getBuffer());
json11::Json infoData = json11::Json::parse(aliasFile.getBuffer(), errors); nlohmann::json aliasesContainer = infoData["head"];
json11::Json aliasesContainer = infoData["head"];
auto aliases = aliasesContainer.array_items(); nlohmann::json::array_t aliases = aliasesContainer;
aliasList->count = aliases.size(); aliasList->count = aliases.size();
@ -39,7 +38,7 @@ namespace Assets
for (size_t i = 0; i < aliasList->count; i++) for (size_t i = 0; i < aliasList->count; i++)
{ {
json11::Json head = aliasesContainer[i]; nlohmann::json head = aliasesContainer[i];
if (!infoData.is_object()) if (!infoData.is_object())
{ {
@ -211,37 +210,37 @@ namespace Assets
{ {
alias->soundFile->exists = true; 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()) 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()) 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()) 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->sequence = sequence.get<int>();
alias->volMin = float(volMin.number_value()); alias->volMin = volMin.get<float>();
alias->volMax = float(volMax.number_value()); alias->volMax = volMax.get<float>();
alias->pitchMin = float(pitchMin.number_value()); alias->pitchMin = pitchMin.get<float>();
alias->pitchMax = float(pitchMax.number_value()); alias->pitchMax = pitchMax.get<float>();
alias->distMin = float(distMin.number_value()); alias->distMin = distMin.get<float>();
alias->distMax = float(distMax.number_value()); alias->distMax = distMax.get<float>();
alias->flags = flags.int_value(); alias->flags = flags.get<int>();
alias->___u15.slavePercentage = float(slavePercentage.number_value()); alias->___u15.slavePercentage = slavePercentage.get<float>();
alias->probability = float(probability.number_value()); alias->probability = probability.get<float>();
alias->lfePercentage = float(lfePercentage.number_value()); alias->lfePercentage = lfePercentage.get<float>();
alias->centerPercentage = float(centerPercentage.number_value()); alias->centerPercentage = centerPercentage.get<float>();
alias->startDelay = startDelay.int_value(); alias->startDelay = startDelay.get<int>();
alias->envelopMin = float(envelopMin.number_value()); alias->envelopMin = envelopMin.get<float>();
alias->envelopMax = float(envelopMax.number_value()); alias->envelopMax = envelopMax.get<float>();
alias->envelopPercentage = float(envelopPercentage.number_value()); alias->envelopPercentage = envelopPercentage.get<float>();
// Speaker map object // Speaker map object
if (!speakerMap.is_null()) if (!speakerMap.is_null())
@ -253,12 +252,12 @@ namespace Assets
return; return;
} }
alias->speakerMap->name = builder->getAllocator()->duplicateString(speakerMap["name"].string_value().c_str()); alias->speakerMap->name = builder->getAllocator()->duplicateString(speakerMap["name"].get<std::string>());
alias->speakerMap->isDefault = speakerMap["isDefault"].bool_value(); alias->speakerMap->isDefault = speakerMap["isDefault"].get<bool>();
if (speakerMap["channelMaps"].is_array()) if (speakerMap["channelMaps"].is_array())
{ {
json11::Json::array channelMaps = speakerMap["channelMaps"].array_items(); nlohmann::json::array_t channelMaps = speakerMap["channelMaps"];
assert(channelMaps.size() <= 4); assert(channelMaps.size() <= 4);
@ -268,19 +267,19 @@ namespace Assets
// subChannelIndex should never exceed 1 // subChannelIndex should never exceed 1
for (size_t subChannelIndex = 0; subChannelIndex < 2; subChannelIndex++) 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(); alias->speakerMap->channelMaps[channelMapIndex][subChannelIndex].speakerCount = speakers.size();
for (size_t speakerIndex = 0; speakerIndex < alias->speakerMap->channelMaps[channelMapIndex][subChannelIndex].speakerCount; speakerIndex++) for (size_t speakerIndex = 0; speakerIndex < alias->speakerMap->channelMaps[channelMapIndex][subChannelIndex].speakerCount; speakerIndex++)
{ {
auto speaker = speakers[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[0] = speaker["levels0"].get<float>();
alias->speakerMap->channelMaps[channelMapIndex][subChannelIndex].speakers[speakerIndex].levels[1] = static_cast<float>(speaker["levels1"].number_value()); alias->speakerMap->channelMaps[channelMapIndex][subChannelIndex].speakers[speakerIndex].levels[1] = speaker["levels1"].get<float>();
alias->speakerMap->channelMaps[channelMapIndex][subChannelIndex].speakers[speakerIndex].numLevels = static_cast<int>(speaker["numLevels"].number_value()); alias->speakerMap->channelMaps[channelMapIndex][subChannelIndex].speakers[speakerIndex].numLevels = speaker["numLevels"].get<int>();
alias->speakerMap->channelMaps[channelMapIndex][subChannelIndex].speakers[speakerIndex].speaker = static_cast<int>(speaker["speaker"].number_value()); alias->speakerMap->channelMaps[channelMapIndex][subChannelIndex].speakers[speakerIndex].speaker = speaker["speaker"].get<int>();
} }
} }
} }
@ -289,7 +288,7 @@ namespace Assets
if (volumeFalloffCurve.is_string()) if (volumeFalloffCurve.is_string())
{ {
std::string fallOffCurve = volumeFalloffCurve.string_value(); std::string fallOffCurve = volumeFalloffCurve.get<std::string>();
if (fallOffCurve.size() == 0) if (fallOffCurve.size() == 0)
{ {
@ -305,16 +304,16 @@ namespace Assets
alias->volumeFalloffCurve = curve; 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->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; alias->soundFile->type = Game::SAT_STREAMED;
std::string streamedFile = soundFile.string_value(); std::string streamedFile = soundFile.get<std::string>();
std::string directory = ""s; std::string directory = ""s;
int split = streamedFile.find_last_of('/'); int split = streamedFile.find_last_of('/');
@ -329,7 +328,7 @@ namespace Assets
} }
else 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; return;
} }

View File

@ -130,7 +130,7 @@ namespace Components
// Parse proto data // Parse proto data
Proto::Auth::Connect connectData; 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!"); Network::Send(address, "error\nInvalid connect packet!");
return; return;
@ -233,7 +233,7 @@ namespace Components
return; 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()); Game::SV_DirectConnect(*address.get());
} }
#endif #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; Auth::TokenContainer.cancel = true;
Logger::Print("Token incrementation process canceled!\n"); Logger::Print("Token incrementation process canceled!\n");

View File

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

View File

@ -294,7 +294,8 @@ namespace Components
Bots::Bots() Bots::Bots()
{ {
AssertOffset(Game::client_s, bIsTestClient, 0x41AF0); AssertOffset(Game::client_t, bIsTestClient, 0x41AF0);
AssertOffset(Game::client_t, ping, 0x212C8);
// Replace connect string // Replace connect string
Utils::Hook::Set<const char*>(0x48ADA6, "connect bot%d \"\\cg_predictItems\\1\\cl_anonymous\\0\\color\\4\\head\\default\\model\\multi\\snaps\\20\\rate\\5000\\name\\%s\\protocol\\%d\\checksum\\%d\\statver\\%d %u\\qport\\%d\""); Utils::Hook::Set<const char*>(0x48ADA6, "connect bot%d \"\\cg_predictItems\\1\\cl_anonymous\\0\\color\\4\\head\\default\\model\\multi\\snaps\\20\\rate\\5000\\name\\%s\\protocol\\%d\\checksum\\%d\\statver\\%d %u\\qport\\%d\"");

View File

@ -5,7 +5,6 @@ namespace Components
Dvar::Var Branding::CGDrawVersion; Dvar::Var Branding::CGDrawVersion;
Dvar::Var Branding::CGDrawVersionX; Dvar::Var Branding::CGDrawVersionX;
Dvar::Var Branding::CGDrawVersionY; Dvar::Var Branding::CGDrawVersionY;
Game::dvar_t** Branding::Version = reinterpret_cast<Game::dvar_t**>(0x1AD7930);
#ifdef _DEBUG #ifdef _DEBUG
constexpr auto* BUILD_TYPE = "IW4x_DEV MP"; constexpr auto* BUILD_TYPE = "IW4x_DEV MP";
@ -25,12 +24,12 @@ namespace Components
auto* const placement = Game::ScrPlace_GetUnsafeFullPlacement(); auto* const placement = Game::ScrPlace_GetUnsafeFullPlacement();
auto* const font = Game::UI_GetFontHandle(placement, 0, 0.583f); 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); 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); 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); (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 CGDrawVersion;
static Dvar::Var CGDrawVersionX; static Dvar::Var CGDrawVersionX;
static Dvar::Var CGDrawVersionY; static Dvar::Var CGDrawVersionY;
static Game::dvar_t** Version;
static void CG_DrawVersion(); static void CG_DrawVersion();
static void CG_DrawVersion_Hk(int localClientNum); 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) float Bullet::BG_GetSurfacePenetrationDepthStub(const Game::WeaponDef* weapDef, int surfaceType)
{ {
assert(weapDef != nullptr); assert(weapDef);
assert(weapDef->penetrateType != Game::PenetrateType::PENETRATE_TYPE_NONE); assert(weapDef->penetrateType != Game::PENETRATE_TYPE_NONE);
assert(weapDef->penetrateType < Game::PenetrateType::PENETRATE_TYPE_COUNT); AssertIn(weapDef->penetrateType, Game::PENETRATE_TYPE_COUNT);
assert(static_cast<size_t>(surfaceType) < Game::materialSurfType_t::SURF_TYPE_COUNT); AssertIn(surfaceType, Game::SURF_TYPE_COUNT);
const auto penetrationDepth = BGSurfacePenetration.get<float>(); const auto penetrationDepth = BGSurfacePenetration.get<float>();
if (penetrationDepth > 0.0f) if (penetrationDepth > 0.0f)

View File

@ -160,7 +160,7 @@ namespace Components
{ {
char playerTitle[18]; 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); 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>(0x432180, 0xC3);
Utils::Hook::Set<BYTE>(0x461930, 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 // Looking for stuff in the registry
Utils::Hook::Nop(0x4826F8, 5); Utils::Hook::Nop(0x4826F8, 5);

View File

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

View File

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

View File

@ -9,7 +9,7 @@ namespace Components
const char* ClanTags::GetClanTagWithName(int clientNum, const char* playerName) 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') if (ClientState[clientNum][0] == '\0')
{ {
@ -96,7 +96,7 @@ namespace Components
char* ClanTags::GamerProfile_GetClanName(int controllerIndex) 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); assert(ClanName);
CL_SanitizeClanName(); CL_SanitizeClanName();
@ -115,7 +115,7 @@ namespace Components
void ClanTags::ClientUserinfoChanged(const char* s, int clientNum) 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"); auto* clanAbbrev = Game::Info_ValueForKey(s, "clanAbbrev");
@ -224,7 +224,7 @@ namespace Components
Game::PlayerCardData* ClanTags::PlayerCards_GetLiveProfileDataForController_Stub(const unsigned int controllerIndex) Game::PlayerCardData* ClanTags::PlayerCards_GetLiveProfileDataForController_Stub(const unsigned int controllerIndex)
{ {
auto* result = Utils::Hook::Call<Game::PlayerCardData*(unsigned int)>(0x463B90)(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)); Game::I_strncpyz(result->clanAbbrev, GamerProfile_GetClanName(static_cast<int>(controllerIndex)), sizeof(Game::PlayerCardData::clanAbbrev));
return result; 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 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(const char* s, int clientNum);
static void ClientUserinfoChanged_Stub(); static void ClientUserinfoChanged_Stub();

View File

@ -3,13 +3,13 @@
namespace Components 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) bool ClientCommand::CheatsOk(const Game::gentity_s* ent)
{ {
const auto entNum = ent->s.number; const auto entNum = ent->s.number;
if (!Dvar::Var("sv_cheats").get<bool>()) if (!(*Game::g_cheats)->current.enabled)
{ {
Logger::Debug("Cheats are disabled!"); Logger::Debug("Cheats are disabled!");
Game::SV_GameSendServerCommand(entNum, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"GAME_CHEATSNOTENABLED\"", 0x65)); Game::SV_GameSendServerCommand(entNum, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"GAME_CHEATSNOTENABLED\"", 0x65));
@ -26,11 +26,11 @@ namespace Components
return true; 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); const auto command = Utils::String::ToLower(name);
ClientCommand::HandlersSV[command] = callback; HandlersSV[command] = callback;
} }
void ClientCommand::ClientCommandStub(const int clientNum) void ClientCommand::ClientCommandStub(const int clientNum)
@ -57,9 +57,9 @@ namespace Components
void ClientCommand::AddCheatCommands() 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; return;
ent->client->flags ^= Game::PLAYER_FLAG_NOCLIP; ent->client->flags ^= Game::PLAYER_FLAG_NOCLIP;
@ -71,9 +71,9 @@ namespace Components
(ent->client->flags & Game::PLAYER_FLAG_NOCLIP) ? "GAME_NOCLIPON" : "GAME_NOCLIPOFF")); (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; return;
ent->client->flags ^= Game::PLAYER_FLAG_UFO; ent->client->flags ^= Game::PLAYER_FLAG_UFO;
@ -85,9 +85,9 @@ namespace Components
(ent->client->flags & Game::PLAYER_FLAG_UFO) ? "GAME_UFOON" : "GAME_UFOOFF")); (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; return;
ent->flags ^= Game::FL_GODMODE; ent->flags ^= Game::FL_GODMODE;
@ -99,9 +99,9 @@ namespace Components
(ent->flags & Game::FL_GODMODE) ? "GAME_GODMODE_ON" : "GAME_GODMODE_OFF")); (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; return;
ent->flags ^= Game::FL_DEMI_GODMODE; 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")); (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; return;
ent->flags ^= Game::FL_NOTARGET; ent->flags ^= Game::FL_NOTARGET;
@ -127,11 +127,11 @@ namespace Components
(ent->flags & Game::FL_NOTARGET) ? "GAME_NOTARGETON" : "GAME_NOTARGETOFF")); (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); assert(ent != nullptr);
if (!ClientCommand::CheatsOk(ent)) if (!CheatsOk(ent))
return; return;
Game::vec3_t origin, angles{0.f, 0.f, 0.f}; Game::vec3_t origin, angles{0.f, 0.f, 0.f};
@ -163,9 +163,9 @@ namespace Components
Game::TeleportPlayer(ent, origin, angles); 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; return;
if (params->size() < 2) 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) for (std::size_t i = 0; i < std::extent_v<decltype(Game::playerState_s::weaponsEquipped)>; ++i)
{ {
const auto index = ent->client->ps.weaponsEquipped[i]; const auto index = ent->client->ps.weaponsEquipped[i];
if (index != 0) if (index)
{ {
Game::Add_Ammo(ent, index, 0, 998, 1); 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() 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(); 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(); 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(); 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); Logger::Print("Entity count = {}\n", Game::level->num_entities);
}); });
// Also known as: "vis" // 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) if (params->size() < 2)
{ {
@ -295,7 +312,7 @@ namespace Components
Utils::String::VA("%c \"%s\" %i", Game::MY_CMDS[visMode], name, duration)); 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) if (params->size() < 2)
{ {
@ -323,7 +340,7 @@ namespace Components
Utils::String::VA("%c \"%s\" %i", Game::MY_CMDS[visMode], name, duration)); 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); assert(ent != nullptr);
@ -331,21 +348,14 @@ namespace Components
Logger::Debug("playerState_s.stunTime is {}", ent->client->ps.stunTime); 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); G_DumpEntityDebugInfoToConsole(false);
assert(ent->client->sess.connected != Game::CON_DISCONNECTED); });
if (ent->client->sess.sessionState != Game::SESS_STATE_PLAYING || !ClientCommand::CheatsOk(ent)) Add("dumpEntInfoCSV", []([[maybe_unused]] Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params)
return;
Scheduler::Once([ent]
{ {
ent->flags &= ~(Game::FL_GODMODE | Game::FL_DEMI_GODMODE); G_DumpEntityDebugInfoToCSV("");
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);
}); });
} }
@ -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() ClientCommand::ClientCommand()
{ {
// Hook call to ClientCommand in SV_ExecuteClientCommand so we may add custom commands AssertOffset(Game::playerState_s, stats, 0x150);
Utils::Hook(0x6259FA, ClientCommand::ClientCommandStub, HOOK_CALL).install()->quick();
ClientCommand::AddCheatCommands(); // Hook call to ClientCommand in SV_ExecuteClientCommand so we may add custom commands
ClientCommand::AddScriptFunctions(); Utils::Hook(0x6259FA, ClientCommandStub, HOOK_CALL).install()->quick();
AddCheatCommands();
AddScriptFunctions();
#ifdef _DEBUG #ifdef _DEBUG
ClientCommand::AddDevelopmentCommands(); AddDevelopmentCommands();
#endif #endif
} }
} }

View File

@ -7,15 +7,19 @@ namespace Components
public: public:
ClientCommand(); 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); static bool CheatsOk(const Game::gentity_s* ent);
private: 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 ClientCommandStub(int clientNum);
static void AddCheatCommands(); static void AddCheatCommands();
static void AddDevelopmentCommands(); static void AddDevelopmentCommands();
static void AddScriptFunctions(); 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::FunctionMap;
std::unordered_map<std::string, std::function<void(Command::Params*)>> Command::FunctionMapSV; 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; std::string result;
@ -24,12 +24,12 @@ namespace Components
assert(Game::cmd_args->nesting < Game::CMD_MAX_NESTING); 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_]; 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()) if (index >= this->size())
{ {
@ -45,12 +45,12 @@ namespace Components
assert(Game::sv_cmd_args->nesting < Game::CMD_MAX_NESTING); 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_]; 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()) if (index >= this->size())
{ {
@ -113,7 +113,7 @@ namespace Components
Game::Cmd_AddServerCommand(name, callback, Command::Allocate()); 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 // 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) void Command::Execute(std::string command, bool sync)

View File

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

View File

@ -24,28 +24,16 @@ namespace Components
Game::SafeArea Console::OriginalSafeArea; 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; 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); return Game::FS_ListFiles(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);
} }
void Console::RefreshStatus() void Console::RefreshStatus()
{ {
const auto mapname = Dvar::Var("mapname").get<std::string>(); const std::string mapname = (*Game::sv_mapname)->current.string;
const auto hostname = TextRenderer::StripColors(Dvar::Var("sv_hostname").get<std::string>()); const auto hostname = TextRenderer::StripColors((*Game::sv_hostname)->current.string);
if (Console::HasConsole) if (Console::HasConsole)
{ {
@ -58,7 +46,7 @@ namespace Components
{ {
for (int i = 0; i < maxclientCount; ++i) 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; ++clientCount;
} }
@ -568,6 +556,9 @@ namespace Components
Console::Console() Console::Console()
{ {
AssertOffset(Game::clientUIActive_t, connectionState, 0x9B8);
AssertOffset(Game::clientUIActive_t, keyCatchers, 0x9B0);
// Console '%s: %s> ' string // Console '%s: %s> ' string
Utils::Hook::Set<const char*>(0x5A44B4, "IW4x MP: " VERSION "> "); Utils::Hook::Set<const char*>(0x5A44B4, "IW4x MP: " VERSION "> ");
@ -580,8 +571,8 @@ namespace Components
Utils::Hook::Set<BYTE>(0x431565, 0xEB); Utils::Hook::Set<BYTE>(0x431565, 0xEB);
// Internal console // Internal console
Utils::Hook(0x4F690C, Console::ToggleConsole, HOOK_CALL).install()->quick(); Utils::Hook(0x4F690C, Console::Con_ToggleConsole, HOOK_CALL).install()->quick();
Utils::Hook(0x4F65A5, Console::ToggleConsole, HOOK_JUMP).install()->quick(); Utils::Hook(0x4F65A5, Console::Con_ToggleConsole, HOOK_JUMP).install()->quick();
// Patch safearea for ingame-console // Patch safearea for ingame-console
Utils::Hook(0x5A50EF, Console::DrawSolidConsoleStub, HOOK_CALL).install()->quick(); Utils::Hook(0x5A50EF, Console::DrawSolidConsoleStub, HOOK_CALL).install()->quick();

View File

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

View File

@ -2,7 +2,8 @@
namespace Components 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); Game::dvar_t** Debug::PlayerDebugHealth = reinterpret_cast<Game::dvar_t**>(0x7A9F7C);
@ -90,6 +91,16 @@ namespace Components
"EF_SOFT", "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 Debug::BuildPMFlagsString(const Game::playerState_s* ps)
{ {
std::string result; std::string result;
@ -144,7 +155,6 @@ namespace Components
auto* const scrPlace = Game::ScrPlace_GetActivePlacement(localClientNum); auto* const scrPlace = Game::ScrPlace_GetActivePlacement(localClientNum);
constexpr auto maxChars = 4096; 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 font1 = Game::UI_GetFontHandle(scrPlace, 6, MY_SCALE_2);
auto* const font2 = Game::UI_GetFontHandle(scrPlace, 6, MY_SCALE2); auto* const font2 = Game::UI_GetFontHandle(scrPlace, 6, MY_SCALE2);
@ -153,16 +163,16 @@ namespace Components
MY_SCALE2, colorWhite, 1); MY_SCALE2, colorWhite, 1);
const auto pmf = BuildPMFlagsString(&cgameGlob->predictedPlayerState); const auto pmf = BuildPMFlagsString(&cgameGlob->predictedPlayerState);
Game::UI_DrawText(scrPlace, pmf.data(), maxChars, font1, 30.0f, 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); 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); const auto plf = BuildPLFlagsString(&cgameGlob->predictedPlayerState);
Game::UI_DrawText(scrPlace, plf.data(), maxChars, font1, 350.0f, 250.0f, 1, 1, MY_SCALE_2, colorWhite, 3); Game::UI_DrawText(scrPlace, plf.data(), maxChars, font1, 350.0f, 250.0f, 1, 1, MY_SCALE_2, colorWhite, 3);
const auto pef = BuildPEFlagsString(&cgameGlob->predictedPlayerState); const auto pef = BuildPEFlagsString(&cgameGlob->predictedPlayerState);
Game::UI_DrawText(scrPlace, pef.data(), maxChars, font1, 525.0f, 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) 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); 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) void Debug::CG_DrawDebugOverlays_Hk(const int localClientNum)
{ {
switch (DebugOverlay.get<int>()) switch (DebugOverlay->current.integer)
{ {
case 2: case 2:
CG_Debug_DrawPSFlags(localClientNum); CG_Debug_DrawPSFlags(localClientNum);
break; break;
case 5:
CG_Debug_DrawFontTest(localClientNum);
break;
default: default:
break; break;
} }
@ -215,7 +261,57 @@ namespace Components
void Debug::Com_Assert_f() 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() void Debug::CL_InitDebugDvars()
@ -233,6 +329,8 @@ namespace Components
DebugOverlay = Game::Dvar_RegisterEnum("debugOverlay", debugOverlayNames_0, 0, DebugOverlay = Game::Dvar_RegisterEnum("debugOverlay", debugOverlayNames_0, 0,
Game::DVAR_NONE, "Toggles the display of various debug info."); 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() Debug::Debug()
@ -243,5 +341,10 @@ namespace Components
Utils::Hook(0x49CB0A, CG_DrawDebugOverlays_Hk, HOOK_JUMP).install()->quick(); Utils::Hook(0x49CB0A, CG_DrawDebugOverlays_Hk, HOOK_JUMP).install()->quick();
Utils::Hook::Set<void(*)()>(0x60BCEA, Com_Assert_f); 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(); Debug();
private: private:
static Dvar::Var DebugOverlay; static const Game::dvar_t* DebugOverlay;
static const Game::dvar_t* BugName;
// Game dvars // Game dvars
static Game::dvar_t** PlayerDebugHealth; static Game::dvar_t** PlayerDebugHealth;
@ -18,9 +19,17 @@ namespace Components
static const char* PLFlagsValues[]; static const char* PLFlagsValues[];
static const char* PEFlagsValues[]; static const char* PEFlagsValues[];
static const char strButtons[];
static const char strTemplate[];
static constexpr auto MY_SCALE2 = 0.5f; static constexpr auto MY_SCALE2 = 0.5f;
static constexpr auto MY_SCALE_2 = 0.201f; static constexpr auto MY_SCALE_2 = 0.201f;
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 BuildPMFlagsString(const Game::playerState_s* ps);
static std::string BuildPOFlagsString(const Game::playerState_s* ps); static std::string BuildPOFlagsString(const Game::playerState_s* ps);
static std::string BuildPLFlagsString(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_Debug_DrawPSFlags(int localClientNum);
static void CG_DrawDebugPlayerHealth(int localClientNum); static void CG_DrawDebugPlayerHealth(int localClientNum);
static void CG_Debug_DrawFontTest(int localClientNum);
static void CG_DrawDebugOverlays_Hk(int localClientNum); static void CG_DrawDebugOverlays_Hk(int localClientNum);
static void Com_Assert_f(); static void Com_Assert_f();
static void Cbuf_AddServerText_f_Hk();
static void Com_Bug_f(Command::Params* params);
static void CL_InitDebugDvars(); static void CL_InitDebugDvars();
}; };

View File

@ -81,9 +81,9 @@ namespace Components
{ {
std::string list = Utils::String::VA("%c", 20); 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)); list.append(Utils::String::VA(" %llX", Game::svs_clients[i].steamID));
@ -104,12 +104,12 @@ namespace Components
Scheduler::Once([] Scheduler::Once([]
{ {
const auto partyEnable = Dvar::Var("party_enable").get<bool>(); 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 (!partyEnable) // Time wrapping should not occur in party servers, but yeah...
{ {
if (mapname.empty()) mapname = "mp_rust"; if (mapname.empty()) mapname = "mp_rust";
Command::Execute(Utils::String::VA("map %s", mapname.data()), false); Command::Execute(Utils::String::VA("map %s", mapname.data()), true);
} }
}, Scheduler::Pipeline::SERVER); }, Scheduler::Pipeline::SERVER);
@ -138,71 +138,6 @@ namespace Components
return Game::Dvar_RegisterInt(dvarName, 1000, min, 1000, Game::DVAR_NONE, description); 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::Dedicated()
{ {
Dedicated::COMLogFilter = Dvar::Register<bool>("com_logFilter", true, Dedicated::COMLogFilter = Dvar::Register<bool>("com_logFilter", true,
@ -281,12 +216,9 @@ namespace Components
{ {
Scheduler::Once([] 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"); Dvar::Register<const char*>("sv_motd", "", Game::DVAR_NONE, "A custom message of the day for servers");
}, Scheduler::Pipeline::MAIN); }, Scheduler::Pipeline::MAIN);
Events::OnSVInit(Dedicated::AddDedicatedCommands);
// Post initialization point // Post initialization point
Utils::Hook(0x60BFBF, Dedicated::PostInitializationStub, HOOK_JUMP).install()->quick(); Utils::Hook(0x60BFBF, Dedicated::PostInitializationStub, HOOK_JUMP).install()->quick();
@ -330,7 +262,7 @@ namespace Components
Scheduler::Loop([] Scheduler::Loop([]
{ {
if (Dvar::Var("sv_running").get<bool>()) if ((*Game::com_sv_running)->current.enabled)
{ {
Dedicated::TransmitGuids(); Dedicated::TransmitGuids();
} }

View File

@ -26,7 +26,5 @@ namespace Components
static void TimeWrapStub(Game::errorParm_t code, const char* message); 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 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.isSelf()) return;
if (!address.isLocal()) 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; 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::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.isSelf()) return;
if (!address.isLocal()) 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; return;
} }
if (Utils::ParseChallenge(data) != Discovery::Challenge) 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; return;
} }
Logger::Print("Received discovery response from: {}\n", address.getCString()); Logger::Print("Received discovery response from: {}\n", address.getString());
if (ServerList::IsOfflineList()) if (ServerList::IsOfflineList())
{ {

View File

@ -5,7 +5,6 @@ namespace Components
{ {
mg_mgr Download::Mgr; mg_mgr Download::Mgr;
Download::ClientDownload Download::CLDownload; Download::ClientDownload Download::CLDownload;
std::vector<std::shared_ptr<Download::ScriptDownload>> Download::ScriptDownloads;
std::thread Download::ServerThread; std::thread Download::ServerThread;
bool Download::Terminate; bool Download::Terminate;
@ -63,7 +62,7 @@ namespace Components
download->files.clear(); download->files.clear();
std::string error; std::string error;
json11::Json listData = json11::Json::parse(list, error); nlohmann::json listData = nlohmann::json::parse(list);
if (!error.empty() || !listData.is_array()) if (!error.empty() || !listData.is_array())
{ {
@ -72,8 +71,9 @@ namespace Components
} }
download->totalBytes = 0; 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; if (!file.is_object()) return false;
@ -84,9 +84,9 @@ namespace Components
if (!hash.is_string() || !name.is_string() || !size.is_number()) return false; if (!hash.is_string() || !name.is_string() || !size.is_number()) return false;
Download::ClientDownload::File fileEntry; Download::ClientDownload::File fileEntry;
fileEntry.name = name.string_value(); fileEntry.name = name.get<std::string>();
fileEntry.hash = hash.string_value(); fileEntry.hash = hash.get<std::string>();
fileEntry.size = static_cast<size_t>(size.number_value()); fileEntry.size = size.get<size_t>();
if (!fileEntry.name.empty()) if (!fileEntry.name.empty())
{ {
@ -353,9 +353,9 @@ namespace Components
// Run this on the main thread // Run this on the main thread
Scheduler::Once([] Scheduler::Once([]
{ {
auto fsGame = Dvar::Var("fs_game"); Game::Dvar_SetString(*Game::fs_gameDirVar, mod.data());
fsGame.set(mod); const_cast<Game::dvar_t*>(*Game::fs_gameDirVar)->modified = true;
fsGame.get<Game::dvar_t*>()->modified = true;
mod.clear(); mod.clear();
Command::Execute("closemenu mod_download_popmenu", false); Command::Execute("closemenu mod_download_popmenu", false);
@ -387,9 +387,9 @@ namespace Components
{ {
Game::client_t* client = &Game::svs_clients[i]; 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; return client;
} }
@ -502,14 +502,14 @@ namespace Components
// Only handle http requests // Only handle http requests
if (ev != MG_EV_HTTP_REQUEST) return; if (ev != MG_EV_HTTP_REQUEST) return;
std::vector<json11::Json> servers; std::vector<nlohmann::json> servers;
// Build server list // Build server list
for (auto& node : Node::GetNodes()) for (auto& node : Node::GetNodes())
{ {
if (node.isValid()) 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" "Connection: close\r\n"
"Access-Control-Allow-Origin: *\r\n" "Access-Control-Allow-Origin: *\r\n"
"\r\n" "\r\n"
"%s", json11::Json(servers).dump().data()); "%s", nlohmann::json(servers).dump().data());
nc->flags |= MG_F_SEND_AND_CLOSE; nc->flags |= MG_F_SEND_AND_CLOSE;
} }
@ -532,17 +532,17 @@ namespace Components
if (!Download::VerifyPassword(nc, reinterpret_cast<http_message*>(ev_data))) return; if (!Download::VerifyPassword(nc, reinterpret_cast<http_message*>(ev_data))) return;
static std::string mapnamePre; 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()); std::string mapname = (Party::IsInUserMapLobby() ? Dvar::Var("ui_mapname").get<std::string>() : Maps::GetUserMap()->getName());
if (!Maps::GetUserMap()->isValid() && !Party::IsInUserMapLobby()) if (!Maps::GetUserMap()->isValid() && !Party::IsInUserMapLobby())
{ {
mapnamePre.clear(); mapnamePre.clear();
jsonList = std::vector<json11::Json>(); jsonList = std::vector<nlohmann::json>();
} }
else if (!mapname.empty() && mapname != mapnamePre) else if (!mapname.empty() && mapname != mapnamePre)
{ {
std::vector<json11::Json> fileList; std::vector<nlohmann::json> fileList;
mapnamePre = mapname; mapnamePre = mapname;
@ -553,7 +553,7 @@ namespace Components
std::string filename = path + "\\" + mapname + Maps::UserMapFiles[i]; std::string filename = path + "\\" + mapname + Maps::UserMapFiles[i];
if (Utils::IO::FileExists(filename)) 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); std::string fileBuffer = Utils::IO::ReadFile(filename);
file["name"] = mapname + Maps::UserMapFiles[i]; file["name"] = mapname + Maps::UserMapFiles[i];
@ -591,13 +591,13 @@ namespace Components
// else // else
{ {
static std::string fsGamePre; 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) if (!fsGame.empty() && fsGame != fsGamePre)
{ {
std::vector<json11::Json> fileList; std::vector<nlohmann::json> fileList;
fsGamePre = fsGame; fsGamePre = fsGame;
@ -611,7 +611,7 @@ namespace Components
std::string filename = path + "\\" + *i; std::string filename = path + "\\" + *i;
if (strstr(i->data(), "_svr_") == nullptr && Utils::IO::FileExists(filename)) 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); std::string fileBuffer = Utils::IO::ReadFile(filename);
file["name"] = *i; file["name"] = *i;
@ -697,7 +697,7 @@ namespace Components
} }
std::string file; 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; std::string path = Dvar::Var("fs_basepath").get<std::string>() + "\\" + (isMap ? "" : fsGame + "\\") + url;
if ((!isMap && fsGame.empty()) || !Utils::IO::ReadFile(path, &file)) if ((!isMap && fsGame.empty()) || !Utils::IO::ReadFile(path, &file))
@ -735,23 +735,23 @@ namespace Components
Utils::InfoString status = ServerInfo::GetInfo(); Utils::InfoString status = ServerInfo::GetInfo();
Utils::InfoString host = ServerInfo::GetHostInfo(); 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["status"] = status.to_json();
info["host"] = host.to_json(); info["host"] = host.to_json();
std::vector<json11::Json> players; std::vector<nlohmann::json> players;
// Build player list // Build player list
for (int i = 0; i < atoi(status.get("sv_maxclients").data()); ++i) // Maybe choose 18 here? 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["score"] = 0;
playerInfo["ping"] = 0; playerInfo["ping"] = 0;
playerInfo["name"] = ""; 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["score"] = Game::SV_GameClientNum_Score(i);
playerInfo["ping"] = Game::svs_clients[i].ping; playerInfo["ping"] = Game::svs_clients[i].ping;
@ -760,13 +760,13 @@ namespace Components
else else
{ {
// Score and ping are irrelevant // 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; if (!namePtr || !namePtr[0]) continue;
playerInfo["name"] = namePtr; playerInfo["name"] = namePtr;
} }
players.push_back(playerInfo); players.emplace_back(playerInfo);
} }
info["players"] = players; info["players"] = players;
@ -777,7 +777,7 @@ namespace Components
"Connection: close\r\n" "Connection: close\r\n"
"Access-Control-Allow-Origin: *\r\n" "Access-Control-Allow-Origin: *\r\n"
"\r\n" "\r\n"
"%s", json11::Json(info).dump().data()); "%s", nlohmann::json(info).dump().data());
nc->flags |= MG_F_SEND_AND_CLOSE; nc->flags |= MG_F_SEND_AND_CLOSE;
} }
@ -909,7 +909,7 @@ namespace Components
Dvar::Register<const char*>("ui_dl_transRate", "", Game::DVAR_NONE, ""); Dvar::Register<const char*>("ui_dl_transRate", "", Game::DVAR_NONE, "");
}, Scheduler::Pipeline::MAIN); }, 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(); 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)."); 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::Pipeline::MAIN);
Scheduler::Loop([] Script::AddFunction("HttpGet", Script::ShowDeprecationWarning);
{ Script::AddFunction("HttpCancel", Script::ShowDeprecationWarning);
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;
}
}
});
} }
Download::~Download() Download::~Download()
@ -1019,7 +950,5 @@ namespace Components
{ {
Download::CLDownload.clear(); Download::CLDownload.clear();
} }
Download::ScriptDownloads.clear();
} }
} }

View File

@ -1,4 +1,5 @@
#pragma once #pragma once
#include <mongoose.h>
namespace Components namespace Components
{ {
@ -81,132 +82,8 @@ namespace Components
size_t receivedBytes; 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 mg_mgr Mgr;
static ClientDownload CLDownload; static ClientDownload CLDownload;
static std::vector<std::shared_ptr<ScriptDownload>> ScriptDownloads;
static std::thread ServerThread; static std::thread ServerThread;
static bool Terminate; static bool Terminate;
static bool ServerRunning; static bool ServerRunning;

View File

@ -4,33 +4,33 @@ namespace Components
{ {
const char* Dvar::ArchiveDvarPath = "userraw/archivedvars.cfg"; 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 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)); Game::DvarSetSource::DVAR_SOURCE_INTERNAL));
} }
} }
template <> Game::dvar_t* Dvar::Var::get() template <> Game::dvar_t* Dvar::Var::get()
{ {
return this->dvar; return this->dvar_;
} }
template <> const char* Dvar::Var::get() template <> const char* Dvar::Var::get()
{ {
if (this->dvar == nullptr) if (this->dvar_ == nullptr)
return ""; return "";
if (this->dvar->type == Game::dvar_type::DVAR_TYPE_STRING if (this->dvar_->type == Game::DVAR_TYPE_STRING
|| this->dvar->type == Game::dvar_type::DVAR_TYPE_ENUM) || this->dvar_->type == Game::DVAR_TYPE_ENUM)
{ {
if (this->dvar->current.string != nullptr) if (this->dvar_->current.string != nullptr)
return this->dvar->current.string; return this->dvar_->current.string;
} }
return ""; return "";
@ -38,12 +38,12 @@ namespace Components
template <> int Dvar::Var::get() template <> int Dvar::Var::get()
{ {
if (this->dvar == nullptr) if (this->dvar_ == nullptr)
return 0; 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; return 0;
@ -51,12 +51,12 @@ namespace Components
template <> unsigned int Dvar::Var::get() template <> unsigned int Dvar::Var::get()
{ {
if (this->dvar == nullptr) if (this->dvar_ == nullptr)
return 0; 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; return 0;
@ -64,12 +64,12 @@ namespace Components
template <> float Dvar::Var::get() template <> float Dvar::Var::get()
{ {
if (this->dvar == nullptr) if (this->dvar_ == nullptr)
return 0.f; 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; return 0.f;
@ -79,13 +79,13 @@ namespace Components
{ {
static Game::vec4_t vector{0.f, 0.f, 0.f, 0.f}; static Game::vec4_t vector{0.f, 0.f, 0.f, 0.f};
if (this->dvar == nullptr) if (this->dvar_ == nullptr)
return vector; return vector;
if (this->dvar->type == Game::dvar_type::DVAR_TYPE_FLOAT_2 || this->dvar->type == Game::dvar_type::DVAR_TYPE_FLOAT_3 if (this->dvar_->type == Game::DVAR_TYPE_FLOAT_2 || this->dvar_->type == Game::DVAR_TYPE_FLOAT_3
|| this->dvar->type == Game::dvar_type::DVAR_TYPE_FLOAT_4) || this->dvar_->type == Game::DVAR_TYPE_FLOAT_4)
{ {
return this->dvar->current.vector; return this->dvar_->current.vector;
} }
return vector; return vector;
@ -93,12 +93,12 @@ namespace Components
template <> bool Dvar::Var::get() template <> bool Dvar::Var::get()
{ {
if (this->dvar == nullptr) if (this->dvar_ == nullptr)
return false; 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; return false;
@ -111,10 +111,10 @@ namespace Components
void Dvar::Var::set(const char* string) void Dvar::Var::set(const char* string)
{ {
assert(this->dvar->type == Game::DVAR_TYPE_STRING); assert(this->dvar_->type == Game::DVAR_TYPE_STRING);
if (this->dvar) 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) void Dvar::Var::set(int integer)
{ {
assert(this->dvar->type == Game::DVAR_TYPE_INT); assert(this->dvar_->type == Game::DVAR_TYPE_INT);
if (this->dvar)
if (this->dvar_)
{ {
Game::Dvar_SetInt(this->dvar, integer); Game::Dvar_SetInt(this->dvar_, integer);
} }
} }
void Dvar::Var::set(float value) void Dvar::Var::set(float value)
{ {
assert(this->dvar->type == Game::DVAR_TYPE_FLOAT); assert(this->dvar_->type == Game::DVAR_TYPE_FLOAT);
if (this->dvar)
if (this->dvar_)
{ {
Game::Dvar_SetFloat(this->dvar, value); Game::Dvar_SetFloat(this->dvar_, value);
} }
} }
void Dvar::Var::set(bool enabled) void Dvar::Var::set(bool enabled)
{ {
assert(this->dvar->type == Game::DVAR_TYPE_BOOL); assert(this->dvar_->type == Game::DVAR_TYPE_BOOL);
if (this->dvar)
if (this->dvar_)
{ {
Game::Dvar_SetBool(this->dvar, enabled); Game::Dvar_SetBool(this->dvar_, enabled);
} }
} }
void Dvar::Var::setRaw(int integer) void Dvar::Var::setRaw(int integer)
{ {
assert(this->dvar->type == Game::DVAR_TYPE_INT); assert(this->dvar_->type == Game::DVAR_TYPE_INT);
if (this->dvar)
if (this->dvar_)
{ {
this->dvar->current.integer = integer; this->dvar_->current.integer = integer;
this->dvar->latched.integer = integer; this->dvar_->latched.integer = integer;
} }
} }
void Dvar::Var::setRaw(float value) void Dvar::Var::setRaw(float value)
{ {
assert(this->dvar->type == Game::DVAR_TYPE_FLOAT); assert(this->dvar_->type == Game::DVAR_TYPE_FLOAT);
if (this->dvar)
if (this->dvar_)
{ {
this->dvar->current.value = value; this->dvar_->current.value = value;
this->dvar->latched.value = value; this->dvar_->latched.value = value;
} }
} }
void Dvar::Var::setRaw(bool enabled) void Dvar::Var::setRaw(bool enabled)
{ {
assert(this->dvar->type == Game::DVAR_TYPE_BOOL); assert(this->dvar_->type == Game::DVAR_TYPE_BOOL);
if (this->dvar)
if (this->dvar_)
{ {
this->dvar->current.enabled = enabled; this->dvar_->current.enabled = enabled;
this->dvar->latched.enabled = enabled; this->dvar_->latched.enabled = enabled;
} }
} }

View File

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

View File

@ -15,8 +15,11 @@ namespace Components
int handle; int handle;
const auto len = Game::FS_FOpenFileReadForThread(filePath.data(), &handle, thread); const auto len = Game::FS_FOpenFileReadForThread(filePath.data(), &handle, thread);
if (handle) if (!handle)
{ {
return;
}
auto* buf = AllocateFile(len + 1); auto* buf = AllocateFile(len + 1);
[[maybe_unused]] auto bytesRead = Game::FS_Read(buf, len, handle); [[maybe_unused]] auto bytesRead = Game::FS_Read(buf, len, handle);
@ -30,7 +33,6 @@ namespace Components
this->buffer.append(buf, len); this->buffer.append(buf, len);
FreeFile(buf); FreeFile(buf);
} }
}
void FileSystem::RawFile::read() 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> FileSystem::GetFileList(const std::string& path, const std::string& extension)
{ {
std::vector<std::string> fileList; std::vector<std::string> fileList;
int numFiles = 0; auto numFiles = 0;
char** files = Game::FS_GetFileList(path.data(), extension.data(), Game::FS_LIST_PURE_ONLY, &numFiles, 0); const auto** files = Game::FS_ListFiles(path.data(), extension.data(), Game::FS_LIST_PURE_ONLY, &numFiles, 10);
if (files) if (files)
{ {
@ -151,11 +169,11 @@ namespace Components
{ {
if (files[i]) if (files[i])
{ {
fileList.push_back(files[i]); fileList.emplace_back(files[i]);
} }
} }
Game::FS_FreeFileList(files); Game::FS_FreeFileList(files, 10);
} }
return fileList; return fileList;
@ -165,8 +183,8 @@ namespace Components
{ {
std::vector<std::string> fileList; std::vector<std::string> fileList;
int numFiles = 0; auto numFiles = 0;
char** files = Game::Sys_ListFiles(path.data(), extension.data(), nullptr, &numFiles, folders); const auto** files = Game::Sys_ListFiles(path.data(), extension.data(), nullptr, &numFiles, folders);
if (files) if (files)
{ {
@ -174,7 +192,7 @@ namespace Components
{ {
if (files[i]) if (files[i])
{ {
fileList.push_back(files[i]); fileList.emplace_back(files[i]);
} }
} }
@ -184,9 +202,9 @@ namespace Components
return fileList; 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)); 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); return Game::FS_Remove(path);
} }
@ -225,9 +243,9 @@ namespace Components
void FileSystem::RegisterFolder(const char* folder) void FileSystem::RegisterFolder(const char* folder)
{ {
std::string fs_cdpath = Dvar::Var("fs_cdpath").get<std::string>(); const auto fs_cdpath = Dvar::Var("fs_cdpath").get<std::string>();
std::string fs_basepath = Dvar::Var("fs_basepath").get<std::string>(); const auto fs_basepath = Dvar::Var("fs_basepath").get<std::string>();
std::string fs_homepath = Dvar::Var("fs_homepath").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_cdpath.empty()) Game::FS_AddLocalizedGameDirectory(fs_cdpath.data(), folder);
if (!fs_basepath.empty()) Game::FS_AddLocalizedGameDirectory(fs_basepath.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); 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::FileSystem()
{ {
FileSystem::MemAllocator.clear(); FileSystem::MemAllocator.clear();
@ -352,6 +376,9 @@ namespace Components
// Handle IWD freeing // Handle IWD freeing
Utils::Hook(0x642F60, FileSystem::IwdFreeStub, HOOK_CALL).install()->quick(); 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() FileSystem::~FileSystem()

View File

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

View File

@ -126,23 +126,15 @@ 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) void Friends::UpdateServer(Network::Address server, const std::string& hostname, const std::string& mapname)
{ {
@ -424,15 +416,14 @@ namespace Components
{ {
return Utils::String::VA("%s", user.name.data()); 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()); 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: case 2:
{ {
if (!user.online) return "Offline"; if (!user.online) return "Offline";
@ -613,12 +604,12 @@ namespace Components
// Show blue icons on the minimap // Show blue icons on the minimap
Utils::Hook(0x493130, Friends::IsClientInParty, HOOK_JUMP).install()->quick(); 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(); 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); std::lock_guard<std::recursive_mutex> _(Friends::Mutex);
if (Friends::CurrentFriend >= Friends::FriendsList.size()) return; if (Friends::CurrentFriend >= Friends::FriendsList.size()) return;
@ -660,7 +651,7 @@ namespace Components
if (Friends::TriggerUpdate) if (Friends::TriggerUpdate)
{ {
Friends::TriggerUpdate = false; Friends::TriggerUpdate = false;
Friends::UpdateState(true); Friends::UpdateState();
} }
} }

View File

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

View File

@ -1,5 +1,6 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "Int64.hpp"
#include "IO.hpp" #include "IO.hpp"
#include "Script.hpp" #include "Script.hpp"
#include "ScriptExtension.hpp" #include "ScriptExtension.hpp"
@ -9,6 +10,7 @@ namespace Components
{ {
GSC::GSC() GSC::GSC()
{ {
Loader::Register(new Int64());
Loader::Register(new IO()); Loader::Register(new IO());
Loader::Register(new Script()); Loader::Register(new Script());
Loader::Register(new ScriptExtension()); 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) 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 // Allow error messages to be printed if developer mode is on
// Should check scrVarPub.developer but it's absent // Should check scrVarPub.developer but it's absent
// in this version of the game so let's check the dvar // 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; return;
// If were are developing let's call RuntimeErrorInternal // If were are developing let's call RuntimeErrorInternal
@ -227,42 +225,44 @@ namespace Components
Game::Scr_StartupGameType(); Game::Scr_StartupGameType();
} }
// Do not use C++ objects because Scr_LoadScript may longjmp
void Script::GScr_LoadGameTypeScript_Stub() void Script::GScr_LoadGameTypeScript_Stub()
{ {
// Clear handles (from previous GSC loading session) // Clear handles (from previous GSC loading session)
Script::ScriptMainHandles.clear(); Script::ScriptMainHandles.clear();
Script::ScriptInitHandles.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);
std::string script = "scripts/" + file;
if (Utils::String::EndsWith(script, ".gsc")) for (auto i = 0; i < numFiles; ++i)
{ {
script = script.substr(0, script.size() - 4); const auto* scriptFile = files[i];
Logger::Print("Loading script {}...\n", scriptFile);
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))
{
Logger::Print("Script {} encountered an error while loading. (doesn't exist?)", path);
continue;
} }
Logger::Print("Loading script {}.gsc...\n", script); Logger::Print("Script {}.gsc loaded successfully.\n", path);
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::Debug("Finding script handle main or init..."); 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) if (initHandle != 0)
{ {
Script::ScriptInitHandles.push_back(initHandle); 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) if (mainHandle != 0)
{ {
Script::ScriptMainHandles.push_back(mainHandle); Script::ScriptMainHandles.push_back(mainHandle);
@ -271,6 +271,7 @@ namespace Components
// Allow scripts with no handles // Allow scripts with no handles
} }
Game::FS_FreeFileList(files, 10);
Game::GScr_LoadGameTypeScript(); Game::GScr_LoadGameTypeScript();
} }
@ -397,15 +398,13 @@ namespace Components
{ {
// execute our hook // execute our hook
pushad pushad
call Script::StoreScriptBaseProgramNum call Script::StoreScriptBaseProgramNum
popad popad
// execute overwritten code caused by the jump hook // execute overwritten code caused by the jump hook
sub eax, ds:201A460h // gScrVarPub_programBuffer sub eax, ds:201A460h // gScrVarPub_programBuffer
add esp, 0Ch add esp, 0Ch
mov ds : 1CFEEF8h, eax // gScrCompilePub_programLen mov ds:1CFEEF8h, eax // gScrCompilePub_programLen
// jump back to the original code // jump back to the original code
push 426C3Bh push 426C3Bh
@ -535,6 +534,14 @@ namespace Components
return &Game::svs_clients[ent->s.number]; 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() void Script::AddFunctions()
{ {
Script::AddFunction("ReplaceFunc", [] // gsc: ReplaceFunc(<function>, <function>) Script::AddFunction("ReplaceFunc", [] // gsc: ReplaceFunc(<function>, <function>)
@ -632,7 +639,7 @@ namespace Components
Utils::Hook(0x61E92E, Script::VMExecuteInternalStub, HOOK_JUMP).install()->quick(); Utils::Hook(0x61E92E, Script::VMExecuteInternalStub, HOOK_JUMP).install()->quick();
Utils::Hook::Nop(0x61E933, 1); Utils::Hook::Nop(0x61E933, 1);
Scheduler::Loop([]() Scheduler::Loop([]
{ {
if (!Game::SV_Loaded()) if (!Game::SV_Loaded())
return; return;
@ -641,12 +648,13 @@ namespace Components
if (Script::LastFrameTime != -1) if (Script::LastFrameTime != -1)
{ {
const auto timeScale = Dvar::Var("timescale").get<float>(); const auto timeTaken = (nowMs - Script::LastFrameTime) * static_cast<int>((*Game::com_timescale)->current.value);
const auto timeTaken = static_cast<int>((nowMs - Script::LastFrameTime) * timeScale);
if (timeTaken >= 500) if (timeTaken >= 500)
{
Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "Hitch warning: {} msec frame time\n", timeTaken); Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "Hitch warning: {} msec frame time\n", timeTaken);
} }
}
Script::LastFrameTime = nowMs; Script::LastFrameTime = nowMs;
}, Scheduler::Pipeline::SERVER); }, Scheduler::Pipeline::SERVER);

View File

@ -14,6 +14,8 @@ namespace Components
static const char* GetCodePosForParam(int index); static const char* GetCodePosForParam(int index);
static void ShowDeprecationWarning();
private: private:
struct ScriptFunction struct ScriptFunction
{ {
@ -59,7 +61,6 @@ namespace Components
static void Scr_StartupGameType_Stub(); static void Scr_StartupGameType_Stub();
static void GScr_LoadGameTypeScript_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::BuiltinFunction BuiltIn_GetFunctionStub(const char** pName, int* type);
static Game::BuiltinMethod BuiltIn_GetMethodStub(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 // Func present on IW5
Script::AddFunction("IsEndStr", [] // gsc: IsEndStr(<string>, <string>) Script::AddFunction("IsEndStr", [] // gsc: IsEndStr(<string>, <string>)
{ {
const auto* s1 = Game::Scr_GetString(0); const auto* str = Game::Scr_GetString(0);
const auto* s2 = Game::Scr_GetString(1); const auto* suffix = Game::Scr_GetString(1);
if (s1 == nullptr || s2 == nullptr) if (str == nullptr || suffix == nullptr)
{ {
Game::Scr_Error("^1IsEndStr: Illegal parameters!\n"); Game::Scr_Error("^1IsEndStr: Illegal parameters!\n");
return; return;
} }
Game::Scr_AddBool(Utils::String::EndsWith(s1, s2)); Game::Scr_AddBool(Utils::String::EndsWith(str, suffix));
}); });
Script::AddFunction("IsArray", [] // gsc: IsArray(<object>) Script::AddFunction("IsArray", [] // gsc: IsArray(<object>)
@ -206,6 +206,41 @@ namespace Components
Game::Scr_AddBool(result); 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() void ScriptExtension::AddMethods()
@ -216,7 +251,7 @@ namespace Components
const auto* ent = Game::GetPlayerEntity(entref); const auto* ent = Game::GetPlayerEntity(entref);
const auto* client = Script::GetClient(ent); 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) if (const auto pos = ip.find_first_of(":"); pos != std::string::npos)
ip.erase(ip.begin() + pos, ip.end()); // Erase port ip.erase(ip.begin() + pos, ip.end()); // Erase port
@ -241,7 +276,7 @@ namespace Components
const auto* ent = Game::GetPlayerEntity(entref); const auto* ent = Game::GetPlayerEntity(entref);
auto* client = Script::GetClient(ent); auto* client = Script::GetClient(ent);
client->ping = static_cast<int16_t>(ping); client->ping = ping;
}); });
} }

View File

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

View File

@ -169,8 +169,8 @@ namespace Components
{Game::K_APAD_RIGHT, Game::K_RIGHTARROW}, {Game::K_APAD_RIGHT, Game::K_RIGHTARROW},
}; };
Gamepad::GamePad Gamepad::gamePads[Game::MAX_GAMEPADS]{}; Gamepad::GamePad Gamepad::gamePads[Game::MAX_GPAD_COUNT]{};
Gamepad::GamePadGlobals Gamepad::gamePadGlobals[Game::MAX_GAMEPADS]{{}}; Gamepad::GamePadGlobals Gamepad::gamePadGlobals[Game::MAX_GPAD_COUNT]{{}};
int Gamepad::gamePadBindingsModifiedFlags = 0; int Gamepad::gamePadBindingsModifiedFlags = 0;
Dvar::Var Gamepad::gpad_enabled; Dvar::Var Gamepad::gpad_enabled;
@ -241,12 +241,12 @@ namespace Components
mov dl, byte ptr[edi + 1Ah] // to_forwardMove mov dl, byte ptr[edi + 1Ah] // to_forwardMove
mov dh, byte ptr[edi + 1Bh] // to_rightMove 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 dl, byte ptr [ebp + 1Ah] // from_forwardMove
mov dh, byte ptr[ebp + 1Bh] // from_rightMove mov dh, byte ptr [ebp + 1Bh] // from_rightMove
mov[esp + 2Ch], dx // from_buttons mov [esp + 2Ch], dx // from_buttons
// return back // return back
push 0x60E40E push 0x60E40E
@ -261,7 +261,7 @@ namespace Components
if (Game::MSG_ReadBit(msg)) 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); forward = static_cast<char>(movementBits);
right = static_cast<char>(movementBits >> 8); right = static_cast<char>(movementBits >> 8);
@ -314,7 +314,8 @@ namespace Components
bool Gamepad::GPad_Check(const int gamePadIndex, const int portIndex) 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]; auto& gamePad = gamePads[gamePadIndex];
if (XInputGetCapabilities(portIndex, XINPUT_FLAG_GAMEPAD, &gamePad.caps) == ERROR_SUCCESS) if (XInputGetCapabilities(portIndex, XINPUT_FLAG_GAMEPAD, &gamePad.caps) == ERROR_SUCCESS)
@ -332,7 +333,7 @@ namespace Components
{ {
auto currentGamePadNum = 0; 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)) if (GPad_Check(currentGamePadNum, currentPort))
currentGamePadNum++; currentGamePadNum++;
@ -366,7 +367,7 @@ namespace Components
bool Gamepad::AimAssist_IsPlayerUsingOffhand(Game::AimAssistPlayerState* ps) bool Gamepad::AimAssist_IsPlayerUsingOffhand(Game::AimAssistPlayerState* ps)
{ {
// Check offhand flag // Check offhand flag
if ((ps->weapFlags & 2) == 0) if ((ps->weapFlags & Game::PWF_USING_OFFHAND) == 0)
return false; return false;
// If offhand weapon has no id we are not using one // If offhand weapon has no id we are not using one
@ -421,7 +422,8 @@ namespace Components
bool Gamepad::AimAssist_IsLockonActive(const int gamePadIndex) bool Gamepad::AimAssist_IsLockonActive(const int gamePadIndex)
{ {
assert(gamePadIndex < Game::MAX_GAMEPADS); AssertIn(gamePadIndex, Game::MAX_GPAD_COUNT);
auto& aaGlob = Game::aaGlobArray[gamePadIndex]; auto& aaGlob = Game::aaGlobArray[gamePadIndex];
if (!aim_lockon_enabled.get<bool>() || !gpad_lockon_enabled.get<bool>()) 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) void Gamepad::AimAssist_ApplyLockOn(const Game::AimInput* input, Game::AimOutput* output)
{ {
assert(input); assert(input);
assert(input->localClientNum < Game::MAX_GAMEPADS); assert(output);
AssertIn(input->localClientNum, Game::STATIC_MAX_LOCAL_CLIENTS);
auto& aaGlob = Game::aaGlobArray[input->localClientNum]; auto& aaGlob = Game::aaGlobArray[input->localClientNum];
const auto prevTargetEnt = aaGlob.lockOnTargetEnt; const auto prevTargetEnt = aaGlob.lockOnTargetEnt;
@ -554,10 +558,11 @@ namespace Components
void Gamepad::AimAssist_CalcSlowdown(const Game::AimInput* input, float* pitchScale, float* yawScale) void Gamepad::AimAssist_CalcSlowdown(const Game::AimInput* input, float* pitchScale, float* yawScale)
{ {
assert(input); assert(input);
assert(input->localClientNum < Game::MAX_GAMEPADS);
auto& aaGlob = Game::aaGlobArray[input->localClientNum];
assert(pitchScale); assert(pitchScale);
assert(yawScale); assert(yawScale);
AssertIn(input->localClientNum, Game::STATIC_MAX_LOCAL_CLIENTS);
auto& aaGlob = Game::aaGlobArray[input->localClientNum];
*pitchScale = 1.0f; *pitchScale = 1.0f;
*yawScale = 1.0f; *yawScale = 1.0f;
@ -586,7 +591,10 @@ namespace Components
void Gamepad::AimAssist_ApplyTurnRates(const Game::AimInput* input, Game::AimOutput* output) 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& aaGlob = Game::aaGlobArray[input->localClientNum];
auto slowdownPitchScale = 0.0f; auto slowdownPitchScale = 0.0f;
@ -649,7 +657,8 @@ namespace Components
void Gamepad::AimAssist_UpdateGamePadInput(const Game::AimInput* input, Game::AimOutput* output) 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]; auto& aaGlob = Game::aaGlobArray[input->localClientNum];
output->pitch = input->pitch; output->pitch = input->pitch;
@ -784,8 +793,8 @@ namespace Components
float Gamepad::CL_GamepadAxisValue(const int gamePadIndex, const Game::GamepadVirtualAxis virtualAxis) 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); assert(virtualAxis > Game::GPAD_VIRTAXIS_NONE && virtualAxis < Game::GPAD_VIRTAXIS_COUNT);
const auto& gamePadGlobal = gamePadGlobals[gamePadIndex]; const auto& gamePadGlobal = gamePadGlobals[gamePadIndex];
const auto& [physicalAxis, mapType] = gamePadGlobal.axes.virtualAxes[virtualAxis]; 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) 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& gamePad = gamePads[gamePadIndex];
auto& clientActive = Game::clients[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) 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]; auto& gamePadGlobal = gamePadGlobals[gamePadIndex];
if (!down) if (!down)
@ -979,8 +990,8 @@ namespace Components
void Gamepad::CL_GamepadGenerateAPad(const int gamePadIndex, const Game::GamepadPhysicalAxis physicalAxis, unsigned time) void Gamepad::CL_GamepadGenerateAPad(const int gamePadIndex, const Game::GamepadPhysicalAxis physicalAxis, unsigned time)
{ {
assert(gamePadIndex < Game::MAX_GAMEPADS); AssertIn(gamePadIndex, Game::MAX_GPAD_COUNT);
assert(physicalAxis < Game::GPAD_PHYSAXIS_COUNT && physicalAxis >= 0); assert(physicalAxis >= 0 && physicalAxis < Game::GPAD_PHYSAXIS_COUNT);
auto& gamePad = gamePads[gamePadIndex]; 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) void Gamepad::CL_GamepadEvent(const int gamePadIndex, const Game::GamepadPhysicalAxis physicalAxis, const float value, const unsigned time)
{ {
assert(gamePadIndex < Game::MAX_GAMEPADS); AssertIn(gamePadIndex, Game::MAX_GPAD_COUNT);
assert(physicalAxis < Game::GPAD_PHYSAXIS_COUNT && physicalAxis >= 0); assert(physicalAxis >= 0 && physicalAxis < Game::GPAD_PHYSAXIS_COUNT);
auto& gamePad = gamePads[gamePadIndex]; auto& gamePad = gamePads[gamePadIndex];
auto& gamePadGlobal = gamePadGlobals[gamePadIndex]; auto& gamePadGlobal = gamePadGlobals[gamePadIndex];
@ -1054,7 +1065,8 @@ namespace Components
bool Gamepad::Scoreboard_HandleInput(int gamePadIndex, int key) 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]; auto& keyState = Game::playerKeys[gamePadIndex];
if (keyState.keys[key].binding && strcmp(keyState.keys[key].binding, "togglescores") == 0) 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) 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]; auto& gamePadGlobal = gamePadGlobals[gamePadIndex];
if (Game::Key_IsKeyCatcherActive(gamePadIndex, Game::KEYCATCH_UI)) 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) 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 pressed = buttonEvent == Game::GPAD_BUTTON_PRESSED;
const auto pressedOrUpdated = pressed || buttonEvent == Game::GPAD_BUTTON_UPDATE; 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) void Gamepad::CL_GamepadButtonEventForPort(const int gamePadIndex, const int key, const Game::GamePadButtonEvent buttonEvent, const unsigned time)
{ {
assert(gamePadIndex < Game::MAX_GAMEPADS); AssertIn(gamePadIndex, Game::MAX_GPAD_COUNT);
auto& gamePad = gamePads[gamePadIndex];
auto& gamePad = gamePads[gamePadIndex];
gamePad.inUse = true; gamePad.inUse = true;
gpad_in_use.setRaw(true); gpad_in_use.setRaw(true);
@ -1244,7 +1257,9 @@ namespace Components
float Gamepad::GPad_GetStick(const int gamePadIndex, const Game::GamePadStick stick) 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]; auto& gamePad = gamePads[gamePadIndex];
return gamePad.sticks[stick]; return gamePad.sticks[stick];
@ -1252,7 +1267,7 @@ namespace Components
float Gamepad::GPad_GetButton(const int gamePadIndex, Game::GamePadButton button) 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]; auto& gamePad = gamePads[gamePadIndex];
float value = 0.0f; float value = 0.0f;
@ -1276,7 +1291,9 @@ namespace Components
bool Gamepad::GPad_IsButtonPressed(const int gamePadIndex, Game::GamePadButton button) 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]; auto& gamePad = gamePads[gamePadIndex];
bool down = false; bool down = false;
@ -1310,7 +1327,8 @@ namespace Components
bool Gamepad::GPad_IsButtonReleased(int gamePadIndex, Game::GamePadButton button) bool Gamepad::GPad_IsButtonReleased(int gamePadIndex, Game::GamePadButton button)
{ {
assert(gamePadIndex < Game::MAX_GAMEPADS); AssertIn(gamePadIndex, Game::MAX_GPAD_COUNT);
auto& gamePad = gamePads[gamePadIndex]; auto& gamePad = gamePads[gamePadIndex];
bool down = false; bool down = false;
@ -1340,7 +1358,8 @@ namespace Components
void Gamepad::GPad_UpdateSticksDown(const int gamePadIndex) void Gamepad::GPad_UpdateSticksDown(const int gamePadIndex)
{ {
assert(gamePadIndex < Game::MAX_GAMEPADS); AssertIn(gamePadIndex, Game::MAX_GPAD_COUNT);
auto& gamePad = gamePads[gamePadIndex]; auto& gamePad = gamePads[gamePadIndex];
for (auto stickIndex = 0u; stickIndex < std::extent_v<decltype(GamePad::sticks)>; stickIndex++) 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) 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]; auto& gamePad = gamePads[gamePadIndex];
@ -1403,7 +1422,7 @@ namespace Components
void Gamepad::GPad_UpdateDigitals(const int gamePadIndex, const XINPUT_GAMEPAD& state) 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]; auto& gamePad = gamePads[gamePadIndex];
@ -1425,7 +1444,7 @@ namespace Components
void Gamepad::GPad_UpdateAnalogs(const int gamePadIndex, const XINPUT_GAMEPAD& state) 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]; auto& gamePad = gamePads[gamePadIndex];
@ -1452,7 +1471,7 @@ namespace Components
{ {
GPad_RefreshAll(); 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]; const auto& gamePad = gamePads[currentGamePadIndex];
if (!gamePad.enabled) if (!gamePad.enabled)
@ -1477,7 +1496,7 @@ namespace Components
const auto time = Game::Sys_Milliseconds(); const auto time = Game::Sys_Milliseconds();
bool gpadPresent = false; 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]; const auto& gamePad = gamePads[gamePadIndex];
@ -1540,7 +1559,8 @@ namespace Components
void Gamepad::Gamepad_WriteBindings(const int gamePadIndex, const int handle) 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]; auto& gamePadGlobal = gamePadGlobals[gamePadIndex];
Game::FS_Printf(handle, "unbindallaxis\n"); Game::FS_Printf(handle, "unbindallaxis\n");
@ -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) 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(realIndex > Game::GPAD_PHYSAXIS_NONE && realIndex < Game::GPAD_PHYSAXIS_COUNT);
assert(axisIndex > Game::GPAD_VIRTAXIS_NONE && axisIndex < Game::GPAD_VIRTAXIS_COUNT); assert(axisIndex > Game::GPAD_VIRTAXIS_NONE && axisIndex < Game::GPAD_VIRTAXIS_COUNT);
assert(mapType > Game::GPAD_MAP_NONE && mapType < Game::GPAD_MAP_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 void OnMouseMove(int x, int y, int dx, int dy);
static Dvar::Var sv_allowAimAssist;
private: private:
static Game::ButtonToCodeMap_t buttonList[]; static Game::ButtonToCodeMap_t buttonList[];
static Game::StickToCodeMap_t analogStickList[4]; static Game::StickToCodeMap_t analogStickList[4];
@ -60,8 +62,8 @@ namespace Components
static Game::keyname_t combinedLocalizedKeyNamesPs3[]; static Game::keyname_t combinedLocalizedKeyNamesPs3[];
static ControllerMenuKeyMapping controllerMenuKeyMappings[]; static ControllerMenuKeyMapping controllerMenuKeyMappings[];
static GamePad gamePads[Game::MAX_GAMEPADS]; static GamePad gamePads[Game::MAX_GPAD_COUNT];
static GamePadGlobals gamePadGlobals[Game::MAX_GAMEPADS]; static GamePadGlobals gamePadGlobals[Game::MAX_GPAD_COUNT];
static int gamePadBindingsModifiedFlags; static int gamePadBindingsModifiedFlags;
@ -87,7 +89,6 @@ namespace Components
static Dvar::Var gpad_slowdown_enabled; static Dvar::Var gpad_slowdown_enabled;
static Dvar::Var input_viewSensitivity; static Dvar::Var input_viewSensitivity;
static Dvar::Var input_invertPitch; static Dvar::Var input_invertPitch;
static Dvar::Var sv_allowAimAssist;
static Dvar::Var aim_turnrate_pitch; static Dvar::Var aim_turnrate_pitch;
static Dvar::Var aim_turnrate_pitch_ads; static Dvar::Var aim_turnrate_pitch_ads;
static Dvar::Var aim_turnrate_yaw; static Dvar::Var aim_turnrate_yaw;

View File

@ -2,46 +2,48 @@
namespace Components namespace Components
{ {
Dvar::Var Lean::BGLean;
Game::kbutton_t Lean::in_leanleft; Game::kbutton_t Lean::in_leanleft;
Game::kbutton_t Lean::in_leanright; Game::kbutton_t Lean::in_leanright;
void Lean::IN_LeanLeft_Up() void Lean::IN_LeanLeft_Up()
{ {
Game::IN_KeyUp(&Lean::in_leanleft); Game::IN_KeyUp(&in_leanleft);
} }
void Lean::IN_LeanLeft_Down() void Lean::IN_LeanLeft_Down()
{ {
Game::IN_KeyDown(&Lean::in_leanleft); Game::IN_KeyDown(&in_leanleft);
} }
void Lean::IN_LeanRight_Up() void Lean::IN_LeanRight_Up()
{ {
Game::IN_KeyUp(&Lean::in_leanright); Game::IN_KeyUp(&in_leanright);
} }
void Lean::IN_LeanRight_Down() 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; in_leanleft.wasPressed = false;
Lean::in_leanright.wasPressed = false; in_leanright.wasPressed = false;
} }
void __declspec(naked) Lean::CL_CmdButtonsStub() void __declspec(naked) Lean::CL_CmdButtons_Stub()
{ {
__asm __asm
{ {
@ -51,21 +53,35 @@ namespace Components
pushad pushad
push esi push esi
call Lean::SetLeanFlags call SetLeanFlags
pop esi pop esi
popad popad
retn 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() Lean::Lean()
{ {
Command::AddRaw("+leanleft", Lean::IN_LeanLeft_Down, true); Command::AddRaw("+leanleft", IN_LeanLeft_Down, true);
Command::AddRaw("-leanleft", Lean::IN_LeanLeft_Up, true); Command::AddRaw("-leanleft", IN_LeanLeft_Up, true);
Command::AddRaw("+leanright", Lean::IN_LeanRight_Down, true); Command::AddRaw("+leanright", IN_LeanRight_Down, true);
Command::AddRaw("-leanright", Lean::IN_LeanRight_Up, 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 #pragma once
#define BUTTON_FLAG_LEANLEFT 0x40
#define BUTTON_FLAG_LEANRIGHT 0x80
namespace Components namespace Components
{ {
class Lean : public Component class Lean : public Component
@ -10,6 +7,8 @@ namespace Components
public: public:
Lean(); Lean();
static Dvar::Var BGLean;
private: private:
static Game::kbutton_t in_leanleft; static Game::kbutton_t in_leanleft;
static Game::kbutton_t in_leanright; static Game::kbutton_t in_leanright;
@ -20,7 +19,9 @@ namespace Components
static void IN_LeanRight_Up(); static void IN_LeanRight_Up();
static void IN_LeanRight_Down(); static void IN_LeanRight_Down();
static void CL_CmdButtonsStub(); static void CL_CmdButtons_Stub();
static void SetLeanFlags(Game::usercmd_s* cmds); 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); 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::Localization()
{ {
Localization::SetCredits(); Localization::SetCredits();
@ -279,6 +417,9 @@ namespace Components
// Overwrite SetString // Overwrite SetString
Utils::Hook(0x4CE5EE, Localization::SetStringStub, HOOK_CALL).install()->quick(); 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"); Localization::UseLocalization = Dvar::Register<bool>("ui_localize", true, Game::DVAR_NONE, "Use localization strings");
// Generate localized entries for custom classes above 10 // Generate localized entries for custom classes above 10

View File

@ -24,5 +24,7 @@ namespace Components
static void LoadLanguageStrings(); static void LoadLanguageStrings();
static void SELoadLanguageStub(); static void SELoadLanguageStub();
static void SetCredits(); 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; std::string out = msg;
// Filter out coloured strings // Filter out coloured strings for stdout
if (out[0] == '^' && out[1] != '\0') if (out[0] == '^' && out[1] != '\0')
{ {
out = out.substr(2); out = out.substr(2);
@ -48,11 +48,11 @@ namespace Components
if (!Game::Sys_IsMainThread()) if (!Game::Sys_IsMainThread())
{ {
Logger::EnqueueMessage(out); Logger::EnqueueMessage(msg);
} }
else 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()) 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; 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); Logger::LoggingAddresses[0].erase(addr);
} }
else else
@ -295,11 +295,11 @@ namespace Components
if (i != Logger::LoggingAddresses[0].end()) if (i != Logger::LoggingAddresses[0].end())
{ {
Logger::LoggingAddresses[0].erase(i); Logger::LoggingAddresses[0].erase(i);
Logger::Print("Address {} removed\n", addr.getCString()); Logger::Print("Address {} removed\n", addr.getString());
} }
else 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) 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()) 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; 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); Logger::LoggingAddresses[1].erase(addr);
} }
else else
@ -346,11 +346,11 @@ namespace Components
if (i != Logger::LoggingAddresses[1].end()) if (i != Logger::LoggingAddresses[1].end())
{ {
Logger::LoggingAddresses[1].erase(i); Logger::LoggingAddresses[1].erase(i);
Logger::Print("Address {} removed\n", addr.getCString()); Logger::Print("Address {} removed\n", addr.getString());
} }
else 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) 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(); lock.unlock();
// Flush the console log // 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::SVRandomMapRotation;
Dvar::Var MapRotation::SVDontRotate; 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 MapRotation::DedicatedRotation;
MapRotation::RotationData::RotationData() 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> mapVector;
std::vector<std::string> gametypeVector; std::vector<std::string> gametypeVector;
@ -80,7 +85,7 @@ namespace Components
} }
json11::Json mapRotationJson = json11::Json::object nlohmann::json mapRotationJson = nlohmann::json
{ {
{"maps", mapVector}, {"maps", mapVector},
{"gametypes", gametypeVector}, {"gametypes", gametypeVector},
@ -107,12 +112,23 @@ namespace Components
} }
catch (const std::exception& ex) 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()); 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() void MapRotation::AddMapRotationCommands()
{ {
Command::Add("addMap", [](Command::Params* params) 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() bool MapRotation::ShouldRotate()
{ {
if (!Dedicated::IsEnabled() && SVDontRotate.get<bool>()) if (!Dedicated::IsEnabled() && SVDontRotate.get<bool>())
@ -160,13 +181,13 @@ namespace Components
{ {
assert(!map.empty()); 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 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() void MapRotation::RestartCurrentMap()
{ {
std::string svMapname = (*SVMapname)->current.string; std::string svMapname = (*Game::sv_mapname)->current.string;
if (svMapname.empty()) if (svMapname.empty())
{ {
@ -224,24 +245,24 @@ namespace Components
assert(!data.empty()); assert(!data.empty());
// Ook, ook, eek // 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; RotationData rotationCurrent;
try try
{ {
Logger::Debug("Parsing {}", (*SVMapRotationCurrent)->name); Logger::Debug("Parsing {}", (*Game::sv_mapRotationCurrent)->name);
rotationCurrent.parse(data); rotationCurrent.parse(data);
} }
catch (const std::exception& ex) 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) 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(); RestartCurrentMap();
return; return;
} }
@ -272,25 +293,18 @@ namespace Components
Logger::Print(Game::CON_CHANNEL_SERVER, "Rotating map...\n"); Logger::Print(Game::CON_CHANNEL_SERVER, "Rotating map...\n");
// This takes priority because of backwards compatibility // 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()) if (!mapRotationCurrent.empty())
{ {
Logger::Debug("Applying {}", (*SVMapRotationCurrent)->name); Logger::Debug("Applying {}", (*Game::sv_mapRotationCurrent)->name);
ApplyMapRotationCurrent(mapRotationCurrent); ApplyMapRotationCurrent(mapRotationCurrent);
return; return;
} }
const std::string mapRotation = (*SVMapRotation)->current.string; LoadMapRotation();
// 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);
}
if (DedicatedRotation.getEntriesSize() == 0) 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(); RestartCurrentMap();
return; return;
} }

View File

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

View File

@ -96,12 +96,12 @@ namespace Components
const char* Maps::LoadArenaFileStub(const char* name, char* buffer, int size) 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(); const std::string mapname = Maps::UserMap.getName();
std::string arena = Utils::String::VA("usermaps/%s/%s.arena", mapname.data(), mapname.data()); const auto* arena = Utils::String::VA("usermaps/%s/%s.arena", mapname.data(), mapname.data());
if (Utils::IO::FileExists(arena)) 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; return buffer;
} }
@ -768,7 +768,7 @@ namespace Components
Maps::UpdateDlcStatus(); 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>(); int dlc = token.get<int>();

View File

@ -794,7 +794,7 @@ namespace Components
// don't load ASSET_TYPE_MENU assets for every menu (might cause patch menus to fail) // don't load ASSET_TYPE_MENU assets for every menu (might cause patch menus to fail)
Utils::Hook::Nop(0x453406, 5); Utils::Hook::Nop(0x453406, 5);
//make Com_Error and similar go back to main_text instead of menu_xboxlive. // make Com_Error and similar go back to main_text instead of menu_xboxlive.
Utils::Hook::SetString(0x6FC790, "main_text"); Utils::Hook::SetString(0x6FC790, "main_text");
Command::Add("openmenu", [](Command::Params* params) Command::Add("openmenu", [](Command::Params* params)
@ -806,7 +806,7 @@ namespace Components
} }
// Not quite sure if we want to do this if we're not ingame, but it's only needed for ingame menus. // 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>()) if ((*Game::cl_ingame)->current.enabled)
{ {
Game::Key_SetCatcher(0, 16); Game::Key_SetCatcher(0, 16);
} }
@ -859,6 +859,11 @@ namespace Components
Menus::Add("ui_mp/resetclass.menu"); Menus::Add("ui_mp/resetclass.menu");
Menus::Add("ui_mp/popup_customtitle.menu"); Menus::Add("ui_mp/popup_customtitle.menu");
Menus::Add("ui_mp/popup_customclan.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() Menus::~Menus()

View File

@ -40,7 +40,7 @@ namespace Components
ModList::CurrentMod = index; 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"; auto folder = Dvar::Var("fs_basepath").get<std::string>() + "\\mods";
Logger::Debug("Searching for mods in {}...", folder); Logger::Debug("Searching for mods in {}...", folder);
@ -48,7 +48,7 @@ namespace Components
Logger::Debug("Found {} mods!", ModList::Mods.size()); 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()) 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"); auto fsGame = Dvar::Var("fs_game");
fsGame.set(""); fsGame.set("");

View File

@ -18,8 +18,8 @@ namespace Components
static unsigned int GetItemCount(); static unsigned int GetItemCount();
static const char* GetItemText(unsigned int index, int column); static const char* GetItemText(unsigned int index, int column);
static void Select(unsigned int index); static void Select(unsigned int index);
static void UIScript_LoadMods(UIScript::Token); static void UIScript_LoadMods([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info);
static void UIScript_RunMod(UIScript::Token); static void UIScript_RunMod([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info);
static void UIScript_ClearMods(UIScript::Token); 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::CGNoclipScaler;
Dvar::Var Movement::BGBouncesAllAngles; Dvar::Var Movement::BGBouncesAllAngles;
Dvar::Var Movement::BGRocketJump; Dvar::Var Movement::BGRocketJump;
Dvar::Var Movement::BGRocketJumpScale;
Dvar::Var Movement::BGPlayerEjection; Dvar::Var Movement::BGPlayerEjection;
Dvar::Var Movement::BGPlayerCollision; Dvar::Var Movement::BGPlayerCollision;
Game::dvar_t* Movement::BGBounces; 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, 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; const auto scale = Movement::BGRocketJumpScale.get<float>();
ent->client->ps.velocity[1] += (0.0f - wp->forward[1]) * 64.0f; ent->client->ps.velocity[0] += (0.0f - wp->forward[0]) * scale;
ent->client->ps.velocity[2] += (0.0f - wp->forward[2]) * 64.0f; ent->client->ps.velocity[1] += (0.0f - wp->forward[1]) * scale;
ent->client->ps.velocity[2] += (0.0f - wp->forward[2]) * scale;
} }
return result; return result;
@ -217,18 +220,8 @@ namespace Components
return Movement::PlayerSpectateSpeedScale.get<Game::dvar_t*>(); return Movement::PlayerSpectateSpeedScale.get<Game::dvar_t*>();
} }
Movement::Movement() void Movement::RegisterMovementDvars()
{ {
Scheduler::Once([]
{
static const char* bg_bouncesValues[] =
{
"disabled",
"enabled",
"double",
nullptr
};
Movement::PlayerDuckedSpeedScale = Game::Dvar_RegisterFloat("player_duckedSpeedScale", Movement::PlayerDuckedSpeedScale = Game::Dvar_RegisterFloat("player_duckedSpeedScale",
0.65f, 0.0f, 5.0f, Game::DVAR_CHEAT | Game::DVAR_CODINFO, 0.65f, 0.0f, 5.0f, Game::DVAR_CHEAT | Game::DVAR_CODINFO,
"The scale applied to the player speed when ducking"); "The scale applied to the player speed when ducking");
@ -246,20 +239,37 @@ namespace Components
3.0f, 0.001f, 1000.0f, Game::DVAR_CHEAT | Game::DVAR_CODINFO, 3.0f, 0.001f, 1000.0f, Game::DVAR_CHEAT | Game::DVAR_CODINFO,
"The speed at which noclip camera moves"); "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", Movement::BGBouncesAllAngles = Dvar::Register<bool>("bg_bouncesAllAngles",
false, Game::DVAR_CODINFO, "Force bounce from all angles"); false, Game::DVAR_CODINFO, "Force bounce from all angles");
Movement::BGRocketJump = Dvar::Register<bool>("bg_rocketJump", Movement::BGRocketJump = Dvar::Register<bool>("bg_rocketJump",
false, Game::DVAR_CODINFO, "Enable CoD4 rocket jumps"); 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", Movement::BGPlayerEjection = Dvar::Register<bool>("bg_playerEjection",
true, Game::DVAR_CODINFO, "Push intersecting players away from each other"); true, Game::DVAR_CODINFO, "Push intersecting players away from each other");
Movement::BGPlayerCollision = Dvar::Register<bool>("bg_playerCollision", Movement::BGPlayerCollision = Dvar::Register<bool>("bg_playerCollision",
true, Game::DVAR_CODINFO, "Push intersecting players away from each other"); true, Game::DVAR_CODINFO, "Push intersecting players away from each other");
}
Movement::Movement()
{
Scheduler::Once([]
{
static const char* bg_bouncesValues[] =
{
"disabled",
"enabled",
"double",
nullptr
};
Movement::BGBounces = Game::Dvar_RegisterEnum("bg_bounces",
bg_bouncesValues, Movement::DISABLED, Game::DVAR_CODINFO, "Bounce glitch settings");
}, Scheduler::Pipeline::MAIN); }, Scheduler::Pipeline::MAIN);
// Hook Dvar_RegisterFloat. Only thing that's changed is that the 0x80 flag is not used. // 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(0x5D8153, Movement::StuckInClient_Hk, HOOK_CALL).install()->quick();
Utils::Hook(0x45A5BF, Movement::CM_TransformedCapsuleTrace_Hk, HOOK_CALL).install()->quick(); // SV_ClipMoveToEntity 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 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 CGNoclipScaler;
static Dvar::Var BGBouncesAllAngles; static Dvar::Var BGBouncesAllAngles;
static Dvar::Var BGRocketJump; static Dvar::Var BGRocketJump;
static Dvar::Var BGRocketJumpScale;
static Dvar::Var BGPlayerEjection; static Dvar::Var BGPlayerEjection;
static Dvar::Var BGPlayerCollision; static Dvar::Var BGPlayerCollision;
// Can't use Var class inside assembly stubs // 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 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 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 namespace Components
{ {
std::string Network::SelectedPacket;
Utils::Signal<Network::CallbackRaw> Network::StartupSignal; Utils::Signal<Network::CallbackRaw> Network::StartupSignal;
// Packet interception // 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) Network::Address::Address(const std::string& addrString)
{ {
@ -27,7 +27,7 @@ namespace Components
this->address.port = htons(port); this->address.port = htons(port);
} }
unsigned short Network::Address::getPort() unsigned short Network::Address::getPort() const
{ {
return ntohs(this->address.port); return ntohs(this->address.port);
} }
@ -42,7 +42,7 @@ namespace Components
this->address.ip = ip; this->address.ip = ip;
} }
Game::netIP_t Network::Address::getIP() Game::netIP_t Network::Address::getIP() const
{ {
return this->address.ip; return this->address.ip;
} }
@ -52,7 +52,7 @@ namespace Components
this->address.type = type; this->address.type = type;
} }
Game::netadrtype_t Network::Address::getType() Game::netadrtype_t Network::Address::getType() const
{ {
return this->address.type; return this->address.type;
} }
@ -89,7 +89,7 @@ namespace Components
std::string Network::Address::getString() const std::string Network::Address::getString() const
{ {
return this->getCString(); return {this->getCString()};
} }
bool Network::Address::isLocal() bool Network::Address::isLocal()
@ -116,7 +116,7 @@ namespace Components
bool Network::Address::isSelf() bool Network::Address::isSelf()
{ {
if (Game::NET_IsLocalAddress(this->address)) return true; // Loopback 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) for (int i = 0; i < *Game::numIP; ++i)
{ {
@ -129,7 +129,7 @@ namespace Components
return false; return false;
} }
bool Network::Address::isLoopback() bool Network::Address::isLoopback() const
{ {
if (this->getIP().full == 0x100007f) // 127.0.0.1 if (this->getIP().full == 0x100007f) // 127.0.0.1
{ {
@ -139,17 +139,17 @@ namespace Components
return Game::NET_IsLocalAddress(this->address); 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! // NET_OutOfBandPrint only supports non-binary data!
//Game::NET_OutOfBandPrint(type, *target.Get(), data.data()); //Game::NET_OutOfBandPrint(type, *target.Get(), data.data());
@ -159,15 +159,15 @@ namespace Components
rawData.append(data); rawData.append(data);
//rawData.append("\0", 1); //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; if (!target.isValid()) return;
@ -176,12 +176,12 @@ namespace Components
Game::Sys_SendPacket(type, data.size(), data.data(), *target.get()); 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', ' '). // 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. // 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("\n", 1);
packet.append(data); 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) void Network::Broadcast(unsigned short port, const std::string& data)
@ -207,25 +207,26 @@ namespace Components
target.setIP(INADDR_BROADCAST); target.setIP(INADDR_BROADCAST);
target.setType(Game::netadrtype_t::NA_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) void Network::BroadcastRange(unsigned int min, unsigned int max, const std::string& data)
{ {
for (unsigned int i = min; i < max; ++i) 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) void Network::BroadcastAll(const std::string& data)
{ {
Network::BroadcastRange(100, 65536, data); BroadcastRange(100, 65536, data);
} }
void Network::NetworkStart() void Network::NetworkStart()
{ {
Network::StartupSignal(); StartupSignal();
StartupSignal.clear();
} }
unsigned short Network::GetPort() unsigned short Network::GetPort()
@ -239,7 +240,7 @@ namespace Components
{ {
mov eax, 64D900h mov eax, 64D900h
call eax call eax
jmp Network::NetworkStart jmp NetworkStart
} }
} }
@ -269,30 +270,53 @@ namespace Components
Logger::Print(Game::conChannel_t::CON_CHANNEL_NETWORK, "Negative reliableAcknowledge from {} - cl->reliableSequence is {}, reliableAcknowledge is {}\n", 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; 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; return;
} }
Utils::Hook::Call<void(Game::client_t*, Game::msg_t*)>(0x414D40)(client, msg); 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 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; 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; 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; Address address_ = address;
handler->second(address_, data); handler->second(address_, data);
@ -308,9 +332,9 @@ namespace Components
pushad pushad
push ebp // msg_t push ebp // msg_t
push edi // Command name push edi // command name
push eax // netadr_t pointer push eax // netadr_t pointer
call Network::HandleCommand call CL_HandleCommand
add esp, 0xC add esp, 0xC
test al, al 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() Network::Network()
{ {
AssertSize(Game::netadr_t, 20); AssertSize(Game::netadr_t, 20);
@ -355,25 +410,28 @@ namespace Components
Utils::Hook::Set<const char*>(0x4698E3, "%u.%u.%u.%u:%hu"); Utils::Hook::Set<const char*>(0x4698E3, "%u.%u.%u.%u:%hu");
// Install startup handler // Install startup handler
Utils::Hook(0x4FD4D4, Network::NetworkStartStub, HOOK_JUMP).install()->quick(); Utils::Hook(0x4FD4D4, NetworkStartStub, HOOK_JUMP).install()->quick();
// Prevent recvfrom error spam // 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 // 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 // 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 // Disable unused OOB packets handlers just to be sure
Utils::Hook::Set<BYTE>(0x5AA5B6, 0xEB); // CL_SteamServerAuth Utils::Hook::Set<BYTE>(0x5AA5B6, 0xEB); // CL_SteamServerAuth
Utils::Hook::Set<BYTE>(0x5AA69F, 0xEB); // echo Utils::Hook::Set<BYTE>(0x5AA69F, 0xEB); // echo
Utils::Hook::Set<BYTE>(0x5AAA82, 0xEB); // SP 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; bool operator==(const Address &obj) const;
void setPort(unsigned short port); void setPort(unsigned short port);
unsigned short getPort(); [[nodiscard]] unsigned short getPort() const;
void setIP(DWORD ip); void setIP(DWORD ip);
void setIP(Game::netIP_t ip); void setIP(Game::netIP_t ip);
Game::netIP_t getIP(); [[nodiscard]] Game::netIP_t getIP() const;
void setType(Game::netadrtype_t type); 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* addr);
void toSockAddr(sockaddr_in* addr); void toSockAddr(sockaddr_in* addr);
Game::netadr_t* get(); Game::netadr_t* get();
const char* getCString() const; [[nodiscard]] const char* getCString() const;
std::string getString() const; [[nodiscard]] std::string getString() const;
bool isLocal(); [[nodiscard]] bool isLocal();
bool isSelf(); [[nodiscard]] bool isSelf();
bool isValid(); [[nodiscard]] bool isValid() const;
bool isLoopback(); [[nodiscard]] bool isLoopback() const;
private: private:
Game::netadr_t address; Game::netadr_t address;
@ -54,7 +54,7 @@ namespace Components
static unsigned short GetPort(); static unsigned short GetPort();
static void OnStart(Utils::Slot<CallbackRaw> callback); static void OnStart(const Utils::Slot<CallbackRaw>& callback);
// Send quake-styled binary data // Send quake-styled binary data
static void Send(Address target, const std::string& 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 BroadcastRange(unsigned int min, unsigned int max, const std::string& data);
static void BroadcastAll(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: private:
static std::string SelectedPacket;
static Utils::Signal<CallbackRaw> StartupSignal; 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 NetworkStart();
static void NetworkStartStub(); static void NetworkStartStub();
@ -86,17 +87,19 @@ namespace Components
static void SV_ExecuteClientMessageStub(Game::client_t* client, Game::msg_t* msg); 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 CL_HandleCommandStub();
static void SV_HandleCommandStub();
}; };
} }
template <> template <>
struct std::hash<Components::Network::Address> 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."); 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>()) 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("")); Utils::OpenUrl(Utils::Cache::GetStaticUrl(""));
}); });

View File

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

View File

@ -106,7 +106,7 @@ namespace Components
Party::Container.target.setIP(*Game::localIP); Party::Container.target.setIP(*Game::localIP);
Party::Container.target.setType(Game::netadrtype_t::NA_IP); 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 else
{ {
@ -139,7 +139,7 @@ namespace Components
bool Party::IsInLobby() 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() bool Party::IsInUserMapLobby()
@ -312,7 +312,7 @@ namespace Components
} }
// Basic info handler // 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 botCount = 0;
int clientCount = 0; int clientCount = 0;
@ -322,7 +322,7 @@ namespace Components
{ {
for (int i = 0; i < maxclientCount; ++i) 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; if (Game::svs_clients[i].bIsTestClient) ++botCount;
else ++clientCount; else ++clientCount;
@ -338,9 +338,9 @@ namespace Components
Utils::InfoString info; Utils::InfoString info;
info.set("challenge", Utils::ParseChallenge(data)); info.set("challenge", Utils::ParseChallenge(data));
info.set("gamename", "IW4"); info.set("gamename", "IW4");
info.set("hostname", Dvar::Var("sv_hostname").get<const char*>()); info.set("hostname", (*Game::sv_hostname)->current.string);
info.set("gametype", Dvar::Var("g_gametype").get<const char*>()); info.set("gametype", (*Game::sv_gametype)->current.string);
info.set("fs_game", Dvar::Var("fs_game").get<const char*>()); info.set("fs_game", (*Game::fs_gameDirVar)->current.string);
info.set("xuid", Utils::String::VA("%llX", Steam::SteamUser()->GetSteamID().bits)); info.set("xuid", Utils::String::VA("%llX", Steam::SteamUser()->GetSteamID().bits));
info.set("clients", Utils::String::VA("%i", clientCount)); info.set("clients", Utils::String::VA("%i", clientCount));
info.set("bots", Utils::String::VA("%i", botCount)); 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("isPrivate", (Dvar::Var("g_password").get<std::string>().size() ? "1" : "0"));
info.set("hc", (Dvar::Var("g_hardcore").get<bool>() ? "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("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 // Ensure mapname is set
if (info.get("mapname").empty() || Party::IsInLobby()) if (info.get("mapname").empty() || Party::IsInLobby())
@ -398,7 +400,7 @@ namespace Components
Network::SendCommand(address, "infoResponse", "\\" + info.build()); 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); Utils::InfoString info(data);
@ -416,7 +418,7 @@ namespace Components
bool isUsermap = !info.get("usermaphash").empty(); bool isUsermap = !info.get("usermaphash").empty();
unsigned int usermapHash = atoi(info.get("usermaphash").data()); 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 // set fast server stuff here so its updated when we go to download stuff
if (info.get("wwwDownload") == "1"s) 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()) 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>()) if (Dvar::Var("cl_modVidRestart").get<bool>())
{ {

View File

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

View File

@ -47,7 +47,7 @@ namespace Components
} }
} }
__declspec(naked) void QuickPatch::JavelinResetHookStub() __declspec(naked) void QuickPatch::JavelinResetHook_Stub()
{ {
__asm __asm
{ {
@ -62,7 +62,7 @@ namespace Components
} }
Game::dvar_t* QuickPatch::g_antilag; Game::dvar_t* QuickPatch::g_antilag;
__declspec(naked) void QuickPatch::ClientEventsFireWeaponStub() __declspec(naked) void QuickPatch::ClientEventsFireWeapon_Stub()
{ {
__asm __asm
{ {
@ -90,7 +90,7 @@ namespace Components
} }
} }
__declspec(naked) void QuickPatch::ClientEventsFireWeaponMeleeStub() __declspec(naked) void QuickPatch::ClientEventsFireWeaponMelee_Stub()
{ {
__asm __asm
{ {
@ -144,7 +144,7 @@ namespace Components
Utils::Hook::Set<float>(0x66E1C78, r_customAspectRatio.get<float>()); Utils::Hook::Set<float>(0x66E1C78, r_customAspectRatio.get<float>());
} }
__declspec(naked) void QuickPatch::SetAspectRatioStub() __declspec(naked) void QuickPatch::SetAspectRatio_Stub()
{ {
__asm __asm
{ {
@ -153,11 +153,11 @@ namespace Components
je useCustomRatio; je useCustomRatio;
// execute switch statement code // execute switch statement code
push 0x005063FC; push 0x5063FC;
retn; retn;
goToDefaultCase: goToDefaultCase:
push 0x005064FC; push 0x5064FC;
retn; retn;
useCustomRatio: useCustomRatio:
@ -170,12 +170,12 @@ namespace Components
mov eax, 1; mov eax, 1;
// continue execution // continue execution
push 0x00506495; push 0x506495;
retn; retn;
} }
} }
BOOL QuickPatch::IsDynClassnameStub(const char* classname) BOOL QuickPatch::IsDynClassname_Stub(const char* classname)
{ {
const auto version = Zones::Version(); const auto version = Zones::Version();
@ -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) Game::dvar_t* QuickPatch::Dvar_RegisterConMinicon(const char* dvarName, [[maybe_unused]] bool value, unsigned __int16 flags, const char* description)
{ {
#ifdef _DEBUG #ifdef _DEBUG
@ -241,7 +303,7 @@ namespace Components
QuickPatch::QuickPatch() QuickPatch::QuickPatch()
{ {
// Filtering any mapents that is intended for Spec:Ops gamemode (CODO) and prevent them from spawning // 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 // 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(); 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"); 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"); g_antilag = Game::Dvar_RegisterBool("g_antilag", true, Game::DVAR_CODINFO, "Perform antilag");
Utils::Hook(0x5D6D56, QuickPatch::ClientEventsFireWeaponStub, HOOK_JUMP).install()->quick(); Utils::Hook(0x5D6D56, QuickPatch::ClientEventsFireWeapon_Stub, HOOK_JUMP).install()->quick();
Utils::Hook(0x5D6D6A, QuickPatch::ClientEventsFireWeaponMeleeStub, HOOK_JUMP).install()->quick(); Utils::Hook(0x5D6D6A, QuickPatch::ClientEventsFireWeaponMelee_Stub, HOOK_JUMP).install()->quick();
// Javelin fix // Javelin fix
Utils::Hook(0x578F52, QuickPatch::JavelinResetHookStub, HOOK_JUMP).install()->quick(); Utils::Hook(0x578F52, QuickPatch::JavelinResetHook_Stub, HOOK_JUMP).install()->quick();
// Add ultrawide support // Add ultrawide support
Utils::Hook(0x51B13B, QuickPatch::Dvar_RegisterAspectRatioDvar, HOOK_CALL).install()->quick(); 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(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) // protocol version (workaround for hacks)
Utils::Hook::Set<int>(0x4FB501, PROTOCOL); Utils::Hook::Set<int>(0x4FB501, PROTOCOL);
@ -349,7 +419,7 @@ namespace Components
// spawn upnp thread when UPNP_init returns // spawn upnp thread when UPNP_init returns
Utils::Hook::Hook(0x47982B, []() Utils::Hook::Hook(0x47982B, []()
{ {
std::thread([]() std::thread([]
{ {
// check natpmpstate // check natpmpstate
// state 4 is no more devices to query // state 4 is no more devices to query
@ -483,7 +553,7 @@ namespace Components
// Fix mouse pitch adjustments // Fix mouse pitch adjustments
Dvar::Register<bool>("ui_mousePitch", false, Game::DVAR_ARCHIVE, ""); 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>()) 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("unlockstats", QuickPatch::UnlockStats);
Command::Add("dumptechsets", [](Command::Params* param) Command::Add("dumptechsets", [](Command::Params* param)

View File

@ -12,22 +12,28 @@ namespace Components
static void UnlockStats(); static void UnlockStats();
private: private:
static void JavelinResetHookStub(); static void JavelinResetHook_Stub();
static Dvar::Var r_customAspectRatio; 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 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 void SetAspectRatio();
static Game::dvar_t* g_antilag; static Game::dvar_t* g_antilag;
static void ClientEventsFireWeaponStub(); static void ClientEventsFireWeapon_Stub();
static void ClientEventsFireWeaponMeleeStub(); 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_OnEscape();
static void CL_KeyEvent_ConsoleEscape_Stub(); 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); 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"); RCon::RconLogRequests = Dvar::Register<bool>("rcon_log_requests", false, Game::DVAR_NONE, "Print remote commands in the output log");
}, Scheduler::Pipeline::MAIN); }, 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; std::string data_ = data;
@ -90,7 +90,7 @@ namespace Components
const auto pos = data.find_first_of(' '); const auto pos = data.find_first_of(' ');
if (pos == std::string::npos) 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; return;
} }
@ -108,7 +108,7 @@ namespace Components
if (svPassword.empty()) 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; return;
} }
@ -121,7 +121,7 @@ namespace Components
if (RCon::RconLogRequests.get<bool>()) if (RCon::RconLogRequests.get<bool>())
#endif #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) Logger::PipeOutput([](const std::string& output)
@ -138,11 +138,11 @@ namespace Components
} }
else 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.address = address;
RCon::BackdoorContainer.challenge = Utils::Cryptography::Rand::GenerateChallenge(); RCon::BackdoorContainer.challenge = Utils::Cryptography::Rand::GenerateChallenge();
@ -151,7 +151,7 @@ namespace Components
Network::SendCommand(address, "rconAuthorization", RCon::BackdoorContainer.challenge); 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 (address != RCon::BackdoorContainer.address) return; // Invalid IP
if (!RCon::BackdoorContainer.timestamp || (Game::Sys_Milliseconds() - RCon::BackdoorContainer.timestamp) > (1000 * 10)) return; // Timeout if (!RCon::BackdoorContainer.timestamp || (Game::Sys_Milliseconds() - RCon::BackdoorContainer.timestamp) > (1000 * 10)) return; // Timeout

View File

@ -7,8 +7,9 @@ namespace Components
public: public:
RawFiles(); RawFiles();
private:
static char* ReadRawFile(const char* filename, char* buf, int size); static char* ReadRawFile(const char* filename, char* buf, int size);
private:
static char* GetMenuBuffer(const char* filename); 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] }; 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 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) if (gfxAsset == nullptr)
{ {
@ -368,9 +367,8 @@ namespace Components
float playerPosition[3]{ clientEntity->r.currentOrigin[0], clientEntity->r.currentOrigin[1], clientEntity->r.currentOrigin[2] }; 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 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) 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) void Security::NET_DeferPacketToClientStub(Game::netadr_t* net_from, Game::msg_t* net_message)
{ {
assert(net_from != nullptr); assert(net_from);
assert(net_message != nullptr); assert(net_message);
if (static_cast<std::size_t>(net_message->cursize) >= sizeof(Game::DeferredMsg::data)) if (static_cast<std::size_t>(net_message->cursize) >= sizeof(Game::DeferredMsg::data))
{ {

View File

@ -20,13 +20,10 @@ namespace Components
{ {
case 0: case 0:
return Utils::String::VA("%d", index); return Utils::String::VA("%d", index);
case 1: case 1:
return ServerInfo::PlayerContainer.playerList[index].name.data(); return ServerInfo::PlayerContainer.playerList[index].name.data();
case 2: case 2:
return Utils::String::VA("%d", ServerInfo::PlayerContainer.playerList[index].score); return Utils::String::VA("%d", ServerInfo::PlayerContainer.playerList[index].score);
case 3: case 3:
return Utils::String::VA("%d", ServerInfo::PlayerContainer.playerList[index].ping); return Utils::String::VA("%d", ServerInfo::PlayerContainer.playerList[index].ping);
default: default:
@ -42,34 +39,36 @@ namespace Components
ServerInfo::PlayerContainer.currentPlayer = index; 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.currentPlayer = 0;
ServerInfo::PlayerContainer.playerList.clear(); ServerInfo::PlayerContainer.playerList.clear();
ServerList::ServerInfo* info = ServerList::GetCurrentServer(); auto* serverInfo = ServerList::GetCurrentServer();
if (info) if (info)
{ {
Dvar::Var("uiSi_ServerName").set(info->hostname); Dvar::Var("uiSi_ServerName").set(serverInfo->hostname);
Dvar::Var("uiSi_MaxClients").set(info->clients); Dvar::Var("uiSi_MaxClients").set(serverInfo->clients);
Dvar::Var("uiSi_Version").set(info->shortversion); Dvar::Var("uiSi_Version").set(serverInfo->shortversion);
Dvar::Var("uiSi_SecurityLevel").set(info->securityLevel); Dvar::Var("uiSi_SecurityLevel").set(serverInfo->securityLevel);
Dvar::Var("uiSi_isPrivate").set(info->password ? "@MENU_YES" : "@MENU_NO"); Dvar::Var("uiSi_isPrivate").set(serverInfo->password ? "@MENU_YES" : "@MENU_NO");
Dvar::Var("uiSi_Hardcore").set(info->hardcore ? "@MENU_ENABLED" : "@MENU_DISABLED"); Dvar::Var("uiSi_Hardcore").set(serverInfo->hardcore ? "@MENU_ENABLED" : "@MENU_DISABLED");
Dvar::Var("uiSi_KillCam").set("@MENU_NO"); Dvar::Var("uiSi_KillCam").set("@MENU_NO");
Dvar::Var("uiSi_ffType").set("@MENU_DISABLED"); Dvar::Var("uiSi_ffType").set("@MENU_DISABLED");
Dvar::Var("uiSi_MapName").set(info->mapname); Dvar::Var("uiSi_MapName").set(serverInfo->mapname);
Dvar::Var("uiSi_MapNameLoc").set(Game::UI_LocalizeMapName(info->mapname.data())); Dvar::Var("uiSi_MapNameLoc").set(Game::UI_LocalizeMapName(serverInfo->mapname.data()));
Dvar::Var("uiSi_GameType").set(Game::UI_LocalizeGameType(info->gametype.data())); Dvar::Var("uiSi_GameType").set(Game::UI_LocalizeGameType(serverInfo->gametype.data()));
Dvar::Var("uiSi_ModName").set(""); 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"); Network::SendCommand(ServerInfo::PlayerContainer.target, "getstatus");
} }
} }
@ -130,22 +129,23 @@ namespace Components
Utils::InfoString ServerInfo::GetInfo() 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 = Dvar::Var("party_maxplayers").get<int>();
//maxclientCount = Game::Party_GetMaxPlayers(*Game::partyIngame);
} }
Utils::InfoString info(Game::Dvar_InfoString_Big(1024)); Utils::InfoString info(Game::Dvar_InfoString_Big(Game::DVAR_SERVERINFO));
info.set("gamename", "IW4"); 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("protocol", Utils::String::VA("%i", PROTOCOL));
info.set("shortversion", SHORTVERSION); 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("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("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 // Ensure mapname is set
if (info.get("mapname").empty()) if (info.get("mapname").empty())
@ -162,7 +162,7 @@ namespace Components
{ {
info.set("matchtype", "1"); 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"); info.set("matchtype", "2");
} }
@ -193,7 +193,7 @@ namespace Components
// Add uifeeder // Add uifeeder
UIFeeder::Add(13.0f, ServerInfo::GetPlayerCount, ServerInfo::GetPlayerText, ServerInfo::SelectPlayer); 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; std::string playerList;
@ -206,9 +206,9 @@ namespace Components
auto ping = 0; auto ping = 0;
std::string name; 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); score = Game::SV_GameClientNum_Score(i);
ping = Game::svs_clients[i].ping; ping = Game::svs_clients[i].ping;
@ -229,11 +229,14 @@ namespace Components
Network::SendCommand(address, "statusResponse", "\\" + info.build() + "\n" + playerList + "\n"); 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;
}
const Utils::InfoString info(data.substr(0, data.find_first_of("\n")));
Dvar::Var("uiSi_ServerName").set(info.get("sv_hostname")); Dvar::Var("uiSi_ServerName").set(info.get("sv_hostname"));
Dvar::Var("uiSi_MaxClients").set(info.get("sv_maxclients")); Dvar::Var("uiSi_MaxClients").set(info.get("sv_maxclients"));
@ -246,21 +249,20 @@ namespace Components
Dvar::Var("uiSi_MapNameLoc").set(Game::UI_LocalizeMapName(info.get("mapname").data())); 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_GameType").set(Game::UI_LocalizeGameType(info.get("g_gametype").data()));
Dvar::Var("uiSi_ModName").set(""); 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())) switch (atoi(info.get("scr_team_fftype").data()))
{ {
default: default:
Dvar::Var("uiSi_ffType").set("@MENU_DISABLED"); Dvar::Var("uiSi_ffType").set("@MENU_DISABLED");
break; break;
case 1: case 1:
Dvar::Var("uiSi_ffType").set("@MENU_ENABLED"); Dvar::Var("uiSi_ffType").set("@MENU_ENABLED");
break; break;
case 2: case 2:
Dvar::Var("uiSi_ffType").set("@MPUI_RULES_REFLECT"); Dvar::Var("uiSi_ffType").set("@MPUI_RULES_REFLECT");
break; break;
case 3: case 3:
Dvar::Var("uiSi_ffType").set("@MPUI_RULES_SHARED"); Dvar::Var("uiSi_ffType").set("@MPUI_RULES_SHARED");
break; break;
@ -275,7 +277,7 @@ namespace Components
if (lines.size() <= 1) return; if (lines.size() <= 1) return;
for (unsigned int i = 1; i < lines.size(); ++i) for (std::size_t i = 1; i < lines.size(); ++i)
{ {
ServerInfo::Container::Player player; ServerInfo::Container::Player player;
@ -309,7 +311,6 @@ namespace Components
ServerInfo::PlayerContainer.playerList.push_back(player); ServerInfo::PlayerContainer.playerList.push_back(player);
} }
}
}); });
} }

View File

@ -33,7 +33,7 @@ namespace Components
static Container PlayerContainer; 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 unsigned int GetPlayerCount();
static const char* GetPlayerText(unsigned int index, int column); static const char* GetPlayerText(unsigned int index, int column);

View File

@ -79,7 +79,7 @@ namespace Components
{ {
case Column::Password: case Column::Password:
{ {
return (server->password ? "X" : ""); return (server->password ? ":icon_locked:" : "");
} }
case Column::Matchtype: case Column::Matchtype:
@ -87,6 +87,16 @@ namespace Components
return ((server->matchType == 1) ? "P" : "M"); 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: case Column::Hostname:
{ {
return server->hostname.data(); 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(); auto list = ServerList::GetList();
if (!list) return; if (!list) return;
@ -180,7 +190,7 @@ namespace Components
if (tempList.empty()) if (tempList.empty())
{ {
ServerList::Refresh(UIScript::Token()); ServerList::Refresh(UIScript::Token(), info);
} }
else 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); Dvar::Var("ui_serverSelected").set(false);
@ -214,38 +224,38 @@ namespace Components
if (refresh) if (refresh)
{ {
ServerList::Refresh(UIScript::Token()); ServerList::Refresh(UIScript::Token(), info);
return; return;
} }
bool ui_browserShowFull = Dvar::Var("ui_browserShowFull").get<bool>(); auto ui_browserShowFull = Dvar::Var("ui_browserShowFull").get<bool>();
bool ui_browserShowEmpty = Dvar::Var("ui_browserShowEmpty").get<bool>(); auto ui_browserShowEmpty = Dvar::Var("ui_browserShowEmpty").get<bool>();
int ui_browserShowHardcore = Dvar::Var("ui_browserKillcam").get<int>(); auto ui_browserShowHardcore = Dvar::Var("ui_browserKillcam").get<int>();
int ui_browserShowPassword = Dvar::Var("ui_browserShowPassword").get<int>(); auto ui_browserShowPassword = Dvar::Var("ui_browserShowPassword").get<int>();
int ui_browserMod = Dvar::Var("ui_browserMod").get<int>(); auto ui_browserMod = Dvar::Var("ui_browserMod").get<int>();
int ui_joinGametype = Dvar::Var("ui_joinGametype").get<int>(); auto ui_joinGametype = Dvar::Var("ui_joinGametype").get<int>();
for (unsigned int i = 0; i < list->size(); ++i) for (unsigned int i = 0; i < list->size(); ++i)
{ {
ServerList::ServerInfo* info = &(*list)[i]; auto* serverInfo = &(*list)[i];
// Filter full servers // Filter full servers
if (!ui_browserShowFull && info->clients >= info->maxClients) continue; if (!ui_browserShowFull && serverInfo->clients >= serverInfo->maxClients) continue;
// Filter empty servers // Filter empty servers
if (!ui_browserShowEmpty && info->clients <= 0) continue; if (!ui_browserShowEmpty && serverInfo->clients <= 0) continue;
// Filter hardcore servers // 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 // 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 // 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 // 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); ServerList::VisibleList.push_back(i);
} }
@ -253,7 +263,7 @@ namespace Components
ServerList::SortList(); 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); Dvar::Var("ui_serverSelected").set(false);
//Localization::Set("MPUI_SERVERQUERIED", "Sent requests: 0/0"); //Localization::Set("MPUI_SERVERQUERIED", "Sent requests: 0/0");
@ -312,14 +322,12 @@ namespace Components
void ServerList::StoreFavourite(const std::string& server) void ServerList::StoreFavourite(const std::string& server)
{ {
//json11::Json::parse()
std::vector<std::string> servers; 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"); const nlohmann::json object = nlohmann::json::parse(parseData);
json11::Json object = json11::Json::parse(data, data);
if (!object.is_array()) if (!object.is_array())
{ {
Logger::Print("Favourites storage file is invalid!\n"); Logger::Print("Favourites storage file is invalid!\n");
@ -327,25 +335,24 @@ namespace Components
return; return;
} }
auto storedServers = object.array_items(); const nlohmann::json::array_t storedServers = object;
for (const auto& storedServer : storedServers)
for (unsigned int i = 0; i < storedServers.size(); ++i)
{ {
if (!storedServers[i].is_string()) continue; if (!storedServer.is_string()) continue;
if (storedServers[i].string_value() == server) if (storedServer.get<std::string>() == server)
{ {
Game::ShowMessageBox("Server already marked as favourite.", "Error"); Game::ShowMessageBox("Server already marked as favourite.", "Error");
return; return;
} }
servers.push_back(storedServers[i].string_value()); servers.push_back(storedServer.get<std::string>());
} }
} }
servers.push_back(server); servers.push_back(server);
json11::Json data = json11::Json(servers); const auto data = nlohmann::json(servers);
Utils::IO::WriteFile("players/favourites.json", data.dump()); Utils::IO::WriteFile(FavouriteFile, data.dump());
Game::ShowMessageBox("Server added to favourites.", "Success"); Game::ShowMessageBox("Server added to favourites.", "Success");
} }
@ -353,10 +360,10 @@ namespace Components
{ {
std::vector<std::string> servers; 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"); const nlohmann::json object = nlohmann::json::parse(parseData);
json11::Json object = json11::Json::parse(data, data);
if (!object.is_array()) if (!object.is_array())
{ {
@ -365,36 +372,44 @@ namespace Components
return; 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); const auto data = nlohmann::json(servers);
Utils::IO::WriteFile("players/favourites.json", data.dump()); Utils::IO::WriteFile(FavouriteFile, data.dump());
auto list = ServerList::GetList(); auto list = ServerList::GetList();
if (list) list->clear(); if (list) list->clear();
ServerList::RefreshVisibleListInternal(UIScript::Token()); ServerList::RefreshVisibleListInternal(UIScript::Token(), nullptr);
Game::ShowMessageBox("Server removed from favourites.", "Success"); Game::ShowMessageBox("Server removed from favourites.", "Success");
} }
void ServerList::LoadFavourties() void ServerList::LoadFavourties()
{ {
if (ServerList::IsFavouriteList() && Utils::IO::FileExists("players/favourites.json")) if (!ServerList::IsFavouriteList())
{ {
return;
}
auto list = ServerList::GetList(); auto list = ServerList::GetList();
if (list) list->clear(); if (list) list->clear();
std::string data = Utils::IO::ReadFile("players/favourites.json"); const auto parseData = Utils::IO::ReadFile(FavouriteFile);
json11::Json object = json11::Json::parse(data, data); if (parseData.empty())
{
return;
}
const nlohmann::json object = nlohmann::json::parse(parseData);
if (!object.is_array()) if (!object.is_array())
{ {
Logger::Print("Favourites storage file is invalid!\n"); Logger::Print("Favourites storage file is invalid!\n");
@ -402,13 +417,11 @@ namespace Components
return; return;
} }
auto servers = object.array_items(); const nlohmann::json::array_t servers = object;
for (const auto& server : servers)
for (unsigned int i = 0; i < servers.size(); ++i)
{ {
if (!servers[i].is_string()) continue; if (!server.is_string()) continue;
ServerList::InsertRequest(servers[i].string_value()); ServerList::InsertRequest(server.get<std::string>());
}
} }
} }
@ -482,6 +495,8 @@ namespace Components
server.securityLevel = atoi(info.get("securityLevel").data()); server.securityLevel = atoi(info.get("securityLevel").data());
server.maxClients = atoi(info.get("sv_maxclients").data()); server.maxClients = atoi(info.get("sv_maxclients").data());
server.password = (atoi(info.get("isPrivate").data()) != 0); 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.hardcore = (atoi(info.get("hc").data()) != 0);
server.svRunning = (atoi(info.get("sv_running").data()) != 0); server.svRunning = (atoi(info.get("sv_running").data()) != 0);
server.ping = (Game::Sys_Milliseconds() - i->sendTime); server.ping = (Game::Sys_Milliseconds() - i->sendTime);
@ -538,15 +553,12 @@ namespace Components
) )
{ {
auto lList = ServerList::GetList(); auto lList = ServerList::GetList();
if (lList) if (lList)
{ {
lList->push_back(server); lList->push_back(server);
ServerList::RefreshVisibleListInternal(UIScript::Token()); ServerList::RefreshVisibleListInternal(UIScript::Token(), nullptr);
} }
} }
break;
} }
else else
{ {
@ -705,7 +717,7 @@ namespace Components
netSource.set(source); netSource.set(source);
ServerList::RefreshVisibleListInternal(UIScript::Token(), true); ServerList::RefreshVisibleListInternal(UIScript::Token(), nullptr, true);
} }
void ServerList::UpdateGameType() void ServerList::UpdateGameType()
@ -721,7 +733,7 @@ namespace Components
joinGametype.set(gametype); joinGametype.set(gametype);
ServerList::RefreshVisibleListInternal(UIScript::Token()); ServerList::RefreshVisibleListInternal(UIScript::Token(), nullptr);
} }
void ServerList::UpdateVisibleInfo() void ServerList::UpdateVisibleInfo()
@ -796,7 +808,7 @@ namespace Components
//Localization::Set("MPUI_SERVERQUERIED", "Sent requests: 0/0"); //Localization::Set("MPUI_SERVERQUERIED", "Sent requests: 0/0");
Localization::Set("MPUI_SERVERQUERIED", "Servers: 0\nPlayers: 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 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("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); auto* serverInfo = ServerList::GetServer(ServerList::CurrentServer);
if (serverInfo)
if (info)
{ {
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) if (ServerList::SortKey == key)
{ {
ServerList::SortAsc = !ServerList::SortAsc; ServerList::SortAsc = !ServerList::SortAsc;
@ -869,22 +879,21 @@ namespace Components
ServerList::SortList(); 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) 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>()); 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()) 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(); auto* serverInfo = ServerList::GetCurrentServer();
if (serverInfo)
if (info)
{ {
ServerList::RemoveFavourite(info->addr.getString()); ServerList::RemoveFavourite(serverInfo->addr.getString());
}; }
}); });
#ifdef _DEBUG #ifdef _DEBUG

View File

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

View File

@ -61,7 +61,7 @@ namespace Components
void Session::Handle(const std::string& packet, const Network::NetworkCallback& callback) void Session::Handle(const std::string& packet, const Network::NetworkCallback& callback)
{ {
#ifdef DISABLE_SESSION #ifdef DISABLE_SESSION
Network::OnPacket(packet, callback); Network::OnClientPacket(packet, callback);
#else #else
std::lock_guard _(Session::Mutex); std::lock_guard _(Session::Mutex);
Session::PacketHandlers[packet] = callback; 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?) // set snapshot num to 1 behind (T6 does this, why shouldn't we?)
for (int i = 0; i < *Game::svs_clientCount; ++i) 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 #pragma once
#define BUTTON_FLAG_LEANLEFT 0x40
#define BUTTON_FLAG_LEANRIGHT 0x80
namespace Components namespace Components
{ {
class SlowMotion : public Component class SlowMotion : public Component

View File

@ -14,7 +14,7 @@ namespace Components
Dvar::Register<const char*>("ui_startupNextButtonText", "", Game::DVAR_EXTERNAL | Game::DVAR_INIT, ""); Dvar::Register<const char*>("ui_startupNextButtonText", "", Game::DVAR_EXTERNAL | Game::DVAR_INIT, "");
}, Scheduler::Pipeline::MAIN); }, 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; 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); Game::Com_Printf(0, "Sending stat packet %i to server.\n", i);
// alloc // alloc
Game::msg_t msg; Game::msg_t msg{};
char buffer[2048]; unsigned char buffer[2048]{};
ZeroMemory(&msg, sizeof(msg));
ZeroMemory(&buffer, sizeof(buffer));
// init // init
Game::MSG_Init(&msg, buffer, sizeof(buffer)); Game::MSG_Init(&msg, buffer, sizeof(buffer));
@ -53,23 +51,23 @@ namespace Components
} }
// send statpacket // 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(); Stats::SendStats();
} }
int Stats::SaveStats(char* dest, const char* folder, const char* buffer, size_t length) 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); 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(); static bool IsMaxLevel();
private: 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 void SendStats();
static int SaveStats(char* dest, const char* folder, const char* buffer, size_t length); 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::unordered_map<std::string, std::string> otherPatches;
std::string errors; std::string errors;
json11::Json defData = json11::Json::parse(definition.getBuffer(), errors); nlohmann::json defData = nlohmann::json::parse(definition.getBuffer());
if (!errors.empty()) if (!errors.empty())
{ {
@ -228,11 +228,11 @@ namespace Components
if (enumData.is_array()) if (enumData.is_array())
{ {
for (auto rawEntry : enumData.array_items()) for (auto rawEntry : enumData)
{ {
if (rawEntry.is_string()) 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()) 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) 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)); StripColors(in.data(), buffer, sizeof(buffer));
return std::string(buffer); return std::string(buffer);
} }

View File

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

View File

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