/src/nospread.cpp

https://github.com/nullworks/cathook · C++ · 971 lines · 689 code · 131 blank · 151 comment · 184 complexity · 671e414273a27cf28cc4299e7ad110db MD5 · raw file

  1. // To anyone reading this and planning to add it to their own cheat,
  2. // It would be nice if you could credit us, the Nullworks/Cathook team.
  3. // Thanks :)
  4. /*
  5. * Cathook
  6. * Copyright (C) 2020 nullworks
  7. * This program is free software: you can redistribute it and/or modify
  8. * it under the terms of the GNU General Public License as published by
  9. * the Free Software Foundation, either version 3 of the License, or
  10. * (at your option) any later version.
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. * You should have received a copy of the GNU General Public License
  16. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. #include "common.hpp"
  19. #include "crits.hpp"
  20. #include "DetourHook.hpp"
  21. #include <regex>
  22. #include <boost/algorithm/string.hpp>
  23. #include "usercmd.hpp"
  24. #include "MiscTemporary.hpp"
  25. #include "AntiAim.hpp"
  26. #include "WeaponData.hpp"
  27. namespace hacks::tf2::nospread
  28. {
  29. static settings::Boolean projectile("nospread.projectile", "false");
  30. /*
  31. * 0 Always on
  32. * 1 Disable if being spectated in first person
  33. * 2 Disable if being spectated
  34. */
  35. static settings::Int specmode("nospread.spectator-mode", "1");
  36. settings::Boolean bullet("nospread.bullet", "false");
  37. settings::Int debug_nospread("nospread.debug", "0");
  38. settings::Boolean center_cone{ "nospread.center-cone", "true" };
  39. settings::Boolean draw{ "nospread.draw-info", "true" };
  40. settings::Boolean draw_mantissa{ "nospread.draw-info.mantissa", "false" };
  41. settings::Boolean correct_ping{ "nospread.correct-ping", "true" };
  42. settings::Boolean use_avg_latency{ "nospread.use-average-latency", "false" };
  43. settings::Boolean extreme_accuracy{ "nospread.use-extreme-accuracy", "false" };
  44. static bool should_nospread = false;
  45. bool is_syncing = false;
  46. bool shouldNoSpread(bool _projectile)
  47. {
  48. switch (*specmode)
  49. {
  50. // Always on
  51. default:
  52. case 0:
  53. break;
  54. // Disable if being spectated in first person
  55. case 1:
  56. if (g_pLocalPlayer->spectator_state == g_pLocalPlayer->FIRSTPERSON)
  57. return false;
  58. break;
  59. // Disable if being spectated
  60. case 2:
  61. if (g_pLocalPlayer->spectator_state != g_pLocalPlayer->NONE)
  62. return false;
  63. };
  64. return _projectile ? *projectile : *bullet;
  65. }
  66. void CreateMove()
  67. {
  68. if (CE_BAD(LOCAL_E) || CE_BAD(LOCAL_W))
  69. return;
  70. if (!shouldNoSpread(true))
  71. return;
  72. // Credits to https://www.unknowncheats.me/forum/team-fortress-2-a/139094-projectile-nospread.html
  73. // Set up Random Seed
  74. int cmd_num = current_user_cmd->command_number;
  75. // Crithack uses different things
  76. if (criticals::isEnabled() && g_pLocalPlayer->weapon_mode != weapon_melee && criticals::crit_cmds.find(LOCAL_W->m_IDX) != criticals::crit_cmds.end() && criticals::crit_cmds.find(LOCAL_W->m_IDX)->second.size())
  77. {
  78. int array_index = criticals::current_index;
  79. if (array_index >= criticals::crit_cmds.at(LOCAL_W->m_IDX).size())
  80. array_index = 0;
  81. // Adjust for nospread
  82. cmd_num = criticals::crit_cmds.at(LOCAL_W->m_IDX).at(array_index);
  83. }
  84. RandomSeed(MD5_PseudoRandom(cmd_num) & 0x7FFFFFFF);
  85. SharedRandomInt(MD5_PseudoRandom(cmd_num) & 0x7FFFFFFF, "SelectWeightedSequence", 0, 0, 0);
  86. for (int i = 0; i < 6; ++i)
  87. RandomFloat();
  88. // Projectile/Huntsman check
  89. if (g_pLocalPlayer->weapon_mode != weapon_projectile && LOCAL_W->m_iClassID() != CL_CLASS(CTFCompoundBow))
  90. return;
  91. // Beggars check
  92. if (CE_INT(LOCAL_W, netvar.iItemDefinitionIndex) == 730)
  93. {
  94. // Check if we released the barrage by releasing m1, also lock bool so people don't just release m1 and tap it again
  95. if (!should_nospread)
  96. should_nospread = !(current_user_cmd->buttons & IN_ATTACK) && g_pLocalPlayer->bAttackLastTick;
  97. if (!CE_INT(LOCAL_W, netvar.m_iClip1) && CE_INT(LOCAL_W, netvar.iReloadMode) == 0)
  98. {
  99. // Reset
  100. should_nospread = false;
  101. return;
  102. }
  103. // Cancel
  104. if (!should_nospread)
  105. return;
  106. }
  107. // Huntsman check
  108. else if (LOCAL_W->m_iClassID() == CL_CLASS(CTFCompoundBow))
  109. {
  110. if (!g_pLocalPlayer->bAttackLastTick || (current_user_cmd->buttons & IN_ATTACK))
  111. return;
  112. }
  113. // Rest of weapons
  114. else if (!(current_user_cmd->buttons & IN_ATTACK))
  115. return;
  116. switch (LOCAL_W->m_iClassID())
  117. {
  118. case CL_CLASS(CTFSyringeGun):
  119. {
  120. float spread = 1.5f;
  121. current_user_cmd->viewangles.x -= RandomFloat(-spread, spread);
  122. current_user_cmd->viewangles.y -= RandomFloat(-spread, spread);
  123. fClampAngle(current_user_cmd->viewangles);
  124. g_pLocalPlayer->bUseSilentAngles = true;
  125. break;
  126. }
  127. case CL_CLASS(CTFCompoundBow):
  128. {
  129. Vector view = current_user_cmd->viewangles;
  130. if (g_pLocalPlayer->bUseSilentAngles)
  131. view = g_pLocalPlayer->v_OrigViewangles;
  132. Vector spread;
  133. Vector src;
  134. re::C_TFWeaponBase::GetProjectileFireSetupHuntsman(RAW_ENT(LOCAL_W), RAW_ENT(LOCAL_E), Vector(23.5f, -8.f, 8.f), &src, &spread, false, 2000.0f);
  135. spread -= view;
  136. current_user_cmd->viewangles -= spread;
  137. g_pLocalPlayer->bUseSilentAngles = true;
  138. fClampAngle(current_user_cmd->viewangles);
  139. break;
  140. }
  141. default:
  142. Vector view = current_user_cmd->viewangles;
  143. if (g_pLocalPlayer->bUseSilentAngles)
  144. view = g_pLocalPlayer->v_OrigViewangles;
  145. Vector spread = re::C_TFWeaponBase::GetSpreadAngles(RAW_ENT(LOCAL_W));
  146. spread -= view;
  147. current_user_cmd->viewangles -= spread;
  148. g_pLocalPlayer->bUseSilentAngles = true;
  149. fClampAngle(current_user_cmd->viewangles);
  150. break;
  151. }
  152. }
  153. static InitRoutine init([]() { EC::Register(EC::CreateMove, CreateMove, "nospread_cm", EC::very_late); });
  154. enum nospread_sync_state
  155. {
  156. NOT_SYNCED = 0,
  157. CORRECTING,
  158. SYNCED,
  159. DEAD_SYNC,
  160. };
  161. // TODO: Attempt to send clc_move over unreliable channel
  162. static bool waiting_perf_data = false;
  163. static bool should_update_time = false;
  164. static bool waiting_for_post_SNM = false;
  165. static bool resync_needed = false;
  166. static bool last_was_player_perf = false;
  167. static bool resynced_this_death = false;
  168. static nospread_sync_state no_spread_synced = NOT_SYNCED; // this will be set to 0 each level init / level shutdown
  169. static bool bad_mantissa = false; // Also reset every levelinit/shutdown
  170. static double float_time_delta = 0.0;
  171. static double ping_at_send = 0.0;
  172. static double last_ping_at_send = 0.0;
  173. static double sent_client_floattime = 0.0;
  174. static double last_correction = 0.0;
  175. static double write_usercmd_correction = 0.0;
  176. static double last_sync_delta_time = 0.0;
  177. static float prediction_seed = 0.0;
  178. static bool use_usercmd_seed = false;
  179. static float current_weapon_spread = 0.0;
  180. static bool first_usercmd = false;
  181. static bool called_from_sendmove = false;
  182. static bool should_update_usercmd_correction = false;
  183. static CUserCmd user_cmd_backup;
  184. static float CalculateMantissaStep(float flValue)
  185. {
  186. int iRawValue = reinterpret_cast<int &>(flValue);
  187. int iExponent = (iRawValue >> 23) & 0xFF;
  188. return powf(2, iExponent - (127 + 23));
  189. }
  190. float GetServerCurTime()
  191. {
  192. // Calculate on our own accord
  193. float server_time = g_GlobalVars->interval_per_tick * CE_INT(LOCAL_E, netvar.nTickBase);
  194. return server_time;
  195. }
  196. // Does the shot have any spread in general?
  197. bool IsPerfectShot(IClientEntity *weapon, float provided_time = 0.0 /*used for optimization*/)
  198. {
  199. float server_time = provided_time == 0.0 ? GetServerCurTime() : provided_time;
  200. float time_since_attack = server_time - NET_FLOAT(weapon, netvar.flLastFireTime);
  201. int nBulletsPerShot = GetWeaponData(weapon)->m_nBulletsPerShot;
  202. if (nBulletsPerShot >= 1)
  203. nBulletsPerShot = ATTRIB_HOOK_FLOAT(nBulletsPerShot, "mult_bullets_per_shot", weapon, 0x0, true);
  204. else
  205. nBulletsPerShot = 1;
  206. if ((nBulletsPerShot == 1 && time_since_attack > 1.25) || (nBulletsPerShot > 1 && time_since_attack > 0.25))
  207. return true;
  208. return false;
  209. }
  210. // Applies nospread
  211. void ApplySpreadCorrection(Vector &angles, int seed, float spread)
  212. {
  213. if (CE_BAD(LOCAL_E) || !LOCAL_E->m_bAlivePlayer() || CE_BAD(LOCAL_W))
  214. return;
  215. IClientEntity *weapon = RAW_ENT(LOCAL_W);
  216. bool is_first_shot_perfect = IsPerfectShot(weapon);
  217. // Size of one WeaponMode_t is 0x40, 0x6fc is the offset to bullets per shot
  218. int nBulletsPerShot = GetWeaponData(weapon)->m_nBulletsPerShot;
  219. if (nBulletsPerShot >= 1)
  220. nBulletsPerShot = ATTRIB_HOOK_FLOAT(nBulletsPerShot, "mult_bullets_per_shot", RAW_ENT(LOCAL_W), 0x0, true);
  221. else
  222. nBulletsPerShot = 1;
  223. // We only have one shot or we do not want to center the cone or it's perfect, so no need to adjust
  224. if ((nBulletsPerShot == 1 || !center_cone) && is_first_shot_perfect)
  225. return;
  226. // We only correct one bullet in this case
  227. if (!center_cone)
  228. nBulletsPerShot = 1;
  229. std::vector<Vector> bullet_corrections;
  230. Vector average_spread(0.0f);
  231. for (int i = 0; i < nBulletsPerShot; i++)
  232. {
  233. RandomSeed(seed + i);
  234. float flX = RandomFloat(-0.5, 0.5) + RandomFloat(-0.5, 0.5);
  235. float flY = RandomFloat(-0.5, 0.5) + RandomFloat(-0.5, 0.5);
  236. // This is our perfect shot, do not adjust
  237. if (is_first_shot_perfect && !i)
  238. {
  239. flX = 0.0f;
  240. flY = 0.0f;
  241. }
  242. fClampAngle(angles);
  243. Vector vShootForward, vShootRight, vShootUp;
  244. AngleVectors3(VectorToQAngle(angles), &vShootForward, &vShootRight, &vShootUp);
  245. Vector fixed_spread = vShootForward + (vShootRight * flX * spread) + (vShootUp * flY * spread);
  246. fixed_spread.NormalizeInPlace();
  247. // Add to the average
  248. average_spread += fixed_spread;
  249. // Add to Bullet spread vector
  250. bullet_corrections.push_back(fixed_spread);
  251. }
  252. // Turn it into an actual average
  253. average_spread /= nBulletsPerShot;
  254. fClampAngle(average_spread);
  255. // What we set our Vector to, start with FLT_MAX as deviation
  256. Vector fixed_spread(FLT_MAX);
  257. // Get the bullet closest to the average
  258. for (auto &spread : bullet_corrections)
  259. {
  260. // Is it closer to the average spread? if yes, use it
  261. if (spread.DistTo(average_spread) < fixed_spread.DistTo(average_spread))
  262. fixed_spread = spread;
  263. }
  264. Vector fixed_angles;
  265. VectorAngles(fixed_spread, fixed_angles);
  266. Vector vCorrection = (angles - fixed_angles);
  267. angles += vCorrection;
  268. fClampAngle(angles);
  269. }
  270. CatCommand debug_mantissa("test_mantissa", "For debug purposes", [](const CCommand &rCmd) {
  271. if (rCmd.ArgC() < 2)
  272. {
  273. g_ICvar->ConsoleColorPrintf(MENU_COLOR, "You must provide float to test.\n");
  274. return;
  275. }
  276. try
  277. {
  278. float float_value = atof(rCmd.Arg(1));
  279. float mantissa_step = CalculateMantissaStep(float_value);
  280. g_ICvar->ConsoleColorPrintf(MENU_COLOR, "Mantissa step for %.3f: %.10f\n", float_value, mantissa_step);
  281. }
  282. catch (std::invalid_argument)
  283. {
  284. g_ICvar->ConsoleColorPrintf(MENU_COLOR, "Invalid float.\n");
  285. }
  286. return;
  287. });
  288. static CatCommand nospread_sync("nospread_sync", "Try to sync client and server time", []() {
  289. if (!bullet)
  290. {
  291. g_ICvar->ConsoleColorPrintf(MENU_COLOR, "Set nospread.enable to true first.\n");
  292. return;
  293. }
  294. should_update_time = true;
  295. no_spread_synced = NOT_SYNCED;
  296. g_ICvar->ConsoleColorPrintf(MENU_COLOR, "Trying to sync Seed...\n");
  297. });
  298. static CatCommand nospread_resync("nospread_resync", "Try to sync client and server time", []() {
  299. if (!bullet)
  300. {
  301. g_ICvar->ConsoleColorPrintf(MENU_COLOR, "Set nospread.enable to true first.\n");
  302. return;
  303. }
  304. if (no_spread_synced == CORRECTING)
  305. {
  306. g_ICvar->ConsoleColorPrintf(MENU_COLOR, "Already syncing.");
  307. return;
  308. }
  309. if (no_spread_synced != SYNCED)
  310. {
  311. g_ICvar->ConsoleColorPrintf(MENU_COLOR, "Can't resync when not previously synced! Use cat_nospread_sync\n");
  312. return;
  313. }
  314. no_spread_synced = CORRECTING;
  315. g_ICvar->ConsoleColorPrintf(MENU_COLOR, "Trying to resync Seed...\n");
  316. });
  317. // Our Detour hooks
  318. DetourHook cl_writeusercmd_detour;
  319. typedef void (*WriteUserCmd_t)(bf_write *, CUserCmd *, CUserCmd *);
  320. DetourHook fx_firebullets_detour;
  321. typedef void (*FX_FireBullets_t)(IClientEntity *, int, Vector *, Vector *, int, int, int, float, float, bool);
  322. // DetourHook net_sendpacket_detour;
  323. // typedef int (*NET_SendPacket_t)(INetChannel *, int, const netadr_t &, const unsigned char *, int, bf_write *, bool);
  324. // false == unchanged, true == Force reliable
  325. // We force the clc_move as a reliable message here to ensure its arrival before we call the original
  326. bool SendNetMessage(INetMessage *data)
  327. {
  328. if (!bullet)
  329. return false;
  330. // if we send clc_move with playerperf command or corrected angles, we must ensure it will be sent via reliable stream
  331. if (should_update_time)
  332. {
  333. if (data->GetType() != clc_Move)
  334. return false;
  335. // and wait for post call
  336. waiting_for_post_SNM = true;
  337. // Force reliable
  338. return true;
  339. }
  340. else if (no_spread_synced)
  341. {
  342. if (data->GetType() != clc_Move)
  343. return false;
  344. }
  345. return false;
  346. }
  347. static Timer wait_perf{};
  348. // We send the playerperf in here to be (mostly) sure that it's sent along with the clc_move.
  349. void SendNetMessagePost()
  350. {
  351. if (!waiting_for_post_SNM || !bullet || (waiting_perf_data && !wait_perf.test_and_set(1000)))
  352. return;
  353. waiting_for_post_SNM = false;
  354. // Create playerperf
  355. NET_StringCmd sCmd("playerperf");
  356. // And send it along with our clc_move. Yes, we are calling SendNetMsg from inside SendNetMsg
  357. ((INetChannel *) (g_IEngine->GetNetChannelInfo()))->SendNetMsg(sCmd, true);
  358. // remember client float time
  359. should_update_time = false;
  360. // Only set when not syncing
  361. if (no_spread_synced == NOT_SYNCED)
  362. sent_client_floattime = Plat_FloatTime();
  363. // force transmit now
  364. ((INetChannel *) (g_IEngine->GetNetChannelInfo()))->Transmit();
  365. if (use_avg_latency)
  366. ping_at_send = ((INetChannel *) (g_IEngine->GetNetChannelInfo()))->GetAvgLatency(FLOW_OUTGOING);
  367. else
  368. ping_at_send = ((INetChannel *) (g_IEngine->GetNetChannelInfo()))->GetLatency(FLOW_OUTGOING);
  369. waiting_perf_data = true;
  370. wait_perf.update();
  371. }
  372. CatCommand debug_flows("debug_flows", "debug", []() { logging::Info("Incoming: %f\n Outgoing: %f", ((INetChannel *) g_IEngine->GetNetChannelInfo())->GetLatency(FLOW_INCOMING), ((INetChannel *) g_IEngine->GetNetChannelInfo())->GetLatency(FLOW_OUTGOING)); });
  373. // false == don't call original, true == call original
  374. // This function is used to parse the playerperf data
  375. bool DispatchUserMessage(bf_read *buf, int type)
  376. {
  377. bool should_call_original = true;
  378. if ((!waiting_perf_data && !last_was_player_perf) || !bullet)
  379. return should_call_original;
  380. // We are looking for TextMsg
  381. if (type != 5)
  382. return should_call_original;
  383. int message_dest = buf->ReadByte();
  384. // Not send to us
  385. if (message_dest != 2)
  386. {
  387. buf->Seek(0);
  388. return should_call_original;
  389. }
  390. double start_time = Plat_FloatTime();
  391. char msg_str[256];
  392. buf->ReadString(msg_str, sizeof(msg_str));
  393. buf->Seek(0);
  394. std::vector<std::string> lines;
  395. boost::split(lines, msg_str, boost::is_any_of("\n"), boost::token_compress_on);
  396. // Regex to find the playerperf data we want/need
  397. static std::regex primary_regex("^(([0-9]+\\.[0-9]+) ([0-9]{1,2}) ([0-9]{1,2}))$");
  398. std::vector<double> vData;
  399. for (auto sStr : lines)
  400. {
  401. std::smatch sMatch;
  402. if (!std::regex_match(sStr, sMatch, primary_regex) || sMatch.size() != 5)
  403. {
  404. static std::regex backup_regex("^(([0-9]+\\.[0-9]+) ([0-9]{1,2}) ([0-9]{1,2}) ([0-9]+\\.[0-9]+) ([0-9]+\\.[0-9]+) vel ([0-9]+\\.[0-9]+))$");
  405. std::smatch sMatch2;
  406. if (std::regex_match(sStr, sMatch2, backup_regex) && sMatch2.size() > 5)
  407. {
  408. last_was_player_perf = true;
  409. should_call_original = false;
  410. }
  411. continue;
  412. }
  413. std::string server_time = sMatch[2].str();
  414. char *tmp;
  415. try
  416. {
  417. vData.push_back(std::strtod(server_time.c_str(), &tmp));
  418. }
  419. // Shouldn't happen
  420. catch (std::invalid_argument)
  421. {
  422. }
  423. }
  424. if (vData.size() < 2)
  425. {
  426. if (!vData.size())
  427. last_was_player_perf = false;
  428. // Still do not call original, we don't want the playerperf spewing everywhere
  429. else
  430. return false;
  431. return should_call_original; // not our case, just return
  432. }
  433. // Less than 1 in step size is literally impossible to predict, although 1 is already pushing it
  434. if (CalculateMantissaStep(vData[0] * 1000.0) < 1.0)
  435. {
  436. if (waiting_perf_data)
  437. g_ICvar->ConsoleColorPrintf(MENU_COLOR, "Couldn't sync nospread: server uptime too low.\n");
  438. waiting_perf_data = false;
  439. last_was_player_perf = true;
  440. should_call_original = false;
  441. bad_mantissa = true;
  442. return should_call_original;
  443. }
  444. bad_mantissa = false;
  445. last_was_player_perf = true;
  446. should_call_original = false;
  447. // Process time!
  448. // ...or not
  449. if (!waiting_perf_data)
  450. return should_call_original;
  451. if (no_spread_synced == NOT_SYNCED)
  452. {
  453. // first, compensate procession time and latency between us and server
  454. double client_time = Plat_FloatTime();
  455. double total_latency = (client_time - (client_time - start_time)) - sent_client_floattime;
  456. // Second compensate latency and get delta time (this might be negative, so be careful!)
  457. float_time_delta = vData[0] - sent_client_floattime;
  458. // We got time with latency included, but only outgoing, so compensate
  459. float_time_delta -= (total_latency / 2.0);
  460. if (debug_nospread)
  461. g_ICvar->ConsoleColorPrintf(MENU_COLOR, "Assumed delta time: %.10f calculated based on %i entries.\n", float_time_delta, (int) vData.size() - 1);
  462. // we need only first output which is latest
  463. waiting_perf_data = false;
  464. // and now collect history
  465. no_spread_synced = CORRECTING;
  466. is_syncing = true;
  467. }
  468. else if (no_spread_synced != SYNCED)
  469. {
  470. // Now sync and Correct
  471. double time_difference = sent_client_floattime - vData[0];
  472. double mantissa_step = CalculateMantissaStep(sent_client_floattime * 1000.0);
  473. // Apply correction
  474. // We must try to be super accurate if the rvar is set, else base on mantissa step
  475. double correction_threshhold = extreme_accuracy ? 0.001 : (mantissa_step / 1000.0 / 2.0);
  476. // Check if we are not precise enough or snapped too hard for it to actually be synced
  477. if (abs(time_difference) > correction_threshhold || abs(last_correction) > mantissa_step / 1000.0)
  478. {
  479. float_time_delta -= time_difference;
  480. // it will auto resync it
  481. resync_needed = true;
  482. // Print debug if desired
  483. if (debug_nospread)
  484. g_ICvar->ConsoleColorPrintf(MENU_COLOR, "Applied correction: %.10f\n", time_difference);
  485. }
  486. // We synced successfully
  487. else
  488. {
  489. if (debug_nospread)
  490. g_ICvar->ConsoleColorPrintf(MENU_COLOR, "Nospread successfully synced. Possible precision loss: %.10f Mantissa step: %.2f\n", time_difference, mantissa_step);
  491. else
  492. g_ICvar->ConsoleColorPrintf(MENU_COLOR, "Nospread successfully synced. Mantissa step: %.2f\n", mantissa_step);
  493. resync_needed = false;
  494. }
  495. last_correction = time_difference;
  496. // We need only first output which is latest
  497. waiting_perf_data = false;
  498. // We are synced.
  499. if (!resync_needed)
  500. no_spread_synced = SYNCED;
  501. }
  502. // May happen when dead
  503. else
  504. {
  505. resync_needed = false;
  506. waiting_perf_data = false;
  507. }
  508. return should_call_original;
  509. };
  510. void CL_SendMove_hook()
  511. {
  512. first_usercmd = true;
  513. called_from_sendmove = false;
  514. if (!no_spread_synced || !shouldNoSpread(false))
  515. {
  516. CL_SendMove_t original = (CL_SendMove_t) cl_nospread_sendmovedetour.GetOriginalFunc();
  517. original();
  518. cl_nospread_sendmovedetour.RestorePatch();
  519. return;
  520. }
  521. // Here we need to calculate process average process time, predict spread and get weapon spread
  522. current_weapon_spread = 0.0;
  523. // first try to get the player and check if he is valid
  524. if (!RAW_ENT(LOCAL_E))
  525. {
  526. // don't set called_from_sendmove here cuz we don't care
  527. CL_SendMove_t original = (CL_SendMove_t) cl_nospread_sendmovedetour.GetOriginalFunc();
  528. original();
  529. cl_nospread_sendmovedetour.RestorePatch();
  530. return;
  531. }
  532. int *choked_packets = nullptr;
  533. if (!choked_packets)
  534. choked_packets = (int *) ((uintptr_t) g_IBaseClientState + offsets::lastoutgoingcommand() + 4);
  535. int new_packets = 1 + *choked_packets;
  536. auto RecheckIfresync_needed = [&new_packets](double asumed_time) -> void {
  537. static Timer s_NextCheck;
  538. // we use it as 1 sec delay
  539. if (s_NextCheck.check(1000) && new_packets == 1 && (no_spread_synced != SYNCED || !LOCAL_E->m_bAlivePlayer()) && !waiting_perf_data)
  540. {
  541. s_NextCheck.update();
  542. // request playerperf once again here, and this time we don't care if it will be sent with clc_move
  543. // reuse this variable, this time we store here predicted time here
  544. sent_client_floattime = asumed_time;
  545. // set it to true to send playerperf with this cmd
  546. should_update_time = true;
  547. // Incase we are dead sync too, unless we already are doing so
  548. if (!LOCAL_E->m_bAlivePlayer() && no_spread_synced != CORRECTING)
  549. {
  550. // Backup data
  551. last_sync_delta_time = float_time_delta;
  552. last_ping_at_send = ping_at_send;
  553. // We now start syncing
  554. no_spread_synced = DEAD_SYNC;
  555. }
  556. // We always set this, and change it later in CL_SendMove if we aren't dead
  557. resynced_this_death = true;
  558. }
  559. };
  560. // try to predict server's float time
  561. // please notice that time delta calculated relative to this code possition
  562. // this means we don't care about procession time of the code below
  563. // but we care about procession time of dynamic code in WriteUserCmd hook
  564. double asumed_real_time = Plat_FloatTime() + float_time_delta;
  565. double predicted_time = asumed_real_time;
  566. predicted_time += write_usercmd_correction * new_packets;
  567. double ping = ((INetChannel *) (g_IEngine->GetNetChannelInfo()))->GetLatency(FLOW_OUTGOING);
  568. if (use_avg_latency)
  569. ping = ((INetChannel *) (g_IEngine->GetNetChannelInfo()))->GetAvgLatency(FLOW_OUTGOING);
  570. if (correct_ping)
  571. // Ping changed, adjust (Provided we are not fakelagging)
  572. if (!(fakelag_amount || (hacks::shared::antiaim::isEnabled() && hacks::shared::antiaim::force_fakelag)) && (int) (ping * 1000.0) != (int) (ping_at_send * 1000.0))
  573. predicted_time += ping - ping_at_send;
  574. // Check if we need to sync
  575. RecheckIfresync_needed(asumed_real_time);
  576. // If we're dead just return original
  577. if (!LOCAL_E->m_bAlivePlayer())
  578. {
  579. CL_SendMove_t original = (CL_SendMove_t) cl_nospread_sendmovedetour.GetOriginalFunc();
  580. original();
  581. cl_nospread_sendmovedetour.RestorePatch();
  582. return;
  583. }
  584. else
  585. {
  586. // We are alive
  587. resynced_this_death = false;
  588. // We should cancel the dead sync process
  589. if (no_spread_synced == DEAD_SYNC)
  590. {
  591. // Restore delta time
  592. float_time_delta = last_sync_delta_time;
  593. ping_at_send = last_ping_at_send;
  594. // Mark as synced
  595. no_spread_synced = SYNCED;
  596. should_update_time = false;
  597. }
  598. }
  599. // Bad weapon
  600. if ((g_pLocalPlayer->weapon_mode != weapon_hitscan && LOCAL_W->m_iClassID() != CL_CLASS(CTFCompoundBow)))
  601. {
  602. CL_SendMove_t original = (CL_SendMove_t) cl_nospread_sendmovedetour.GetOriginalFunc();
  603. original();
  604. cl_nospread_sendmovedetour.RestorePatch();
  605. return;
  606. }
  607. float current_time = GetServerCurTime();
  608. // Check if we are attacking, if not then no point in adjusting
  609. if (!current_user_cmd || !(current_user_cmd->buttons & IN_ATTACK))
  610. {
  611. CL_SendMove_t original = (CL_SendMove_t) cl_nospread_sendmovedetour.GetOriginalFunc();
  612. original();
  613. cl_nospread_sendmovedetour.RestorePatch();
  614. return;
  615. }
  616. // If we have a perfect shot and we don#t want to center the whole cone, returne too
  617. if (IsPerfectShot(RAW_ENT(LOCAL_W), current_time) && !center_cone)
  618. {
  619. CL_SendMove_t original = (CL_SendMove_t) cl_nospread_sendmovedetour.GetOriginalFunc();
  620. original();
  621. cl_nospread_sendmovedetour.RestorePatch();
  622. return;
  623. }
  624. current_weapon_spread = re::C_TFWeaponBaseGun::GetWeaponSpread(RAW_ENT(LOCAL_W));
  625. // Bad spread
  626. if (!IsFinite(current_weapon_spread))
  627. {
  628. CL_SendMove_t original = (CL_SendMove_t) cl_nospread_sendmovedetour.GetOriginalFunc();
  629. original();
  630. cl_nospread_sendmovedetour.RestorePatch();
  631. return;
  632. }
  633. if (*debug_nospread >= 2)
  634. g_ICvar->ConsoleColorPrintf(MENU_COLOR, "Predicted: %.6f Assumed: %.6f Correction: %.6f Commands: %i\n", predicted_time, asumed_real_time, write_usercmd_correction, new_packets);
  635. // Try to predict seed now
  636. // The important thing to understand this: server_random_seed set as soon as ProcessUsercmds called. And it's called in clc_move process function, so as soon as server accepts our packet, not when it actually processed.
  637. // This means every usercmd in 1 clc_move will have almost same random seed - if process time less than mantissa step for random seed number, then same "random" number will be set for each usercmd in this packet
  638. // Only adjust if not using usercmd seed
  639. if (!use_usercmd_seed)
  640. prediction_seed = (float) (predicted_time * 1000.0);
  641. // Call original
  642. called_from_sendmove = true;
  643. double time_start = Plat_FloatTime();
  644. CL_SendMove_t original = (CL_SendMove_t) cl_nospread_sendmovedetour.GetOriginalFunc();
  645. original();
  646. cl_nospread_sendmovedetour.RestorePatch();
  647. double time_end = Plat_FloatTime();
  648. called_from_sendmove = false;
  649. // Update the processing time if we actually processed stuff
  650. if (should_update_usercmd_correction)
  651. {
  652. // How long it took for us to process each cmd? We will add this number next time we will process usercommands
  653. write_usercmd_correction = (time_end - time_start) / new_packets;
  654. should_update_usercmd_correction = false;
  655. }
  656. }
  657. void WriteUserCmd_hook(bf_write *buf, CUserCmd *to, CUserCmd *from)
  658. {
  659. // Called by a demo recorder or we shouldn't compensate it.
  660. if ((no_spread_synced != SYNCED && !resync_needed) || !shouldNoSpread(false) || current_weapon_spread == 0.0)
  661. {
  662. WriteUserCmd_t original = (WriteUserCmd_t) cl_writeusercmd_detour.GetOriginalFunc();
  663. original(buf, to, from);
  664. cl_writeusercmd_detour.RestorePatch();
  665. return;
  666. }
  667. // Save the original from
  668. CUserCmd backup_from = *from;
  669. if (!(to->buttons & IN_ATTACK))
  670. {
  671. user_cmd_backup = *to;
  672. first_usercmd = false;
  673. WriteUserCmd_t original = (WriteUserCmd_t) cl_writeusercmd_detour.GetOriginalFunc();
  674. original(buf, to, from);
  675. cl_writeusercmd_detour.RestorePatch();
  676. return;
  677. }
  678. // Save the original to
  679. CUserCmd backup_to = *to;
  680. // If nospread fully synced, the only thing we need to do is to use already known float time with our random seed
  681. ApplySpreadCorrection(to->viewangles, reinterpret_cast<int &>(prediction_seed) & 0xFF, current_weapon_spread);
  682. // We are processing, so we should update it
  683. should_update_usercmd_correction = true;
  684. user_cmd_backup = *to;
  685. first_usercmd = false;
  686. WriteUserCmd_t original = (WriteUserCmd_t) cl_writeusercmd_detour.GetOriginalFunc();
  687. original(buf, to, from);
  688. cl_writeusercmd_detour.RestorePatch();
  689. // Restore the original from
  690. *from = backup_from;
  691. // Restore the original to
  692. *to = backup_to;
  693. }
  694. void FX_FireBullets_hook(IClientEntity *weapon, int player, Vector *origin, Vector *angles, int weapon_idx, int bullet_mode, int seed, float spread, float damage, bool is_critical)
  695. {
  696. // Not synced/weapon bad
  697. if (!weapon || (no_spread_synced != SYNCED && !resync_needed) || !bullet || (IsPerfectShot(weapon) && !center_cone))
  698. {
  699. FX_FireBullets_t original = (FX_FireBullets_t) fx_firebullets_detour.GetOriginalFunc();
  700. original(weapon, player, origin, angles, weapon_idx, bullet_mode, seed, spread, damage, is_critical);
  701. fx_firebullets_detour.RestorePatch();
  702. return;
  703. }
  704. Vector corrected_angles = *angles;
  705. ApplySpreadCorrection(corrected_angles, seed, spread);
  706. FX_FireBullets_t original = (FX_FireBullets_t) fx_firebullets_detour.GetOriginalFunc();
  707. original(weapon, player, origin, &corrected_angles, weapon_idx, bullet_mode, seed, spread, damage, is_critical);
  708. fx_firebullets_detour.RestorePatch();
  709. }
  710. /*int NET_SendPacket_hook(INetChannel *chan, int sock, const netadr_t &to, const unsigned char *data, int length, bf_write *pVoicePayload, bool bUseCompression)
  711. {
  712. logging::Info("Packet size: %d", length);
  713. NET_SendPacket_t original = (NET_SendPacket_t) net_sendpacket_detour.GetOriginalFunc();
  714. auto ret_val = original(chan, sock, to, data, length, pVoicePayload, bUseCompression);
  715. net_sendpacket_detour.RestorePatch();
  716. return ret_val;
  717. }*/
  718. static Timer update_nospread_timer{};
  719. static InitRoutine init_bulletnospread([]() {
  720. // Get our detour hooks running
  721. static auto writeusercmd_addr = gSignatures.GetClientSignature("55 89 E5 57 56 53 83 EC 2C 8B 45 ? 8B 7D ? 8B 5D ? 89 45 ? 8B 40");
  722. cl_writeusercmd_detour.Init(writeusercmd_addr, (void *) WriteUserCmd_hook);
  723. static auto fx_firebullets_addr = gSignatures.GetClientSignature("55 89 E5 57 56 53 81 EC 0C 01 00 00 8B 45 ? 8B 7D ? 89 85");
  724. fx_firebullets_detour.Init(fx_firebullets_addr, (void *) FX_FireBullets_hook);
  725. /*static auto net_sendpacket_addr = gSignatures.GetEngineSignature("55 89 E5 57 56 53 81 EC EC 20 00 00 C7 85 ? ? ? ? 00 00 00 00 8B 45");
  726. net_sendpacket_detour.Init(net_sendpacket_addr, (void *) NET_SendPacket_hook);*/
  727. // Register Event callbacks
  728. EC::Register(
  729. EC::CreateMove,
  730. []() {
  731. if (bullet)
  732. {
  733. static auto sv_usercmd_custom_random_seed = g_ICvar->FindVar("sv_usercmd_custom_random_seed");
  734. if (!sv_usercmd_custom_random_seed)
  735. sv_usercmd_custom_random_seed = g_ICvar->FindVar("sv_usercmd_custom_random_seed");
  736. // Server owner decided it would be a great idea to give the user control over the random seed
  737. else if (!sv_usercmd_custom_random_seed->GetBool())
  738. {
  739. auto seed = MD5_PseudoRandom(current_user_cmd->command_number) & 0x7FFFFFFF;
  740. prediction_seed = *reinterpret_cast<float *>(&seed);
  741. use_usercmd_seed = true;
  742. }
  743. // Normal server
  744. else
  745. use_usercmd_seed = false;
  746. // Synced, mark as such to other modules
  747. if (no_spread_synced == SYNCED)
  748. is_syncing = false;
  749. // Not synced currently, try to sync
  750. if (no_spread_synced == NOT_SYNCED && !bad_mantissa)
  751. {
  752. is_syncing = true;
  753. should_update_time = true;
  754. update_nospread_timer.update();
  755. }
  756. // Else if mantissa bad, update every 10 mins
  757. else if (no_spread_synced == NOT_SYNCED && update_nospread_timer.test_and_set(10 * 60 * 1000))
  758. no_spread_synced = CORRECTING;
  759. }
  760. },
  761. "nospread_createmove2");
  762. #if ENABLE_VISUALS
  763. EC::Register(
  764. EC::Draw,
  765. []() {
  766. if (bullet && (draw || draw_mantissa) && CE_GOOD(LOCAL_E) && LOCAL_E->m_bAlivePlayer())
  767. {
  768. std::string draw_string = "";
  769. rgba_t draw_color = colors::white;
  770. switch (no_spread_synced)
  771. {
  772. case NOT_SYNCED:
  773. {
  774. if (bad_mantissa)
  775. {
  776. draw_color = colors::red;
  777. draw_string = "Server uptime too Low!";
  778. }
  779. else
  780. {
  781. draw_color = colors::orange;
  782. draw_string = "Not Syncing";
  783. }
  784. break;
  785. }
  786. case CORRECTING:
  787. case DEAD_SYNC:
  788. {
  789. draw_color = colors::yellow;
  790. draw_string = "Syncing...";
  791. break;
  792. }
  793. case SYNCED:
  794. {
  795. draw_color = colors::green;
  796. draw_string = "Synced.";
  797. break;
  798. }
  799. default:
  800. break;
  801. }
  802. if (draw)
  803. AddCenterString(draw_string, draw_color);
  804. if (draw_mantissa && no_spread_synced != NOT_SYNCED)
  805. AddCenterString("Mantissa step size: " + std::to_string((int) CalculateMantissaStep(1000.0 * (Plat_FloatTime() + float_time_delta))), draw_color);
  806. }
  807. },
  808. "nospread_draw");
  809. #endif
  810. EC::Register(
  811. EC::LevelInit,
  812. []() {
  813. no_spread_synced = NOT_SYNCED;
  814. last_was_player_perf = false;
  815. bad_mantissa = false;
  816. waiting_perf_data = false;
  817. },
  818. "nospread_levelinit");
  819. EC::Register(
  820. EC::LevelShutdown,
  821. []() {
  822. no_spread_synced = NOT_SYNCED;
  823. last_was_player_perf = false;
  824. bad_mantissa = false;
  825. waiting_perf_data = false;
  826. },
  827. "nospread_levelshutdown");
  828. EC::Register(
  829. EC::Shutdown,
  830. []() {
  831. cl_writeusercmd_detour.Shutdown();
  832. fx_firebullets_detour.Shutdown();
  833. // net_sendpacket_detour.Shutdown();
  834. },
  835. "nospread_shutdown");
  836. });
  837. } // namespace hacks::tf2::nospread