Rewrite crithack

This commit is contained in:
BenCat07 2020-04-15 16:15:26 +02:00
parent 5a88fb7842
commit fddbac7d9d
17 changed files with 739 additions and 363 deletions

View File

@ -1,13 +1,11 @@
<Tab name="Crits" padding="4 4 4 4">
<Box padding="12 6 6 6" width="content" height="content" name="Crits">
<List width="200">
<AutoVariable width="fill" target="crit.key" label="Crit Key"/>
<AutoVariable width="fill" target="crit.auto" label="Always Crit (Ignore Crit Key)"/>
<AutoVariable width="fill" target="crit.info" label="Crit Info"/>
<AutoVariable width="fill" target="crit.experimental" label="Instant Crits"/>
<AutoVariable width="fill" target="crit.melee" label="Melee Crits"/>
<AutoVariable width="fill" target="crit.experimental.melee" label="Experimental Melee Crits"/>
<AutoVariable width="fill" target="crit.force-gameplay" label="Do Not Hinder Gameplay"/>
<AutoVariable width="fill" target="crit.enabled" label="Enable Crithack" tooltip="Note that melee crits will not be enabled"/>
<AutoVariable width="fill" target="crit.key" label="Crit Key" tooltip="Key used for non-melee crits"/>
<AutoVariable width="fill" target="crit.draw-info" label="Crit Info" tooltip="Draw Useful information on screen"/>
<AutoVariable width="fill" target="crit.melee" label="Melee Crits" tooltip="Enable Melee crits (Not bound to key)"/>
<AutoVariable width="fill" target="crit.old-mode" label="Old mode" tooltip="Use the old method for crits"/>
</List>
</Box>
</Tab>

View File

@ -121,6 +121,7 @@ public:
offset_t flChargeBeginTime;
offset_t flLastFireTime;
offset_t flObservedCritChance;
offset_t hThrower;
offset_t hMyWeapons;

View File

@ -80,6 +80,10 @@ struct offsets
{
return PlatformOffset(7, undefined, undefined);
}
static constexpr uint32_t CreateMoveLate()
{
return PlatformOffset(21, undefined, undefined);
}
static constexpr uint32_t CreateMove()
{
return PlatformOffset(22, undefined, undefined);

View File

@ -1,37 +1,7 @@
/*
* crits.h
*
* Created on: Feb 25, 2017
* Author: nullifiedcat
*/
#pragma once
class CUserCmd;
class IClientEntity;
// BUGBUG TODO this struct is outdated
struct crithack_saved_state
{
float unknown2868;
float unknown2864;
int unknown2620;
float unknown2880;
char unknown2839;
float bucket2616;
int seed2876;
void Save(IClientEntity *entity);
void Load(IClientEntity *entity);
};
#include "settings/Bool.hpp"
namespace criticals
{
bool random_crits_enabled();
extern settings::Boolean enabled;
extern settings::Boolean melee;
} // namespace criticals
#include <unordered_map>
extern int *g_PredictionRandomSeed;
extern std::unordered_map<int, int> command_number_mod;

View File

@ -40,5 +40,7 @@ public:
bool isHackActive();
extern CUserCmd *current_user_cmd;
// Seperate user cmd used in the late createmove
extern CUserCmd *current_late_user_cmd;
extern GlobalSettings g_Settings;

View File

@ -119,6 +119,9 @@ std::unique_ptr<char[]> strfmt(const char *fmt, ...);
bool HasWeapon(CachedEntity *ent, int wantedId);
bool IsAmbassador(CachedEntity *ent);
bool AmbassadorCanHeadshot();
// Validate a UserCmd
#define VERIFIED_CMD_SIZE 90
void ValidateUserCmd(CUserCmd *cmd, int sequence_nr);
// Convert a TF2 handle into an IDX -> ENTITY(IDX)
int HandleToIDX(int handle);

View File

@ -11,6 +11,8 @@ enum ec_types
{
/* Note: engine prediction is run on this kind of CreateMove */
CreateMove = 0,
/* Note: this is the CreatMove one layer higher, and should only be used for things that mess with command number*/
CreateMoveLate,
/* This kind of CreateMove will run earlier than all CreateMove events
* and guranteed to run before EnginePrediction
*/

View File

@ -41,6 +41,7 @@ DECLARE_HOOKED_METHOD(LevelShutdown, void, void *);
// ClientMode + 4
DECLARE_HOOKED_METHOD(FireGameEvent, void, void *, IGameEvent *);
// IBaseClient
DECLARE_HOOKED_METHOD(CreateMoveLate, void, void *this_, int sequence_nr, float input_sample_time, bool arg3)
DECLARE_HOOKED_METHOD(DispatchUserMessage, bool, void *, int, bf_read &);
DECLARE_HOOKED_METHOD(IN_KeyEvent, int, void *, int, ButtonCode_t, const char *);
DECLARE_HOOKED_METHOD(FrameStageNotify, void, void *, ClientFrameStage_t);

View File

@ -129,7 +129,7 @@ public:
float mult2 = *(float *) (unk3);
float multiplier = 0.5f;
int seed = C_BaseEntity::m_nPredictionRandomSeed() ^ (owner->entindex() | self->entindex());
int seed = C_BaseEntity::m_nPredictionRandomSeed() ^ (owner->entindex() | (self->entindex() << 8));
RandomSeed(seed);
bool result = true;

View File

@ -10,23 +10,168 @@
#include <stdint.h>
#include <mathlib/vector.h>
#include "checksum_crc.h"
class CUserCmd
{
public:
virtual ~CUserCmd(){};
CUserCmd()
{
Reset();
}
void Reset()
{
command_number = 0;
tick_count = 0;
viewangles.Init();
forwardmove = 0.0f;
sidemove = 0.0f;
upmove = 0.0f;
buttons = 0;
impulse = 0;
weaponselect = 0;
weaponsubtype = 0;
random_seed = 0;
mousedx = 0;
mousedy = 0;
hasbeenpredicted = false;
}
CUserCmd &operator=(const CUserCmd &src)
{
if (this == &src)
return *this;
command_number = src.command_number;
tick_count = src.tick_count;
viewangles = src.viewangles;
forwardmove = src.forwardmove;
sidemove = src.sidemove;
upmove = src.upmove;
buttons = src.buttons;
impulse = src.impulse;
weaponselect = src.weaponselect;
weaponsubtype = src.weaponsubtype;
random_seed = src.random_seed;
mousedx = src.mousedx;
mousedy = src.mousedy;
hasbeenpredicted = src.hasbeenpredicted;
return *this;
}
CUserCmd(const CUserCmd &src)
{
*this = src;
}
CRC32_t GetChecksum(void) const
{
CRC32_t crc;
CRC32_Init(&crc);
CRC32_ProcessBuffer(&crc, &command_number, sizeof(command_number));
CRC32_ProcessBuffer(&crc, &tick_count, sizeof(tick_count));
CRC32_ProcessBuffer(&crc, &viewangles, sizeof(viewangles));
CRC32_ProcessBuffer(&crc, &forwardmove, sizeof(forwardmove));
CRC32_ProcessBuffer(&crc, &sidemove, sizeof(sidemove));
CRC32_ProcessBuffer(&crc, &upmove, sizeof(upmove));
CRC32_ProcessBuffer(&crc, &buttons, sizeof(buttons));
CRC32_ProcessBuffer(&crc, &impulse, sizeof(impulse));
CRC32_ProcessBuffer(&crc, &weaponselect, sizeof(weaponselect));
CRC32_ProcessBuffer(&crc, &weaponsubtype, sizeof(weaponsubtype));
CRC32_ProcessBuffer(&crc, &random_seed, sizeof(random_seed));
CRC32_ProcessBuffer(&crc, &mousedx, sizeof(mousedx));
CRC32_ProcessBuffer(&crc, &mousedy, sizeof(mousedy));
CRC32_Final(&crc);
return crc;
}
// Allow command, but negate gameplay-affecting values
void MakeInert(void)
{
viewangles.Init();
forwardmove = 0.f;
sidemove = 0.f;
upmove = 0.f;
buttons = 0;
impulse = 0;
}
char pad0[4];
// For matching server and client commands for debugging
int command_number;
// the tick the client created this command
int tick_count;
// Player instantaneous view angles.
Vector viewangles;
// Intended velocities
// forward velocity.
float forwardmove;
// sideways velocity.
float sidemove;
// upward velocity.
float upmove;
// Attack button states
int buttons;
uint8_t impulse;
// Impulse command issued.
byte impulse;
char pad1[3];
// Current weapon id
int weaponselect;
int weaponsubtype;
int random_seed;
short mousedx;
short mousedy;
int random_seed; // For shared random functions
short mousedx; // mouse accum in x from create move
short mousedy; // mouse accum in y from create move
// Client only, tracks whether we've predicted this command at least once
bool hasbeenpredicted;
};
char pad2[3];
} __attribute__((packed));
class CVerifiedUserCmd
{
public:
CUserCmd m_cmd;
CRC32_t m_crc;
} __attribute__((packed));
inline CRC32_t GetChecksum(CUserCmd *cmd)
{
CRC32_t crc;
CRC32_Init(&crc);
CRC32_ProcessBuffer(&crc, &cmd->command_number, sizeof(cmd->command_number));
CRC32_ProcessBuffer(&crc, &cmd->tick_count, sizeof(cmd->tick_count));
CRC32_ProcessBuffer(&crc, &cmd->viewangles, sizeof(cmd->viewangles));
CRC32_ProcessBuffer(&crc, &cmd->forwardmove, sizeof(cmd->forwardmove));
CRC32_ProcessBuffer(&crc, &cmd->sidemove, sizeof(cmd->sidemove));
CRC32_ProcessBuffer(&crc, &cmd->upmove, sizeof(cmd->upmove));
CRC32_ProcessBuffer(&crc, &cmd->buttons, sizeof(cmd->buttons));
CRC32_ProcessBuffer(&crc, &cmd->impulse, sizeof(cmd->impulse));
CRC32_ProcessBuffer(&crc, &cmd->weaponselect, sizeof(cmd->weaponselect));
CRC32_ProcessBuffer(&crc, &cmd->weaponsubtype, sizeof(cmd->weaponsubtype));
CRC32_ProcessBuffer(&crc, &cmd->random_seed, sizeof(cmd->random_seed));
CRC32_ProcessBuffer(&crc, &cmd->mousedx, sizeof(cmd->mousedx));
CRC32_ProcessBuffer(&crc, &cmd->mousedy, sizeof(cmd->mousedy));
CRC32_Final(&crc);
return crc;
}
inline CUserCmd *GetCmds(void *iinput)
{
return *(CUserCmd **) ((uintptr_t) iinput + 0xfc);
}
inline CVerifiedUserCmd *GetVerifiedCmds(void *iinput)
{
return *(CVerifiedUserCmd **) ((uintptr_t) iinput + 0x100);
}

View File

@ -50,6 +50,7 @@ void NetVars::Init()
"m_AttributeList"); // hmmm
this->flChargeBeginTime = gNetvars.get_offset("DT_WeaponPipebombLauncher", "PipebombLauncherLocalData", "m_flChargeBeginTime");
this->flLastFireTime = gNetvars.get_offset("DT_TFWeaponBase", "LocalActiveTFWeaponData", "m_flLastFireTime");
this->flObservedCritChance = gNetvars.get_offset("DT_TFWeaponBase", "LocalActiveTFWeaponData", "m_flObservedCritChance");
this->bDistributed = gNetvars.get_offset("DT_CurrencyPack", "m_bDistributed");
this->_condition_bits = gNetvars.get_offset("DT_TFPlayer", "m_Shared", "m_ConditionList", "_condition_bits");
this->m_flStealthNoAttackExpire = gNetvars.get_offset("DT_TFPlayer", "m_Shared", "tfsharedlocaldata", "m_flStealthNoAttackExpire");

View File

@ -1,357 +1,571 @@
/*
* crits.cpp
*
* Created on: Feb 25, 2017
* Author: nullifiedcat
*/
#include <settings/Bool.hpp>
#include "common.hpp"
#include "Backtrack.hpp"
#include "PlayerTools.hpp"
#include "crits.hpp"
#include "netadr.h"
std::unordered_map<int, int> command_number_mod{};
namespace criticals
{
static settings::Boolean crit_info{ "crit.info", "false" };
settings::Boolean enabled{ "crit.enabled", "false" };
static settings::Boolean draw{ "crit.draw-info", "false" };
settings::Boolean melee{ "crit.melee", "false" };
static settings::Button crit_key{ "crit.key", "<null>" };
static settings::Boolean auto_crit{ "crit.auto", "false" };
static settings::Boolean crit_melee{ "crit.melee", "false" };
static settings::Boolean crit_legiter{ "crit.force-gameplay", "false" };
static settings::Boolean crit_experimental{ "crit.experimental", "false" };
static settings::Boolean old_mode{ "crit.old-mode", "false" };
int find_next_random_crit_for_weapon(IClientEntity *weapon)
// How much is added to bucket per shot?
float added_per_shot = 0.0f;
class weapon_info
{
int tries = 0, number = current_user_cmd->command_number, found = 0, seed_backup;
crithack_saved_state state{};
state.Save(weapon);
seed_backup = *g_PredictionRandomSeed;
while (!found && tries < 4096)
public:
float crit_bucket{};
unsigned int weapon_seed{};
unsigned unknown1{};
unsigned unknown2{};
bool unknown3{};
float unknown4{};
int crit_attempts{};
int crit_count{};
float observed_crit_chance{};
bool unknown7{};
weapon_info()
{
*g_PredictionRandomSeed = MD5_PseudoRandom(number) & 0x7FFFFFFF;
found = re::C_TFWeaponBase::CalcIsAttackCritical(weapon);
if (found)
break;
++tries;
++number;
}
void Load(IClientEntity *weapon)
{
crit_bucket = *(float *) ((uintptr_t) weapon + 0xa38);
weapon_seed = *(unsigned int *) ((uintptr_t) weapon + 0xb3c);
unknown1 = *(unsigned int *) ((uintptr_t) weapon + 0xb30);
unknown2 = *(unsigned int *) ((uintptr_t) weapon + 0xb34);
unknown3 = *(bool *) ((uintptr_t) weapon + 0xb17);
unknown4 = *(float *) ((uintptr_t) weapon + 0xb40);
crit_attempts = *(int *) ((uintptr_t) weapon + 0xa3c);
crit_count = *(int *) ((uintptr_t) weapon + 0xa40);
observed_crit_chance = *(float *) ((uintptr_t) weapon + 0xbfc);
unknown7 = *(bool *) ((uintptr_t) weapon + 0xb18);
}
weapon_info(IClientEntity *weapon)
{
Load(weapon);
}
void restore_data(IClientEntity *weapon)
{
*(float *) ((uintptr_t) weapon + 0xa38) = crit_bucket;
*(unsigned int *) ((uintptr_t) weapon + 0xb3c) = weapon_seed;
*(unsigned int *) ((uintptr_t) weapon + 0xb30) = unknown1;
*(unsigned int *) ((uintptr_t) weapon + 0xb34) = unknown2;
*(bool *) ((uintptr_t) weapon + 0xb17) = unknown3;
*(float *) ((uintptr_t) weapon + 0xb40) = unknown4;
*(int *) ((uintptr_t) weapon + 0xa3c) = crit_attempts;
*(int *) ((uintptr_t) weapon + 0xa40) = crit_count;
*(float *) ((uintptr_t) weapon + 0xbfc) = observed_crit_chance;
*(bool *) ((uintptr_t) weapon + 0xb18) = unknown7;
}
};
float GetWithdrawMult(IClientEntity *wep)
{
weapon_info info(wep);
// Increment call count
int call_count = info.crit_count + 1;
// How many times there was a check for crits
int crit_checks = info.crit_attempts + 1;
float flMultiply = 0.5;
if (g_pLocalPlayer->weapon_mode != weapon_melee)
{
float flTmp = min(max((((float) call_count / (float) crit_checks) - 0.1) * 1.111, 0.0), 1.0);
flMultiply = (flTmp * 2.0) + 1.0;
}
*g_PredictionRandomSeed = seed_backup;
if (!crit_experimental || g_pLocalPlayer->weapon_mode == weaponmode::weapon_melee)
state.Load(weapon);
if (found)
return number;
return 0;
float flToRemove = flMultiply * 3.0;
return flToRemove;
}
void unfuck_bucket(IClientEntity *weapon)
static CatCommand debug_print_mult("debug_print_mult", "print Withdraw multiplier", []() {
if (CE_BAD(LOCAL_E) || CE_BAD(LOCAL_W))
return;
logging::Info("Multiplier: %f", GetWithdrawMult(RAW_ENT(LOCAL_W)));
});
static Timer bucket_cap_refresh{};
float GetBucketCap()
{
static bool changed;
static float return_value;
static ConVar *tf_weapon_criticals = g_ICvar->FindVar("tf_weapon_criticals_bucket_cap");
if (bucket_cap_refresh.test_and_set(5000))
return_value = tf_weapon_criticals->GetFloat();
return return_value;
}
// Add damage parameter adds the same value that AddToCritbucket would add
bool IsAllowedToWithdrawFromCritBucket(IClientEntity *wep, float flDamage, bool add_damage = true)
{
weapon_info info(wep);
if (add_damage)
{
info.crit_bucket += flDamage;
if (info.crit_bucket > GetBucketCap())
info.crit_bucket = GetBucketCap();
}
float flToRemove = GetWithdrawMult(wep) * flDamage;
// Can remove
if (flToRemove <= info.crit_bucket)
return true;
return false;
}
// As opposed to the function above this does actual writing
void SimulateNormalShot(IClientEntity *wep, float flDamage)
{
weapon_info info(wep);
info.crit_bucket += flDamage;
if (info.crit_bucket > GetBucketCap())
info.crit_bucket = GetBucketCap();
// Write other values important for iteration
info.crit_attempts++;
info.restore_data(wep);
}
bool CanWeaponCrit(IClientEntity *wep)
{
// Are we allowed to even use that much?
if (g_pLocalPlayer->weapon_mode != weapon_melee && !IsAllowedToWithdrawFromCritBucket(wep, added_per_shot))
return false;
return true;
}
// Calculate shots until crit
int ShotsUntilCrit(IClientEntity *wep)
{
weapon_info info(wep);
// How many shots until we can crit
int shots = 0;
// Predicting 100 shots should be fine
for (shots = 0; shots < 100; shots++)
{
if (IsAllowedToWithdrawFromCritBucket(wep, added_per_shot, true))
break;
// Do calculations
SimulateNormalShot(wep, added_per_shot);
}
// Restore variables
info.restore_data(wep);
return shots;
}
typedef float (*AttribHookFloat_t)(float, const char *, IClientEntity *, void *, bool);
// Calculate Crit penalty
float CalcCritPenalty(IClientEntity *wep)
{
// Need this to get crit chance from weapon
static uintptr_t AttribHookFloat = gSignatures.GetClientSignature("55 89 E5 57 56 53 83 EC 6C C7 45 ? 00 00 00 00 A1 ? ? ? ? C7 45 ? 00 00 00 00 8B 75 ? 85 C0 0F 84 ? ? ? ? 8D 55 ? 89 04 24 31 DB 89 54 24");
static AttribHookFloat_t AttribHookFloat_fn = AttribHookFloat_t(AttribHookFloat);
// Player specific Multiplier
float crit_mult = re::CTFPlayerShared::GetCritMult((re::CTFPlayerShared *) (((uintptr_t) RAW_ENT(LOCAL_E)) + 0x5f3));
// Weapon specific Multiplier
static std::string query = "mult_crit_chance";
float flMultCritChance = AttribHookFloat_fn(crit_mult * 0.02, query.c_str(), wep, 0, 1);
return flMultCritChance;
}
std::pair<float, float> CritMultInfo(IClientEntity *wep)
{
float cur_crit = CalcCritPenalty(wep);
float observed_chance = CE_FLOAT(LOCAL_W, netvar.flObservedCritChance);
float needed_chance = cur_crit + 0.1f;
return std::pair<float, float>(observed_chance, needed_chance);
}
CatCommand debug_observed("debug_observed_crit", "debug", []() {
if (CE_GOOD(LOCAL_E) && CE_GOOD(LOCAL_W))
logging::Info("%f", CE_FLOAT(LOCAL_W, netvar.flObservedCritChance));
});
CatCommand debug_damage("debug_print_damage_until_crit", "debug", []() {
if (CE_GOOD(LOCAL_E) && CE_GOOD(LOCAL_W))
{
float penalty = CalcCritPenalty(RAW_ENT(LOCAL_W));
float observed_chance = CE_FLOAT(LOCAL_W, netvar.flObservedCritChance);
rgba_t color = colors::green;
if (penalty + 0.1f < observed_chance)
color = colors::red;
Color valve_color(color.r * 255.0f, color.g * 255.0f, color.b * 255.0f, color.a * 255.0f);
g_ICvar->ConsoleColorPrintf(valve_color, "%f\n", observed_chance);
}
});
CatCommand debug_shots("debug_print_shots_until_crit", "debug", []() {
if (CE_GOOD(LOCAL_E) && CE_GOOD(LOCAL_W))
logging::Info("%d", ShotsUntilCrit(RAW_ENT(LOCAL_W)));
});
int next_crit_tick()
{
auto wep = RAW_ENT(LOCAL_W);
int old_seed = MD5_PseudoRandom(current_late_user_cmd->command_number) & 0x7FFFFFFF;
// Try 4096 times
for (int i = 0; i < 4096; i++)
{
int cmd_number = current_late_user_cmd->command_number + i;
// Set random seed
*g_PredictionRandomSeed = MD5_PseudoRandom(cmd_number) & 0x7FFFFFFF;
// Save weapon state to not break anything
weapon_info info(wep);
bool is_crit = re::C_TFWeaponBase::CalcIsAttackCritical(wep);
// Restore state
info.restore_data(wep);
// Is a crit
if (is_crit)
{
*g_PredictionRandomSeed = old_seed;
return cmd_number;
}
}
*g_PredictionRandomSeed = old_seed;
return -1;
}
static Timer random_crit_refresh{};
bool random_crits_enabled()
{
static bool return_value;
static ConVar *tf_weapon_criticals = g_ICvar->FindVar("tf_weapon_criticals");
if (random_crit_refresh.test_and_set(3000))
return_value = tf_weapon_criticals->GetBool();
return return_value;
}
static int force_ticks = 0;
bool IsEnabled()
{
// Random crits on
if (!random_crits_enabled())
return false;
// Weapon specific checks
if (force_ticks || ((melee && g_pLocalPlayer->weapon_mode == weapon_melee) || (enabled && g_pLocalPlayer->weapon_mode != weapon_melee && (!crit_key || crit_key.isKeyDown()))))
return true;
return false;
}
// clang-format on
static int idx = 0;
static std::vector<int> crit_cmds;
void force_crit()
{
if (!IsEnabled())
return;
if (!old_mode && g_pLocalPlayer->weapon_mode != weapon_melee && LOCAL_W->m_iClassID() != CL_CLASS(CTFPipebombLauncher))
{
if (crit_cmds.size())
{
if (idx > crit_cmds.size())
idx = 0;
// Magic crit cmds
current_late_user_cmd->command_number = crit_cmds[idx];
current_late_user_cmd->random_seed = MD5_PseudoRandom(current_late_user_cmd->command_number) & 0x7FFFFFFF;
idx++;
}
}
else
{
int next_crit = next_crit_tick();
if (next_crit != -1)
{
// We can just force to nearest crit for melee, and sticky launchers apparently
if (g_pLocalPlayer->weapon_mode == weapon_melee || LOCAL_W->m_iClassID() == CL_CLASS(CTFPipebombLauncher))
{
if (LOCAL_W->m_iClassID() == CL_CLASS(CTFPipebombLauncher))
{
if (!force_ticks)
force_ticks = 3;
force_ticks--;
}
current_late_user_cmd->command_number = next_crit;
current_late_user_cmd->random_seed = MD5_PseudoRandom(next_crit) & 0x7FFFFFFF;
}
// No crit, don't attack
else if (current_late_user_cmd->command_number != next_crit)
current_user_cmd->buttons &= ~IN_ATTACK;
}
}
}
void update_cmds()
{
// Melee weapons do not matter
if (g_pLocalPlayer->weapon_mode == weapon_melee)
return;
auto weapon = RAW_ENT(LOCAL_W);
static float last_bucket;
static int last_weapon;
if (current_user_cmd->command_number)
changed = false;
float &bucket = re::C_TFWeaponBase::crit_bucket_(weapon);
if (bucket != last_bucket)
int lowest_value = INT_MAX;
int cur_cmdnum = current_late_user_cmd->command_number;
bool old_cmds = false;
for (auto &cmd : crit_cmds)
if (cmd < cur_cmdnum)
old_cmds = true;
if (weapon->entindex() != last_weapon || old_cmds)
{
if (changed && weapon->entindex() == last_weapon)
// Used later
int xor_dat = (weapon->entindex() << 8 | LOCAL_E->m_IDX);
// Clear old data
crit_cmds.clear();
added_per_shot = 0.0f;
// 30.000.000 seems like a good cap
for (int i = cur_cmdnum, j = 5; i <= cur_cmdnum + 30000000 + 200 && j > 0; i++)
{
bucket = last_bucket;
// Reverse seed
int iSeed = MD5_PseudoRandom(i) & 0x7fffffff;
int tempSeed = iSeed ^ (xor_dat);
RandomSeed(tempSeed);
int iResult = RandomInt(0, 9999);
// Returns something low? Store it.
if (iResult <= lowest_value)
{
if (i > cur_cmdnum + 200)
{
// Found lower value
if (iResult < lowest_value)
{
crit_cmds.clear();
lowest_value = iResult;
j = 5;
}
crit_cmds.push_back(i);
if (added_per_shot == 0.0f)
{
int backup_seed = *g_PredictionRandomSeed;
// Set random seed
*g_PredictionRandomSeed = MD5_PseudoRandom(i) & 0x7FFFFFFF;
// Save weapon state to not break anything
weapon_info info(weapon);
// We modify and write using this
weapon_info write(weapon);
// Set Values so they don't get in the way
write.crit_bucket = GetBucketCap();
write.crit_attempts = 100000000;
write.crit_count = 0;
write.observed_crit_chance = 0.0f;
// Write onto weapon
write.restore_data(weapon);
bool is_crit = re::C_TFWeaponBase::CalcIsAttackCritical(weapon);
if (is_crit)
{
weapon_info new_info(weapon);
// How much is Subtracted per shot
added_per_shot = (GetBucketCap() - new_info.crit_bucket) / 3.0f;
}
// Restore original state
info.restore_data(weapon);
// Adjust with multiplier
*g_PredictionRandomSeed = backup_seed;
}
j--;
}
}
}
changed = true;
}
last_weapon = weapon->entindex();
last_bucket = bucket;
}
struct cached_calculation_s
// Fix bucket on non-local servers
void unfuck_bucket(IClientEntity *weapon)
{
int command_number;
int init_command;
int weapon_entity;
};
INetChannel *ch = (INetChannel *) g_IEngine->GetNetChannelInfo();
if (!ch)
return;
auto addr = ch->GetRemoteAddress();
// Local server needs no fixing
if (addr.type == NA_LOOPBACK)
return;
// Melee doesn't either
if (g_pLocalPlayer->weapon_mode == weapon_melee)
return;
static cached_calculation_s cached_calculation{};
static float last_bucket;
static int last_weapon;
static unsigned last_tick;
static float last_fire_time;
static int number = 0;
static int lastnumber = 0;
static int lastusercmd = 0;
static const model_t *lastweapon = nullptr;
bool force_crit(IClientEntity *weapon)
{
auto command_number = current_user_cmd->command_number;
if (lastnumber < command_number || lastweapon != weapon->GetModel() || lastnumber - command_number > 1000)
weapon_info info(weapon);
float bucket = info.crit_bucket;
float fire_time = NET_FLOAT(weapon, netvar.flLastFireTime);
if (weapon->entindex() == last_weapon)
{
if (!*crit_experimental || g_pLocalPlayer->weapon_mode == weapon_melee)
if (bucket != last_bucket && last_bucket != 0)
{
if (cached_calculation.init_command > command_number || command_number - cached_calculation.init_command > 4096 || (command_number && (cached_calculation.command_number < command_number)))
cached_calculation.weapon_entity = 0;
if (cached_calculation.weapon_entity == weapon->entindex())
return bool(cached_calculation.command_number);
}
number = find_next_random_crit_for_weapon(weapon);
}
else
number = lastnumber;
// logging::Info("Found critical: %d -> %d", command_number,
// number);
if (crit_experimental && GetWeaponMode() != weapon_melee)
{
cached_calculation.command_number = number;
cached_calculation.weapon_entity = LOCAL_W->m_IDX;
if (!crit_legiter)
{
if (number && number != command_number)
if (last_tick == tickcount)
{
command_number_mod[command_number] = number;
auto ch = (INetChannel *) g_IEngine->GetNetChannelInfo();
*(int *) ((uint64_t) ch + offsets::m_nOutSequenceNr()) = number - 1;
bucket = last_bucket;
info.crit_bucket = bucket;
info.restore_data(weapon);
return;
}
}
else if (number && number != command_number && number - 30 < command_number)
{
command_number_mod[command_number] = number;
auto ch = (INetChannel *) g_IEngine->GetNetChannelInfo();
*(int *) ((uint64_t) ch + offsets::m_nOutSequenceNr()) = number - 1;
// We want to adjust it ourselves
if (bucket > last_bucket)
bucket = last_bucket;
}
// We shot, add to bucket
if (last_fire_time != fire_time)
{
bucket += added_per_shot;
// Don't overshoot
if (bucket > GetBucketCap())
bucket = GetBucketCap();
}
}
else
{
if (!crit_legiter)
{
if (command_number != number && number && number != command_number)
current_user_cmd->buttons &= ~IN_ATTACK;
else
current_user_cmd->buttons |= IN_ATTACK;
}
else
{
if (command_number + 30 > number && number && number != command_number)
current_user_cmd->buttons &= ~IN_ATTACK;
else
current_user_cmd->buttons |= IN_ATTACK;
}
}
lastweapon = weapon->GetModel();
lastnumber = number;
return number != 0;
last_fire_time = fire_time;
last_tick = tickcount;
last_weapon = weapon->entindex();
last_bucket = bucket;
info.crit_bucket = bucket;
info.restore_data(weapon);
}
void handle_melee_crit(IClientEntity *weapon)
void CreateMove()
{
// Should i crit or save my crits?
bool in_range = false;
for (int i = 1; i <= g_IEngine->GetMaxClients(); i++)
{
// We already found out we should crit
if (in_range)
break;
CachedEntity *target = ENTITY(i);
// Various ignores
if (CE_BAD(target) || !target->m_bAlivePlayer() || !player_tools::shouldTarget(target) || !target->m_bEnemy())
continue;
// Shorten code
namespace bt = hacks::shared::backtrack;
// If backtracking, use the backtrack ticks
if (bt::isBacktrackEnabled)
{
// Loop all ticks
for (int j = 0; j < 66; j++)
{
auto bt_data = bt::headPositions[i][j];
// Invalid tick
if (!bt::ValidTick(bt_data, target))
continue;
// If within 400 HU range then crit, else just farm crits
if (bt_data.collidable.center.DistTo(g_pLocalPlayer->v_Eye) < 400.0f)
{
in_range = true;
break;
}
}
}
else
{
Vector ent_orig = target->m_vecOrigin();
// Center ent origin
ent_orig += (RAW_ENT(target)->GetCollideable()->OBBMaxs() + RAW_ENT(target)->GetCollideable()->OBBMins()) / 2;
// In range, crit!
if (ent_orig.DistTo(g_pLocalPlayer->v_Eye) < 400.0f)
{
in_range = true;
break;
}
}
}
if (in_range)
force_crit(weapon);
else
{
// Please don't crit thanks
if (find_next_random_crit_for_weapon(weapon) == current_user_cmd->command_number)
{
// Backup
int original_cmd = current_user_cmd->command_number;
// How many crits in a row?
int found = 0;
// Ensure not to cockblock when critboosted, 5 should be plently
for (int i = 0; i < 10; i++)
{
current_user_cmd->command_number++;
if (find_next_random_crit_for_weapon(weapon) == current_user_cmd->command_number)
found++;
}
// If we found less than 10 crits in a row, please don't crit thanks
if (found < 10)
current_user_cmd->buttons &= ~IN_ATTACK;
current_user_cmd->command_number = original_cmd;
}
}
}
void create_move()
{
if (!crit_key && !crit_melee && !auto_crit)
if (!enabled && !melee && !draw)
return;
if (!random_crits_enabled())
if (CE_BAD(LOCAL_E) || CE_BAD(LOCAL_W))
return;
if (CE_BAD(LOCAL_W))
unfuck_bucket(RAW_ENT(LOCAL_W));
// Update cmds
update_cmds();
if (!enabled && !melee)
return;
if (current_user_cmd->command_number)
lastusercmd = current_user_cmd->command_number;
if (!force_ticks && (!(melee && g_pLocalPlayer->weapon_mode == weapon_melee) && crit_key && !crit_key.isKeyDown()))
return;
if (!current_late_user_cmd->command_number)
return;
IClientEntity *weapon = RAW_ENT(LOCAL_W);
if (!re::C_TFWeaponBase::IsBaseCombatWeapon(weapon))
return;
if (!re::C_TFWeaponBase::AreRandomCritsEnabled(weapon))
return;
if (!CanShoot())
if (!re::C_TFWeaponBase::CanFireCriticalShot(weapon, false, nullptr))
return;
unfuck_bucket(weapon);
if ((current_user_cmd->buttons & IN_ATTACK) && ((crit_key && crit_key.isKeyDown()) || auto_crit) && current_user_cmd->command_number)
if (force_ticks || (CanShoot() && current_late_user_cmd->buttons & IN_ATTACK))
{
if (GetWeaponMode() == weapon_melee)
handle_melee_crit(weapon);
// Are we even allowed to crit?
if (CanWeaponCrit(RAW_ENT(LOCAL_W)))
force_crit();
}
}
// Storage
static int last_crit_tick = -1;
static int last_bucket = 0;
static int shots_until_crit = 0;
static int last_wep = 0;
static std::pair<float, float> crit_mult_info;
void Draw()
{
if (!draw)
return;
if (!g_IEngine->GetNetChannelInfo())
last_crit_tick = -1;
if (CE_GOOD(LOCAL_E) && CE_GOOD(LOCAL_W))
{
auto wep = RAW_ENT(LOCAL_W);
float bucket = weapon_info(wep).crit_bucket;
// Reset because too old
if (wep->entindex() != last_wep || last_crit_tick - current_late_user_cmd->command_number < 0 || (last_crit_tick - current_late_user_cmd->command_number) * g_GlobalVars->interval_per_tick > 30)
last_crit_tick = next_crit_tick();
// Used by multiple things
bool can_crit = CanWeaponCrit(wep);
if (bucket != last_bucket || wep->entindex() != last_wep)
{
// Recalculate shots until crit
if (!can_crit)
shots_until_crit = ShotsUntilCrit(wep);
// Recalc crit multiplier info
crit_mult_info = CritMultInfo(wep);
}
// Display for when crithack is active/can't be active
if (IsEnabled() && last_crit_tick != -1)
AddCenterString("Forcing Crits!", colors::red);
// Mark if the weapon can randomly crit or not
if (!re::C_TFWeaponBase::CanFireCriticalShot(RAW_ENT(LOCAL_W), false, nullptr))
{
AddCenterString("Weapon cannot randomly crit.", colors::red);
return;
}
if (!can_crit)
AddCenterString("Shots until crit: " + std::to_string(shots_until_crit), colors::orange);
// Mark bucket as ready/not ready
auto color = colors::red;
if (can_crit)
color = colors::green;
AddCenterString("Crit Bucket: " + std::to_string(bucket), color);
// Print crit Multiplier information
// Crit chance is low enough
if (crit_mult_info.first < crit_mult_info.second)
color = colors::green;
// Cannot crit
else
force_crit(weapon);
}
else if ((current_user_cmd->buttons & IN_ATTACK) && current_user_cmd->command_number && GetWeaponMode() == weapon_melee && (crit_melee || auto_crit) && g_pLocalPlayer->weapon()->m_iClassID() != CL_CLASS(CTFKnife))
{
handle_melee_crit(weapon);
}
else if ((current_user_cmd->buttons & IN_ATTACK) && current_user_cmd->command_number && GetWeaponMode() != weapon_melee && crit_key)
{
// Save crits!
if (find_next_random_crit_for_weapon(weapon) == current_user_cmd->command_number)
color = colors::red;
AddCenterString("Crit Penalty: " + std::to_string(crit_mult_info.first), color);
// Time until crit
if (old_mode && can_crit && last_crit_tick != -1)
{
// Backup
int original_cmd = current_user_cmd->command_number;
// How many crits in a row?
int found = 0;
// Ensure not to cockblock when critboosted, 5 should be plently
for (int i = 0; i < 6; i++)
{
current_user_cmd->command_number++;
if (find_next_random_crit_for_weapon(weapon) == current_user_cmd->command_number)
found++;
}
// If we found less than 5 crits in a row, please don't crit thanks
if (found < 5)
current_user_cmd->buttons &= ~IN_ATTACK;
current_user_cmd->command_number = original_cmd;
// Ticks / Ticks per second
float time = (last_crit_tick - current_late_user_cmd->command_number) * g_GlobalVars->interval_per_tick;
AddCenterString("Crit in " + std::to_string(time) + "s", colors::orange);
}
// Update
last_bucket = bucket;
last_wep = wep->entindex();
}
}
bool random_crits_enabled()
void LevelShutdown()
{
static ConVar *tf_weapon_criticals = g_ICvar->FindVar("tf_weapon_criticals");
return tf_weapon_criticals->GetBool();
last_crit_tick = -1;
}
#if ENABLE_VISUALS
void draw()
{
if (CE_BAD(LOCAL_W))
return;
IClientEntity *weapon = RAW_ENT(LOCAL_W);
if (!weapon)
return;
if (!re::C_TFWeaponBase::IsBaseCombatWeapon(weapon))
return;
if (!re::C_TFWeaponBase::AreRandomCritsEnabled(weapon))
return;
if (crit_info && CE_GOOD(LOCAL_W))
{
if (crit_key.isKeyDown() || auto_crit)
{
AddCenterString("FORCED CRITS!", colors::red);
}
IF_GAME(IsTF2())
{
if (!random_crits_enabled())
AddCenterString("Random crits are disabled", colors::yellow);
else
{
if (!re::C_TFWeaponBase::CanFireCriticalShot(RAW_ENT(LOCAL_W), false, nullptr))
AddCenterString("Weapon can't randomly crit", colors::yellow);
else if (lastusercmd)
{
if (number > lastusercmd)
{
float nextcrit = ((float) number - (float) lastusercmd) / (float) 90;
if (nextcrit > 0.0f)
{
AddCenterString(format("Time to next crit: ", nextcrit, "s"), colors::orange);
}
}
AddCenterString("Weapon can randomly crit");
}
}
AddCenterString(format("Bucket: ", re::C_TFWeaponBase::crit_bucket_(RAW_ENT(LOCAL_W))));
}
// AddCenterString(format("Time: ",
// *(float*)((uintptr_t)RAW_ENT(LOCAL_W) + 2872u)));
}
}
#endif
static InitRoutine init([]() {
EC::Register(EC::CreateMove, criticals::create_move, "cm_crits", EC::very_late);
#if ENABLE_VISUALS
EC::Register(EC::Draw, criticals::draw, "draw_crits");
#endif
EC::Register(EC::CreateMoveLate, CreateMove, "crit_cm");
EC::Register(EC::Draw, Draw, "crit_draw");
EC::Register(EC::LevelShutdown, LevelShutdown, "crit_lvlshutdown");
});
} // namespace criticals
void crithack_saved_state::Load(IClientEntity *entity)
{
*(float *) (uintptr_t(entity) + 2868) = unknown2868;
*(float *) (uintptr_t(entity) + 2864) = unknown2864;
*(float *) (uintptr_t(entity) + 2880) = unknown2880;
*(float *) (uintptr_t(entity) + 2616) = bucket2616;
*(int *) (uintptr_t(entity) + 2620) = unknown2620;
*(int *) (uintptr_t(entity) + 2876) = seed2876;
*(char *) (uintptr_t(entity) + 2839) = unknown2839;
}
void crithack_saved_state::Save(IClientEntity *entity)
{
unknown2868 = *(float *) (uintptr_t(entity) + 2868);
unknown2864 = *(float *) (uintptr_t(entity) + 2864);
unknown2880 = *(float *) (uintptr_t(entity) + 2880);
bucket2616 = *(float *) (uintptr_t(entity) + 2616);
unknown2620 = *(int *) (uintptr_t(entity) + 2620);
seed2876 = *(int *) (uintptr_t(entity) + 2876);
unknown2839 = *(char *) (uintptr_t(entity) + 2839);
}

View File

@ -42,6 +42,7 @@ void GlobalSettings::Init()
}
CUserCmd *current_user_cmd{ nullptr };
CUserCmd *current_late_user_cmd{ nullptr };
bool isHackActive()
{

View File

@ -185,6 +185,7 @@ void hack::Hook()
hooks::client.Set(g_IBaseClient);
hooks::client.HookMethod(HOOK_ARGS(DispatchUserMessage));
hooks::client.HookMethod(HOOK_ARGS(CreateMoveLate));
#if ENABLE_VISUALS
hooks::client.HookMethod(HOOK_ARGS(FrameStageNotify));
hooks::client.HookMethod(HOOK_ARGS(IN_KeyEvent));

View File

@ -1575,6 +1575,15 @@ bool IsEntityVisiblePenetration(CachedEntity *entity, int hb)
return false;
}
void ValidateUserCmd(CUserCmd *cmd, int sequence_nr)
{
CRC32_t crc = GetChecksum(cmd);
if (crc != GetVerifiedCmds(g_IInput)[sequence_nr % VERIFIED_CMD_SIZE].m_crc)
{
*cmd = GetVerifiedCmds(g_IInput)[sequence_nr % VERIFIED_CMD_SIZE].m_cmd;
}
}
// Used for getting class names
CatCommand print_classnames("debug_print_classnames", "Lists classnames currently available in console", []() {
// Create a tmp ent for the loop

View File

@ -411,4 +411,44 @@ DEFINE_HOOKED_METHOD(CreateMove, bool, void *this_, float input_sample_time, CUs
}
return ret;
}
void WriteCmd(IInput *input, CUserCmd *cmd, int sequence_nr)
{
// Write the usercmd
GetVerifiedCmds(input)[sequence_nr % VERIFIED_CMD_SIZE].m_cmd = *cmd;
GetVerifiedCmds(input)[sequence_nr % VERIFIED_CMD_SIZE].m_crc = GetChecksum(cmd);
GetCmds(input)[sequence_nr % VERIFIED_CMD_SIZE] = *cmd;
}
DEFINE_HOOKED_METHOD(CreateMoveLate, void, void *this_, int sequence_nr, float input_sample_time, bool arg3)
{
// Call original function, includes Early CreateMove
original::CreateMoveLate(this_, sequence_nr, input_sample_time, arg3);
CUserCmd *cmd = g_IInput->GetUserCmd(sequence_nr);
if (!cmd)
return;
current_late_user_cmd = cmd;
if (!isHackActive())
{
WriteCmd(g_IInput, current_late_user_cmd, sequence_nr);
return;
}
if (!g_IEngine->IsInGame())
{
WriteCmd(g_IInput, current_late_user_cmd, sequence_nr);
return;
}
PROF_SECTION(CreateMoveLate);
// Run EC
EC::run(EC::CreateMoveLate);
// Write the usercmd
WriteCmd(g_IInput, current_late_user_cmd, sequence_nr);
}
} // namespace hooked_methods

View File

@ -10,26 +10,10 @@ namespace hooked_methods
DEFINE_HOOKED_METHOD(GetUserCmd, CUserCmd *, IInput *this_, int sequence_number)
{
CUserCmd *def = original::GetUserCmd(this_, sequence_number);
int oldcmd;
INetChannel *ch;
if (def == nullptr)
return def;
if (command_number_mod.find(def->command_number) != command_number_mod.end())
{
// logging::Info("Replacing command %i with %i", def->command_number,
// command_number_mod[def->command_number]);
oldcmd = def->command_number;
def->command_number = command_number_mod[def->command_number];
def->random_seed = MD5_PseudoRandom(unsigned(def->command_number)) & 0x7fffffff;
command_number_mod.erase(command_number_mod.find(oldcmd));
*(int *) ((unsigned) g_IBaseClientState + offsets::lastoutgoingcommand()) = def->command_number - 1;
ch = (INetChannel *) g_IEngine->GetNetChannelInfo(); //*(INetChannel**)((unsigned)g_IBaseClientState
//+ offsets::m_NetChannel());
*(int *) ((unsigned) ch + offsets::m_nOutSequenceNr()) = def->command_number - 1;
}
return def;
// We need to overwrite this if crithack is on
if (criticals::enabled || criticals::melee)
return &GetCmds(this_)[sequence_number % VERIFIED_CMD_SIZE];
else
return original::GetUserCmd(this_, sequence_number);
}
} // namespace hooked_methods
} // namespace hooked_methods