diff --git a/README.md b/README.md index ba3f9f7..bf80207 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,6 @@ SmokeAPI does not require any manual configuration. By default, it uses the most |-------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------|:-------:| | `$version` | A technical field reserved for future use by tools like GUI config editors | Integer | `1` | | `logging` | Toggles generation of `*.log` file | Boolean | `false` | -| `hook_steamclient` | When installed in hook mode, this option toggles between hooking steamclient(64).dll and steam_api(64).dll | Boolean | `true` | | `unlock_all` | Toggles whether all DLCs should be unlocked by default | Boolean | `true` | | `override` | When `unlock_all` is `true`, this option serves as a blacklist of DLC IDs, which should remain locked. When `unlock_all` is `false`, this option serves as a whitelist of DLC IDs, which should become unlocked | List of Integers | `[]` | | `dlc_ids` | When game requests list of all DLCs from Steam and the number of registered DLCs is greater than 64, Steam may not return all of them. In this case, SmokeAPI will fetch all released DLCs from Web API. In some games, however (like Monster Hunter: World), web api also doesn't return all possible DLCs. To address this issue, you can specify the missing DLC IDsยน in this option. For some games (including MH:W), however, it is not necessary because SmokeAPI will also automatically fetch a [manually maintained list of DLC IDs] that are missing from web api | List of Integers | `[]` | diff --git a/res/SmokeAPI.json b/res/SmokeAPI.json index cd9000e..51703dd 100644 --- a/res/SmokeAPI.json +++ b/res/SmokeAPI.json @@ -1,7 +1,6 @@ { - "$version": 1, + "$version": 2, "logging": true, - "hook_steamclient": true, "unlock_all": true, "override": [], "dlc_ids": [], diff --git a/src/smoke_api/smoke_api.cpp b/src/smoke_api/smoke_api.cpp index 2e59dc7..eaad4ad 100644 --- a/src/smoke_api/smoke_api.cpp +++ b/src/smoke_api/smoke_api.cpp @@ -24,6 +24,65 @@ namespace smoke_api { Path self_directory; + void init_koalageddon_mode() { +#ifndef _WIN64 + logger->info("๐Ÿจ Detected Koalageddon mode ๐Ÿ’ฅ"); + + dll_monitor::init({VSTDLIB_DLL, STEAMCLIENT_DLL}, [](const HMODULE& library, const String& name) { + original_library = library; // TODO: Is this necessary? + + if (name == VSTDLIB_DLL) { + // Family Sharing functions + DETOUR(Coroutine_Create) + } else if (name == STEAMCLIENT_DLL) { + // Unlocking functions + // TODO: Un-hardcode the pattern + const String pattern("55 8B EC 8B ?? ?? ?? ?? ?? 81 EC ?? ?? ?? ?? 53 FF 15"); + auto Log_Interface_address = (FunctionAddress) patcher::find_pattern_address( + win_util::get_module_info(library), "Log_Interface", pattern + ); + if (Log_Interface_address) { + DETOUR_EX(Log_Interface, Log_Interface_address) + } + } + }); +#endif + } + + void init_proxy_mode() { + logger->info("๐Ÿ”€ Detected proxy mode"); + + original_library = loader::load_original_library(self_directory, ORIGINAL_DLL); + } + + void init_hook_mode() { + logger->info("๐Ÿช Detected hook mode"); + + dll_monitor::init(STEAMCLIENT_DLL, [](const HMODULE& library) { + original_library = library; + + DETOUR(CreateInterface) + }); + + // Hooking steam_api has show itself to be less desirable than steamclient + // for the reasons outlined below: + // + // Calling original in flat functions will actually call the hooked functions + // because the original function redirects the execution to a function taken + // from self pointer, which would have been hooked by SteamInternal_*Interface + // functions. + // + // Furthermore, turns out that many flat functions share the same body, + // which looks like the following snippet: + // + // mov rax, qword ptr ds:[rcx] + // jmp qword ptr ds:[rax+immediate] + // + // This means that we end up inadvertently hooking unintended functions. + // Given that hooking steam_api has no apparent benefits, but has inherent flaws, + // the support for it has been dropped from this project. + } + void init(HMODULE self_module) { try { DisableThreadLibraryCalls(self_module); @@ -51,68 +110,13 @@ namespace smoke_api { if (is_hook_mode) { hook::init(true); - if (util::strings_are_equal(exe_name, "steam.exe")) { -#ifndef _WIN64 - logger->info("๐Ÿจ Detected Koalageddon mode ๐Ÿ’ฅ"); - - dll_monitor::init({VSTDLIB_DLL, STEAMCLIENT_DLL}, [](const HMODULE& library, const String& name) { - original_library = library; // TODO: Is this necessary? - - if (name == VSTDLIB_DLL) { - // Family Sharing functions - DETOUR(Coroutine_Create) - } else if (name == STEAMCLIENT_DLL) { - // Unlocking functions - // TODO: Un-hardcode the pattern - const String pattern("55 8B EC 8B ?? ?? ?? ?? ?? 81 EC ?? ?? ?? ?? 53 FF 15"); - auto Log_Interface_address = (FunctionAddress) patcher::find_pattern_address( - win_util::get_module_info(library), "Log_Interface", pattern - ); - if (Log_Interface_address) { - DETOUR_EX(Log_Interface, Log_Interface_address) - } - } - }); -#endif - } else if (config.hook_steamclient) { // target steamclient(64).dll - logger->info("๐Ÿช Detected hook mode for SteamClient"); - - dll_monitor::init(STEAMCLIENT_DLL, [](const HMODULE& library) { - original_library = library; - - DETOUR(CreateInterface) - }); - } else { // target steam_api.dll - logger->info("๐Ÿช Detected hook mode for Steam_API"); - - dll_monitor::init(ORIGINAL_DLL, [](const HMODULE& library) { - original_library = library; - - DETOUR(SteamInternal_FindOrCreateUserInterface) - DETOUR(SteamInternal_CreateInterface) - DETOUR(SteamApps) - DETOUR(SteamClient) - DETOUR(SteamUser) - - DETOUR(SteamAPI_ISteamApps_BIsSubscribedApp) - DETOUR(SteamAPI_ISteamApps_BIsDlcInstalled) - DETOUR(SteamAPI_ISteamApps_GetDLCCount) - DETOUR(SteamAPI_ISteamApps_BGetDLCDataByIndex) - DETOUR(SteamAPI_ISteamClient_GetISteamGenericInterface) - - DETOUR(SteamAPI_ISteamInventory_GetResultStatus) - DETOUR(SteamAPI_ISteamInventory_GetResultItems) - DETOUR(SteamAPI_ISteamInventory_GetResultItemProperty) - DETOUR(SteamAPI_ISteamInventory_CheckResultSteamID) - DETOUR(SteamAPI_ISteamInventory_GetAllItems) - DETOUR(SteamAPI_ISteamInventory_GetItemsByID) - DETOUR(SteamAPI_ISteamInventory_GetItemDefinitionIDs) - }); + if (util::strings_are_equal(exe_name, "steam.exe") && !util::is_x64()) { + init_koalageddon_mode(); + } else { + init_hook_mode(); } } else { - logger->info("๐Ÿ”€ Detected proxy mode for Steam_API"); - - original_library = loader::load_original_library(self_directory, ORIGINAL_DLL); + init_proxy_mode(); } logger->info("๐Ÿš€ Initialization complete"); } catch (const Exception& ex) { diff --git a/src/smoke_api/smoke_api.hpp b/src/smoke_api/smoke_api.hpp index 3aabdaf..39a615f 100644 --- a/src/smoke_api/smoke_api.hpp +++ b/src/smoke_api/smoke_api.hpp @@ -27,7 +27,6 @@ namespace smoke_api { struct Config { uint32_t $version = 1; bool logging = false; - bool hook_steamclient = true; bool unlock_all = true; Set override; Vector dlc_ids; @@ -35,10 +34,9 @@ namespace smoke_api { Vector inventory_items; NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( - Config, $version, + Config, $version, // NOLINT(misc-const-correctness) logging, unlock_all, - hook_steamclient, override, dlc_ids, auto_inject_inventory, diff --git a/src/steam_impl/steam_apps.cpp b/src/steam_impl/steam_apps.cpp index 63a6a6f..b9dce54 100644 --- a/src/steam_impl/steam_apps.cpp +++ b/src/steam_impl/steam_apps.cpp @@ -159,18 +159,18 @@ namespace steam_apps { return count; }; - if (app_id) { + if (app_id != 0) { logger->debug("{} -> App ID: {}", function_name, app_id); } - // Compute count only once // FIXME: This doesn't work in Koalageddon mode + // Compute count only once + // FIXME: This doesn't work in Koalageddon mode original_dlc_count = original_function(); logger->debug("{} -> Original DLC count: {}", function_name, original_dlc_count); const auto injected_count = static_cast(config.dlc_ids.size()); logger->debug("{} -> Injected DLC count: {}", function_name, injected_count); - if (original_dlc_count < max_dlc) { return total_count(original_dlc_count + injected_count); } diff --git a/src/steamclient_virtuals/client_user.cpp b/src/steamclient_virtuals/client_user.cpp index 776c04b..8a7f2a2 100644 --- a/src/steamclient_virtuals/client_user.cpp +++ b/src/steamclient_virtuals/client_user.cpp @@ -3,6 +3,7 @@ using namespace smoke_api; +// TODO: Implement? VIRTUAL(bool) IClientUser_IsSubscribedApp(PARAMS(AppId_t app_id)) { // NOLINT(misc-unused-parameters) return steam_apps::IsDlcUnlocked(__func__, 0, app_id); }