8 Commits

Author SHA1 Message Date
acidicoala
53d28ee65d Fixed flat functions 2022-05-30 13:48:18 +03:00
acidicoala
5b76d155a8 Added IClientApps, IClientAppManager, IClientUser 2022-05-29 16:57:34 +03:00
acidicoala
be9fa39508 Fix 64-bit build 2022-05-29 13:36:19 +03:00
acidicoala
5d1abc6498 [WIP] Koalageddon mode 2022-05-29 04:45:22 +03:00
acidicoala
5afdd59044 Fixed crashing at legacy versions 2022-05-20 22:04:01 +03:00
acidicoala
179be28097 Invalidate cmake cache 2022-05-14 16:18:21 +03:00
acidicoala
08290d3559 Updated README 2022-05-14 15:22:29 +03:00
acidicoala
1c9270676d Added link to forum topic 2022-05-10 15:45:47 +03:00
31 changed files with 234 additions and 98 deletions

View File

@@ -4,7 +4,7 @@ on: push
jobs:
ci:
name: CI
uses: acidicoala/KoalaBox/.github/workflows/build-and-package.yml@1f09cc8e2efa189573acb6958e6419598340797a
uses: acidicoala/KoalaBox/.github/workflows/build-and-package.yml@d9b0d1a00beb065a9a931a60ef615ce8d1fc7164
permissions:
contents: write
with:

1
.idea/.gitignore generated vendored
View File

@@ -3,3 +3,4 @@
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
vcs.xml

22
.idea/cmake.xml generated
View File

@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CMakeSharedSettings">
<configurations>
<configuration PROFILE_NAME="Debug [32]" ENABLED="true" GENERATION_DIR="build/32" CONFIG_NAME="Debug" TOOLCHAIN_NAME="Visual Studio 2022 [x86]" GENERATION_OPTIONS="-G &quot;Visual Studio 17 2022&quot; -A Win32">
<ADDITIONAL_GENERATION_ENVIRONMENT>
<envs>
<env name="VERSION_SUFFIX" value="-debug" />
</envs>
</ADDITIONAL_GENERATION_ENVIRONMENT>
</configuration>
<configuration PROFILE_NAME="Debug [64]" ENABLED="true" GENERATION_DIR="build/64" CONFIG_NAME="Debug" TOOLCHAIN_NAME="Visual Studio 2022 [amd64]" GENERATION_OPTIONS="-G &quot;Visual Studio 17 2022&quot; -A x64">
<ADDITIONAL_GENERATION_ENVIRONMENT>
<envs>
<env name="VERSION_SUFFIX" value="-debug" />
</envs>
</ADDITIONAL_GENERATION_ENVIRONMENT>
</configuration>
<configuration PROFILE_NAME="Relase [64]" ENABLED="true" GENERATION_DIR="build/64/release" CONFIG_NAME="Release" TOOLCHAIN_NAME="Visual Studio 2022 [amd64]" GENERATION_OPTIONS="-G &quot;Visual Studio 17 2022&quot; -A x64" />
</configurations>
</component>
</project>

7
.idea/vcs.xml generated
View File

@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
<mapping directory="$PROJECT_DIR$/KoalaBox" vcs="Git" />
</component>
</project>

View File

@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.22)
project(SmokeAPI VERSION 1.0.0)
project(SmokeAPI VERSION 1.0.2)
include(KoalaBox/cmake/KoalaBox.cmake)
@@ -53,11 +53,23 @@ set(
src/steam_functions/steam_functions.hpp
src/steam_types/steam_types.hpp
src/steamclient_exports/steamclient.cpp
src/vstdlib/vstdlib.cpp
src/main.cpp
${GENERATED_LINKER_EXPORTS}
)
if (CMAKE_SIZEOF_VOID_P EQUAL 4)
set(
SMOKE_API_SOURCES ${SMOKE_API_SOURCES}
src/koalageddon/vstdlib.cpp
src/koalageddon/steamclient.cpp
src/steamclient_virtuals/client_app_manager.cpp
src/steamclient_virtuals/client_apps.cpp
src/steamclient_virtuals/client_inventory.cpp
src/steamclient_virtuals/client_user.cpp
src/steamclient_virtuals/client_utils.cpp
)
endif ()
add_library(SmokeAPI SHARED ${SMOKE_API_SOURCES} ${VERSION_RESOURCE})
configure_output_name(${ORIGINAL_DLL})
@@ -65,3 +77,4 @@ configure_output_name(${ORIGINAL_DLL})
configure_include_directories()
target_link_libraries(SmokeAPI PRIVATE KoalaBox)

View File

@@ -12,7 +12,7 @@ ___
📥 [Download the latest release](https://github.com/acidicoala/SmokeAPI/releases/latest)
💬 [Official forum topic]()
💬 [Official forum topic](https://cs.rin.ru/forum/viewtopic.php?p=2597932#p2597932)
## Introduction
@@ -87,6 +87,14 @@ SmokeAPI does not require any manual configuration. By default, it uses the most
[SmokeAPI.json]: res/SmokeAPI.json
[manually maintained list of DLC IDs]: https://github.com/acidicoala/public-entitlements/blob/main/steam/v1/dlc.json
## Extra info
### How SmokeAPI works in games with large number of DLCs
Some games that have a lot of DLCs begin ownership verification by querying the Steamworks API for a list of all available DLCs. Once the game receives the list, it will go over each item and check the ownership. The issue arises from the fact that response from Steamworks SDK may max out at 64, depending on how much unowned DLC the user has. To alleviate this issue, SmokeAPI will make a web request to Steam API for a full list of DLCs, which works well most of the time. Unfortunately, even the web API does not solve all of our problems, because it will only return DLCs that are available in Steam store. This means that DLCs without a dedicated store offer, such as pre-order DLCs will be left out. That's where the `dlc_ids` config option comes into play. You can specify those missing DLC IDs there, and SmokeAPI will make them available to the game. However, this introduces the need for manual configuration, which goes against the ideals of this project. To remedy this issue SmokeAPI will also fetch [this document] stored in a GitHub repository. It contains all the DLC IDs missing from Steam store. The document is hand-crafted using data from https://steamdb.com. This enables SmokeAPI to unlock all DLCs without any config file at all. Feel free to report games that have more than 64 DLCs, *and* have DLCs without a dedicated store page. They will be added to the list of missing DLC IDs to facilitate configless operation.
[this document]: https://github.com/acidicoala/public-entitlements/blob/main/steam/v1/dlc.json
## 👋 Acknowledgements
SmokeAPI makes use of the following open source projects:

View File

@@ -0,0 +1,58 @@
#include <smoke_api/smoke_api.hpp>
#include <steam_functions/steam_functions.hpp>
#include <koalabox/hook.hpp>
using namespace smoke_api;
DLL_EXPORT(void) Log_Interface(const char* interface_name, const char* function_name) {
try {
void**** parent_ebp;
__asm mov parent_ebp, ebp
auto* interface_address = (*parent_ebp)[2];
static Set<String> hooked_functions;
auto hook_function = [&](const auto hook_function, const String& name, const int ordinal) {
if (hooked_functions.contains(name)) {
return;
}
hook::swap_virtual_func_or_throw(interface_address, name, ordinal, (FunctionAddress) (hook_function));
hooked_functions.insert(name);
};
const auto compound_name = interface_name + String("::") + function_name;
#define HOOK(FUNC, ORDINAL) hook_function(FUNC, #FUNC, ORDINAL);
if (compound_name == "IClientAppManager::IsAppDlcInstalled") {
HOOK(IClientAppManager_IsAppDlcInstalled, 8)
} else if (compound_name == "IClientApps::GetDLCCount") {
HOOK(IClientApps_GetDLCCount, 8)
} else if (compound_name == "IClientApps::BGetDLCDataByIndex") {
HOOK(IClientApps_BGetDLCDataByIndex, 9)
} else if (compound_name == "IClientUser::IsSubscribedApp") {
HOOK(IClientUser_IsSubscribedApp, 0xB5)
} else if (util::strings_are_equal(interface_name, "IClientInventory")) {
if (util::strings_are_equal(function_name, "GetResultItems")) {
auto* function_address = interface_address[0x2]; // TODO: Un-hardcode
logger->debug("{} -> {}::{} @ {}", __func__, interface_name, function_name, function_address);
}
} else if (util::strings_are_equal(interface_name, "IClientUtils")) {
if (util::strings_are_equal(function_name, "GetAppID")) {
auto* function_address = interface_address[0x12]; // TODO: Un-hardcode
logger->debug("{} -> {}::{} @ {}", __func__, interface_name, function_name, function_address);
}
}
GET_ORIGINAL_FUNCTION(Log_Interface)
Log_Interface_o(interface_name, function_name);
} catch (const Exception& ex) {
logger->error("{} -> Error: {}", __func__, ex.what());
}
}

View File

@@ -19,15 +19,15 @@ VIRTUAL(bool) SharedLibraryStopPlaying(PARAMS(void* arg)) { // NOLINT(misc-unuse
struct CallbackData {
[[maybe_unused]] void* pad1[1];
void* set_callback_name_address;
[[maybe_unused]] void* pad15[15];
void* callback_address;
void* set_callback_name_address; // to_do: fetch online
[[maybe_unused]] void* pad19[17];
void* callback_address; // to_do: fetch online
};
struct CoroutineData {
CallbackData* callback_data;
[[maybe_unused]] uint32_t pad3[3];
const char* callback_name;
CallbackData* callback_data; // to_do: fetch online
[[maybe_unused]] uint32_t pad4[3];
const char* callback_name; // to_do: fetch online
};
VIRTUAL(void) set_callback_name(PARAMS(const char** p_name)) {

View File

@@ -2,13 +2,17 @@
#include <steam_functions/steam_functions.hpp>
#include <build_config.h>
#include <koalabox/loader.hpp>
#include <koalabox/config_parser.hpp>
#include <koalabox/file_logger.hpp>
#include <koalabox/win_util.hpp>
#include <koalabox/hook.hpp>
#include <koalabox/dll_monitor.hpp>
#include <koalabox/file_logger.hpp>
#include <koalabox/hook.hpp>
#include <koalabox/loader.hpp>
#include <koalabox/win_util.hpp>
#ifndef _WIN64
#include <koalabox/patcher.hpp>
#endif
#define DETOUR_EX(FUNC, ADDRESS) hook::detour_or_warn(ADDRESS, #FUNC, reinterpret_cast<FunctionAddress>(FUNC));
#define DETOUR(FUNC) hook::detour_or_warn(original_library, #FUNC, reinterpret_cast<FunctionAddress>(FUNC));
namespace smoke_api {
@@ -47,14 +51,29 @@ namespace smoke_api {
if (is_hook_mode) {
hook::init(true);
if (util::strings_are_equal(exe_name, "steam.exe")) { // target vstdlib_s.dll
if (util::strings_are_equal(exe_name, "steam.exe")) {
#ifndef _WIN64
logger->info("🐨 Detected Koalageddon mode 💥");
dll_monitor::init(VSTDLIB_DLL, [](const HMODULE& library) {
original_library = library;
dll_monitor::init({VSTDLIB_DLL, STEAMCLIENT_DLL}, [](const HMODULE& library, const String& name) {
original_library = library; // TODO: Is this necessary?
DETOUR(Coroutine_Create)
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");
@@ -116,8 +135,8 @@ namespace smoke_api {
}
}
bool should_unlock(uint32_t appId) {
return config.unlock_all != config.override.contains(appId);
bool should_unlock(uint32_t app_id) {
return config.unlock_all != config.override.contains(app_id);
}
}

View File

@@ -58,6 +58,6 @@ namespace smoke_api {
void shutdown();
bool should_unlock(uint32_t appId);
bool should_unlock(uint32_t app_id);
}

View File

@@ -6,15 +6,15 @@ using namespace smoke_api;
// ISteamApps
DLL_EXPORT(bool) SteamAPI_ISteamApps_BIsSubscribedApp(ISteamApps*, AppId_t appID) {
return steam_apps::IsSubscribedApp(__func__, appID);
return steam_apps::IsDlcUnlocked(__func__, 0, appID);
}
DLL_EXPORT(bool) SteamAPI_ISteamApps_BIsDlcInstalled(ISteamApps*, AppId_t appID) {
return steam_apps::IsDlcInstalled(__func__, appID);
return steam_apps::IsDlcUnlocked(__func__, 0, appID);
}
DLL_EXPORT(int) SteamAPI_ISteamApps_GetDLCCount(ISteamApps* self) {
return steam_apps::GetDLCCount(__func__, [&]() {
return steam_apps::GetDLCCount(__func__, 0, [&]() {
GET_ORIGINAL_FUNCTION(SteamAPI_ISteamApps_GetDLCCount)
return SteamAPI_ISteamApps_GetDLCCount_o(self);
@@ -29,7 +29,7 @@ DLL_EXPORT(bool) SteamAPI_ISteamApps_BGetDLCDataByIndex(
char* pchName,
int cchNameBufferSize
) {
return steam_apps::GetDLCDataByIndex(__func__, iDLC, pAppID, pbAvailable, pchName, cchNameBufferSize, [&]() {
return steam_apps::GetDLCDataByIndex(__func__, 0, iDLC, pAppID, pbAvailable, pchName, cchNameBufferSize, [&]() {
GET_ORIGINAL_FUNCTION(SteamAPI_ISteamApps_BGetDLCDataByIndex)
return SteamAPI_ISteamApps_BGetDLCDataByIndex_o(
@@ -111,7 +111,7 @@ DLL_EXPORT(bool) SteamAPI_ISteamInventory_GetResultItemProperty(
) {
return steam_inventory::GetResultItemProperty(
__func__, resultHandle, unItemIndex, pchPropertyName, pchValueBuffer, punValueBufferSizeOut, [&]() {
GET_ORIGINAL_VIRTUAL_FUNCTION(SteamAPI_ISteamInventory_GetResultItemProperty)
GET_ORIGINAL_FUNCTION(SteamAPI_ISteamInventory_GetResultItemProperty)
return SteamAPI_ISteamInventory_GetResultItemProperty_o(
self, resultHandle, unItemIndex, pchPropertyName, pchValueBuffer, punValueBufferSizeOut
@@ -126,7 +126,7 @@ DLL_EXPORT(bool) SteamAPI_ISteamInventory_CheckResultSteamID(
CSteamID steamIDExpected
) {
return steam_inventory::CheckResultSteamID(__func__, resultHandle, steamIDExpected, [&]() {
GET_ORIGINAL_VIRTUAL_FUNCTION(SteamAPI_ISteamInventory_CheckResultSteamID)
GET_ORIGINAL_FUNCTION(SteamAPI_ISteamInventory_CheckResultSteamID)
return SteamAPI_ISteamInventory_CheckResultSteamID_o(self, resultHandle, steamIDExpected);
});

View File

@@ -4,15 +4,15 @@
using namespace smoke_api;
VIRTUAL(bool) ISteamApps_BIsSubscribedApp(PARAMS(AppId_t appID)) { // NOLINT(misc-unused-parameters)
return steam_apps::IsSubscribedApp(__func__, appID);
return steam_apps::IsDlcUnlocked(__func__, 0, appID);
}
VIRTUAL(bool) ISteamApps_BIsDlcInstalled(PARAMS(AppId_t appID)) { // NOLINT(misc-unused-parameters)
return steam_apps::IsDlcInstalled(__func__, appID);
return steam_apps::IsDlcUnlocked(__func__, 0, appID);
}
VIRTUAL(int) ISteamApps_GetDLCCount(PARAMS()) {
return steam_apps::GetDLCCount(__func__, [&]() {
return steam_apps::GetDLCCount(__func__, 0, [&]() {
GET_ORIGINAL_VIRTUAL_FUNCTION(ISteamApps_GetDLCCount)
return ISteamApps_GetDLCCount_o(ARGS());
@@ -28,7 +28,7 @@ VIRTUAL(bool) ISteamApps_BGetDLCDataByIndex(
int cchNameBufferSize
)
) {
return steam_apps::GetDLCDataByIndex(__func__, iDLC, pAppID, pbAvailable, pchName, cchNameBufferSize, [&]() {
return steam_apps::GetDLCDataByIndex(__func__, 0, iDLC, pAppID, pbAvailable, pchName, cchNameBufferSize, [&]() {
GET_ORIGINAL_VIRTUAL_FUNCTION(ISteamApps_BGetDLCDataByIndex)
return ISteamApps_BGetDLCDataByIndex_o(

View File

@@ -90,7 +90,7 @@ namespace steam_functions {
const auto version_number = stoi(version_string.substr(prefix.length()));
if (version_number < min_version) {
util::panic("Unsupported old version of {}: {}", version_string, version_number);
logger->warn("Legacy version of {}: {}", version_string, version_number);
}
if (version_number > max_version) {

View File

@@ -118,9 +118,20 @@ DLL_EXPORT(bool) SteamAPI_ISteamInventory_GetItemDefinitionProperty(
ISteamInventory*, SteamItemDef_t, const char*, char*, uint32_t*
);
// vstdlib
// koalageddon
DLL_EXPORT(HCoroutine) Coroutine_Create(void* callback_address, struct CoroutineData* data);
DLL_EXPORT(void) Log_Interface(const char* interface_name, const char* function_name);
// IClientApps
VIRTUAL(int) IClientApps_GetDLCCount(PARAMS(AppId_t));
VIRTUAL(bool) IClientApps_BGetDLCDataByIndex(PARAMS(AppId_t, int, AppId_t*, bool*, char*, int));
// IClientAppManager
VIRTUAL(bool) IClientAppManager_IsAppDlcInstalled(PARAMS(AppId_t, AppId_t));
// IClientUser
VIRTUAL(bool) IClientUser_IsSubscribedApp(PARAMS(AppId_t));
namespace steam_functions {
using namespace koalabox;

View File

@@ -51,14 +51,15 @@ void save_to_cache(const String& app_id_str) {
}
}
void fetch_and_cache_dlcs() {
uint32_t app_id;
try {
app_id = steam_functions::get_app_id_or_throw();
logger->info("Detected App ID: {}", app_id);
} catch (const Exception& ex) {
logger->error("Failed to get app ID: {}", ex.what());
return;
void fetch_and_cache_dlcs(AppId_t app_id) {
if (not app_id) {
try {
app_id = steam_functions::get_app_id_or_throw();
logger->info("Detected App ID: {}", app_id);
} catch (const Exception& ex) {
logger->error("Failed to get app ID: {}", ex.what());
return;
}
}
const auto app_id_str = std::to_string(app_id);
@@ -131,29 +132,31 @@ void fetch_and_cache_dlcs() {
}
}
String get_app_id_log(const AppId_t app_id) {
return app_id ? fmt::format("App ID: {}, ", app_id) : "";
}
namespace steam_apps{
namespace steam_apps {
bool IsSubscribedApp(const String& function_name, AppId_t appID) {
const auto subscribed = should_unlock(appID);
bool IsDlcUnlocked(const String& function_name, AppId_t app_id, AppId_t dlc_id) {
const auto app_id_unlocked = not app_id or should_unlock(app_id); // true if app_id == 0
const auto dlc_id_unlocked = should_unlock(dlc_id);
logger->info("{} -> App ID: {}, Subscribed: {}", function_name, appID, subscribed);
const auto installed = app_id_unlocked and dlc_id_unlocked;
return subscribed;
}
bool IsDlcInstalled(const String& function_name, AppId_t appID) {
const auto installed = should_unlock(appID);
logger->info("{} -> App ID: {}, Installed: {}", function_name, appID, installed);
logger->info("{} -> {}DLC ID: {}, Unlocked: {}", function_name, get_app_id_log(app_id), dlc_id, installed);
return installed;
}
int GetDLCCount(const String& function_name, const std::function<int()>& original_function) {
int GetDLCCount(const String& function_name, const AppId_t app_id, const std::function<int()>& original_function) {
static std::mutex section;
std::lock_guard<std::mutex> guard(section);
if (app_id) {
logger->debug("{} -> App ID: {}", function_name, app_id);
}
// Compute count only once
static int total_count = [&]() {
original_dlc_count = original_function();
@@ -171,7 +174,7 @@ namespace steam_apps{
}
logger->debug("Game has {} or more DLCs. Fetching DLCs from a web API.", max_dlc);
fetch_and_cache_dlcs();
fetch_and_cache_dlcs(app_id);
const auto fetched_count = static_cast<int>(cached_dlcs.size());
logger->debug("{} -> Fetched/cached DLC count: {}", function_name, fetched_count);
@@ -186,8 +189,9 @@ namespace steam_apps{
bool GetDLCDataByIndex(
const String& function_name,
AppId_t app_id,
int iDLC,
AppId_t* pAppID,
AppId_t* pDlcId,
bool* pbAvailable,
char* pchName,
int cchNameBufferSize,
@@ -195,13 +199,13 @@ namespace steam_apps{
) {
const auto print_dlc_info = [&](const String& tag) {
logger->info(
"{} -> [{}] index: {}, App ID: {}, available: {}, name: '{}'",
function_name, tag, iDLC, *pAppID, *pbAvailable, pchName
"{} -> [{}] {}index: {}, DLC ID: {}, available: {}, name: '{}'",
function_name, tag, get_app_id_log(app_id), iDLC, *pDlcId, *pbAvailable, pchName
);
};
const auto fill_dlc_info = [&](const AppId_t id) {
*pAppID = id;
*pDlcId = id;
*pbAvailable = should_unlock(id);
auto name = fmt::format("DLC #{} with ID: {} ", iDLC, id);
@@ -216,8 +220,8 @@ namespace steam_apps{
return false;
}
const auto app_id = config.dlc_ids[index];
fill_dlc_info(app_id);
const auto dlc_id = config.dlc_ids[index];
fill_dlc_info(dlc_id);
print_dlc_info("injected");
return true;
};
@@ -229,7 +233,7 @@ namespace steam_apps{
const auto success = original_function();
if (success) {
*pbAvailable = should_unlock(*pAppID);
*pbAvailable = should_unlock(*pDlcId);
print_dlc_info("original");
} else {
logger->warn("{} -> original function failed for index: {}", function_name, iDLC);
@@ -254,8 +258,8 @@ namespace steam_apps{
// Cached index
if (iDLC < cached_dlcs.size()) {
const auto app_id = cached_dlcs[iDLC];
fill_dlc_info(app_id);
const auto dlc_id = cached_dlcs[iDLC];
fill_dlc_info(dlc_id);
print_dlc_info("cached");
return true;
}

View File

@@ -4,16 +4,15 @@ using namespace koalabox;
namespace steam_apps {
bool IsSubscribedApp(const String& function_name, AppId_t appID);
bool IsDlcUnlocked(const String& function_name, AppId_t app_id, AppId_t dlc_id);
bool IsDlcInstalled(const String& function_name, AppId_t appID);
int GetDLCCount(const String& function_name, const std::function<int()>& original_function);
int GetDLCCount(const String& function_name, AppId_t app_id, const std::function<int()>& original_function);
bool GetDLCDataByIndex(
const String& function_name,
AppId_t app_id,
int iDLC,
AppId_t* pAppID,
AppId_t* pDlcId,
bool* pbAvailable,
char* pchName,
int cchNameBufferSize,

View File

@@ -0,0 +1,13 @@
#include <smoke_api/smoke_api.hpp>
#include <steam_impl/steam_impl.hpp>
using namespace smoke_api;
VIRTUAL(bool) IClientAppManager_IsAppDlcInstalled(
PARAMS( // NOLINT(misc-unused-parameters)
AppId_t app_id,
AppId_t dlc_id
)
) {
return steam_apps::IsDlcUnlocked(__func__, app_id, dlc_id);
}

View File

@@ -0,0 +1,31 @@
#include <smoke_api/smoke_api.hpp>
#include <steam_impl/steam_impl.hpp>
using namespace smoke_api;
VIRTUAL(int) IClientApps_GetDLCCount(PARAMS(AppId_t appId)) {
return steam_apps::GetDLCCount(__func__, appId, [&]() {
GET_ORIGINAL_VIRTUAL_FUNCTION(IClientApps_GetDLCCount)
return IClientApps_GetDLCCount_o(ARGS(appId));
});
}
VIRTUAL(bool) IClientApps_BGetDLCDataByIndex(
PARAMS(
AppId_t appID,
int iDLC,
AppId_t* pDlcID,
bool* pbAvailable,
char* pchName,
int cchNameBufferSize
)
) {
return steam_apps::GetDLCDataByIndex(__func__, appID, iDLC, pDlcID, pbAvailable, pchName, cchNameBufferSize, [&]() {
GET_ORIGINAL_VIRTUAL_FUNCTION(IClientApps_BGetDLCDataByIndex)
return IClientApps_BGetDLCDataByIndex_o(
ARGS(appID, iDLC, pDlcID, pbAvailable, pchName, cchNameBufferSize)
);
});
}

View File

@@ -0,0 +1,8 @@
#include <smoke_api/smoke_api.hpp>
#include <steam_impl/steam_impl.hpp>
using namespace smoke_api;
VIRTUAL(bool) IClientUser_IsSubscribedApp(PARAMS(AppId_t app_id)) { // NOLINT(misc-unused-parameters)
return steam_apps::IsDlcUnlocked(__func__, 0, app_id);
}