Merge branch 'develop' into branding
This commit is contained in:
commit
144978e758
64
CHANGELOG.md
64
CHANGELOG.md
@ -4,6 +4,70 @@ All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog v0.3.0](http://keepachangelog.com/en/0.3.0/) and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## [0.7.0] - 2022-01-05
|
||||
|
||||
### Added
|
||||
|
||||
- Added controller support (#75)
|
||||
- Added aim assist for controllers (#75)
|
||||
- Unlock camera_thirdPersonCrosshairOffset Dvar (#68)
|
||||
- Added support for building custom Fonts with Zonebuilder (#88)
|
||||
- Added colorblind friendly team colors (#101)
|
||||
- Added emojis based on titlecards and emblems to use in the chat and server names Example: `:nuke:` (#130)
|
||||
- Upon leaving a server 'archive' dvars (saved in the config file) will be reset to the value they had prior to joining the server (#134)
|
||||
- Implement muteClient command for the game chat (#159)
|
||||
- Implement unmute command for the game chat (#159)
|
||||
- Add sv_allowAimAssist Dvar (#75)
|
||||
- Add sv_allowColoredNames (#130)
|
||||
- Add sv_randomMapRotation Dvar (#146)
|
||||
- Add rcon_log_requests Dvar (#195)
|
||||
- Add player_duckedSpeedScale Dvar (#141)
|
||||
- Add player_proneSpeedScale Dvar (#141)
|
||||
- Add cg_ufo_scaler Dvar (#158)
|
||||
- Add cg_noclip_scaler Dvar (#158)
|
||||
- Add bg_bouncesAllAngles Dvar (#158)
|
||||
- Add bg_rocketJump Dvar (#158)
|
||||
- Add bg_elevators Dvar (#156)
|
||||
- Implement noclip client command (#152)
|
||||
- Implement ufo client command (#152)
|
||||
- Implement God client command (#152)
|
||||
- Implement demigod client command (#152)
|
||||
- Implement notarget client command (#152)
|
||||
- Add noclip GSC Function (#152)
|
||||
- Add ufo GSC Function (#152)
|
||||
- Add God GSC Function (#152)
|
||||
- Add demigod GSC Function (#152)
|
||||
- Add notarget GSC Function (#152)
|
||||
- Add replaceFunc GSC Function (#144)
|
||||
|
||||
### Changed
|
||||
|
||||
- Renamed sv_enableBounces to bg_bounces (#158)
|
||||
- Renamed g_playerCollision to bg_playerEjection (#158)
|
||||
- Renamed g_playerEjection to bg_playerCollision (#158)
|
||||
- Setviewpos client command works outside private matches (#163)
|
||||
- Ufo client command works outside of private matches (#152)
|
||||
- Noclip client command works outside of private matches (#152)
|
||||
- If a player name is less than 3 characters server will change it to `Unknown Soldier` (#130)
|
||||
- scr_player_forceautoassign Dvar is false by default
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed issue where CoD:O DLC Maps caused DirectX crash following `vid_restart` (#37)
|
||||
- Fixes and improvements to Zonebuilder
|
||||
- Fixed issue where the game froze following base game script throwing an error (#74)
|
||||
- Fixed RCon on party servers (#91 - #95)
|
||||
- Fixed slow motion during final killcams (#111 - #107)
|
||||
- Fixed sound issue that causes the game to freeze (#106)
|
||||
- Fixed issue where materials strings found in hostnames, player names, chat etc. caused the game to crash (#113)
|
||||
- Fixed issue with servers displaying an invalid player count (#144)
|
||||
|
||||
### 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.6.1] - 2020-12-23
|
||||
|
||||
### Added
|
||||
|
@ -22,9 +22,6 @@
|
||||
| `--force-unit-tests` | Always compile unit tests. |
|
||||
| `--force-exception-handler` | Install custom unhandled exception handler even for Debug builds. |
|
||||
| `--force-minidump-upload` | Upload minidumps even for Debug builds. |
|
||||
| `--disable-bitmessage` | Disable use of BitMessage completely. |
|
||||
| `--disable-base128` | Disable base128 encoding for minidumps. |
|
||||
| `--no-new-structure` | Do not use new virtual path structure (separating headers and source files). |
|
||||
| `--iw4x-zones` | Zonebuilder generates iw4x zones that cannot be loaded without IW4x specific patches. |
|
||||
|
||||
## Command line arguments
|
||||
|
2
deps/libtommath
vendored
2
deps/libtommath
vendored
@ -1 +1 @@
|
||||
Subproject commit 5108f12350b6daa4aa5dbc846517ad1db2f8388a
|
||||
Subproject commit 4b47368501321c795d5b54d87a5bab35a21a7940
|
@ -83,6 +83,7 @@ namespace Components
|
||||
Loader::Register(new ModelSurfs());
|
||||
Loader::Register(new PlayerName());
|
||||
Loader::Register(new QuickPatch());
|
||||
Loader::Register(new Security());
|
||||
Loader::Register(new ServerInfo());
|
||||
Loader::Register(new ServerList());
|
||||
Loader::Register(new SlowMotion());
|
||||
@ -105,6 +106,7 @@ namespace Components
|
||||
Loader::Register(new ClientCommand());
|
||||
Loader::Register(new ScriptExtension());
|
||||
Loader::Register(new Branding());
|
||||
Loader::Register(new RawMouse());
|
||||
|
||||
Loader::Pregame = false;
|
||||
}
|
||||
|
@ -85,6 +85,7 @@ namespace Components
|
||||
#include "Modules/Network.hpp"
|
||||
#include "Modules/Theatre.hpp"
|
||||
#include "Modules/QuickPatch.hpp"
|
||||
#include "Modules/Security.hpp"
|
||||
#include "Modules/Node.hpp"
|
||||
#include "Modules/RCon.hpp"
|
||||
#include "Modules/Party.hpp" // Destroys the order, but requires network classes :D
|
||||
@ -136,3 +137,4 @@ namespace Components
|
||||
#include "Modules/Gamepad.hpp"
|
||||
#include "Modules/ScriptExtension.hpp"
|
||||
#include "Modules/Branding.hpp"
|
||||
#include "Modules/RawMouse.hpp"
|
||||
|
@ -28,7 +28,7 @@ namespace Components
|
||||
if (!rawfile || Game::DB_IsXAssetDefault(Game::XAssetType::ASSET_TYPE_RAWFILE, this->filePath.data())) return;
|
||||
|
||||
this->buffer.resize(Game::DB_GetRawFileLen(rawfile));
|
||||
Game::DB_GetRawBuffer(rawfile, const_cast<char*>(this->buffer.data()), this->buffer.size());
|
||||
Game::DB_GetRawBuffer(rawfile, this->buffer.data(), static_cast<int>(this->buffer.size()));
|
||||
}
|
||||
|
||||
FileSystem::FileReader::FileReader(const std::string& file) : handle(0), name(file)
|
||||
|
@ -77,16 +77,14 @@ namespace Components
|
||||
|
||||
std::string Logger::Format(const char** message)
|
||||
{
|
||||
const size_t bufferSize = 0x10000;
|
||||
Utils::Memory::Allocator allocator;
|
||||
char* buffer = allocator.allocateArray<char>(bufferSize);
|
||||
char buffer[4096] = {0};
|
||||
|
||||
va_list ap = reinterpret_cast<char*>(const_cast<char**>(&message[1]));
|
||||
//va_start(ap, *message);
|
||||
_vsnprintf_s(buffer, bufferSize, bufferSize, *message, ap);
|
||||
|
||||
_vsnprintf_s(buffer, _TRUNCATE, *message, ap);
|
||||
va_end(ap);
|
||||
|
||||
return buffer;
|
||||
return {buffer};
|
||||
}
|
||||
|
||||
void Logger::Flush()
|
||||
@ -138,8 +136,8 @@ namespace Components
|
||||
{
|
||||
if (!data) return;
|
||||
|
||||
std::string buffer(data);
|
||||
for (auto& addr : Logger::LoggingAddresses[gLog & 1])
|
||||
const std::string buffer(data);
|
||||
for (const auto& addr : Logger::LoggingAddresses[gLog & 1])
|
||||
{
|
||||
Network::SendCommand(addr, "print", buffer);
|
||||
}
|
||||
@ -373,9 +371,9 @@ namespace Components
|
||||
Logger::MessageMutex.unlock();
|
||||
|
||||
// Flush the console log
|
||||
if (int fh = *reinterpret_cast<int*>(0x1AD8F28))
|
||||
if (const auto logfile = *reinterpret_cast<int*>(0x1AD8F28))
|
||||
{
|
||||
Game::FS_FCloseFile(fh);
|
||||
Game::FS_FCloseFile(logfile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,355 +2,289 @@
|
||||
|
||||
namespace Components
|
||||
{
|
||||
Dvar::Var Movement::PlayerDuckedSpeedScale;
|
||||
Dvar::Var Movement::PlayerLastStandCrawlSpeedScale;
|
||||
Dvar::Var Movement::PlayerProneSpeedScale;
|
||||
Dvar::Var Movement::PlayerSpectateSpeedScale;
|
||||
Dvar::Var Movement::CGUfoScaler;
|
||||
Dvar::Var Movement::CGNoclipScaler;
|
||||
Dvar::Var Movement::BGBouncesAllAngles;
|
||||
Dvar::Var Movement::BGRocketJump;
|
||||
Dvar::Var Movement::BGPlayerEjection;
|
||||
Dvar::Var Movement::BGPlayerCollision;
|
||||
Game::dvar_t* Movement::BGBounces;
|
||||
|
||||
float Movement::PM_CmdScaleForStance(const Game::pmove_s* pm)
|
||||
{
|
||||
assert(pm->ps != nullptr);
|
||||
|
||||
const auto* playerState = pm->ps;
|
||||
float scale;
|
||||
|
||||
if (playerState->viewHeightLerpTime != 0 && playerState->viewHeightLerpTarget == 0xB)
|
||||
{
|
||||
scale = pm->cmd.serverTime - playerState->viewHeightLerpTime / 400.0f;
|
||||
|
||||
if (0.0f <= scale)
|
||||
{
|
||||
if (scale > 1.0f)
|
||||
{
|
||||
scale = 1.0f;
|
||||
return scale * 0.15f + (1.0f - scale) * 0.65f;
|
||||
}
|
||||
|
||||
if (scale != 0.0f)
|
||||
{
|
||||
return scale * 0.15f + (1.0f - scale) * 0.65f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((playerState->viewHeightLerpTime != 0 && playerState->viewHeightLerpTarget == 0x28) &&
|
||||
playerState->viewHeightLerpDown == 0)
|
||||
{
|
||||
scale = 400.0f / pm->cmd.serverTime - playerState->viewHeightLerpTime;
|
||||
|
||||
if (0.0f <= scale)
|
||||
{
|
||||
if (scale > 1.0f)
|
||||
{
|
||||
scale = 1.0f;
|
||||
}
|
||||
else if (scale != 0.0f)
|
||||
{
|
||||
return scale * 0.65f + (1.0f - scale) * 0.15f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scale = 1.0f;
|
||||
const auto stance = Game::PM_GetEffectiveStance(playerState);
|
||||
|
||||
if (stance == Game::PM_EFF_STANCE_PRONE)
|
||||
{
|
||||
scale = Movement::PlayerProneSpeedScale.get<float>();
|
||||
}
|
||||
|
||||
else if (stance == Game::PM_EFF_STANCE_DUCKED)
|
||||
{
|
||||
scale = Movement::PlayerDuckedSpeedScale.get<float>();
|
||||
}
|
||||
|
||||
else if (stance == Game::PM_EFF_STANCE_LASTSTANDCRAWL)
|
||||
{
|
||||
scale = Movement::PlayerLastStandCrawlSpeedScale.get<float>();
|
||||
}
|
||||
|
||||
return scale;
|
||||
}
|
||||
|
||||
__declspec(naked) void Movement::PM_CmdScaleForStanceStub()
|
||||
{
|
||||
__asm
|
||||
{
|
||||
pushad
|
||||
|
||||
push edx
|
||||
call Movement::PM_CmdScaleForStance // pm
|
||||
add esp, 4
|
||||
|
||||
popad
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
float Movement::PM_MoveScale(Game::playerState_s* ps, float forwardmove,
|
||||
float rightmove, float upmove)
|
||||
{
|
||||
assert(ps != nullptr);
|
||||
|
||||
auto max = (std::fabsf(forwardmove) < std::fabsf(rightmove))
|
||||
? std::fabsf(rightmove)
|
||||
: std::fabsf(forwardmove);
|
||||
|
||||
if (std::fabsf(upmove) > max)
|
||||
{
|
||||
max = std::fabsf(upmove);
|
||||
}
|
||||
|
||||
if (max == 0.0f)
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
auto total = std::sqrtf(forwardmove * forwardmove
|
||||
+ rightmove * rightmove + upmove * upmove);
|
||||
auto scale = (ps->speed * max) / (127.0f * total);
|
||||
|
||||
if (ps->pm_flags & Game::PMF_WALKING || ps->leanf != 0.0f)
|
||||
{
|
||||
scale *= 0.4f;
|
||||
}
|
||||
|
||||
if (ps->pm_type == Game::PM_NOCLIP)
|
||||
{
|
||||
return scale * Movement::CGNoclipScaler.get<float>();
|
||||
}
|
||||
|
||||
if (ps->pm_type == Game::PM_UFO)
|
||||
{
|
||||
return scale * Movement::CGUfoScaler.get<float>();
|
||||
}
|
||||
|
||||
if (ps->pm_type == Game::PM_SPECTATOR)
|
||||
{
|
||||
return scale * Movement::PlayerSpectateSpeedScale.get<float>();
|
||||
}
|
||||
|
||||
return scale;
|
||||
}
|
||||
|
||||
__declspec(naked) void Movement::PM_MoveScaleStub()
|
||||
{
|
||||
__asm
|
||||
{
|
||||
pushad
|
||||
|
||||
push [esp + 0xC + 0x20] // upmove
|
||||
push [esp + 0xC + 0x20] // rightmove
|
||||
push [esp + 0xC + 0x20] // forwardmove
|
||||
push esi // ps
|
||||
call Movement::PM_MoveScale
|
||||
add esp, 0x10
|
||||
|
||||
popad
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
__declspec(naked) void Movement::PM_StepSlideMoveStub()
|
||||
{
|
||||
__asm
|
||||
{
|
||||
// Check the value of BGBounces
|
||||
push ecx
|
||||
push eax
|
||||
|
||||
mov eax, Movement::BGBounces
|
||||
mov ecx, dword ptr [eax + 0x10]
|
||||
test ecx, ecx
|
||||
|
||||
pop eax
|
||||
pop ecx
|
||||
|
||||
// Do not bounce if BGBounces is 0
|
||||
jle noBounce
|
||||
|
||||
// Bounce
|
||||
push 0x4B1B34
|
||||
retn
|
||||
|
||||
noBounce:
|
||||
// Original game code
|
||||
cmp dword ptr [esp + 0x24], 0
|
||||
push 0x4B1B48
|
||||
retn
|
||||
}
|
||||
}
|
||||
|
||||
void Movement::PM_ProjectVelocityStub(const float* velIn, const float* normal, float* velOut)
|
||||
{
|
||||
const auto lengthSquared2D = velIn[0] * velIn[0] + velIn[1] * velIn[1];
|
||||
|
||||
if (std::fabsf(normal[2]) < 0.001f || lengthSquared2D == 0.0)
|
||||
{
|
||||
velOut[0] = velIn[0];
|
||||
velOut[1] = velIn[1];
|
||||
velOut[2] = velIn[2];
|
||||
return;
|
||||
}
|
||||
|
||||
auto newZ = velIn[0] * normal[0] + velIn[1] * normal[1];
|
||||
newZ = -newZ / normal[2];
|
||||
const auto lengthScale = std::sqrtf((velIn[2] * velIn[2] + lengthSquared2D)
|
||||
/ (newZ * newZ + lengthSquared2D));
|
||||
|
||||
if (Movement::BGBouncesAllAngles.get<bool>()
|
||||
|| (lengthScale < 1.f || newZ < 0.f || velIn[2] > 0.f))
|
||||
{
|
||||
velOut[0] = velIn[0] * lengthScale;
|
||||
velOut[1] = velIn[1] * lengthScale;
|
||||
velOut[2] = newZ * lengthScale;
|
||||
}
|
||||
}
|
||||
|
||||
// Double bounces
|
||||
void Movement::Jump_ClearState_Hk(Game::playerState_s* ps)
|
||||
{
|
||||
if (Movement::BGBounces->current.integer != Movement::DOUBLE)
|
||||
{
|
||||
Game::Jump_ClearState(ps);
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
auto* result = Game::Weapon_RocketLauncher_Fire(ent, weaponIndex, spread, wp, gunVel, lockParms, a7);
|
||||
|
||||
if (ent->client != nullptr && BGRocketJump.get<bool>())
|
||||
{
|
||||
ent->client->ps.velocity[0] += (0 - wp->forward[0]) * 64.0f;
|
||||
ent->client->ps.velocity[1] += (0 - wp->forward[1]) * 64.0f;
|
||||
ent->client->ps.velocity[2] += (0 - wp->forward[2]) * 64.0f;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int Movement::StuckInClient_Hk(Game::gentity_s* self)
|
||||
{
|
||||
if (Movement::BGPlayerEjection.get<bool>())
|
||||
{
|
||||
return Utils::Hook::Call<int(Game::gentity_s*)>(0x402D30)(self); // StuckInClient
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Movement::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)
|
||||
{
|
||||
if (Movement::BGPlayerCollision.get<bool>())
|
||||
{
|
||||
Utils::Hook::Call<void(Game::trace_t*, const float*, const float*,
|
||||
const Game::Bounds*, const Game::Bounds*, int, const float*, const float*)>
|
||||
(0x478300)
|
||||
(results, start, end, bounds, capsule, contents, origin, angles); // CM_TransformedCapsuleTrace
|
||||
}
|
||||
}
|
||||
|
||||
Game::dvar_t* Movement::Dvar_RegisterLastStandSpeedScale(const char* dvarName, float value,
|
||||
float min, float max, unsigned __int16 /*flags*/, const char* description)
|
||||
{
|
||||
Movement::PlayerLastStandCrawlSpeedScale = Dvar::Register<float>(dvarName, value,
|
||||
min, max, Game::DVAR_CHEAT | Game::DVAR_CODINFO, description);
|
||||
|
||||
return Movement::PlayerLastStandCrawlSpeedScale.get<Game::dvar_t*>();
|
||||
}
|
||||
|
||||
Game::dvar_t* Movement::Dvar_RegisterSpectateSpeedScale(const char* dvarName, float value,
|
||||
float min, float max, unsigned __int16 /*flags*/, const char* description)
|
||||
{
|
||||
Movement::PlayerSpectateSpeedScale = Dvar::Register<float>(dvarName, value,
|
||||
min, max, Game::DVAR_CHEAT | Game::DVAR_CODINFO, description);
|
||||
|
||||
return Movement::PlayerSpectateSpeedScale.get<Game::dvar_t*>();
|
||||
}
|
||||
|
||||
Movement::Movement()
|
||||
{
|
||||
Dvar::OnInit([]
|
||||
{
|
||||
static const char* bg_bouncesValues[] =
|
||||
{
|
||||
"disabled",
|
||||
"enabled",
|
||||
"double",
|
||||
nullptr
|
||||
};
|
||||
|
||||
Movement::PlayerDuckedSpeedScale = Dvar::Register<float>("player_duckedSpeedScale",
|
||||
0.65f, 0.0f, 5.0f, Game::DVAR_CHEAT | Game::DVAR_CODINFO,
|
||||
"The scale applied to the player speed when ducking");
|
||||
|
||||
Movement::PlayerProneSpeedScale = Dvar::Register<float>("player_proneSpeedScale",
|
||||
0.15f, 0.0f, 5.0f, Game::DVAR_CHEAT | Game::DVAR_CODINFO,
|
||||
"The scale applied to the player speed when crawling");
|
||||
|
||||
// 3arc naming convention
|
||||
Movement::CGUfoScaler = Dvar::Register<float>("cg_ufo_scaler",
|
||||
6.0f, 0.001f, 1000.0f, Game::DVAR_CHEAT | Game::DVAR_CODINFO,
|
||||
"The speed at which ufo camera moves");
|
||||
|
||||
Movement::CGNoclipScaler = Dvar::Register<float>("cg_noclip_scaler",
|
||||
3.0f, 0.001f, 1000.0f, Game::DVAR_CHEAT | Game::DVAR_CODINFO,
|
||||
"The speed at which noclip camera moves");
|
||||
|
||||
Movement::BGBounces = Game::Dvar_RegisterEnum("bg_bounces",
|
||||
bg_bouncesValues, Movement::DISABLED, Game::DVAR_CODINFO, "Bounce glitch settings");
|
||||
|
||||
Movement::BGBouncesAllAngles = Dvar::Register<bool>("bg_bouncesAllAngles",
|
||||
false, Game::DVAR_CODINFO, "Force bounce from all angles");
|
||||
|
||||
Movement::BGRocketJump = Dvar::Register<bool>("bg_rocketJump",
|
||||
false, Game::DVAR_CODINFO, "Enable CoD4 rocket jumps");
|
||||
|
||||
Movement::BGPlayerEjection = Dvar::Register<bool>("bg_playerEjection",
|
||||
true, Game::DVAR_CODINFO, "Push intersecting players away from each other");
|
||||
|
||||
Movement::BGPlayerCollision = Dvar::Register<bool>("bg_playerCollision",
|
||||
true, Game::DVAR_CODINFO, "Push intersecting players away from each other");
|
||||
});
|
||||
|
||||
// Hook PM_CmdScaleForStance in PM_CmdScale_Walk
|
||||
Utils::Hook(0x572F34, Movement::PM_CmdScaleForStanceStub, HOOK_CALL).install()->quick();
|
||||
|
||||
//Hook PM_CmdScaleForStance in PM_GetMaxSpeed
|
||||
Utils::Hook(0x57395F, Movement::PM_CmdScaleForStanceStub, HOOK_CALL).install()->quick();
|
||||
|
||||
// Hook Dvar_RegisterFloat. Only thing that's changed is that the 0x80 flag is not used.
|
||||
Utils::Hook(0x448B66, Movement::Dvar_RegisterLastStandSpeedScale, HOOK_CALL).install()->quick();
|
||||
|
||||
// Hook Dvar_RegisterFloat. Only thing that's changed is that the 0x80 flag is not used.
|
||||
Utils::Hook(0x448990, Movement::Dvar_RegisterSpectateSpeedScale, HOOK_CALL).install()->quick();
|
||||
|
||||
// Hook PM_MoveScale so we can add custom speed scale for Ufo and Noclip
|
||||
Utils::Hook(0x56F845, Movement::PM_MoveScaleStub, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x56FABD, Movement::PM_MoveScaleStub, HOOK_CALL).install()->quick();
|
||||
|
||||
// Bounce logic
|
||||
Utils::Hook(0x4B1B2D, Movement::PM_StepSlideMoveStub, HOOK_JUMP).install()->quick();
|
||||
Utils::Hook(0x57383E, Movement::Jump_ClearState_Hk, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x4B1B97, Movement::PM_ProjectVelocityStub, HOOK_CALL).install()->quick();
|
||||
|
||||
// Rocket jump
|
||||
Utils::Hook(0x4A4F9B, Movement::Weapon_RocketLauncher_Fire_Hk, HOOK_CALL).install()->quick(); // FireWeapon
|
||||
|
||||
// Hook StuckInClient & CM_TransformedCapsuleTrace
|
||||
// so we can prevent intersecting players from being pushed away from each other
|
||||
Utils::Hook(0x5D8153, Movement::StuckInClient_Hk, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x45A5BF, Movement::CM_TransformedCapsuleTrace_Hk, HOOK_CALL).install()->quick(); // SV_ClipMoveToEntity
|
||||
Utils::Hook(0x5A0CAD, Movement::CM_TransformedCapsuleTrace_Hk, HOOK_CALL).install()->quick(); // CG_ClipMoveToEntity
|
||||
}
|
||||
Dvar::Var Movement::PlayerSpectateSpeedScale;
|
||||
Dvar::Var Movement::CGUfoScaler;
|
||||
Dvar::Var Movement::CGNoclipScaler;
|
||||
Dvar::Var Movement::BGBouncesAllAngles;
|
||||
Dvar::Var Movement::BGRocketJump;
|
||||
Dvar::Var Movement::BGPlayerEjection;
|
||||
Dvar::Var Movement::BGPlayerCollision;
|
||||
Game::dvar_t* Movement::BGBounces;
|
||||
Game::dvar_t* Movement::PlayerDuckedSpeedScale;
|
||||
Game::dvar_t* Movement::PlayerProneSpeedScale;
|
||||
|
||||
__declspec(naked) void Movement::PM_PlayerDuckedSpeedScaleStub()
|
||||
{
|
||||
__asm
|
||||
{
|
||||
push eax
|
||||
mov eax, Movement::PlayerDuckedSpeedScale
|
||||
fld dword ptr [eax + 0x10] // dvar_t.current.value
|
||||
pop eax
|
||||
|
||||
// Game's code
|
||||
pop ecx
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
__declspec(naked) void Movement::PM_PlayerProneSpeedScaleStub()
|
||||
{
|
||||
__asm
|
||||
{
|
||||
push eax
|
||||
mov eax, Movement::PlayerProneSpeedScale
|
||||
fld dword ptr [eax + 0x10] // dvar_t.current.value
|
||||
pop eax
|
||||
|
||||
// Game's code
|
||||
pop ecx
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
float Movement::PM_MoveScale(Game::playerState_s* ps, float fmove,
|
||||
float rmove, float umove)
|
||||
{
|
||||
assert(ps != nullptr);
|
||||
|
||||
auto max = std::fabsf(fmove) < std::fabsf(rmove)
|
||||
? std::fabsf(rmove) : std::fabsf(fmove);
|
||||
|
||||
if (std::fabsf(umove) > max)
|
||||
{
|
||||
max = std::fabsf(umove);
|
||||
}
|
||||
|
||||
if (max == 0.0f)
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
auto total = std::sqrtf(fmove * fmove
|
||||
+ rmove * rmove + umove * umove);
|
||||
auto scale = (static_cast<float>(ps->speed) * max) / (127.0f * total);
|
||||
|
||||
if (ps->pm_flags & Game::PMF_WALKING || ps->leanf != 0.0f)
|
||||
{
|
||||
scale *= 0.4f;
|
||||
}
|
||||
|
||||
switch (ps->pm_type)
|
||||
{
|
||||
case Game::pmtype_t::PM_NOCLIP:
|
||||
scale *= Movement::CGNoclipScaler.get<float>();
|
||||
break;
|
||||
case Game::pmtype_t::PM_UFO:
|
||||
scale *= Movement::CGUfoScaler.get<float>();
|
||||
break;
|
||||
case Game::pmtype_t::PM_SPECTATOR:
|
||||
scale *= Movement::PlayerSpectateSpeedScale.get<float>();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return scale;
|
||||
}
|
||||
|
||||
__declspec(naked) void Movement::PM_MoveScaleStub()
|
||||
{
|
||||
__asm
|
||||
{
|
||||
pushad
|
||||
|
||||
push [esp + 0xC + 0x20] // umove
|
||||
push [esp + 0xC + 0x20] // rmove
|
||||
push [esp + 0xC + 0x20] // fmove
|
||||
push esi // ps
|
||||
call Movement::PM_MoveScale
|
||||
add esp, 0x10
|
||||
|
||||
popad
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
__declspec(naked) void Movement::PM_StepSlideMoveStub()
|
||||
{
|
||||
__asm
|
||||
{
|
||||
// Check the value of BGBounces
|
||||
push ecx
|
||||
push eax
|
||||
|
||||
mov eax, Movement::BGBounces
|
||||
mov ecx, dword ptr [eax + 0x10]
|
||||
test ecx, ecx
|
||||
|
||||
pop eax
|
||||
pop ecx
|
||||
|
||||
// Do not bounce if BGBounces is 0
|
||||
jle noBounce
|
||||
|
||||
// Bounce
|
||||
push 0x4B1B34
|
||||
retn
|
||||
|
||||
noBounce:
|
||||
// Original game code
|
||||
cmp dword ptr [esp + 0x24], 0
|
||||
push 0x4B1B48
|
||||
retn
|
||||
}
|
||||
}
|
||||
|
||||
void Movement::PM_ProjectVelocityStub(const float* velIn, const float* normal, float* velOut)
|
||||
{
|
||||
const auto lengthSquared2D = velIn[0] * velIn[0] + velIn[1] * velIn[1];
|
||||
|
||||
if (std::fabsf(normal[2]) < 0.001f || lengthSquared2D == 0.0)
|
||||
{
|
||||
velOut[0] = velIn[0];
|
||||
velOut[1] = velIn[1];
|
||||
velOut[2] = velIn[2];
|
||||
return;
|
||||
}
|
||||
|
||||
auto newZ = velIn[0] * normal[0] + velIn[1] * normal[1];
|
||||
newZ = -newZ / normal[2];
|
||||
const auto lengthScale = std::sqrtf((velIn[2] * velIn[2] + lengthSquared2D)
|
||||
/ (newZ * newZ + lengthSquared2D));
|
||||
|
||||
if (Movement::BGBouncesAllAngles.get<bool>()
|
||||
|| (lengthScale < 1.f || newZ < 0.f || velIn[2] > 0.f))
|
||||
{
|
||||
velOut[0] = velIn[0] * lengthScale;
|
||||
velOut[1] = velIn[1] * lengthScale;
|
||||
velOut[2] = newZ * lengthScale;
|
||||
}
|
||||
}
|
||||
|
||||
// Double bounces
|
||||
void Movement::Jump_ClearState_Hk(Game::playerState_s* ps)
|
||||
{
|
||||
if (Movement::BGBounces->current.integer != Movement::DOUBLE)
|
||||
{
|
||||
Game::Jump_ClearState(ps);
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
auto* result = Game::Weapon_RocketLauncher_Fire(ent, weaponIndex, spread, wp, gunVel, lockParms, a7);
|
||||
|
||||
if (ent->client != nullptr && BGRocketJump.get<bool>())
|
||||
{
|
||||
ent->client->ps.velocity[0] += (0.0f - wp->forward[0]) * 64.0f;
|
||||
ent->client->ps.velocity[1] += (0.0f - wp->forward[1]) * 64.0f;
|
||||
ent->client->ps.velocity[2] += (0.0f - wp->forward[2]) * 64.0f;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int Movement::StuckInClient_Hk(Game::gentity_s* self)
|
||||
{
|
||||
if (Movement::BGPlayerEjection.get<bool>())
|
||||
{
|
||||
return Utils::Hook::Call<int(Game::gentity_s*)>(0x402D30)(self); // StuckInClient
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Movement::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)
|
||||
{
|
||||
if (Movement::BGPlayerCollision.get<bool>())
|
||||
{
|
||||
Utils::Hook::Call<void(Game::trace_t*, const float*, const float*,
|
||||
const Game::Bounds*, const Game::Bounds*, int, const float*, const float*)>
|
||||
(0x478300)
|
||||
(results, start, end, bounds, capsule, contents, origin, angles); // CM_TransformedCapsuleTrace
|
||||
}
|
||||
}
|
||||
|
||||
Game::dvar_t* Movement::Dvar_RegisterSpectateSpeedScale(const char* dvarName, float value,
|
||||
float min, float max, unsigned __int16 /*flags*/, const char* description)
|
||||
{
|
||||
Movement::PlayerSpectateSpeedScale = Dvar::Register<float>(dvarName, value,
|
||||
min, max, Game::DVAR_CHEAT | Game::DVAR_CODINFO, description);
|
||||
|
||||
return Movement::PlayerSpectateSpeedScale.get<Game::dvar_t*>();
|
||||
}
|
||||
|
||||
Movement::Movement()
|
||||
{
|
||||
Dvar::OnInit([]
|
||||
{
|
||||
static const char* bg_bouncesValues[] =
|
||||
{
|
||||
"disabled",
|
||||
"enabled",
|
||||
"double",
|
||||
nullptr
|
||||
};
|
||||
|
||||
Movement::PlayerDuckedSpeedScale = Game::Dvar_RegisterFloat("player_duckedSpeedScale",
|
||||
0.65f, 0.0f, 5.0f, Game::DVAR_CHEAT | Game::DVAR_CODINFO,
|
||||
"The scale applied to the player speed when ducking");
|
||||
|
||||
Movement::PlayerProneSpeedScale = Game::Dvar_RegisterFloat("player_proneSpeedScale",
|
||||
0.15f, 0.0f, 5.0f, Game::DVAR_CHEAT | Game::DVAR_CODINFO,
|
||||
"The scale applied to the player speed when crawling");
|
||||
|
||||
// 3arc naming convention
|
||||
Movement::CGUfoScaler = Dvar::Register<float>("cg_ufo_scaler",
|
||||
6.0f, 0.001f, 1000.0f, Game::DVAR_CHEAT | Game::DVAR_CODINFO,
|
||||
"The speed at which ufo camera moves");
|
||||
|
||||
Movement::CGNoclipScaler = Dvar::Register<float>("cg_noclip_scaler",
|
||||
3.0f, 0.001f, 1000.0f, Game::DVAR_CHEAT | Game::DVAR_CODINFO,
|
||||
"The speed at which noclip camera moves");
|
||||
|
||||
Movement::BGBounces = Game::Dvar_RegisterEnum("bg_bounces",
|
||||
bg_bouncesValues, Movement::DISABLED, Game::DVAR_CODINFO, "Bounce glitch settings");
|
||||
|
||||
Movement::BGBouncesAllAngles = Dvar::Register<bool>("bg_bouncesAllAngles",
|
||||
false, Game::DVAR_CODINFO, "Force bounce from all angles");
|
||||
|
||||
Movement::BGRocketJump = Dvar::Register<bool>("bg_rocketJump",
|
||||
false, Game::DVAR_CODINFO, "Enable CoD4 rocket jumps");
|
||||
|
||||
Movement::BGPlayerEjection = Dvar::Register<bool>("bg_playerEjection",
|
||||
true, Game::DVAR_CODINFO, "Push intersecting players away from each other");
|
||||
|
||||
Movement::BGPlayerCollision = Dvar::Register<bool>("bg_playerCollision",
|
||||
true, Game::DVAR_CODINFO, "Push intersecting players away from each other");
|
||||
});
|
||||
|
||||
// Hook Dvar_RegisterFloat. Only thing that's changed is that the 0x80 flag is not used.
|
||||
Utils::Hook(0x448990, Movement::Dvar_RegisterSpectateSpeedScale, HOOK_CALL).install()->quick();
|
||||
|
||||
// PM_CmdScaleForStance
|
||||
Utils::Hook(0x572D9B, Movement::PM_PlayerDuckedSpeedScaleStub, HOOK_JUMP).install()->quick();
|
||||
Utils::Hook(0x572DA5, Movement::PM_PlayerProneSpeedScaleStub, HOOK_JUMP).install()->quick();
|
||||
|
||||
// Hook PM_MoveScale so we can add custom speed scale for Ufo and Noclip
|
||||
Utils::Hook(0x56F845, Movement::PM_MoveScaleStub, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x56FABD, Movement::PM_MoveScaleStub, HOOK_CALL).install()->quick();
|
||||
|
||||
// Bounce logic
|
||||
Utils::Hook(0x4B1B2D, Movement::PM_StepSlideMoveStub, HOOK_JUMP).install()->quick();
|
||||
Utils::Hook(0x57383E, Movement::Jump_ClearState_Hk, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x4B1B97, Movement::PM_ProjectVelocityStub, HOOK_CALL).install()->quick();
|
||||
|
||||
// Rocket jump
|
||||
Utils::Hook(0x4A4F9B, Movement::Weapon_RocketLauncher_Fire_Hk, HOOK_CALL).install()->quick(); // FireWeapon
|
||||
|
||||
// Hook StuckInClient & CM_TransformedCapsuleTrace
|
||||
// so we can prevent intersecting players from being pushed away from each other
|
||||
Utils::Hook(0x5D8153, Movement::StuckInClient_Hk, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x45A5BF, Movement::CM_TransformedCapsuleTrace_Hk, HOOK_CALL).install()->quick(); // SV_ClipMoveToEntity
|
||||
Utils::Hook(0x5A0CAD, Movement::CM_TransformedCapsuleTrace_Hk, HOOK_CALL).install()->quick(); // CG_ClipMoveToEntity
|
||||
}
|
||||
}
|
||||
|
@ -2,45 +2,43 @@
|
||||
|
||||
namespace Components
|
||||
{
|
||||
class Movement : public Component
|
||||
{
|
||||
public:
|
||||
Movement();
|
||||
class Movement : public Component
|
||||
{
|
||||
public:
|
||||
Movement();
|
||||
|
||||
private:
|
||||
enum BouncesSettings { DISABLED, ENABLED, DOUBLE };
|
||||
private:
|
||||
enum BouncesSettings { DISABLED, ENABLED, DOUBLE };
|
||||
|
||||
static Dvar::Var PlayerDuckedSpeedScale;
|
||||
static Dvar::Var PlayerLastStandCrawlSpeedScale;
|
||||
static Dvar::Var PlayerProneSpeedScale;
|
||||
static Dvar::Var PlayerSpectateSpeedScale;
|
||||
static Dvar::Var CGUfoScaler;
|
||||
static Dvar::Var CGNoclipScaler;
|
||||
static Dvar::Var BGBouncesAllAngles;
|
||||
static Dvar::Var BGRocketJump;
|
||||
static Dvar::Var BGPlayerEjection;
|
||||
static Dvar::Var BGPlayerCollision;
|
||||
// Can't use Var class inside assembly stubs
|
||||
static Game::dvar_t* BGBounces;
|
||||
static Dvar::Var PlayerSpectateSpeedScale;
|
||||
static Dvar::Var CGUfoScaler;
|
||||
static Dvar::Var CGNoclipScaler;
|
||||
static Dvar::Var BGBouncesAllAngles;
|
||||
static Dvar::Var BGRocketJump;
|
||||
static Dvar::Var BGPlayerEjection;
|
||||
static Dvar::Var BGPlayerCollision;
|
||||
// Can't use Var class inside assembly stubs
|
||||
static Game::dvar_t* BGBounces;
|
||||
static Game::dvar_t* PlayerDuckedSpeedScale;
|
||||
static Game::dvar_t* PlayerProneSpeedScale;
|
||||
|
||||
static float PM_CmdScaleForStance(const Game::pmove_s* move);
|
||||
static void PM_CmdScaleForStanceStub();
|
||||
static void PM_PlayerDuckedSpeedScaleStub();
|
||||
static void PM_PlayerProneSpeedScaleStub();
|
||||
|
||||
static float PM_MoveScale(Game::playerState_s* ps, float forwardmove, float rightmove, float upmove);
|
||||
static void PM_MoveScaleStub();
|
||||
static float PM_MoveScale(Game::playerState_s* ps, float fmove, float rmove, float umove);
|
||||
static void PM_MoveScaleStub();
|
||||
|
||||
// Bounce logic
|
||||
static void PM_StepSlideMoveStub();
|
||||
static void PM_ProjectVelocityStub(const float* velIn, const float* normal, float* velOut);
|
||||
static void Jump_ClearState_Hk(Game::playerState_s* ps);
|
||||
// Bounce logic
|
||||
static void PM_StepSlideMoveStub();
|
||||
static void PM_ProjectVelocityStub(const float* velIn, const float* normal, float* velOut);
|
||||
static void Jump_ClearState_Hk(Game::playerState_s* ps);
|
||||
|
||||
static Game::gentity_s* Weapon_RocketLauncher_Fire_Hk(Game::gentity_s* ent, unsigned int weaponIndex, float spread, Game::weaponParms* wp, const float* gunVel, Game::lockonFireParms* lockParms, bool a7);
|
||||
static Game::gentity_s* Weapon_RocketLauncher_Fire_Hk(Game::gentity_s* ent, unsigned int weaponIndex, float spread, Game::weaponParms* wp, const float* gunVel, Game::lockonFireParms* lockParms, bool a7);
|
||||
|
||||
// Player collison
|
||||
static int StuckInClient_Hk(Game::gentity_s* self);
|
||||
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);
|
||||
// Player collison
|
||||
static int StuckInClient_Hk(Game::gentity_s* self);
|
||||
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_RegisterLastStandSpeedScale(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 Game::dvar_t* Dvar_RegisterSpectateSpeedScale(const char* dvarName, float value, float min, float max, unsigned __int16 flags, const char* description);
|
||||
};
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ namespace Components
|
||||
{
|
||||
if (!sv_allowColoredNames.get<bool>())
|
||||
{
|
||||
char nameBuffer[64] = { 0 };
|
||||
char nameBuffer[64] = {0};
|
||||
TextRenderer::StripColors(name, nameBuffer, sizeof(nameBuffer));
|
||||
TextRenderer::StripAllTextIcons(nameBuffer, buffer, size);
|
||||
}
|
||||
@ -26,12 +26,12 @@ namespace Components
|
||||
}
|
||||
}
|
||||
|
||||
__declspec(naked) void PlayerName::ClientUserinfoChanged()
|
||||
__declspec(naked) void PlayerName::ClientCleanName()
|
||||
{
|
||||
__asm
|
||||
{
|
||||
mov eax, [esp + 4h] // length
|
||||
//sub eax, 1
|
||||
|
||||
push eax
|
||||
|
||||
push ecx // name
|
||||
@ -53,12 +53,57 @@ namespace Components
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
char* PlayerName::CleanStrStub(char* string)
|
||||
{
|
||||
TextRenderer::StripColors(string, string, strlen(string) + 1);
|
||||
return string;
|
||||
}
|
||||
|
||||
bool PlayerName::CopyClientNameCheck(char* dest, const char* source, int size)
|
||||
{
|
||||
Utils::Hook::Call<void(char*, const char*, int)>(0x4D6F80)(dest, source, size); // I_strncpyz
|
||||
|
||||
auto i = 0;
|
||||
while (i < size - 1 && dest[i] != '\0')
|
||||
{
|
||||
if (dest[i] > 125 || dest[i] < 32 || dest[i] == '%')
|
||||
{
|
||||
return false; // Illegal string
|
||||
}
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
__declspec(naked) void PlayerName::SV_UserinfoChangedStub()
|
||||
{
|
||||
__asm
|
||||
{
|
||||
call CopyClientNameCheck
|
||||
test al, al
|
||||
|
||||
jnz returnSafe
|
||||
|
||||
pushad
|
||||
|
||||
push 1 // tellThem
|
||||
push INVALID_NAME_MSG // reason
|
||||
push edi // drop
|
||||
mov eax, 0x4D1600 // SV_DropClient
|
||||
call eax
|
||||
add esp, 0xC
|
||||
|
||||
popad
|
||||
|
||||
returnSafe:
|
||||
push 0x401988
|
||||
retn
|
||||
}
|
||||
}
|
||||
|
||||
PlayerName::PlayerName()
|
||||
{
|
||||
sv_allowColoredNames = Dvar::Register<bool>("sv_allowColoredNames", true, Game::dvar_flag::DVAR_NONE, "Allow colored names on the server");
|
||||
@ -66,13 +111,17 @@ namespace Components
|
||||
// Disable SV_UpdateUserinfo_f, to block changing the name ingame
|
||||
Utils::Hook::Set<BYTE>(0x6258D0, 0xC3);
|
||||
|
||||
// Allow colored names ingame
|
||||
Utils::Hook(0x5D8B40, ClientUserinfoChanged, HOOK_JUMP).install()->quick();
|
||||
// Allow colored names ingame. Hook placed in ClientUserinfoChanged
|
||||
Utils::Hook(0x5D8B40, ClientCleanName, HOOK_JUMP).install()->quick();
|
||||
|
||||
// Though, don't apply that to overhead names.
|
||||
Utils::Hook(0x581932, GetClientName, HOOK_CALL).install()->quick();
|
||||
|
||||
// Patch I_CleanStr
|
||||
Utils::Hook(0x4AD470, CleanStrStub, HOOK_JUMP).install()->quick();
|
||||
|
||||
// Detect invalid characters including '%' to prevent format string vulnerabilities.
|
||||
// Kicks the player as soon as possible
|
||||
Utils::Hook(0x401983, SV_UserinfoChangedStub, HOOK_JUMP).install()->quick();
|
||||
}
|
||||
}
|
||||
|
@ -11,9 +11,14 @@ namespace Components
|
||||
|
||||
private:
|
||||
static Dvar::Var sv_allowColoredNames;
|
||||
// Message used when kicking players
|
||||
static constexpr auto INVALID_NAME_MSG = "Invalid name detected";
|
||||
|
||||
static char* CleanStrStub(char* string);
|
||||
static void ClientUserinfoChanged();
|
||||
static void ClientCleanName();
|
||||
static char* GetClientName(int localClientNum, int index, char* buf, size_t size);
|
||||
|
||||
static bool CopyClientNameCheck(char* dest, const char* source, int size);
|
||||
static void SV_UserinfoChangedStub();
|
||||
};
|
||||
}
|
||||
|
@ -47,62 +47,6 @@ namespace Components
|
||||
}
|
||||
}
|
||||
|
||||
int QuickPatch::MsgReadBitsCompressCheckSV(const char *from, char *to, int size)
|
||||
{
|
||||
static char buffer[0x8000];
|
||||
|
||||
if (size > 0x800) return 0;
|
||||
size = Game::MSG_ReadBitsCompress(from, buffer, size);
|
||||
|
||||
if (size > 0x800) return 0;
|
||||
std::memcpy(to, buffer, size);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
int QuickPatch::MsgReadBitsCompressCheckCL(const char *from, char *to, int size)
|
||||
{
|
||||
static char buffer[0x100000];
|
||||
|
||||
if (size > 0x20000) return 0;
|
||||
size = Game::MSG_ReadBitsCompress(from, buffer, size);
|
||||
|
||||
if (size > 0x20000) return 0;
|
||||
std::memcpy(to, buffer, size);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
int QuickPatch::SVCanReplaceServerCommand(Game::client_t* /*client*/, const char* /*cmd*/)
|
||||
{
|
||||
// This is a fix copied from V2. As I don't have time to investigate, let's simply trust them
|
||||
return -1;
|
||||
}
|
||||
|
||||
long QuickPatch::AtolAdjustPlayerLimit(const char* string)
|
||||
{
|
||||
return std::min(atol(string), 18l);
|
||||
}
|
||||
|
||||
void QuickPatch::SelectStringTableEntryInDvarStub()
|
||||
{
|
||||
Command::ClientParams params;
|
||||
|
||||
if (params.size() >= 4)
|
||||
{
|
||||
const auto* dvarName = params[3];
|
||||
const auto* dvar = Game::Dvar_FindVar(dvarName);
|
||||
|
||||
if (Command::Find(dvarName) ||
|
||||
(dvar != nullptr && dvar->flags & (Game::DVAR_WRITEPROTECTED | Game::DVAR_CHEAT | Game::DVAR_READONLY)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Game::CL_SelectStringTableEntryInDvar_f();
|
||||
}
|
||||
|
||||
__declspec(naked) void QuickPatch::JavelinResetHookStub()
|
||||
{
|
||||
__asm
|
||||
@ -117,69 +61,6 @@ namespace Components
|
||||
}
|
||||
}
|
||||
|
||||
__declspec(naked) int QuickPatch::G_GetClientScore()
|
||||
{
|
||||
__asm
|
||||
{
|
||||
mov eax, [esp + 4] // index
|
||||
mov ecx, ds : 1A831A8h // level: &g_clients
|
||||
|
||||
test ecx, ecx;
|
||||
jz invalid_ptr;
|
||||
|
||||
imul eax, 366Ch
|
||||
mov eax, [eax + ecx + 3134h]
|
||||
ret
|
||||
|
||||
invalid_ptr:
|
||||
xor eax, eax
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
bool QuickPatch::InvalidNameCheck(char* dest, const char* source, int size)
|
||||
{
|
||||
Utils::Hook::Call<void(char*, const char*, int)>(0x4D6F80)(dest, source, size); // I_strncpyz
|
||||
|
||||
for (int i = 0; i < size - 1; i++)
|
||||
{
|
||||
if (!dest[i]) break;
|
||||
|
||||
if (dest[i] > 125 || dest[i] < 32 || dest[i] == '%')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
__declspec(naked) void QuickPatch::InvalidNameStub()
|
||||
{
|
||||
static const char* kick_reason = "Invalid name detected.";
|
||||
|
||||
__asm
|
||||
{
|
||||
call InvalidNameCheck;
|
||||
test al, al
|
||||
|
||||
jnz returnSafe;
|
||||
|
||||
pushad;
|
||||
push 1;
|
||||
push kick_reason;
|
||||
push edi;
|
||||
mov eax, 0x004D1600; // SV_DropClientInternal
|
||||
call eax;
|
||||
add esp, 12;
|
||||
popad;
|
||||
|
||||
returnSafe:
|
||||
push 0x00401988;
|
||||
retn;
|
||||
}
|
||||
}
|
||||
|
||||
Game::dvar_t* QuickPatch::g_antilag;
|
||||
__declspec(naked) void QuickPatch::ClientEventsFireWeaponStub()
|
||||
{
|
||||
@ -372,9 +253,6 @@ namespace Components
|
||||
Utils::Hook(0x5D6D56, QuickPatch::ClientEventsFireWeaponStub, HOOK_JUMP).install()->quick();
|
||||
Utils::Hook(0x5D6D6A, QuickPatch::ClientEventsFireWeaponMeleeStub, HOOK_JUMP).install()->quick();
|
||||
|
||||
// Disallow invalid player names
|
||||
Utils::Hook(0x401983, QuickPatch::InvalidNameStub, HOOK_JUMP).install()->quick();
|
||||
|
||||
// Javelin fix
|
||||
Utils::Hook(0x578F52, QuickPatch::JavelinResetHookStub, HOOK_JUMP).install()->quick();
|
||||
|
||||
@ -619,21 +497,6 @@ namespace Components
|
||||
}
|
||||
});
|
||||
|
||||
// Exploit fixes
|
||||
Utils::Hook::Set<BYTE>(0x412370, 0xC3); // SV_SteamAuthClient
|
||||
Utils::Hook::Set<BYTE>(0x5A8C70, 0xC3); // CL_HandleRelayPacket
|
||||
Utils::Hook(0x414D92, QuickPatch::MsgReadBitsCompressCheckSV, HOOK_CALL).install()->quick(); // SV_ExecuteClientCommands
|
||||
Utils::Hook(0x4A9F56, QuickPatch::MsgReadBitsCompressCheckCL, HOOK_CALL).install()->quick(); // CL_ParseServerMessage
|
||||
Utils::Hook(0x407376, QuickPatch::SVCanReplaceServerCommand , HOOK_CALL).install()->quick(); // SV_CanReplaceServerCommand
|
||||
Utils::Hook(0x5B67ED, QuickPatch::AtolAdjustPlayerLimit , HOOK_CALL).install()->quick(); // PartyHost_HandleJoinPartyRequest
|
||||
Utils::Hook::Nop(0x41698E, 5); // Disable Svcmd_EntityList_f
|
||||
|
||||
// Patch selectStringTableEntryInDvar
|
||||
Utils::Hook::Set(0x405959, QuickPatch::SelectStringTableEntryInDvarStub);
|
||||
|
||||
// Patch G_GetClientScore for uninitialised game
|
||||
Utils::Hook(0x469AC0, QuickPatch::G_GetClientScore, HOOK_JUMP).install()->quick();
|
||||
|
||||
// Ignore call to print 'Offhand class mismatch when giving weapon...'
|
||||
Utils::Hook(0x5D9047, 0x4BB9B0, HOOK_CALL).install()->quick();
|
||||
|
||||
|
@ -12,21 +12,8 @@ namespace Components
|
||||
static void UnlockStats();
|
||||
|
||||
private:
|
||||
static void SelectStringTableEntryInDvarStub();
|
||||
|
||||
static int SVCanReplaceServerCommand(Game::client_t *client, const char *cmd);
|
||||
static int G_GetClientScore();
|
||||
|
||||
static int MsgReadBitsCompressCheckSV(const char *from, char *to, int size);
|
||||
static int MsgReadBitsCompressCheckCL(const char *from, char *to, int size);
|
||||
|
||||
static long AtolAdjustPlayerLimit(const char* string);
|
||||
|
||||
static void JavelinResetHookStub();
|
||||
|
||||
static bool InvalidNameCheck(char* dest, const char* source, int size);
|
||||
static void InvalidNameStub();
|
||||
|
||||
static Dvar::Var r_customAspectRatio;
|
||||
static Game::dvar_t* Dvar_RegisterAspectRatioDvar(const char* dvarName, const char** valueList, int defaultIndex, unsigned __int16 flags, const char* description);
|
||||
static void SetAspectRatioStub();
|
||||
|
162
src/Components/Modules/RawMouse.cpp
Normal file
162
src/Components/Modules/RawMouse.cpp
Normal file
@ -0,0 +1,162 @@
|
||||
#include <STDInclude.hpp>
|
||||
|
||||
namespace Components
|
||||
{
|
||||
Dvar::Var RawMouse::M_RawInput;
|
||||
int RawMouse::MouseRawX = 0;
|
||||
int RawMouse::MouseRawY = 0;
|
||||
|
||||
void RawMouse::IN_ClampMouseMove()
|
||||
{
|
||||
tagRECT rc;
|
||||
tagPOINT curPos;
|
||||
|
||||
GetCursorPos(&curPos);
|
||||
GetWindowRect(Window::GetWindow(), &rc);
|
||||
auto isClamped = false;
|
||||
if (curPos.x >= rc.left)
|
||||
{
|
||||
if (curPos.x >= rc.right)
|
||||
{
|
||||
curPos.x = rc.right - 1;
|
||||
isClamped = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
curPos.x = rc.left;
|
||||
isClamped = true;
|
||||
}
|
||||
if (curPos.y >= rc.top)
|
||||
{
|
||||
if (curPos.y >= rc.bottom)
|
||||
{
|
||||
curPos.y = rc.bottom - 1;
|
||||
isClamped = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
curPos.y = rc.top;
|
||||
isClamped = true;
|
||||
}
|
||||
|
||||
if (isClamped)
|
||||
{
|
||||
SetCursorPos(curPos.x, curPos.y);
|
||||
}
|
||||
}
|
||||
|
||||
BOOL RawMouse::OnRawInput(LPARAM lParam, WPARAM)
|
||||
{
|
||||
auto dwSize = sizeof(RAWINPUT);
|
||||
static BYTE lpb[sizeof(RAWINPUT)];
|
||||
|
||||
GetRawInputData((HRAWINPUT)lParam, RID_INPUT, lpb, &dwSize, sizeof(RAWINPUTHEADER));
|
||||
|
||||
auto* raw = reinterpret_cast<RAWINPUT*>(lpb);
|
||||
if (raw->header.dwType == RIM_TYPEMOUSE)
|
||||
{
|
||||
// Is there's really absolute mouse on earth?
|
||||
if (raw->data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE)
|
||||
{
|
||||
MouseRawX = raw->data.mouse.lLastX;
|
||||
MouseRawY = raw->data.mouse.lLastY;
|
||||
}
|
||||
else
|
||||
{
|
||||
MouseRawX += raw->data.mouse.lLastX;
|
||||
MouseRawY += raw->data.mouse.lLastY;
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void RawMouse::IN_RawMouseMove()
|
||||
{
|
||||
static auto r_fullscreen = Dvar::Var("r_fullscreen");
|
||||
|
||||
if (GetForegroundWindow() == Window::GetWindow())
|
||||
{
|
||||
if (r_fullscreen.get<bool>())
|
||||
IN_ClampMouseMove();
|
||||
|
||||
static auto oldX = 0, oldY = 0;
|
||||
|
||||
auto dx = MouseRawX - oldX;
|
||||
auto dy = MouseRawY - oldY;
|
||||
|
||||
oldX = MouseRawX;
|
||||
oldY = MouseRawY;
|
||||
|
||||
// Don't use raw input for menu?
|
||||
// Because it needs to call the ScreenToClient
|
||||
tagPOINT curPos;
|
||||
GetCursorPos(&curPos);
|
||||
Game::s_wmv->oldPos = curPos;
|
||||
ScreenToClient(Window::GetWindow(), &curPos);
|
||||
|
||||
auto recenterMouse = Game::CL_MouseEvent(curPos.x, curPos.y, dx, dy);
|
||||
|
||||
if (recenterMouse)
|
||||
{
|
||||
Game::IN_RecenterMouse();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RawMouse::IN_RawMouse_Init()
|
||||
{
|
||||
if (Window::GetWindow() && RawMouse::M_RawInput.get<bool>())
|
||||
{
|
||||
#ifdef DEBUG
|
||||
Logger::Print("Raw Mouse Init.\n");
|
||||
#endif
|
||||
|
||||
RAWINPUTDEVICE Rid[1];
|
||||
Rid[0].usUsagePage = 0x01; // HID_USAGE_PAGE_GENERIC
|
||||
Rid[0].usUsage = 0x02; // HID_USAGE_GENERIC_MOUSE
|
||||
Rid[0].dwFlags = RIDEV_INPUTSINK;
|
||||
Rid[0].hwndTarget = Window::GetWindow();
|
||||
|
||||
RegisterRawInputDevices(Rid, ARRAYSIZE(Rid), sizeof(Rid[0]));
|
||||
}
|
||||
}
|
||||
|
||||
void RawMouse::IN_Init()
|
||||
{
|
||||
Game::IN_Init();
|
||||
IN_RawMouse_Init();
|
||||
}
|
||||
|
||||
void RawMouse::IN_MouseMove()
|
||||
{
|
||||
if (RawMouse::M_RawInput.get<bool>())
|
||||
{
|
||||
IN_RawMouseMove();
|
||||
}
|
||||
else
|
||||
{
|
||||
Game::IN_MouseMove();
|
||||
}
|
||||
}
|
||||
|
||||
RawMouse::RawMouse()
|
||||
{
|
||||
Utils::Hook(0x475E65, RawMouse::IN_MouseMove, HOOK_JUMP).install()->quick();
|
||||
Utils::Hook(0x475E8D, RawMouse::IN_MouseMove, HOOK_JUMP).install()->quick();
|
||||
Utils::Hook(0x475E9E, RawMouse::IN_MouseMove, HOOK_JUMP).install()->quick();
|
||||
|
||||
Utils::Hook(0x467C03, RawMouse::IN_Init, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x64D095, RawMouse::IN_Init, HOOK_JUMP).install()->quick();
|
||||
|
||||
Dvar::OnInit([]()
|
||||
{
|
||||
RawMouse::M_RawInput = Dvar::Register<bool>("m_rawinput", true, Game::dvar_flag::DVAR_ARCHIVE, "Use raw mouse input, Improves accuracy & has better support for higher polling rates. Use in_restart to take effect if not enabled.");
|
||||
});
|
||||
|
||||
Window::OnWndMessage(WM_INPUT, RawMouse::OnRawInput);
|
||||
Window::OnCreate(RawMouse::IN_RawMouse_Init);
|
||||
}
|
||||
}
|
20
src/Components/Modules/RawMouse.hpp
Normal file
20
src/Components/Modules/RawMouse.hpp
Normal file
@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
namespace Components
|
||||
{
|
||||
class RawMouse : public Component
|
||||
{
|
||||
public:
|
||||
RawMouse();
|
||||
private:
|
||||
static Dvar::Var M_RawInput;
|
||||
static int MouseRawX, MouseRawY;
|
||||
|
||||
static void IN_ClampMouseMove();
|
||||
static BOOL OnRawInput(LPARAM lParam, WPARAM);
|
||||
static void IN_RawMouseMove();
|
||||
static void IN_RawMouse_Init();
|
||||
static void IN_Init();
|
||||
static void IN_MouseMove();
|
||||
};
|
||||
}
|
@ -4,6 +4,115 @@ namespace Components
|
||||
{
|
||||
const char* ScriptExtension::QueryStrings[] = { R"(..)", R"(../)", R"(..\)" };
|
||||
|
||||
std::unordered_map<std::uint16_t, Game::ent_field_t> ScriptExtension::CustomEntityFields;
|
||||
std::unordered_map<std::uint16_t, Game::client_fields_s> ScriptExtension::CustomClientFields;
|
||||
|
||||
void ScriptExtension::AddEntityField(const char* name, Game::fieldtype_t type,
|
||||
const Game::ScriptCallbackEnt& setter, const Game::ScriptCallbackEnt& getter)
|
||||
{
|
||||
static std::uint16_t fieldOffsetStart = 15; // fields count
|
||||
assert((fieldOffsetStart & Game::ENTFIELD_MASK) == Game::ENTFIELD_ENTITY);
|
||||
|
||||
ScriptExtension::CustomEntityFields[fieldOffsetStart] = {name, fieldOffsetStart, type, setter, getter};
|
||||
++fieldOffsetStart;
|
||||
}
|
||||
|
||||
void ScriptExtension::AddClientField(const char* name, Game::fieldtype_t type,
|
||||
const Game::ScriptCallbackClient& setter, const Game::ScriptCallbackClient& getter)
|
||||
{
|
||||
static std::uint16_t fieldOffsetStart = 21; // fields count
|
||||
assert((fieldOffsetStart & Game::ENTFIELD_MASK) == Game::ENTFIELD_ENTITY);
|
||||
|
||||
const auto offset = fieldOffsetStart | Game::ENTFIELD_CLIENT; // This is how client field's offset is calculated
|
||||
|
||||
// Use 'index' in 'array' as map key. It will be used later in Scr_SetObjectFieldStub
|
||||
ScriptExtension::CustomClientFields[fieldOffsetStart] = {name, offset, type, setter, getter};
|
||||
++fieldOffsetStart;
|
||||
}
|
||||
|
||||
void ScriptExtension::GScr_AddFieldsForEntityStub()
|
||||
{
|
||||
for (const auto& [offset, field] : ScriptExtension::CustomEntityFields)
|
||||
{
|
||||
Game::Scr_AddClassField(Game::ClassNum::CLASS_NUM_ENTITY, field.name, field.ofs);
|
||||
}
|
||||
|
||||
Utils::Hook::Call<void()>(0x4A7CF0)(); // GScr_AddFieldsForClient
|
||||
|
||||
for (const auto& [offset, field] : ScriptExtension::CustomClientFields)
|
||||
{
|
||||
Game::Scr_AddClassField(Game::ClassNum::CLASS_NUM_ENTITY, field.name, field.ofs);
|
||||
}
|
||||
}
|
||||
|
||||
// Because some functions are inlined we have to hook this function instead of Scr_SetEntityField
|
||||
int ScriptExtension::Scr_SetObjectFieldStub(unsigned int classnum, int entnum, int offset)
|
||||
{
|
||||
if (classnum == Game::ClassNum::CLASS_NUM_ENTITY)
|
||||
{
|
||||
const auto entity_offset = static_cast<std::uint16_t>(offset);
|
||||
|
||||
const auto got = ScriptExtension::CustomEntityFields.find(entity_offset);
|
||||
if (got != ScriptExtension::CustomEntityFields.end())
|
||||
{
|
||||
got->second.setter(&Game::g_entities[entnum], offset);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// No custom generic field was found, let the game handle it
|
||||
return Game::Scr_SetObjectField(classnum, entnum, offset);
|
||||
}
|
||||
|
||||
// Offset was already converted to array 'index' following binop offset & ~Game::ENTFIELD_MASK
|
||||
void ScriptExtension::Scr_SetClientFieldStub(Game::gclient_s* client, int offset)
|
||||
{
|
||||
const auto client_offset = static_cast<std::uint16_t>(offset);
|
||||
|
||||
const auto got = ScriptExtension::CustomClientFields.find(client_offset);
|
||||
if (got != ScriptExtension::CustomClientFields.end())
|
||||
{
|
||||
got->second.setter(client, &got->second);
|
||||
return;
|
||||
}
|
||||
|
||||
// No custom field client was found, let the game handle it
|
||||
Game::Scr_SetClientField(client, offset);
|
||||
}
|
||||
|
||||
void ScriptExtension::Scr_GetEntityFieldStub(int entnum, int offset)
|
||||
{
|
||||
if ((offset & Game::ENTFIELD_MASK) == Game::ENTFIELD_CLIENT)
|
||||
{
|
||||
// If we have a ENTFIELD_CLIENT offset we need to check g_entity is actually a fully connected client
|
||||
if (Game::g_entities[entnum].client != nullptr)
|
||||
{
|
||||
const auto client_offset = static_cast<std::uint16_t>(offset & ~Game::ENTFIELD_MASK);
|
||||
|
||||
const auto got = ScriptExtension::CustomClientFields.find(client_offset);
|
||||
if (got != ScriptExtension::CustomClientFields.end())
|
||||
{
|
||||
// Game functions probably don't ever need to use the reference to client_fields_s...
|
||||
got->second.getter(Game::g_entities[entnum].client, &got->second);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Regular entity offsets can be searched directly in our custom handler
|
||||
const auto entity_offset = static_cast<std::uint16_t>(offset);
|
||||
|
||||
const auto got = ScriptExtension::CustomEntityFields.find(entity_offset);
|
||||
if (got != ScriptExtension::CustomEntityFields.end())
|
||||
{
|
||||
got->second.getter(&Game::g_entities[entnum], offset);
|
||||
return;
|
||||
}
|
||||
|
||||
// No custom generic field was found, let the game handle it
|
||||
Game::Scr_GetEntityField(entnum, offset);
|
||||
}
|
||||
|
||||
void ScriptExtension::AddFunctions()
|
||||
{
|
||||
// File functions
|
||||
@ -247,11 +356,46 @@ namespace Components
|
||||
Game::Scr_AddIString(value);
|
||||
}
|
||||
|
||||
void ScriptExtension::AddEntityFields()
|
||||
{
|
||||
ScriptExtension::AddEntityField("entityflags", Game::fieldtype_t::F_INT,
|
||||
[](Game::gentity_s* ent, [[maybe_unused]] int offset)
|
||||
{
|
||||
ent->flags = Game::Scr_GetInt(0);
|
||||
},
|
||||
[](Game::gentity_s* ent, [[maybe_unused]] int offset)
|
||||
{
|
||||
Game::Scr_AddInt(ent->flags);
|
||||
});
|
||||
}
|
||||
|
||||
void ScriptExtension::AddClientFields()
|
||||
{
|
||||
ScriptExtension::AddClientField("clientflags", Game::fieldtype_t::F_INT,
|
||||
[](Game::gclient_s* pSelf, [[maybe_unused]] const Game::client_fields_s* pField)
|
||||
{
|
||||
pSelf->flags = Game::Scr_GetInt(0);
|
||||
},
|
||||
[](Game::gclient_s* pSelf, [[maybe_unused]] const Game::client_fields_s* pField)
|
||||
{
|
||||
Game::Scr_AddInt(pSelf->flags);
|
||||
});
|
||||
}
|
||||
|
||||
ScriptExtension::ScriptExtension()
|
||||
{
|
||||
ScriptExtension::AddFunctions();
|
||||
ScriptExtension::AddMethods();
|
||||
ScriptExtension::AddEntityFields();
|
||||
ScriptExtension::AddClientFields();
|
||||
|
||||
// Correct builtin function pointer
|
||||
Utils::Hook::Set<void(*)()>(0x79A90C, ScriptExtension::Scr_TableLookupIStringByRow);
|
||||
|
||||
Utils::Hook(0x4EC721, ScriptExtension::GScr_AddFieldsForEntityStub, HOOK_CALL).install()->quick(); // GScr_AddFieldsForEntity
|
||||
|
||||
Utils::Hook(0x41BED2, ScriptExtension::Scr_SetObjectFieldStub, HOOK_CALL).install()->quick(); // SetEntityFieldValue
|
||||
Utils::Hook(0x5FBF01, ScriptExtension::Scr_SetClientFieldStub, HOOK_CALL).install()->quick(); // Scr_SetObjectField
|
||||
Utils::Hook(0x4FF413, ScriptExtension::Scr_GetEntityFieldStub, HOOK_CALL).install()->quick(); // Scr_GetObjectField
|
||||
}
|
||||
}
|
||||
|
@ -7,11 +7,28 @@ namespace Components
|
||||
public:
|
||||
ScriptExtension();
|
||||
|
||||
static void AddEntityField(const char* name, Game::fieldtype_t type, const Game::ScriptCallbackEnt& setter, const Game::ScriptCallbackEnt& getter);
|
||||
static void AddClientField(const char* name, Game::fieldtype_t type, const Game::ScriptCallbackClient& setter, const Game::ScriptCallbackClient& getter);
|
||||
|
||||
private:
|
||||
static const char* QueryStrings[];
|
||||
|
||||
static std::unordered_map<std::uint16_t, Game::ent_field_t> CustomEntityFields;
|
||||
static std::unordered_map<std::uint16_t, Game::client_fields_s> CustomClientFields;
|
||||
|
||||
static void GScr_AddFieldsForEntityStub();
|
||||
|
||||
// Two hooks because it makes our code cleaner (luckily functions were not inlined)
|
||||
static int Scr_SetObjectFieldStub(unsigned int classnum, int entnum, int offset);
|
||||
static void Scr_SetClientFieldStub(Game::gclient_s* client, int offset);
|
||||
|
||||
// One hook because functions were inlined
|
||||
static void Scr_GetEntityFieldStub(int entnum, int offset);
|
||||
|
||||
static void AddFunctions();
|
||||
static void AddMethods();
|
||||
static void AddEntityFields();
|
||||
static void AddClientFields();
|
||||
static void Scr_TableLookupIStringByRow();
|
||||
};
|
||||
}
|
||||
|
112
src/Components/Modules/Security.cpp
Normal file
112
src/Components/Modules/Security.cpp
Normal file
@ -0,0 +1,112 @@
|
||||
#include <STDInclude.hpp>
|
||||
|
||||
namespace Components
|
||||
{
|
||||
int Security::MsgReadBitsCompressCheckSV(const char* from, char* to, int size)
|
||||
{
|
||||
static char buffer[0x8000];
|
||||
|
||||
if (size > 0x800) return 0;
|
||||
size = Game::MSG_ReadBitsCompress(from, buffer, size);
|
||||
|
||||
if (size > 0x800) return 0;
|
||||
std::memcpy(to, buffer, size);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
int Security::MsgReadBitsCompressCheckCL(const char* from, char* to, int size)
|
||||
{
|
||||
static char buffer[0x100000];
|
||||
|
||||
if (size > 0x20000) return 0;
|
||||
size = Game::MSG_ReadBitsCompress(from, buffer, size);
|
||||
|
||||
if (size > 0x20000) return 0;
|
||||
std::memcpy(to, buffer, size);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
int Security::SVCanReplaceServerCommand(Game::client_t* /*client*/, const char* /*cmd*/)
|
||||
{
|
||||
// This is a fix copied from V2. As I don't have time to investigate, let's simply trust them
|
||||
return -1;
|
||||
}
|
||||
|
||||
long Security::AtolAdjustPlayerLimit(const char* string)
|
||||
{
|
||||
return std::min<long>(std::atol(string), 18);
|
||||
}
|
||||
|
||||
void Security::SelectStringTableEntryInDvarStub()
|
||||
{
|
||||
Command::ClientParams params;
|
||||
|
||||
if (params.size() >= 4)
|
||||
{
|
||||
const auto* dvarName = params[3];
|
||||
const auto* dvar = Game::Dvar_FindVar(dvarName);
|
||||
|
||||
if (Command::Find(dvarName) ||
|
||||
(dvar != nullptr && dvar->flags & (Game::DVAR_WRITEPROTECTED | Game::DVAR_CHEAT | Game::DVAR_READONLY)))
|
||||
{
|
||||
Logger::Print(0, "CL_SelectStringTableEntryInDvar_f: illegal parameter\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Game::CL_SelectStringTableEntryInDvar_f();
|
||||
}
|
||||
|
||||
__declspec(naked) int Security::G_GetClientScore()
|
||||
{
|
||||
__asm
|
||||
{
|
||||
mov eax, [esp + 4] // index
|
||||
mov ecx, ds:1A831A8h // level: &g_clients
|
||||
|
||||
test ecx, ecx
|
||||
jz invalid_ptr
|
||||
|
||||
imul eax, 366Ch
|
||||
mov eax, [eax + ecx + 3134h]
|
||||
ret
|
||||
|
||||
invalid_ptr:
|
||||
xor eax, eax
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
void Security::G_LogPrintfStub(const char* fmt)
|
||||
{
|
||||
Game::G_LogPrintf("%s", fmt);
|
||||
}
|
||||
|
||||
Security::Security()
|
||||
{
|
||||
// Exploit fixes
|
||||
Utils::Hook(0x414D92, MsgReadBitsCompressCheckSV, HOOK_CALL).install()->quick(); // SV_ExecuteClientCommands
|
||||
Utils::Hook(0x4A9F56, MsgReadBitsCompressCheckCL, HOOK_CALL).install()->quick(); // CL_ParseServerMessage
|
||||
Utils::Hook(0x407376, SVCanReplaceServerCommand, HOOK_CALL).install()->quick(); // SV_CanReplaceServerCommand
|
||||
|
||||
Utils::Hook::Set<BYTE>(0x412370, 0xC3); // SV_SteamAuthClient
|
||||
Utils::Hook::Set<BYTE>(0x5A8C70, 0xC3); // CL_HandleRelayPacket
|
||||
|
||||
Utils::Hook::Nop(0x41698E, 5); // Disable Svcmd_EntityList_f
|
||||
|
||||
// Patch selectStringTableEntryInDvar
|
||||
Utils::Hook::Set<void(*)()>(0x405959, Security::SelectStringTableEntryInDvarStub);
|
||||
|
||||
// Patch G_GetClientScore for uninitialized game
|
||||
Utils::Hook(0x469AC0, G_GetClientScore, HOOK_JUMP).install()->quick();
|
||||
|
||||
// Requests can be malicious
|
||||
Utils::Hook(0x5B67ED, AtolAdjustPlayerLimit, HOOK_CALL).install()->quick(); // PartyHost_HandleJoinPartyRequest
|
||||
|
||||
// Patch unsecure call to G_LogPrint inside GScr_LogPrint
|
||||
// This function is unsafe because IW devs forgot to G_LogPrintf("%s", fmt)
|
||||
Utils::Hook(0x5F70B5, G_LogPrintfStub, HOOK_CALL).install()->quick();
|
||||
}
|
||||
}
|
24
src/Components/Modules/Security.hpp
Normal file
24
src/Components/Modules/Security.hpp
Normal file
@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
namespace Components
|
||||
{
|
||||
class Security : public Component
|
||||
{
|
||||
public:
|
||||
Security();
|
||||
|
||||
private:
|
||||
static int MsgReadBitsCompressCheckSV(const char* from, char* to, int size);
|
||||
static int MsgReadBitsCompressCheckCL(const char* from, char* to, int size);
|
||||
|
||||
static int SVCanReplaceServerCommand(Game::client_t* client, const char* cmd);
|
||||
|
||||
static long AtolAdjustPlayerLimit(const char* string);
|
||||
|
||||
static void SelectStringTableEntryInDvarStub();
|
||||
|
||||
static int G_GetClientScore();
|
||||
|
||||
static void G_LogPrintfStub(const char* fmt);
|
||||
};
|
||||
}
|
@ -1356,11 +1356,11 @@ namespace Components
|
||||
if (*in) // height
|
||||
in++;
|
||||
|
||||
if(*in) // material name length + material name characters
|
||||
if (*in) // material name length + material name characters
|
||||
{
|
||||
const auto materialNameLength = *in;
|
||||
in++;
|
||||
for(auto i = 0; i < materialNameLength; i++)
|
||||
for (auto i = 0; i < materialNameLength; i++)
|
||||
{
|
||||
if (*in)
|
||||
in++;
|
||||
@ -1370,7 +1370,7 @@ namespace Components
|
||||
continue;
|
||||
}
|
||||
|
||||
if(*in == FONT_ICON_SEPARATOR_CHARACTER)
|
||||
if (*in == FONT_ICON_SEPARATOR_CHARACTER)
|
||||
{
|
||||
const auto* fontIconEndPos = &in[1];
|
||||
FontIconInfo fontIcon{};
|
||||
@ -1386,6 +1386,7 @@ namespace Components
|
||||
++current;
|
||||
++in;
|
||||
}
|
||||
|
||||
*out = '\0';
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,8 @@ namespace Components
|
||||
|
||||
HWND Window::MainWindow = nullptr;
|
||||
BOOL Window::CursorVisible = TRUE;
|
||||
std::unordered_map<UINT, Utils::Slot<Window::WndProcCallback>> Window::WndMessageCallbacks;
|
||||
Utils::Signal<Window::CreateCallback> Window::CreateSignals;
|
||||
|
||||
int Window::Width()
|
||||
{
|
||||
@ -66,6 +68,16 @@ namespace Components
|
||||
return Window::MainWindow;
|
||||
}
|
||||
|
||||
void Window::OnWndMessage(UINT Msg, Utils::Slot<Window::WndProcCallback> callback)
|
||||
{
|
||||
WndMessageCallbacks.emplace(Msg, callback);
|
||||
}
|
||||
|
||||
void Window::OnCreate(Utils::Slot<CreateCallback> callback)
|
||||
{
|
||||
CreateSignals.connect(callback);
|
||||
}
|
||||
|
||||
int Window::IsNoBorder()
|
||||
{
|
||||
return Window::NoBorder.get<bool>();
|
||||
@ -121,6 +133,9 @@ namespace Components
|
||||
HWND WINAPI Window::CreateMainWindow(DWORD dwExStyle, LPCSTR lpClassName, LPCSTR lpWindowName, DWORD dwStyle, int X, int Y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam)
|
||||
{
|
||||
Window::MainWindow = CreateWindowExA(dwExStyle, lpClassName, lpWindowName, dwStyle, X, Y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam);
|
||||
|
||||
CreateSignals();
|
||||
|
||||
return Window::MainWindow;
|
||||
}
|
||||
|
||||
@ -132,15 +147,21 @@ namespace Components
|
||||
|
||||
BOOL WINAPI Window::MessageHandler(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
if (Msg == WM_SETCURSOR)
|
||||
if (const auto cb = WndMessageCallbacks.find(Msg); cb != WndMessageCallbacks.end())
|
||||
{
|
||||
Window::ApplyCursor();
|
||||
return TRUE;
|
||||
return cb->second(lParam, wParam);
|
||||
}
|
||||
|
||||
return Utils::Hook::Call<BOOL(__stdcall)(HWND, UINT, WPARAM, LPARAM)>(0x4731F0)(hWnd, Msg, wParam, lParam);
|
||||
}
|
||||
|
||||
void Window::EnableDpiAwareness()
|
||||
{
|
||||
const Utils::Library user32{"user32.dll"};
|
||||
|
||||
user32.invokePascal<void>("SetProcessDpiAwarenessContext", DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
|
||||
}
|
||||
|
||||
Window::Window()
|
||||
{
|
||||
// Borderless window
|
||||
@ -184,5 +205,13 @@ namespace Components
|
||||
|
||||
// Use custom message handler
|
||||
Utils::Hook::Set(0x64D298, Window::MessageHandler);
|
||||
|
||||
Window::OnWndMessage(WM_SETCURSOR, [](WPARAM, LPARAM)
|
||||
{
|
||||
Window::ApplyCursor();
|
||||
return TRUE;
|
||||
});
|
||||
|
||||
Window::EnableDpiAwareness();
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,9 @@ namespace Components
|
||||
class Window : public Component
|
||||
{
|
||||
public:
|
||||
typedef BOOL(WndProcCallback)(WPARAM wParam, LPARAM lParam);
|
||||
typedef void(CreateCallback)();
|
||||
|
||||
Window();
|
||||
|
||||
static int Width();
|
||||
@ -18,10 +21,15 @@ namespace Components
|
||||
|
||||
static HWND GetWindow();
|
||||
|
||||
static void OnWndMessage(UINT Msg, Utils::Slot<WndProcCallback> callback);
|
||||
|
||||
static void OnCreate(Utils::Slot<CreateCallback> callback);
|
||||
private:
|
||||
static BOOL CursorVisible;
|
||||
static Dvar::Var NoBorder;
|
||||
static Dvar::Var NativeCursor;
|
||||
static std::unordered_map<UINT, Utils::Slot<WndProcCallback>> WndMessageCallbacks;
|
||||
static Utils::Signal<CreateCallback> CreateSignals;
|
||||
|
||||
static HWND MainWindow;
|
||||
|
||||
@ -36,5 +44,7 @@ namespace Components
|
||||
|
||||
static void StyleHookStub();
|
||||
static HWND WINAPI CreateMainWindow(DWORD dwExStyle, LPCSTR lpClassName, LPCSTR lpWindowName, DWORD dwStyle, int X, int Y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam);
|
||||
|
||||
static void EnableDpiAwareness();
|
||||
};
|
||||
}
|
||||
|
@ -153,6 +153,7 @@ namespace Game
|
||||
FS_IsShippedIWD_t FS_IsShippedIWD = FS_IsShippedIWD_t(0x642440);
|
||||
FS_Delete_t FS_Delete = FS_Delete_t(0x48A5B0);
|
||||
|
||||
G_LogPrintf_t G_LogPrintf = G_LogPrintf_t(0x4B0150);
|
||||
G_GetWeaponIndexForName_t G_GetWeaponIndexForName = G_GetWeaponIndexForName_t(0x49E540);
|
||||
G_SpawnEntitiesFromString_t G_SpawnEntitiesFromString = G_SpawnEntitiesFromString_t(0x4D8840);
|
||||
G_PrintEntities_t G_PrintEntities = G_PrintEntities_t(0x4E6A50);
|
||||
@ -295,6 +296,12 @@ namespace Game
|
||||
|
||||
Scr_ClearOutParams_t Scr_ClearOutParams = Scr_ClearOutParams_t(0x4386E0);
|
||||
|
||||
Scr_GetObjectField_t Scr_GetObjectField = Scr_GetObjectField_t(0x4FF3D0);
|
||||
Scr_SetObjectField_t Scr_SetObjectField = Scr_SetObjectField_t(0x4F20F0);
|
||||
Scr_GetEntityField_t Scr_GetEntityField = Scr_GetEntityField_t(0x4E8390);
|
||||
Scr_SetClientField_t Scr_SetClientField = Scr_SetClientField_t(0x4A6DF0);
|
||||
Scr_AddClassField_t Scr_AddClassField = Scr_AddClassField_t(0x4C0E70);
|
||||
|
||||
GetEntity_t GetEntity = GetEntity_t(0x4BC270);
|
||||
GetPlayerEntity_t GetPlayerEntity = GetPlayerEntity_t(0x49C4A0);
|
||||
|
||||
@ -415,6 +422,13 @@ namespace Game
|
||||
PM_Trace_t PM_Trace = PM_Trace_t(0x441F60);
|
||||
PM_GetEffectiveStance_t PM_GetEffectiveStance = PM_GetEffectiveStance_t(0x412540);
|
||||
|
||||
CL_MouseEvent_t CL_MouseEvent = CL_MouseEvent_t(0x4D7C50);
|
||||
IN_RecenterMouse_t IN_RecenterMouse = IN_RecenterMouse_t(0x463D80);
|
||||
|
||||
IN_MouseMove_t IN_MouseMove = IN_MouseMove_t(0x64C490);
|
||||
IN_Init_t IN_Init = IN_Init_t(0x45D620);
|
||||
IN_Shutdown_t IN_Shutdown = IN_Shutdown_t(0x426360);
|
||||
|
||||
XAssetHeader* DB_XAssetPool = reinterpret_cast<XAssetHeader*>(0x7998A8);
|
||||
unsigned int* g_poolSize = reinterpret_cast<unsigned int*>(0x7995E8);
|
||||
|
||||
@ -539,6 +553,11 @@ namespace Game
|
||||
|
||||
level_locals_t* level = reinterpret_cast<level_locals_t*>(0x1A831A8);
|
||||
|
||||
WinMouseVars_t* s_wmv = reinterpret_cast<WinMouseVars_t*>(0x649D640);
|
||||
|
||||
int* window_center_x = reinterpret_cast<int*>(0x649D638);
|
||||
int* window_center_y = reinterpret_cast<int*>(0x649D630);
|
||||
|
||||
void Sys_LockRead(FastCriticalSection* critSect)
|
||||
{
|
||||
InterlockedIncrement(&critSect->readCount);
|
||||
|
@ -341,7 +341,7 @@ namespace Game
|
||||
typedef int(__cdecl * FS_FOpenFileReadForThread_t)(const char *filename, int *file, int thread);
|
||||
extern FS_FOpenFileReadForThread_t FS_FOpenFileReadForThread;
|
||||
|
||||
typedef int(__cdecl * FS_FCloseFile_t)(int fh);
|
||||
typedef int(__cdecl * FS_FCloseFile_t)(int stream);
|
||||
extern FS_FCloseFile_t FS_FCloseFile;
|
||||
|
||||
typedef bool(__cdecl * FS_FileExists_t)(const char* file);
|
||||
@ -380,6 +380,9 @@ namespace Game
|
||||
typedef int(__cdecl* FS_Delete_t)(const char* fileName);
|
||||
extern FS_Delete_t FS_Delete;
|
||||
|
||||
typedef void(__cdecl * G_LogPrintf_t)(const char* fmt, ...);
|
||||
extern G_LogPrintf_t G_LogPrintf;
|
||||
|
||||
typedef unsigned int(__cdecl * G_GetWeaponIndexForName_t)(const char*);
|
||||
extern G_GetWeaponIndexForName_t G_GetWeaponIndexForName;
|
||||
|
||||
@ -744,6 +747,21 @@ namespace Game
|
||||
typedef void(__cdecl * Scr_ParamError_t)(unsigned int paramIndex, const char*);
|
||||
extern Scr_ParamError_t Scr_ParamError;
|
||||
|
||||
typedef void(__cdecl * Scr_GetObjectField_t)(unsigned int classnum, int entnum, int offset);
|
||||
extern Scr_GetObjectField_t Scr_GetObjectField;
|
||||
|
||||
typedef int(__cdecl * Scr_SetObjectField_t)(unsigned int classnum, int entnum, int offset);
|
||||
extern Scr_SetObjectField_t Scr_SetObjectField;
|
||||
|
||||
typedef void(__cdecl * Scr_SetClientField_t)(gclient_s* client, int offset);
|
||||
extern Scr_SetClientField_t Scr_SetClientField;
|
||||
|
||||
typedef void(__cdecl * Scr_GetEntityField_t)(int entnum, int offset);
|
||||
extern Scr_GetEntityField_t Scr_GetEntityField;
|
||||
|
||||
typedef void(__cdecl * Scr_AddClassField_t)(unsigned int classnum, const char* name, unsigned int offset);
|
||||
extern Scr_AddClassField_t Scr_AddClassField;
|
||||
|
||||
typedef gentity_s*(__cdecl * GetPlayerEntity_t)(scr_entref_t entref);
|
||||
extern GetPlayerEntity_t GetPlayerEntity;
|
||||
|
||||
@ -990,6 +1008,21 @@ namespace Game
|
||||
typedef EffectiveStance(__cdecl * PM_GetEffectiveStance_t)(const playerState_s* ps);
|
||||
extern PM_GetEffectiveStance_t PM_GetEffectiveStance;
|
||||
|
||||
typedef int(__cdecl * CL_MouseEvent_t)(int x, int y, int dx, int dy);
|
||||
extern CL_MouseEvent_t CL_MouseEvent;
|
||||
|
||||
typedef void(__cdecl * IN_RecenterMouse_t)();
|
||||
extern IN_RecenterMouse_t IN_RecenterMouse;
|
||||
|
||||
typedef void(__cdecl * IN_MouseMove_t)();
|
||||
extern IN_MouseMove_t IN_MouseMove;
|
||||
|
||||
typedef void(__cdecl * IN_Init_t)();
|
||||
extern IN_Init_t IN_Init;
|
||||
|
||||
typedef void(__cdecl * IN_Shutdown_t)();
|
||||
extern IN_Shutdown_t IN_Shutdown;
|
||||
|
||||
extern XAssetHeader* DB_XAssetPool;
|
||||
extern unsigned int* g_poolSize;
|
||||
|
||||
@ -1120,6 +1153,11 @@ namespace Game
|
||||
|
||||
extern level_locals_t* level;
|
||||
|
||||
extern WinMouseVars_t* s_wmv;
|
||||
|
||||
extern int* window_center_x;
|
||||
extern int* window_center_y;
|
||||
|
||||
void Sys_LockRead(FastCriticalSection* critSect);
|
||||
void Sys_UnlockRead(FastCriticalSection* critSect);
|
||||
|
||||
|
@ -231,6 +231,17 @@ namespace Game
|
||||
FL_MOVER_SLIDE = 0x8000000
|
||||
};
|
||||
|
||||
enum ClassNum : unsigned int
|
||||
{
|
||||
CLASS_NUM_ENTITY = 0x0,
|
||||
CLASS_NUM_HUDELEM = 0x1,
|
||||
CLASS_NUM_PATHNODE = 0x2,
|
||||
CLASS_NUM_VEHICLENODE = 0x3,
|
||||
CLASS_NUM_VEHTRACK_SEGMENT = 0x4,
|
||||
CLASS_NUM_FXENTITY = 0x5,
|
||||
CLASS_NUM_COUNT = 0x6,
|
||||
};
|
||||
|
||||
typedef enum
|
||||
{
|
||||
HITLOC_NONE,
|
||||
@ -5705,6 +5716,53 @@ namespace Game
|
||||
|
||||
static_assert(sizeof(gentity_s) == 0x274);
|
||||
|
||||
enum $1C4253065710F064DA9E4D59ED6EC544
|
||||
{
|
||||
ENTFIELD_ENTITY = 0x0,
|
||||
ENTFIELD_SENTIENT = 0x2000,
|
||||
ENTFIELD_ACTOR = 0x4000,
|
||||
ENTFIELD_CLIENT = 0x6000,
|
||||
ENTFIELD_VEHICLE = 0x8000,
|
||||
ENTFIELD_MASK = 0xE000,
|
||||
};
|
||||
|
||||
enum fieldtype_t
|
||||
{
|
||||
F_INT = 0x0,
|
||||
F_SHORT = 0x1,
|
||||
F_BYTE = 0x2,
|
||||
F_FLOAT = 0x3,
|
||||
F_CSTRING = 0x4,
|
||||
F_STRING = 0x5,
|
||||
F_VECTOR = 0x6,
|
||||
F_ENTITY = 0x7,
|
||||
F_ENTHANDLE = 0x8,
|
||||
F_ANGLES_YAW = 0x9,
|
||||
F_OBJECT = 0xA,
|
||||
F_MODEL = 0xB,
|
||||
};
|
||||
|
||||
struct ent_field_t
|
||||
{
|
||||
const char* name;
|
||||
int ofs;
|
||||
fieldtype_t type;
|
||||
void(__cdecl * setter)(gentity_s*, int);
|
||||
void(__cdecl * getter)(gentity_s*, int);
|
||||
};
|
||||
|
||||
struct client_fields_s
|
||||
{
|
||||
const char* name;
|
||||
int ofs;
|
||||
fieldtype_t type;
|
||||
void(__cdecl * setter)(gclient_s*, const client_fields_s*);
|
||||
void(__cdecl * getter)(gclient_s*, const client_fields_s*);
|
||||
};
|
||||
|
||||
typedef void(__cdecl * ScriptCallbackEnt)(gentity_s*, int);
|
||||
typedef void(__cdecl * ScriptCallbackClient)(gclient_s*, const client_fields_s*);
|
||||
|
||||
struct lockonFireParms
|
||||
{
|
||||
bool lockon;
|
||||
@ -6966,7 +7024,7 @@ namespace Game
|
||||
SHELLSHOCK_VIEWTYPE_NONE = 0x2,
|
||||
};
|
||||
|
||||
struct shellshock_parms_t
|
||||
struct shellshock_parms_t
|
||||
{
|
||||
struct
|
||||
{
|
||||
@ -7415,6 +7473,16 @@ namespace Game
|
||||
|
||||
static_assert(sizeof(level_locals_t) == 0x2F78);
|
||||
|
||||
struct WinMouseVars_t
|
||||
{
|
||||
int oldButtonState;
|
||||
tagPOINT oldPos;
|
||||
bool mouseActive;
|
||||
bool mouseInitialized;
|
||||
};
|
||||
|
||||
static_assert(sizeof(WinMouseVars_t) == 0x10);
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#ifndef IDA
|
||||
|
@ -11,7 +11,7 @@ namespace Utils
|
||||
static_assert(Buffers != 0 && MinBufferSize != 0, "Buffers and MinBufferSize mustn't be 0");
|
||||
|
||||
VAProvider() : currentBuffer(0) {}
|
||||
~VAProvider() {}
|
||||
~VAProvider() = default;
|
||||
|
||||
const char* get(const char* format, va_list ap)
|
||||
{
|
||||
@ -25,7 +25,7 @@ namespace Utils
|
||||
|
||||
while (true)
|
||||
{
|
||||
int res = vsnprintf_s(entry->buffer, entry->size, _TRUNCATE, format, ap);
|
||||
const auto res = _vsnprintf_s(entry->buffer, entry->size, _TRUNCATE, format, ap);
|
||||
if (res > 0) break; // Success
|
||||
if (res == 0) return ""; // Error
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user