Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/dllmain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ static ID3D11RenderTargetView* g_render_target_view = nullptr;
static WNDPROC g_original_wnd_proc = nullptr;
static bool g_initialized = false;
static bool g_show_menu = true;
static HMODULE g_h_module = nullptr;

// ============================================================================
// HOOKS
Expand Down Expand Up @@ -76,7 +77,8 @@ static HRESULT __stdcall HookedPresent(IDXGISwapChain* swap_chain, UINT sync_int
ImGui_ImplWin32_Init(g_hwnd);
ImGui_ImplDX11_Init(g_device, g_context);

// Subclass window
// Subclass window - declare WndProc first
extern LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Declaring an extern function inside a function body is generally discouraged as it obscures dependencies and can make the code harder to maintain. It is better to declare WndProc at the top of the file or in a shared header file.

g_original_wnd_proc = reinterpret_cast<WNDPROC>(
SetWindowLongPtr(g_hwnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(WndProc)));
Comment on lines +80 to 83

Copilot AI Apr 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This forward declaration uses extern (external linkage), but the actual WndProc definition below is static (internal linkage). That linkage mismatch is a compile error in C++. Fix by forward-declaring it as static (ideally at file scope above HookedPresent), or remove the extern and move a correct prototype before first use.

Copilot uses AI. Check for mistakes.

Expand Down Expand Up @@ -328,8 +330,6 @@ DWORD WINAPI MainThread(LPVOID) {
return 0;
}

static HMODULE g_h_module = nullptr;

BOOL APIENTRY DllMain(HMODULE h_module, DWORD reason, LPVOID reserved) {
if (reason == DLL_PROCESS_ATTACH) {
g_h_module = h_module;
Expand Down
8 changes: 4 additions & 4 deletions src/features/skinchanger/items.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,12 +139,12 @@ std::vector<DumpedItemDef> ItemDatabase::Search(std::string_view query) {

std::string lower_query;
lower_query.reserve(query.size());
for (char c : query) lower_query += std::tolower(static_cast<unsigned char>(c));
for (char c : query) lower_query += static_cast<char>(std::tolower(static_cast<unsigned char>(c)));

for (const auto& item : s_items) {
std::string lower_name = item.name;
std::transform(lower_name.begin(), lower_name.end(), lower_name.begin(),
[](unsigned char c) { return std::tolower(c); });
[](unsigned char c) -> char { return static_cast<char>(std::tolower(c)); });
if (lower_name.find(lower_query) != std::string::npos)
results.push_back(item);
}
Expand All @@ -162,13 +162,13 @@ std::vector<DumpedItemDef> ItemDatabase::Search(std::string_view query, ItemType

std::string lower_query;
lower_query.reserve(query.size());
for (char c : query) lower_query += std::tolower(static_cast<unsigned char>(c));
for (char c : query) lower_query += static_cast<char>(std::tolower(static_cast<unsigned char>(c)));

for (const auto& item : s_items) {
if (item.type != type) continue;
std::string lower_name = item.name;
std::transform(lower_name.begin(), lower_name.end(), lower_name.begin(),
[](unsigned char c) { return std::tolower(c); });
[](unsigned char c) -> char { return static_cast<char>(std::tolower(c)); });
if (lower_name.find(lower_query) != std::string::npos)
results.push_back(item);
}
Expand Down
38 changes: 19 additions & 19 deletions src/features/skinchanger/skinchanger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -137,22 +137,22 @@ void SkinChanger::Shutdown() {
static uintptr_t GetInventoryManager() {
if (!SkinChanger::s_fn_inventory_manager) return 0;
using Fn = uintptr_t(__fastcall*)();
__try { return ((Fn)SkinChanger::s_fn_inventory_manager)(); }
__except (EXCEPTION_EXECUTE_HANDLER) { return 0; }
try { return ((Fn)SkinChanger::s_fn_inventory_manager)(); }
catch (...) { return 0; }
Comment on lines +140 to +141

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Using C++ try/catch instead of SEH __try/__except may no longer guard against access violations.

Previously this used __try/__except, which can handle SEH exceptions (e.g., access violations) when calling engine/game function pointers. Standard C++ try/catch (...) will not catch SEH unless the code is compiled with /EHa, so access violations may now crash the process instead of being handled. Either keep SEH for this boundary call or ensure /EHa is required and documented.

Comment on lines +140 to +141

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Replacing Structured Exception Handling (__try/__except) with standard C++ exceptions (try/catch) is dangerous in this context. Standard C++ exceptions do not catch hardware exceptions like Access Violations (EXCEPTION_ACCESS_VIOLATION) on Windows unless the code is compiled with the /EHa flag. Since this code interacts with game memory via potentially unstable pointers, using try/catch (...) will likely fail to prevent crashes that the original SEH code would have handled. This pattern is repeated throughout this file (e.g., lines 170, 177, 362, 490, 502, 542).

Comment on lines +140 to +141

Copilot AI Apr 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replacing SEH (__try/__except) with C++ try/catch here will not catch access violations or other structured exceptions under the current build flags (CMakeLists.txt does not enable /EHa or an SEH-to-C++ translator). These calls are invoking scanned function pointers, so a bad address will still crash the process. Please restore __try/__except for these guard calls, or explicitly enable SEH translation (/EHa or _set_se_translator) and justify the behavior change.

Suggested change
try { return ((Fn)SkinChanger::s_fn_inventory_manager)(); }
catch (...) { return 0; }
__try {
return ((Fn)SkinChanger::s_fn_inventory_manager)();
}
__except (EXCEPTION_EXECUTE_HANDLER) {
return 0;
}

Copilot uses AI. Check for mistakes.
}

static uintptr_t GetLocalInventory(uintptr_t manager) {
if (!manager) return 0;
// vtable index 68 (wh-esports) — was 57 in older versions
using Fn = uintptr_t(__fastcall*)(void*);
return reinterpret_cast<Fn>((*reinterpret_cast<void***>(manager))[68])(manager);
return reinterpret_cast<Fn>((*reinterpret_cast<void***>(manager))[68])(reinterpret_cast<void*>(manager));
}

static bool EquipItemInLoadout(uintptr_t manager, int team, int slot, uint64_t item_id) {
if (!manager) return false;
// vtable index 65 (wh-esports) — was 54 in older versions
using Fn = bool(__fastcall*)(void*, int, int, uint64_t);
return reinterpret_cast<Fn>((*reinterpret_cast<void***>(manager))[65])(manager, team, slot, item_id);
return reinterpret_cast<Fn>((*reinterpret_cast<void***>(manager))[65])(reinterpret_cast<void*>(manager), team, slot, item_id);
}

static uint64_t GetInventoryOwner(uintptr_t inventory) {
Expand All @@ -167,15 +167,15 @@ static uint64_t GetInventoryOwner(uintptr_t inventory) {
static CEconItem_t* CreateEconItem() {
if (!SkinChanger::s_fn_create_econ_item) return nullptr;
using Fn = CEconItem_t*(__cdecl*)();
__try { return ((Fn)SkinChanger::s_fn_create_econ_item)(); }
__except (EXCEPTION_EXECUTE_HANDLER) { return nullptr; }
try { return ((Fn)SkinChanger::s_fn_create_econ_item)(); }
catch (...) { return nullptr; }
Comment on lines +170 to +171

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Same SEH vs C++ exception handling concern for econ item creation and attribute setting.

This function (and SetDynamicAttributeValue, ApplyGloves, SetModel, SetMeshGroupMask) now uses try/catch (...) instead of __try/__except. If these engine function pointers can raise SEH faults (e.g., due to engine changes or invalid state), C++ exceptions will not catch them and the process may crash. To maintain the previous fault-tolerance, either keep SEH around these calls or document and enforce that only C++ exceptions can be thrown here (via build config/contracts).

Comment on lines 167 to +171

Copilot AI Apr 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue as above: C++ catch(...) will not intercept SEH faults from calling a potentially-invalid scanned function pointer, so this no longer provides crash protection. Prefer keeping the original __try/__except guard (or add /EHa / SEH translator if you intend to rely on catch(...)).

Copilot uses AI. Check for mistakes.
}

static void SetDynamicAttributeValue(CEconItem_t* item, uint16_t attr_index, float value) {
if (!SkinChanger::s_fn_set_dynamic_attr || !item) return;
using Fn = void(__fastcall*)(CEconItem_t*, uint16_t, float);
__try { ((Fn)SkinChanger::s_fn_set_dynamic_attr)(item, attr_index, value); }
__except (EXCEPTION_EXECUTE_HANDLER) {}
try { ((Fn)SkinChanger::s_fn_set_dynamic_attr)(item, attr_index, value); }
catch (...) {}
Comment on lines 174 to +178

Copilot AI Apr 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue as above: this try/catch won’t catch structured exceptions (e.g., access violations) thrown by the engine call, so the guard is ineffective with the current exception model. Consider reverting to __try/__except for this boundary call, or introduce explicit SEH translation in the build/runtime.

Copilot uses AI. Check for mistakes.
}

bool SkinChanger::CreateAndEquipItem(uintptr_t inventory, uintptr_t manager,
Expand Down Expand Up @@ -207,7 +207,7 @@ bool SkinChanger::CreateAndEquipItem(uintptr_t inventory, uintptr_t manager,
// Add to SO cache via SOCreated (vtable 0)
using SOCreatedFn = void(__fastcall*)(void*, SOID_t, CEconItem_t*, int);
SOID_t owner = *reinterpret_cast<SOID_t*>(inventory + 0x10);
reinterpret_cast<SOCreatedFn>((*reinterpret_cast<void***>(inventory))[0])(inventory, owner, item, 0);
reinterpret_cast<SOCreatedFn>((*reinterpret_cast<void***>(inventory))[0])(reinterpret_cast<void*>(inventory), owner, item, 0);

bool equipped = EquipItemInLoadout(manager, team, slot, fake_id);
if (equipped) {
Expand Down Expand Up @@ -359,8 +359,8 @@ void SkinChanger::ApplyGloves(uintptr_t inventory, uintptr_t pawn, uintptr_t vie
// SetMeshGroupMask on viewmodel
if (s_fn_set_mesh_group_mask) {
using Fn = void(__fastcall*)(uintptr_t, uint32_t);
__try { ((Fn)s_fn_set_mesh_group_mask)(view_model, 1); }
__except (EXCEPTION_EXECUTE_HANDLER) {}
try { ((Fn)s_fn_set_mesh_group_mask)(view_model, 1); }
catch (...) {}
Comment on lines +362 to +363

Copilot AI Apr 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C++ catch(...) here won’t catch access violations from the engine call unless you compile with /EHa or install an SEH-to-C++ translator. This code previously used SEH to prevent crashes; consider reverting to __try/__except around this external call boundary.

Suggested change
try { ((Fn)s_fn_set_mesh_group_mask)(view_model, 1); }
catch (...) {}
__try {
((Fn)s_fn_set_mesh_group_mask)(view_model, 1);
} __except (EXCEPTION_EXECUTE_HANDLER) {
}

Copilot uses AI. Check for mistakes.
}
}
}
Expand Down Expand Up @@ -487,24 +487,24 @@ void SkinChanger::OnFrameStageNotify(int stage) {
const DumpedItemDef* def = ItemDatabase::FindByDefIndex(target_def);
if (def && !def->model_path.empty()) {
using SetModelFn = void(__fastcall*)(uintptr_t, const char*);
__try {
try {
((SetModelFn)s_fn_set_model)(entity, def->model_path.c_str());
if (view_model)
((SetModelFn)s_fn_set_model)(view_model, def->model_path.c_str());
}
__except (EXCEPTION_EXECUTE_HANDLER) {}
catch (...) {}
}
Comment on lines 489 to 496

Copilot AI Apr 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The try/catch guard around this scanned function pointer call will not catch SEH exceptions (e.g., AV) with the current build configuration, so it won’t prevent hard crashes. Please restore __try/__except (or add /EHa / SEH translation) for these engine boundary calls.

Copilot uses AI. Check for mistakes.
}

// MeshGroupMask
if (s_fn_set_mesh_group_mask) {
using Fn = void(__fastcall*)(uintptr_t, uint32_t);
__try {
try {
((Fn)s_fn_set_mesh_group_mask)(entity, 2);
if (view_model)
((Fn)s_fn_set_mesh_group_mask)(view_model, 2);
}
__except (EXCEPTION_EXECUTE_HANDLER) {}
catch (...) {}
Comment on lines 499 to +507

Copilot AI Apr 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above: catch(...) won’t intercept structured exceptions from this engine call without /EHa or an SEH translator, so the crash-guard behavior has effectively been removed. Consider reverting to __try/__except around this call site.

Copilot uses AI. Check for mistakes.
}
continue;
}
Expand All @@ -529,7 +529,7 @@ void SkinChanger::OnFrameStageNotify(int stage) {
if (!skin.custom_name.empty()) {
char* name_ptr = reinterpret_cast<char*>(item_view + offsets::C_EconItemView::m_szCustomName);
std::memset(name_ptr, 0, 161);
std::strncpy(name_ptr, skin.custom_name.c_str(), 160);
strncpy_s(name_ptr, 161, skin.custom_name.c_str(), 160);
}

// MeshGroupMask — 2 for legacy, 1 for CS2-native
Expand All @@ -539,15 +539,15 @@ void SkinChanger::OnFrameStageNotify(int stage) {
if (const DumpedPaintKit* pk = ItemDatabase::FindPaintKit(skin.paint_kit_id))
legacy = pk->is_legacy;
uint32_t mask = legacy ? 2 : 1;
__try {
try {
((Fn)s_fn_set_mesh_group_mask)(entity, mask);
if (view_model) {
uint32_t vm_weapon = *reinterpret_cast<uint32_t*>(view_model + 0x38);
if (vm_weapon == static_cast<uint32_t>(i))
((Fn)s_fn_set_mesh_group_mask)(view_model, mask);
}
}
__except (EXCEPTION_EXECUTE_HANDLER) {}
catch (...) {}
}
}
}
Expand All @@ -557,7 +557,7 @@ void SkinChanger::OnSetModel(void* entity, const char*& model_path) {
if (!s_knife.enabled) return;

// Redirect viewmodel model to custom knife model
const DumpedItemDef* def = ItemDatabase::FindByDefIndex(s_knife.defIndex);
const DumpedItemDef* def = ItemDatabase::FindByDefIndex(s_knife.def_index);
if (def && !def->model_path.empty()) {
model_path = def->model_path.c_str();
}
Expand Down
2 changes: 2 additions & 0 deletions src/features/skinchanger/skinchanger.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ class SkinChanger {
static GloveInfo s_added_gloves;

static uintptr_t s_client_base;

public:
static uintptr_t s_fn_create_econ_item;
static uintptr_t s_fn_set_dynamic_attr;
static uintptr_t s_fn_get_econ_item_system;
Expand Down
12 changes: 7 additions & 5 deletions src/sdk/inventory.hpp
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
#pragma once

#include <Windows.h>
#include <windows.h>
#include <cstdint>
#include <cstddef>
#include <type_traits>
#include <utility>

namespace cs2 {

template<typename T>
inline T CallVFunc(void* thisptr, size_t index, auto&&... args) {
using Fn = T(__thiscall*)(void*, decltype(args)...);
return (*reinterpret_cast<Fn**>(thisptr))[index](thisptr, args...);
template<typename T, size_t Index, typename... Args>
inline T CallVFunc(void* thisptr, Args&&... args) {
using Fn = T(__thiscall*)(void*, std::remove_reference_t<Args>...);
return (*reinterpret_cast<Fn**>(thisptr))[Index](thisptr, std::forward<Args>(args)...);
Comment on lines +13 to +14

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The use of std::remove_reference_t<Args>... in the function pointer signature strips reference qualifiers. If a virtual function expects a reference (e.g., const Vector&), the Fn type will incorrectly be defined as taking the object by value, leading to stack corruption or incorrect behavior. Using Args... directly in the function pointer signature is more appropriate as it preserves the value category (lvalue/rvalue) of the arguments passed to the template.

    using Fn = T(__thiscall*)(void*, Args...);
    return (*reinterpret_cast<Fn**>(thisptr))[Index](thisptr, std::forward<Args>(args)...);

}

struct SOID_t {
Expand Down
Loading