#include #include #include #include #include #include #include #include #include #if COMPILE_STORE_MODE #include #endif namespace steam_impl { typedef Map> FunctionOrdinalMap; FunctionOrdinalMap steam_client_ordinal_map = { // NOLINT(cert-err58-cpp) {"ISteamClient_GetISteamApps", { {6, 16}, {7, 18}, {8, 15}, {9, 16}, {12, 15}, } }, {"ISteamClient_GetISteamUser", { {6, 6}, {7, 5}, } }, {"ISteamClient_GetISteamUtils", { {6, 12}, {7, 9}, } }, {"ISteamClient_GetISteamGenericInterface", { {7, 14}, {8, 13}, {12, 12}, } }, {"ISteamClient_GetISteamInventory", { {17, 34}, {18, 35}, } } }; FunctionOrdinalMap steam_apps_ordinal_map = { // NOLINT(cert-err58-cpp) {"ISteamApps_BIsSubscribedApp", {{2, 6}}}, {"ISteamApps_BIsDlcInstalled", {{2, 7}}}, {"ISteamApps_GetDLCCount", {{2, 10}}}, {"ISteamApps_BGetDLCDataByIndex", {{2, 11}}}, }; FunctionOrdinalMap steam_user_ordinal_map = { // NOLINT(cert-err58-cpp) {"ISteamUser_UserHasLicenseForApp", { {12, 15}, {13, 16}, {15, 17}, }} }; FunctionOrdinalMap steam_utils_ordinal_map = { // NOLINT(cert-err58-cpp) {"ISteamUtils_GetAppID", {{2, 9}}} }; FunctionOrdinalMap steam_inventory_ordinal_map = { // NOLINT(cert-err58-cpp) {"ISteamInventory_GetResultStatus", {{1, 0}}}, {"ISteamInventory_GetResultItems", {{1, 1}}}, {"ISteamInventory_GetResultItemProperty", {{2, 2}}}, {"ISteamInventory_CheckResultSteamID", {{1, 3}, {2, 4}}}, {"ISteamInventory_GetAllItems", {{1, 5}, {2, 6}}}, {"ISteamInventory_GetItemsByID", {{1, 6}, {2, 7}}}, {"ISteamInventory_SerializeResult", {{1, 7}, {2, 8}}}, {"ISteamInventory_GetItemDefinitionIDs", {{1, 20}, {2, 21}}}, }; int extract_version_number( const String& version_string, const String& prefix, int min_version, int max_version ) { LOG_DEBUG("Hooking interface '{}'", version_string) try { const auto version_number = stoi(version_string.substr(prefix.length())); if (version_number < min_version) { LOG_WARN("Legacy version of {}: {}", version_string, version_number) } if (version_number > max_version) { LOG_WARN( "Unsupported new version of {}: {}. Fallback version {} will be used", version_string, version_number, max_version ) } return version_number; } catch (const std::exception& ex) { koalabox::util::panic("Failed to extract version number from: '{}'", version_string); } } int get_ordinal(const FunctionOrdinalMap& ordinal_map, const String& function_name, int interface_version) { const auto& map = ordinal_map.at(function_name); for (auto it = map.rbegin(); it != map.rend(); it++) { const auto [version, ordinal] = *it; if (interface_version >= version) { return ordinal; } } koalabox::util::panic("Invalid interface version ({}) for function {}", interface_version, function_name); } #define HOOK_VIRTUALS(MAP, FUNC) \ koalabox::hook::swap_virtual_func( \ globals::address_map, \ interface, \ #FUNC, \ get_ordinal(MAP, #FUNC, version_number), \ reinterpret_cast(FUNC) \ ); #define HOOK_STEAM_CLIENT(FUNC) HOOK_VIRTUALS(steam_client_ordinal_map, FUNC) #define HOOK_STEAM_APPS(FUNC) HOOK_VIRTUALS(steam_apps_ordinal_map, FUNC) #define HOOK_STEAM_USER(FUNC) HOOK_VIRTUALS(steam_user_ordinal_map, FUNC) #define HOOK_STEAM_INVENTORY(FUNC) HOOK_VIRTUALS(steam_inventory_ordinal_map, FUNC) void hook_virtuals(void* interface, const String& version_string) { if (interface == nullptr) { // Game has tried to use an interface before initializing steam api return; } static Set hooked_interfaces; if (hooked_interfaces.contains(interface)) { // This interface is already hooked. Skipping it. return; } static Mutex section; const MutexLockGuard guard(section); if (version_string.starts_with(STEAM_CLIENT)) { const auto version_number = extract_version_number(version_string, STEAM_CLIENT, 6, 20); HOOK_STEAM_CLIENT(ISteamClient_GetISteamApps) HOOK_STEAM_CLIENT(ISteamClient_GetISteamUser) if (version_number >= 7) { HOOK_STEAM_CLIENT(ISteamClient_GetISteamGenericInterface) } if (version_number >= 17) { HOOK_STEAM_CLIENT(ISteamClient_GetISteamInventory) } } else if (version_string.starts_with(STEAM_APPS)) { const auto version_number = extract_version_number(version_string, STEAM_APPS, 2, 8); HOOK_STEAM_APPS(ISteamApps_BIsSubscribedApp) if (version_number >= 3) { HOOK_STEAM_APPS(ISteamApps_BIsDlcInstalled) } if (version_number >= 4) { HOOK_STEAM_APPS(ISteamApps_GetDLCCount) HOOK_STEAM_APPS(ISteamApps_BGetDLCDataByIndex) } } else if (version_string.starts_with(STEAM_USER)) { const auto version_number = extract_version_number(version_string, STEAM_USER, 9, 21); if (version_number >= 12) { HOOK_STEAM_USER(ISteamUser_UserHasLicenseForApp) } } else if (version_string.starts_with(STEAM_INVENTORY)) { const auto version_number = extract_version_number(version_string, STEAM_INVENTORY, 1, 3); HOOK_STEAM_INVENTORY(ISteamInventory_GetResultStatus) HOOK_STEAM_INVENTORY(ISteamInventory_GetResultItems) HOOK_STEAM_INVENTORY(ISteamInventory_CheckResultSteamID) HOOK_STEAM_INVENTORY(ISteamInventory_GetAllItems) HOOK_STEAM_INVENTORY(ISteamInventory_GetItemsByID) HOOK_STEAM_INVENTORY(ISteamInventory_SerializeResult) HOOK_STEAM_INVENTORY(ISteamInventory_GetItemDefinitionIDs) if (version_number >= 2) { HOOK_STEAM_INVENTORY(ISteamInventory_GetResultItemProperty) } } else if (version_string.starts_with(CLIENT_ENGINE)) { #if COMPILE_STORE_MODE store::steamclient::process_client_engine(reinterpret_cast(interface)); #endif } else { return; } hooked_interfaces.insert(interface); } HSteamPipe get_steam_pipe_or_throw() { const auto& steam_api_module = koalabox::win_util::get_module_handle_or_throw(STEAMAPI_DLL); void* GetHSteamPipe_address; try { GetHSteamPipe_address = (void*) koalabox::win_util::get_proc_address_or_throw( steam_api_module, "SteamAPI_GetHSteamPipe" ); } catch (const Exception& ex) { GetHSteamPipe_address = (void*) koalabox::win_util::get_proc_address_or_throw( steam_api_module, "GetHSteamPipe" ); } typedef HSteamPipe (__cdecl* GetHSteamPipe_t)(); const auto GetHSteamPipe_o = (GetHSteamPipe_t) GetHSteamPipe_address; return GetHSteamPipe_o(); } template F get_virtual_function(void* interface, int ordinal) { auto* v_table = (void***) interface; return (F) (*v_table)[ordinal]; } template auto call_virtual_function(void* interface, F function, Args... args) { #ifdef _WIN64 void* RCX = interface; #else void* ECX = interface; void* EDX = nullptr; #endif return function(ARGS(args...)); } AppId_t get_app_id_or_throw() { // Get CreateInterface const auto& steam_client_module = koalabox::win_util::get_module_handle_or_throw(STEAMCLIENT_DLL); auto* CreateInterface_address = (void*) koalabox::win_util::get_proc_address_or_throw( steam_client_module, "CreateInterface" ); auto* CreateInterface_o = PLH::FnCast(CreateInterface_address, CreateInterface); // Get ISteamClient int result; auto* i_steam_client = CreateInterface_o("SteamClient006", &result); if (i_steam_client == nullptr) { throw koalabox::util::exception("Failed to obtain SteamClient006 interface. Result: {}", result); } // Get GetISteamUtils typedef void*** (__fastcall* GetISteamUtils_t)(PARAMS(HSteamPipe hSteamPipe, const char* version)); const auto steam_utils_ordinal = steam_client_ordinal_map["ISteamClient_GetISteamUtils"][6]; const auto GetISteamUtils_o = get_virtual_function(i_steam_client, steam_utils_ordinal); // Get ISteamUtils const auto steam_pipe = get_steam_pipe_or_throw(); auto* i_steam_utils = call_virtual_function(i_steam_client, GetISteamUtils_o, steam_pipe, "SteamUtils002"); // Get GetAppID typedef uint32_t (__fastcall* GetAppID_t)(PARAMS()); const auto get_app_id_ordinal = steam_utils_ordinal_map["ISteamUtils_GetAppID"][2]; const auto GetAppID_o = get_virtual_function(i_steam_utils, get_app_id_ordinal); return call_virtual_function(i_steam_utils, GetAppID_o); } }