diff --git a/README.md b/README.md index 5ebd583f..2ddf74e6 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,14 @@ -# Cathook Multihack +# Cathook Training Software ![banner](https://raw.githubusercontent.com/nullifiedcat/cathook/master/banner.png) -cathook is a multihack for Team Fortress 2 for Linux. cathook includes some joke features like +## Discord Server +[Official Discord Server](https://discord.gg/kvNVNSX) + +cathook is a training software designed for Team Fortress 2 for Linux. cathook includes some joke features like * Always/Never spycrab * Ignore Hoovy -* 100% Casual/Comp coin flip hack +* 100% Casual/Comp coin flip * Encrypted chat * Emoji ESP * Fidget Spinner crosshair @@ -13,29 +16,50 @@ cathook is a multihack for Team Fortress 2 for Linux. cathook includes some joke and a lot of useful features, including * Anti Backstab with option to say "No" voice command when spy tries to backstab you -* Heal Arrows hack (overheal an enemy for 1200 health with single huntsman arrow, you can also do it with buildings!) +* Heal Arrows exploit (overheal an enemy for 1200 health with single huntsman arrow, you can also do it with buildings!) * Extremely customizable spam (you can make spam lines that'll include name of random dead enemy pyro or sniper) * Follow Bots * Working crit hack [FULL LIST OF FEATURES HERE](https://github.com/nullifiedcat/cathook/wiki/List-of-features) -# Discord Server -[Official Discord Server](https://discord.gg/kvNVNSX) - # INSTALLATION -Ubuntu dependencies installation: +You **have** to use gcc6, not 7 or 5! + +You also have to have g++-6-multilib package. + +Ubuntu gcc6 installation: (check if you have gcc-6 installed already by typing `gcc-6 -v` ```bash -sudo apt update && sudo apt install build-essential software-properties-common -y && sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y && sudo apt update && sudo apt install gcc-snapshot -y && sudo apt update && sudo apt install git libc6-dev gcc-6 g++-6 libc6-dev:i386 g++-6-multilib gdb libsdl2-dev libglew-dev libfreetype6-dev libfreetype6-dev:i386 -y +sudo apt update && sudo apt install build-essential software-properties-common -y && sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y && sudo apt update && sudo apt install gcc-snapshot g++-6-multilib gcc-6 g++-6 -y +``` + +Ubuntu other dependencies installation: + +```bash +sudo apt update && sudo apt install git libssl-dev:i386 libc6-dev:i386 gdb libsdl2-dev libglew-dev:i386 libfreetype6-dev:i386 -y ``` -Arch dependencies installation:: +Arch gcc6 & dependencies installation: ```bash sudo pacman -Syu && sudo pacman -S gdb gdb-common glew1.10 lib32-glew1.10 rsync --noconfirm && yes | sudo pacman -U https://archive.archlinux.org/packages/g/gcc-multilib/gcc-multilib-6.3.1-2-x86_64.pkg.tar.xz https://archive.archlinux.org/packages/g/gcc-libs-multilib/gcc-libs-multilib-6.3.1-2-x86_64.pkg.tar.xz https://archive.archlinux.org/packages/l/lib32-gcc-libs/lib32-gcc-libs-6.3.1-2-x86_64.pkg.tar.xz ``` +If you don't use Ubuntu or Arch (or if Arch script gets outdated), here's the list of what cathook requires: + +* `gcc-6` +* `g++-6` +* `gcc-6-multilib` +* `g++-6-multilib` +* `gdb` (for the injection script, you can use different injector if you want) +* `libssl-dev:i386` +* `libc6-dev:i386` +* `libsdl2-dev` +* `libglew-dev:i386` +* `libfreetype6-dev:i386` +* `rsync` (only for copying shaders/fonts to tf2 data directory, `update-data` script) + Cathook installation script: ```bash diff --git a/banner.png b/banner.png index 95872206..28c2649a 100644 Binary files a/banner.png and b/banner.png differ diff --git a/makefile b/makefile index e25d84fb..bb4f5f96 100644 --- a/makefile +++ b/makefile @@ -52,7 +52,7 @@ ifndef CLANG LDFLAGS+=-flto endif endif -LDLIBS=-l:libSDL2-2.0.so.0 -static -l:libc.so.6 -static -l:libstdc++.so.6 -l:libtier0.so -l:libvstdlib.so -static -l:libGLEW.so -l:libfreetype.so +LDLIBS=-lssl -l:libSDL2-2.0.so.0 -static -l:libc.so.6 -static -l:libstdc++.so.6 -l:libtier0.so -l:libvstdlib.so -static -l:libGLEW.so -l:libfreetype.so SRC_DIR = src RES_DIR = res OUT_NAME = libcathook.so diff --git a/src/backpacktf.cpp b/src/backpacktf.cpp new file mode 100644 index 00000000..14fbf154 --- /dev/null +++ b/src/backpacktf.cpp @@ -0,0 +1,181 @@ +/* + * backpacktf.cpp + * + * Created on: Jul 23, 2017 + * Author: nullifiedcat + */ + +#include "backpacktf.hpp" +#include "json.hpp" +#include "https_request.hpp" + +#include "common.h" + +#include "cvwrapper.h" + +#include +#include + +namespace backpacktf { + +std::unordered_map cache {}; +std::queue pending_queue {}; +std::mutex queue_mutex {}; +std::mutex cache_mutex {}; + +std::string api_key_s = ""; +bool valid_api_key = false; + +CatVar enable_bptf(CV_SWITCH, "bptf_enable", "0", "Enable backpack.tf", "Enable backpack.tf integration\nYou have to set your API key in cat_bptf_key"); +CatCommand api_key("bptf_key", "Set API Key", [](const CCommand& args) { + api_key_s = args.ArgS(); + logging::Info("API key changed!"); + valid_api_key = false; + if (api_key_s.length() != 24) { + logging::Info("API key must be exactly 24 characters long"); + valid_api_key = false; + } else { + valid_api_key = true; + } +}); + + +void store_data(unsigned id, unsigned value, bool no_value, bool outdated_value); + +void processing_thread() { + logging::Info("[bp.tf] Starting the thread"); + while (true) { + if (enabled()) { + try { + std::vector batch {}; + int count = 0; + { + std::lock_guard lock(queue_mutex); + while (not pending_queue.empty() && ++count < 100) { + batch.push_back(pending_queue.front()); + pending_queue.pop(); + } + } + logging::Info("[bp.tf] Requesting data for %d users", count); + if (count) { + std::string id_list = ""; + logging::Info("Constructing list"); + for (const auto& x : batch) { + x->pending = false; + logging::Info("Adding %u to the list", x->id); + id_list += format("[U:1:", x->id, "],"); + } + logging::Info("List constructed"); + // Remove trailing ',' + id_list = id_list.substr(0, id_list.length() - 1); + std::string query = format("steamids=", id_list, "&key=", api_key_s); + logging::Info("Query constructed"); + try { + auto sock = https::RAII_HTTPS_Socket("backpack.tf"); + logging::Info("Socket created"); + sock.ssl_connect(); + logging::Info("Socket connected (%d)", sock.sock_); + std::string response = sock.get("/api/users/info/v1?" + query); + logging::Info("Request sent"); + if (response.find("HTTP/1.1 200 OK\r\n") != 0) { + size_t status = response.find("\r\n"); + throw std::runtime_error("Response isn't 200 OK! It's " + response.substr(0, status)); + } + + std::string body = response.substr(response.find("\r\n\r\n") + 4); + + logging::Info("[bp.tf] Response: %s", body.c_str()); + + try { + nlohmann::json data(body); + nlohmann::json users = data["users"]; + std::lock_guard lock(cache_mutex); + for (auto it = users.begin(); it != users.end(); ++it) { + try { + unsigned userid = strtoul(it.key().substr(5).c_str(), nullptr, 10); + const auto& v = it.value(); + if (not v.is_object()) { + logging::Info("Data for %u (%s) is not an object!", userid, it.key()); + continue; + } + logging::Info("Parsing data for user %u (%s)", userid, v["name"]); + const auto& inv = v["inventory"]["440"]; + if (inv.find("value") == inv.end()) { + logging::Info("Private backpack"); + store_data(userid, 0, true, false); + } else { + float value = float(inv["value"]); + unsigned updated = unsigned(inv["updated"]); + store_data(userid, value * REFINED_METAL_PRICE, false, (unsigned(time(0)) - updated > OUTDATED_AGE)); + } + } catch (std::exception& ex) { + logging::Info("Error while parsing user %s", it.key().c_str()); + } + } + } catch (std::exception& e) { + logging::Info("[bp.tf] Exception while parsing response: %s", e.what()); + } + + } catch (std::exception& e) { + logging::Info("[bp.tf] HTTPS exception: %s", e.what()); + } + } + } catch (std::exception& e) { + logging::Info("[bp.tf] Thread exception: %s", e.what()); + } + } + sleep(REQUEST_INTERVAL); + } +} + +void request_data(unsigned id) { + if (cache[id].pending) return; + cache[id].pending = true; + { + std::lock_guard lock(queue_mutex); + pending_queue.push(&cache[id]); + } +} + +bool enabled() { + return enable_bptf && valid_api_key; +} + +backpack_data_s& access_data(unsigned id) { + try { + return cache.at(id); + } catch (std::out_of_range& oor) { + cache.emplace(id, backpack_data_s {}); + cache.at(id).id = id; + return cache.at(id); + } +} + +void store_data(unsigned id, unsigned value, bool none, bool outdated) { + auto& d = access_data(id); + d.last_request = unsigned(time(0)); + d.bad = false; + d.value = value; + d.no_value = none; + d.outdated_value = outdated; + d.pending = false; +} + +const backpack_data_s& get_data(unsigned id) { + auto& d = access_data(id); + if (d.bad || ((unsigned)time(0) - MAX_CACHE_AGE > cache[id].last_request)) { + request_data(id); + } + return d; +} + +std::thread& GetBackpackTFThread() { + static std::thread thread(processing_thread); + return thread; +} + +void init() { + GetBackpackTFThread(); +} + +} diff --git a/src/backpacktf.hpp b/src/backpacktf.hpp new file mode 100644 index 00000000..bf5a12b7 --- /dev/null +++ b/src/backpacktf.hpp @@ -0,0 +1,35 @@ +/* + * backpacktf.hpp + * + * Created on: Jul 23, 2017 + * Author: nullifiedcat + */ + +#pragma once + +class CatVar; + +namespace backpacktf { + +constexpr float REFINED_METAL_PRICE = 0.075f; // $ +constexpr unsigned REQUEST_INTERVAL = 10; // Make a backpack.tf request every 30 seconds +constexpr unsigned MAX_CACHE_AGE = 60 * 30; +constexpr unsigned OUTDATED_AGE = 60 * 60 * 24 * 3; // After how many seconds backpack is marked "outdated" (possibly private) + +extern CatVar enable_bptf; + +struct backpack_data_s { + bool pending { false }; + bool bad { true }; + bool no_value { false }; // No recorded value + bool outdated_value { false }; // Outdated value. Private inventory? + unsigned last_request { 0 }; + unsigned value { 0 }; + unsigned id { 0 }; +}; + +const backpack_data_s& get_data(unsigned id); +void init(); +bool enabled(); + +} diff --git a/src/common.h b/src/common.h index 07319983..1c3362da 100644 --- a/src/common.h +++ b/src/common.h @@ -83,6 +83,7 @@ extern "C" { #include "hooks/hookedmethods.h" #include "classinfo/classinfo.hpp" #include "crits.h" +#include "backpacktf.hpp" #if ENABLE_GUI #include "gui/GUI.h" diff --git a/src/gui/im/Playerlist.cpp b/src/gui/im/Playerlist.cpp index b9b4ada7..bf434e1c 100644 --- a/src/gui/im/Playerlist.cpp +++ b/src/gui/im/Playerlist.cpp @@ -83,7 +83,31 @@ void RenderPlayer(int eid) { if (bgcolor.a) { ImGui::PopStyleColor(); } - ImGui::SameLine(); + if (backpacktf::enabled()) { + ImGui::SameLine(); + if (!info.friendsID) { + ImGui::Text("[BOT]"); + } + const auto& d = backpacktf::get_data(info.friendsID); + if (d.bad && not d.pending) { + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.0f, 0.0f, 1.0f)); + ImGui::Text("Error"); + } else if (d.pending) { + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.6f, 0.6f, 0.6f, 1.0f)); + ImGui::Text("Fetching"); + } else if (d.no_value) { + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 0.0f, 1.0f)); + ImGui::Text("No value"); + } else if (d.outdated_value) { + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.6f, 0.6f, 0.6f, 1.0f)); + ImGui::Text("$%u", d.value); + } else { + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.0f, 0.8f, 0.0f, 1.0f)); + ImGui::Text("$%u", d.value); + } + ImGui::PopStyleColor(); + } + ImGui::SameLine(610); ImGui::PushItemWidth(200.0f); if (ImGui::ColorEdit3("", data.color)) { if (!data.color.r && !data.color.b && !data.color.g) { @@ -104,7 +128,7 @@ void RenderPlayerlist() { ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 1)); std::vector teammates {}; for (int i = 1; i < 32; i++) { - if (g_pPlayerResource->GetTeam(i) == LOCAL_E->m_iTeam) { + if (!g_Settings.bInvalid && (g_pPlayerResource->GetTeam(i) == LOCAL_E->m_iTeam)) { teammates.push_back(i); continue; } diff --git a/src/hack.cpp b/src/hack.cpp index 87e29fdf..5a107fa1 100644 --- a/src/hack.cpp +++ b/src/hack.cpp @@ -272,8 +272,10 @@ void hack::Initialize() { hacks::shared::anticheat::Init(); hacks::tf2::healarrow::Init(); InitSpinner(); - hacks::shared::spam::Init(); logging::Info("Initialized Fidget Spinner"); + hacks::shared::spam::Init(); + backpacktf::init(); + logging::Info("Initialized Backpack.TF integration"); } diff --git a/src/https_request.cpp b/src/https_request.cpp new file mode 100644 index 00000000..c0993683 --- /dev/null +++ b/src/https_request.cpp @@ -0,0 +1,127 @@ +/* + * https_request.cpp + * + * Created on: Jul 23, 2017 + * Author: nullifiedcat + */ + +#include "https_request.hpp" + +#include "logging.h" + +#include +#include +#include + +#include + +#include +#include + + +namespace https { + +SSL_CTX *ssl_context; + +bool initialized = false; + +RAII_HTTPS_Socket::RAII_HTTPS_Socket(const std::string& host) : hostname_(host) { + if (!initialized) { + logging::Info("Initializing SSL"); + initialize(); + } + sock_ = socket(AF_INET, SOCK_STREAM, 0); + if (sock_ < 0) { + throw std::runtime_error("Socket creation error"); + } + host_ = gethostbyname(hostname_.c_str()); + if (not host_) { + throw std::runtime_error("Could not resolve hostname: " + host); + } + memset(&addr_, 0, sizeof(addr_)); + addr_.sin_family = AF_INET; + addr_.sin_port = htons(443); + memcpy(&addr_.sin_addr.s_addr, host_->h_addr, host_->h_length); + if (connect(sock_, (sockaddr *)&addr_, sizeof(addr_)) < 0) { + close(sock_); + sock_ = -1; + throw std::runtime_error("Couldn't connect to host"); + } + ssl_connect(); +} + +RAII_HTTPS_Socket::~RAII_HTTPS_Socket() { + logging::Info("Cleaning up HTTPS socket"); + ssl_die(); + if (sock_ >= 0) + close(sock_); +} + +bool RAII_HTTPS_Socket::ssl_connect() { + connection_ = SSL_new(ssl_context); + SSL_set_fd(connection_, sock_); + if (SSL_connect(connection_) != 1) { + printf("SSL connection error\n"); + return false; + } + return true; +} + +void RAII_HTTPS_Socket::ssl_die() { + if (connection_) { + SSL_free(connection_); + connection_ = nullptr; + } +} + +std::string RAII_HTTPS_Socket::get(const std::string& path) { + constexpr size_t rq_size = 1024 * 8; + constexpr size_t rs_size = 1024 * 1024; + + std::unique_ptr + buffer_rq(new char[rq_size]), + buffer_rs(new char[rs_size]); + + memset(buffer_rq.get(), 0, rq_size); + int rq_length = snprintf(buffer_rq.get(), rq_size, "GET %s HTTP/1.0\r\nHost: %s\r\n\r\n", path.c_str(), hostname_.c_str()); + int sent = 0; + int chunk = 0; + logging::Info("Writing %d bytes to socket %d", rq_length, sock_); + do { + chunk = SSL_write(connection_, buffer_rq.get() + sent, rq_length - sent); + logging::Info("Written %d bytes", chunk); + if (chunk < 0) { + throw std::runtime_error("Error writing to Secure Socket: " + std::to_string(ERR_get_error())); + } else if (chunk == 0) { + break; + } + sent += chunk; + } while (sent < rq_length); + + memset(buffer_rs.get(), 0, rs_size); + int total = rs_size - 1; + int received = 0; + do { + chunk = SSL_read(connection_, buffer_rs.get() + received, total - received); + if (chunk < 0) + throw std::runtime_error("Error reading from socket"); + if (chunk == 0) + break; + received += chunk; + } while (received < total); + + if (received == total) + throw std::runtime_error("Response too large"); + + return std::string(buffer_rs.get()); +} + + +void initialize() { + SSL_load_error_strings(); + SSL_library_init(); + ssl_context = SSL_CTX_new(SSLv23_client_method()); + initialized = true; +} + +} diff --git a/src/https_request.hpp b/src/https_request.hpp new file mode 100644 index 00000000..f56a7adc --- /dev/null +++ b/src/https_request.hpp @@ -0,0 +1,44 @@ +/* + * https_request.hpp + * + * Created on: Jul 23, 2017 + * Author: nullifiedcat + */ + +#pragma once + +#include +#include + +#include "beforecheaders.h" +#include +#include "aftercheaders.h" + + +namespace https { + +extern SSL_CTX *ssl_context; + +class RAII_HTTPS_Socket { +public: + RAII_HTTPS_Socket(const std::string& host); + ~RAII_HTTPS_Socket(); + + bool ssl_connect(); + void ssl_die(); + + std::string get(const std::string& path); + + const std::string hostname_; + + hostent *host_ { nullptr }; + sockaddr_in addr_ {}; + int sock_ { -1 }; + SSL *connection_ { nullptr }; +}; + +void initialize(); + +} + + diff --git a/src/prediction.cpp b/src/prediction.cpp index a56bd9ad..00f23597 100644 --- a/src/prediction.cpp +++ b/src/prediction.cpp @@ -222,10 +222,17 @@ Vector ProjectilePrediction_Engine(CachedEntity* ent, int hb, float speed, float return result; } +CatVar debug_pp_extrapolate(CV_SWITCH, "debug_pp_extrapolate", "0", "Extrapolate entity position when predicting projectiles"); +CatVar debug_pp_rockettimeping(CV_SWITCH, "debug_pp_rocket_time_ping", "0", "Compensate for ping in pp"); + Vector ProjectilePrediction(CachedEntity* ent, int hb, float speed, float gravitymod, float entgmod) { if (!ent) return Vector(); Vector result; - GetHitbox(ent, hb, result); + if (not debug_pp_extrapolate) { + GetHitbox(ent, hb, result); + } else { + result = SimpleLatencyPrediction(ent, hb); + } if (speed == 0.0f) return Vector(); float dtg = DistanceToGround(ent); Vector vel; @@ -250,6 +257,7 @@ Vector ProjectilePrediction(CachedEntity* ent, int hb, float speed, float gravit if (curpos.z < result.z - dtg) curpos.z = result.z - dtg; } float rockettime = g_pLocalPlayer->v_Eye.DistTo(curpos) / speed; + if (debug_pp_rockettimeping) rockettime += g_IEngine->GetNetChannelInfo()->GetLatency(FLOW_OUTGOING); if (fabs(rockettime - currenttime) < mindelta) { besttime = currenttime; bestpos = curpos;