From 4f93515bac40b025c2da61fc9679ac91e3ea65e7 Mon Sep 17 00:00:00 2001 From: acidicoala <67734819+acidicoala@users.noreply.github.com> Date: Fri, 13 Jan 2023 03:49:07 +0300 Subject: [PATCH] Renamed koalageddon mode to store mode --- .idea/misc.xml | 3 + CMakeLists.txt | 66 +++-- KoalaBox | 2 +- LICENSE.txt | 12 - README.adoc | 275 ++++++++++++++++++ README.md | 128 -------- UNLICENSE.txt | 24 ++ build.ps1 | 2 +- res/SmokeAPI.config.json | 6 +- src/{smoke_api => common}/app_cache.cpp | 2 +- src/{smoke_api => common}/app_cache.hpp | 0 .../steamclient_exports.cpp} | 1 + src/common/steamclient_exports.hpp | 3 + src/core/api.cpp | 15 +- src/core/api.hpp | 4 +- src/core/types.hpp | 12 +- .../exports}/steam_api_flat.cpp | 39 ++- .../exports}/steam_api_internal.cpp | 1 - .../exports}/steam_api_unversioned.cpp | 1 - .../virtuals}/isteamapps.cpp | 2 +- .../virtuals}/isteamclient.cpp | 2 +- .../virtuals}/isteaminventory.cpp | 2 +- .../virtuals}/isteamuser.cpp | 12 +- .../virtuals}/steam_api_virtuals.hpp | 0 src/koalageddon/kg_cache.cpp | 33 --- src/koalageddon/kg_cache.hpp | 11 - src/koalageddon/vstdlib.hpp | 27 -- src/smoke_api/config.cpp | 4 +- src/smoke_api/config.hpp | 7 +- src/smoke_api/smoke_api.cpp | 51 ++-- src/smoke_api/smoke_api.hpp | 1 + src/steam_api_exports/steam_api_exports.hpp | 36 --- src/steam_impl/steam_apps.cpp | 2 +- src/steam_impl/steam_impl.cpp | 15 +- src/steam_impl/steam_inventory.hpp | 1 - .../steamclient/client_app_manager.cpp | 1 + .../steamclient/client_apps.cpp | 4 +- .../steamclient/client_inventory.cpp | 2 +- .../steamclient/client_user.cpp | 4 +- .../steamclient/client_utils.cpp | 2 +- .../steamclient/steamclient.cpp | 10 +- .../steamclient/steamclient.hpp | 2 +- .../koalageddon.cpp => store_mode/store.cpp} | 51 ++-- .../koalageddon.hpp => store_mode/store.hpp} | 14 +- src/store_mode/store_api.cpp | 19 ++ src/store_mode/store_api.hpp | 10 + src/store_mode/store_cache.cpp | 32 ++ src/store_mode/store_cache.hpp | 11 + .../vstdlib}/vstdlib.cpp | 29 +- src/store_mode/vstdlib/vstdlib.hpp | 27 ++ 50 files changed, 582 insertions(+), 438 deletions(-) delete mode 100644 LICENSE.txt create mode 100644 README.adoc delete mode 100644 README.md create mode 100644 UNLICENSE.txt rename src/{smoke_api => common}/app_cache.cpp (97%) rename src/{smoke_api => common}/app_cache.hpp (100%) rename src/{steamclient_exports/steamclient.cpp => common/steamclient_exports.cpp} (89%) create mode 100644 src/common/steamclient_exports.hpp rename src/{steam_api_exports => game_mode/exports}/steam_api_flat.cpp (96%) rename src/{steam_api_exports => game_mode/exports}/steam_api_internal.cpp (93%) rename src/{steam_api_exports => game_mode/exports}/steam_api_unversioned.cpp (97%) rename src/{steam_api_virtuals => game_mode/virtuals}/isteamapps.cpp (96%) rename src/{steam_api_virtuals => game_mode/virtuals}/isteamclient.cpp (97%) rename src/{steam_api_virtuals => game_mode/virtuals}/isteaminventory.cpp (98%) rename src/{steam_api_virtuals => game_mode/virtuals}/isteamuser.cpp (55%) rename src/{steam_api_virtuals => game_mode/virtuals}/steam_api_virtuals.hpp (100%) delete mode 100644 src/koalageddon/kg_cache.cpp delete mode 100644 src/koalageddon/kg_cache.hpp delete mode 100644 src/koalageddon/vstdlib.hpp delete mode 100644 src/steam_api_exports/steam_api_exports.hpp rename src/{koalageddon => store_mode}/steamclient/client_app_manager.cpp (88%) rename src/{koalageddon => store_mode}/steamclient/client_apps.cpp (87%) rename src/{koalageddon => store_mode}/steamclient/client_inventory.cpp (98%) rename src/{koalageddon => store_mode}/steamclient/client_user.cpp (72%) rename src/{koalageddon => store_mode}/steamclient/client_utils.cpp (75%) rename src/{koalageddon => store_mode}/steamclient/steamclient.cpp (98%) rename src/{koalageddon => store_mode}/steamclient/steamclient.hpp (97%) rename src/{koalageddon/koalageddon.cpp => store_mode/store.cpp} (72%) rename src/{koalageddon/koalageddon.hpp => store_mode/store.hpp} (76%) create mode 100644 src/store_mode/store_api.cpp create mode 100644 src/store_mode/store_api.hpp create mode 100644 src/store_mode/store_cache.cpp create mode 100644 src/store_mode/store_cache.hpp rename src/{koalageddon => store_mode/vstdlib}/vstdlib.cpp (78%) create mode 100644 src/store_mode/vstdlib/vstdlib.hpp diff --git a/.idea/misc.xml b/.idea/misc.xml index 3d1fc74..9a3ffef 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -2,6 +2,9 @@ + + + diff --git a/CMakeLists.txt b/CMakeLists.txt index 7ad5b30..ff5844f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,14 +19,14 @@ file(GLOB DLL_INPUT "res/dll/*/sdk/redistributable_bin/${DLL_SUFFIX}.dll") set( STEAM_API_EXPORTS - "${CMAKE_CURRENT_SOURCE_DIR}/src/steam_api_exports/steam_api_flat.cpp" - "${CMAKE_CURRENT_SOURCE_DIR}/src/steam_api_exports/steam_api_internal.cpp" - "${CMAKE_CURRENT_SOURCE_DIR}/src/steam_api_exports/steam_api_unversioned.cpp" + "src/game_mode/exports/steam_api_flat.cpp" + "src/game_mode/exports/steam_api_internal.cpp" + "src/game_mode/exports/steam_api_unversioned.cpp" ) configure_linker_exports( FORWARDED_DLL "${STEAMAPI_DLL}_o" - INPUT_SOURCES_DIR "${CMAKE_CURRENT_SOURCE_DIR}/src/steam_api_exports" + INPUT_SOURCES_DIR "${CMAKE_CURRENT_SOURCE_DIR}/src/game_mode/exports" INPUT_DLLS "${DLL_INPUT}" DEP_SOURCES "${STEAM_API_EXPORTS}" ) @@ -35,6 +35,10 @@ configure_build_config(extra_build_config) set( SMOKE_API_SOURCES + src/common/app_cache.cpp + src/common/app_cache.hpp + src/common/steamclient_exports.cpp + src/common/steamclient_exports.hpp src/core/api.cpp src/core/api.hpp src/core/globals.cpp @@ -43,53 +47,51 @@ set( src/core/paths.hpp src/core/types.cpp src/core/types.hpp - src/smoke_api/app_cache.cpp - src/smoke_api/app_cache.hpp + src/game_mode/exports/steam_api_flat.cpp + src/game_mode/exports/steam_api_internal.cpp + src/game_mode/exports/steam_api_unversioned.cpp + src/game_mode/virtuals/isteamapps.cpp + src/game_mode/virtuals/isteamclient.cpp + src/game_mode/virtuals/isteaminventory.cpp + src/game_mode/virtuals/isteamuser.cpp + src/game_mode/virtuals/steam_api_virtuals.hpp src/smoke_api/config.cpp src/smoke_api/config.hpp src/smoke_api/smoke_api.cpp src/smoke_api/smoke_api.hpp - src/steam_api_exports/steam_api_exports.hpp - src/steam_api_exports/steam_api_flat.cpp - src/steam_api_exports/steam_api_internal.cpp - src/steam_api_exports/steam_api_unversioned.cpp - src/steam_api_virtuals/isteamapps.cpp - src/steam_api_virtuals/isteamclient.cpp - src/steam_api_virtuals/isteaminventory.cpp - src/steam_api_virtuals/isteamuser.cpp - src/steam_api_virtuals/steam_api_virtuals.hpp src/steam_impl/steam_apps.cpp src/steam_impl/steam_apps.hpp src/steam_impl/steam_client.cpp src/steam_impl/steam_client.hpp + src/steam_impl/steam_impl.cpp + src/steam_impl/steam_impl.hpp src/steam_impl/steam_inventory.cpp src/steam_impl/steam_inventory.hpp src/steam_impl/steam_user.cpp src/steam_impl/steam_user.hpp - src/steam_impl/steam_impl.cpp - src/steam_impl/steam_impl.hpp - src/steamclient_exports/steamclient.cpp src/main.cpp ${GENERATED_LINKER_EXPORTS} ) -# Include koalageddon mode sources only in 32-bit builds +# Include store_mode mode sources only in 32-bit builds if (CMAKE_SIZEOF_VOID_P EQUAL 4) set( SMOKE_API_SOURCES ${SMOKE_API_SOURCES} - src/koalageddon/kg_cache.cpp - src/koalageddon/kg_cache.hpp - src/koalageddon/koalageddon.cpp - src/koalageddon/koalageddon.hpp - src/koalageddon/vstdlib.cpp - src/koalageddon/vstdlib.hpp - src/koalageddon/steamclient/client_app_manager.cpp - src/koalageddon/steamclient/client_apps.cpp - src/koalageddon/steamclient/client_inventory.cpp - src/koalageddon/steamclient/client_user.cpp - src/koalageddon/steamclient/client_utils.cpp - src/koalageddon/steamclient/steamclient.cpp - src/koalageddon/steamclient/steamclient.hpp + src/store_mode/steamclient/client_app_manager.cpp + src/store_mode/steamclient/client_apps.cpp + src/store_mode/steamclient/client_inventory.cpp + src/store_mode/steamclient/client_user.cpp + src/store_mode/steamclient/client_utils.cpp + src/store_mode/steamclient/steamclient.cpp + src/store_mode/steamclient/steamclient.hpp + src/store_mode/vstdlib/vstdlib.cpp + src/store_mode/vstdlib/vstdlib.hpp + src/store_mode/store.cpp + src/store_mode/store.hpp + src/store_mode/store_api.cpp + src/store_mode/store_api.hpp + src/store_mode/store_cache.cpp + src/store_mode/store_cache.hpp ) endif () diff --git a/KoalaBox b/KoalaBox index c1e97c8..44b0553 160000 --- a/KoalaBox +++ b/KoalaBox @@ -1 +1 @@ -Subproject commit c1e97c8101fe0369985aad85298b1b6b3e3c9ca6 +Subproject commit 44b0553105af8a7352def9e879f59e1da26bcba6 diff --git a/LICENSE.txt b/LICENSE.txt deleted file mode 100644 index e15023f..0000000 --- a/LICENSE.txt +++ /dev/null @@ -1,12 +0,0 @@ -Copyright (c) 2022 by acidicoala - -Permission to use, copy, modify, and/or distribute this software for any purpose -with or without fee is hereby granted. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS -OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER -TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF -THIS SOFTWARE. diff --git a/README.adoc b/README.adoc new file mode 100644 index 0000000..7cad36e --- /dev/null +++ b/README.adoc @@ -0,0 +1,275 @@ += ๐Ÿจ SmokeAPI โ™จ + +_Legit DLC Unlocker for Steamworks_ + +image::https://www.gstatic.com/android/keyboard/emojikitchen/20201001/u1f428/u1f428_u2615.png[,256,align="center"] + +== โœจ Features + +* ๐Ÿ”“ Legit DLC Unlocking +* ๐Ÿ›… Inventory emulation +* ๐Ÿ“ Config-less operation +* Multiple installation methods: +** ๐Ÿ›๏ธ Store mode +** ๐ŸŽฎ Game mode +*** ๐Ÿช Hook mode +*** ๐Ÿ”€ Proxy mode + +== ๐Ÿ”— Links + +:forum-topic: https://cs.rin.ru/forum/viewtopic.php?p=2597932#p2597932[SmokeAPI forum topic] + +* ๐Ÿ“ฅ https://github.com/acidicoala/SmokeAPI/releases/latest[Download the latest release] + +* ๐Ÿ’ฌ {forum-topic} + +== ๐Ÿ“– Introduction + +=== What is SmokeAPI? + +SmokeAPI is a DLC unlocker for the games that are legitimately owned in your Steam account. +It attempts to spoof games that use Steamworks SDK into believing that you own desired DLCs. +However, SmokeAPI does not modify the rest of the Steamworks SDK, hence features like multiplayer, achievements, etc. remain fully functional. + +.Supported versions +[%collapsible] +==== +SmokeAPI aims to support all released SteamAPI versions. +When it encountered a new, unsupported interface version, it will fall back on the latest supported version. +Below is a list of supported interface versions: + +* ISteamClient v6 โ€” v20. (Versions before 6 did not contain any DLC related interfaces) +* ISteamApps v2 โ€” v8. (Version 1 did not contain any DLC related functions) +* ISteamUser v12 โ€” v21. (Versions before 12 did not contain any DLC related functions) +* ISteamInventory v1 โ€” v3. + +Steam inventory does not work in all games with steam inventory because of custom implementation, and online checks. +A list of games where inventory emulation has been shown to work is as follows: + +* Project Winter +* Euro Truck Simulator 2 +* Hero Siege (if you bypass EAC) +==== + +== ๐Ÿ›  Installation Instructions + +WARNING: Please proceed with installation at your own risk. +Usage of this unlocker entails breaking one or more terms of service, which might result in a permanent loss of your account. + +:smokeapi_release: https://github.com/acidicoala/SmokeAPI/releases/latest[SmokeAPI Releases] + +SmokeAPI supports 2 main modes of installation: *Store* mode and *Game* mode, which are described in the next section. + +NOTE: It is worth noting that the following instructions describe a _manual_ installation method. +You can benefit from _automatic_ installation and GUI configuration from Koalageddon v2 (Coming soon). + +=== ๐Ÿ›๏ธ Store mode + +In this installation mode, SmokeAPI is loaded into the Steam process, which makes it able to affect all Steam games. + +:steam-dir: the Steam directoryfootnote:fn-steam-dir[The root directory where Steam is installed] + +. Download the latest Koaloader release zip from https://github.com/acidicoala/Koaloader/releases/latest[Koaloader Releases]. +. From Koaloader archive unpack `version.dll` from `version-32`, and place it in {steam-dir}. +. In {steam-dir} create the following Koaloader configuration file: ++ +.`Koaloader.json` +[%collapsible] +==== +[source,json] +---- +{ + "auto_load": false, + "targets": [ + "Steam.exe" + ], + "modules": [ + { + "path": "SmokeAPI.dll", + "required": true + } + ] +} +---- +==== +. Download the latest SmokeAPI release zip from {smokeapi_release}. +. From SmokeAPI archive unpack `steam_api.dll`, rename it to `SmokeAPI.dll`, and place it in {steam-dir}. + +=== ๐ŸŽฎ Game mode + +In this installation mode, SmokeAPI is loaded into a game process, which makes limits it to that particular game only. +This mode itself supports 2 modes: hook mode and proxy mode. +Try installing the unlocker in hook mode first. +If it doesn't work, try installing it in proxy mode. + +==== ๐Ÿช Hook mode + +. Download the latest Koaloader release zip from https://github.com/acidicoala/Koaloader/releases/latest[Koaloader Releases]. +. From Koaloader archive unpack `version.dll` from version-32/64, depending on the game bitness, and place it next to the game exe file. +. Download the latest SmokeAPI release zip from {smokeapi_release}. +. From SmokeAPI archive unpack `steam_api.dll`/`steam_api64.dll`, depending on the game bitness, rename it to `SmokeAPI.dll`, and place it next to the game exe file. + +==== ๐Ÿ”€ Proxy mode + +. Find `steam_api.dll`/`steam_api64.dll` file in game directory, and add `_o` to its name, e.g. `steam_api64_o.dll`. +. Download the latest SmokeAPI release zip from {smokeapi_release}. +. From SmokeAPI archive unpack `steam_api.dll`/`steam_api64.dll`, depending on the game bitness, and place it next to the original steam_api DLL file. + +If the unlocker is not working as expected, then please fully read the https://gist.github.com/acidicoala/2c131cb90e251f97c0c1dbeaf2c174dc[Generic Unlocker Installation Instructions] before seeking support in the {forum-topic}. + +== โš™ Configuration + +NOTE: This document describes configuration for version 2 of SmokeAPI. +You can find the version 1 documentation https://github.com/acidicoala/SmokeAPI/blob/v1.0.3/README.md#-configuration[here]. + +:steam_config: https://github.com/acidicoala/public-entitlements/blob/main/steam/v2/store_config.json +:fn-app-id: footnote:fn-app-id[App/DLC IDs can be obtained from https://steamdb.info. Keep in mind that you need to be signed in with a steam account in order to see accurate inventory item IDs on that website.] + +SmokeAPI does not require any manual configuration. +By default, it uses the most reasonable options and tries to unlock all DLCs that it can. +However, there might be circumstances in which you need more custom-tailored behaviour, such as disabling certain DLCs, or selectively enabling just a few of them. +In this case you can use a configuration file link:res/SmokeAPI.config.json[SmokeAPI.config.json] that you can find here in this repository or in the release zip. +To use it, simply place it next to the SmokeAPI DLL. +It will be read upon each launch of a game or the store. +In the absence of the config file, default values specified below will be used. +The configuration file is expected to conform to the Json standard. + +`logging`:: Toggles generation of a `SmokeAPI.log.log` file. ++ +[horizontal] +Type::: Boolean +Default::: `false` + +`unlock_family_sharing`:: Toggles Family Sharing bypass, which enables the borrower of a shared library to start and continue playing games when library owner is playing as well. ++ +[horizontal] +Type::: Boolean +Default::: `true` + +`default_app_status`:: This option sets the default DLC unlocking behaviour. ++ +[horizontal] +Possible values::: ++ +[horizontal] +`original`:::: Leaves DLC unlock status unmodified, unless specified otherwise. +`unlocked`:::: Unlocks all DLCs in all games, unless specified otherwise. +Type::: String +Default::: `unlocked` + +`override_app_status`:: This option overrides the status of all DLCs that belong to a specified app ID{fn-app-id}. ++ +[horizontal] +Possible values::: An object with key-value pairs, where the key corresponds to the app ID, and value to the app status. +Possible app status values are defined in the `default_app_status` option. +Type::: Object +Default::: `{}` + +`override_dlc_status`:: This option overrides the status of individual DLCs, regardless of the corresponding app status. ++ +[horizontal] +Possible values::: An object with key-value pairs, where the key corresponds to the app ID, and value to the app status. +Possible app status values are defined in the `default_app_status` option. +Furthermore, it is possible to lock even the legitimately locked DLCs by setting the corresponding app status value to `locked`. +Type::: Object (Map of String to String) +Default::: `{}` + +`auto_inject_inventory`:: Toggles whether SmokeAPI should automatically inject a list of all registered inventory items, when a game queries user inventory ++ +[horizontal] +Type::: Boolean +Default::: `true` + +`extra_inventory_items`:: A list of inventory items IDs{fn-app-id} that will be added in addition to the automatically injected items. ++ +[horizontal] +Type::: Array (of Integers) +Default::: `[]` + +=== Advanced options + +`$version`:: A technical field reserved for use by tools like GUI config editors. +Do not modify this value. ++ +[horizontal] +Type::: Integer +Default::: `2` + +`extra_dlcs`:: See <> to understand the use case for this option. ++ +[horizontal] +Possible values::: An object with key-value pairs, where the key corresponds to the app ID, and value to the object that contains DLC IDs. +The format is the same as in the aforementioned GitHub config. +Type::: Object (Map of String to Object) +Default::: `{}` + +`store_config`:: An object that specifies offsets required for store mode operation. +It will override the config fetched from {steam_config}[remote source] or local cache. +Do not modify this value. ++ +[horizontal] +Type::: Object (Map of String to Integer) +Default::: See {steam_config}[online config] + +== Extra info + +=== How SmokeAPI works in games with large number of DLCs + +Some games that have a large number 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 DLCs 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 `extra_dlcs` 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 a https://github.com/acidicoala/public-entitlements/blob/main/steam/v2/dlc.json[manually maintained list of extra DLCs] stored in a GitHub repository. +The purpose of this document is to contain all the DLC IDs that are lacking a Steam store page. +This enables SmokeAPI to unlock all DLCs without any config file at all. +Feel free to report in the {forum-topic} 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 config-less operation. + +== ๐Ÿ—๏ธ Building from source + +=== โœ… Requirements + +:fn-lower-ver: footnote:lower-versions[Older versions may be supported as well.] + +* CMake v3.24 (Make sure that cmake is available from powershell) +* Visual Studio 2022{fn-lower-ver}. +* Tested on Windows 11 SDK (10.0.22621.0){fn-lower-ver}. + +=== ๐Ÿ‘จโ€๐Ÿ’ป Commands + +Build the project + +---- +.\build.ps1 +---- + +where + +[horizontal] +arch::: `32` or `64` +config::: `Debug` or `Release` + +For example: + +---- +.\build.ps1 32 Debug +---- + +== ๐Ÿ‘‹ Acknowledgements + +SmokeAPI makes use of the following open source projects: + +- https://github.com/libcpr/cpr[C++ Requests] +- https://github.com/nlohmann/json[JSON for Modern C++] +- https://github.com/stevemk14ebr/PolyHook_2_0[PolyHook 2] +- https://github.com/gabime/spdlog[spdlog] + +== ๐Ÿ“„ License + +This software is licensed under the https://unlicense.org/[Unlicense], terms of which are available in link:UNLICENSE.txt[UNLICENSE.txt] \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index 941e5eb..0000000 --- a/README.md +++ /dev/null @@ -1,128 +0,0 @@ -# ๐Ÿจ SmokeAPI โ™จ - -**Legit DLC Unlocker for Steamworks** -___ - -**Features** - -- ๐Ÿ”“ Legit DLC Unlocking -- ๐Ÿช Hook mode and Proxy mode installation -- ๐Ÿ“ Configless operation -- ๐Ÿ›… Inventory emulation - -๐Ÿ“ฅ [Download the latest release](https://github.com/acidicoala/SmokeAPI/releases/latest) - -๐Ÿ’ฌ [Official forum topic](https://cs.rin.ru/forum/viewtopic.php?p=2597932#p2597932) - -## โ„น Introduction - -
What is SmokeAPI? - -SmokeAPI is a DLC unlocker for the games that are legitimately owned in your Steam account. It attempts to fool games that use Steamworks SDK into thinking that you own the desired DLCs. However, SmokeAPI does not modify the rest of the Steamworks SDK, hence features like multiplayer, achievements, etc. remain fully functional. - -
- - -
Supported versions - -SmokeAPI aims to support all released SteamAPI versions. When it encountered a new, unsupported interface version, it will fall back on the latest supported version. Below is a list of supported interface versions: - -- ISteamClient v6โ€”v20. (Versions before 6 did not contain any DLC related interfaces) -- ISteamApps v2โ€”v8. (Version 1 did not contain any DLC related functions) -- ISteamUser v12โ€”v21. (Versions before 12 did not contain any DLC related functions) -- ISteamInventory v1โ€”v3. - -Steam inventory does not work in all games with steam inventory because of custom implementation, and online checks. -The list of games where inventory emulation has been shown to work is as follows: -- Project Winter -- Euro Truck Simulator 2 -- Bloons TD 6 - -
- -## ๐Ÿ›  Installation Instructions - -This unlocker supports 2 modes of installation: *Hook* mode and *Proxy* mode. -Try installing the unlocker in hook mode first. If it doesn't work, try installing it in proxy mode. - -#### ๐Ÿช Hook mode - -1. Download the latest Koaloader release zip from [Koaloader Releases]. -2. From Koaloader archive unpack `version.dll` from version-32/64, depending on the game bitness, and place it next to the game exe file. -3. Download the latest SmokeAPI release zip from [SmokeAPI Releases]. -4. From SmokeAPI archive unpack `steam_api.dll`/`steam_api64.dll`, depending on the game bitness, rename it to `SmokeAPI.dll`, and place it next to the game exe file. - -#### ๐Ÿ”€ Proxy mode - -1. Find `steam_api.dll`/`steam_api64.dll` file in game directory, and add `_o` to its name, e.g. `steam_api64_o.dll`. -2. Download the latest SmokeAPI release zip from [SmokeAPI Releases]. -3. From SmokeAPI archive unpack `steam_api.dll`/`steam_api64.dll`, depending on the game bitness, and place it next to the original steam_api DLL file. - -If the unlocker is not working as expected, then please fully read the [Generic Unlocker Installation Instructions] before seeking help in the support forum. - -[Koaloader Releases]: https://github.com/acidicoala/Koaloader/releases/latest - -[SmokeAPI Releases]: https://github.com/acidicoala/SmokeAPI/releases/latest - -[Generic Unlocker Installation Instructions]: https://gist.github.com/acidicoala/2c131cb90e251f97c0c1dbeaf2c174dc - -## โš™ Configuration - -SmokeAPI does not require any manual configuration. By default, it uses the most reasonable options and tries to unlock all DLCs that it can. However, there might be circumstances in which you need more custom-tailored behaviour, such as disabling certain DLCs, or selectively enabling just a few of them. In this case you can use a configuration file [SmokeAPI.config.json] that you can find here in this repository or in the release zip. To use it, simply place it next to the SmokeAPI DLL. It will be read upon each launch of a game. In the absence of the config file, default value specified below will be used. - -| Option | Description | Type | Default | -|-------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------|:-------:| -| `$version` | A technical field reserved for use by tools like GUI config editors. Do not modify this value. | Integer | `2` | -| `logging` | Toggles generation of `SmokeAPI.log.log` file | Boolean | `false` | -| `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 | `[]` | -| `extra_dlcs` | When a game requests number of all DLCs from Steam and gets a response that is equal to or greater than 64, it means that we need to get extra DLCs because Steam returns maximum 64 values this way. In this case, SmokeAPI will fetch extra DLCs from several online source, such as Steam API and a [manually maintained list of DLC IDs] from GitHub. However, in some cases these sources doesn't return all possible DLCs. To address this issue, you can specify the missing DLC IDsยน in this option. | Object | `{}` | -| `auto_inject_inventory` | Toggles whether SmokeAPI should automatically inject a list of all registered inventory items, when a game queries user inventory | Boolean | `true` | -| `extra_inventory_items` | A list of inventory items IDsยน that will be added in addition to the automatically injected items | List of Integers | `[]` | -| `koalageddon_config` | An object that specifies patterns and offsets required for koalageddon mode. It can be used to override online config for testing or development purposes. | Object | `null` | - -ยน DLC/Item IDs can be obtained from https://steamdb.info. You need to be logged in with your steam account in order to see accurate inventory item IDs. - -[SmokeAPI.config.json]: res/SmokeAPI.config.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 `extra_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 - -## โœ’๏ธ TODO -- Describe how Koalageddon mode works and its config parameters -- Describe the organisation of the project - -## ๐Ÿ—๏ธ Building from source - -### Requirements -- Git -- CMake v3.24 -- Visual Studio 2022 - - Tested on Windows 11 SDK (10.0.22621.0). Lower versions may be supported as well. - -TODO: build.ps1 - -## ๐Ÿ‘‹ Acknowledgements - -SmokeAPI makes use of the following open source projects: - -- [C++ Requests](https://github.com/libcpr/cpr) -- [JSON for Modern C++](https://github.com/nlohmann/json) -- [PolyHook 2](https://github.com/stevemk14ebr/PolyHook_2_0) -- [spdlog](https://github.com/gabime/spdlog) - -## ๐Ÿ“„ License - -This software is licensed under [BSD Zero Clause License], terms of which are available in [LICENSE.txt] - -[BSD Zero Clause License]: https://choosealicense.com/licenses/0bsd/ - -[LICENSE.txt]: LICENSE.txt diff --git a/UNLICENSE.txt b/UNLICENSE.txt new file mode 100644 index 0000000..3c577b0 --- /dev/null +++ b/UNLICENSE.txt @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to \ No newline at end of file diff --git a/build.ps1 b/build.ps1 index fdc94c9..41b0901 100644 --- a/build.ps1 +++ b/build.ps1 @@ -3,6 +3,6 @@ Set-Location (Get-Item $PSScriptRoot) -. ./KoalaBox/build.ps1 @args +. ./KoalaBox/build.ps1 SmokeAPI @args Build-Project diff --git a/res/SmokeAPI.config.json b/res/SmokeAPI.config.json index 37f83a9..ce29d6c 100644 --- a/res/SmokeAPI.config.json +++ b/res/SmokeAPI.config.json @@ -12,6 +12,8 @@ "4321": "unlocked", "5678": "locked" }, + "auto_inject_inventory": true, + "extra_inventory_items": [], "extra_dlcs": { "1234": { "dlcs": { @@ -24,7 +26,5 @@ } } }, - "auto_inject_inventory": true, - "extra_inventory_items": [], - "koalageddon_config": null + "store_config": null } diff --git a/src/smoke_api/app_cache.cpp b/src/common/app_cache.cpp similarity index 97% rename from src/smoke_api/app_cache.cpp rename to src/common/app_cache.cpp index 2e9c8bd..93a5d59 100644 --- a/src/smoke_api/app_cache.cpp +++ b/src/common/app_cache.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include #include diff --git a/src/smoke_api/app_cache.hpp b/src/common/app_cache.hpp similarity index 100% rename from src/smoke_api/app_cache.hpp rename to src/common/app_cache.hpp diff --git a/src/steamclient_exports/steamclient.cpp b/src/common/steamclient_exports.cpp similarity index 89% rename from src/steamclient_exports/steamclient.cpp rename to src/common/steamclient_exports.cpp index 3312072..a6c1be3 100644 --- a/src/steamclient_exports/steamclient.cpp +++ b/src/common/steamclient_exports.cpp @@ -1,3 +1,4 @@ +#include #include DLL_EXPORT(void*) CreateInterface(const char* interface_string, int* out_result) { diff --git a/src/common/steamclient_exports.hpp b/src/common/steamclient_exports.hpp new file mode 100644 index 0000000..d175fe0 --- /dev/null +++ b/src/common/steamclient_exports.hpp @@ -0,0 +1,3 @@ +#include + +DLL_EXPORT(void*) CreateInterface(const char* interface_string, int* out_result); diff --git a/src/core/api.cpp b/src/core/api.cpp index 5cdefcb..9069381 100644 --- a/src/core/api.cpp +++ b/src/core/api.cpp @@ -27,7 +27,7 @@ namespace api { std::optional> fetch_dlcs_from_steam(AppId_t app_id) noexcept { try { - const auto url = fmt::format("https://store.steampowered.com/dlc/{}/ajaxgetdlclist", app_id); + const auto url = fmt::format("https://store_mode.steampowered.com/dlc/{}/ajaxgetdlclist", app_id); const auto json = koalabox::http_client::fetch_json(url); const auto response = json.get(); @@ -43,17 +43,4 @@ namespace api { } } - std::optional fetch_koalageddon_config() noexcept { - try { - const String url = - "https://raw.githubusercontent.com/acidicoala/public-entitlements/main/koalageddon/v2/steam.json"; - const auto kg_config_json = koalabox::http_client::fetch_json(url); - - return kg_config_json.get(); - } catch (const Exception& e) { - LOG_ERROR("Failed to fetch Koalageddon config from GitHub: {}", e.what()) - return std::nullopt; - } - } - } diff --git a/src/core/api.hpp b/src/core/api.hpp index 4d65478..ca7d229 100644 --- a/src/core/api.hpp +++ b/src/core/api.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include namespace api { @@ -8,6 +8,4 @@ namespace api { std::optional> fetch_dlcs_from_steam(AppId_t app_id) noexcept; - std::optional fetch_koalageddon_config() noexcept; - } diff --git a/src/core/types.hpp b/src/core/types.hpp index 49a6acc..60f7269 100644 --- a/src/core/types.hpp +++ b/src/core/types.hpp @@ -1,9 +1,10 @@ #pragma once -#include -#include #include #include +#include +#include +#include /** * By default, virtual functions are declared with __thiscall @@ -23,7 +24,7 @@ * * In x86-64 however, there is only one calling convention, * so __fastcall is simply ignored. However, RDX in this case - * will store the 1st actual argument to the function, so we + * will store_mode the 1st actual argument to the function, so we * have to omit it from the function signature. * * The macros below implement the above-mentioned considerations. @@ -60,9 +61,9 @@ #define DETOUR_VSTDLIB(FUNC) $DETOUR(vstdlib::FUNC, #FUNC, globals::vstdlib_module) #ifdef _WIN64 -#define COMPILE_KOALAGEDDON 0 +#define COMPILE_STORE_MODE 0 #else -#define COMPILE_KOALAGEDDON 1 +#define COMPILE_STORE_MODE 1 #endif constexpr auto STEAM_APPS = "STEAMAPPS_INTERFACE_VERSION"; @@ -134,5 +135,6 @@ public: NLOHMANN_DEFINE_TYPE_INTRUSIVE(DLC, appid, name) static Vector get_dlcs_from_apps(const AppDlcNameMap& apps, AppId_t app_id); + static DlcNameMap get_dlc_map_from_vector(const Vector& vector); }; diff --git a/src/steam_api_exports/steam_api_flat.cpp b/src/game_mode/exports/steam_api_flat.cpp similarity index 96% rename from src/steam_api_exports/steam_api_flat.cpp rename to src/game_mode/exports/steam_api_flat.cpp index f173c59..7a632e1 100644 --- a/src/steam_api_exports/steam_api_flat.cpp +++ b/src/game_mode/exports/steam_api_flat.cpp @@ -1,9 +1,9 @@ -#include #include #include #include #include #include +#include // ISteamApps @@ -92,6 +92,20 @@ DLL_EXPORT(EResult) SteamAPI_ISteamInventory_GetResultStatus( ); } +DLL_EXPORT(bool) SteamAPI_ISteamInventory_GetItemDefinitionIDs( + void* self, + SteamItemDef_t* pItemDefIDs, + uint32_t* punItemDefIDsArraySize +) { + return steam_inventory::GetItemDefinitionIDs( + __func__, pItemDefIDs, punItemDefIDsArraySize, [&]() { + GET_ORIGINAL_FUNCTION_STEAMAPI(SteamAPI_ISteamInventory_GetItemDefinitionIDs) + + return SteamAPI_ISteamInventory_GetItemDefinitionIDs_o(self, pItemDefIDs, punItemDefIDsArraySize); + } + ); +} + DLL_EXPORT(bool) SteamAPI_ISteamInventory_GetResultItems( void* self, SteamInventoryResult_t resultHandle, @@ -189,20 +203,6 @@ DLL_EXPORT(bool) SteamAPI_ISteamInventory_SerializeResult( ); } -DLL_EXPORT(bool) SteamAPI_ISteamInventory_GetItemDefinitionIDs( - void* self, - SteamItemDef_t* pItemDefIDs, - uint32_t* punItemDefIDsArraySize -) { - return steam_inventory::GetItemDefinitionIDs( - __func__, pItemDefIDs, punItemDefIDsArraySize, [&]() { - GET_ORIGINAL_FUNCTION_STEAMAPI(SteamAPI_ISteamInventory_GetItemDefinitionIDs) - - return SteamAPI_ISteamInventory_GetItemDefinitionIDs_o(self, pItemDefIDs, punItemDefIDsArraySize); - } - ); -} - // ISteamUser DLL_EXPORT(EUserHasLicenseForAppResult) SteamAPI_ISteamUser_UserHasLicenseForApp( @@ -210,8 +210,15 @@ DLL_EXPORT(EUserHasLicenseForAppResult) SteamAPI_ISteamUser_UserHasLicenseForApp CSteamID steamID, AppId_t dlcID ) { + AppId_t app_id; + try { + app_id = steam_impl::get_app_id_or_throw(); + } catch (const Exception& e) { + LOG_ERROR("{} -> Error getting app id: {}", __func__, e.what()) + } + return steam_user::UserHasLicenseForApp( - __func__, steam_impl::get_app_id_or_throw(), dlcID, [&]() { + __func__, app_id, dlcID, [&]() { GET_ORIGINAL_FUNCTION_STEAMAPI(SteamAPI_ISteamUser_UserHasLicenseForApp) return SteamAPI_ISteamUser_UserHasLicenseForApp_o(self, steamID, dlcID); diff --git a/src/steam_api_exports/steam_api_internal.cpp b/src/game_mode/exports/steam_api_internal.cpp similarity index 93% rename from src/steam_api_exports/steam_api_internal.cpp rename to src/game_mode/exports/steam_api_internal.cpp index 9255e29..fad9d51 100644 --- a/src/steam_api_exports/steam_api_internal.cpp +++ b/src/game_mode/exports/steam_api_internal.cpp @@ -1,4 +1,3 @@ -#include #include DLL_EXPORT(void*) SteamInternal_FindOrCreateUserInterface(HSteamUser hSteamUser, const char* version) { diff --git a/src/steam_api_exports/steam_api_unversioned.cpp b/src/game_mode/exports/steam_api_unversioned.cpp similarity index 97% rename from src/steam_api_exports/steam_api_unversioned.cpp rename to src/game_mode/exports/steam_api_unversioned.cpp index a5cf207..1b93151 100644 --- a/src/steam_api_exports/steam_api_unversioned.cpp +++ b/src/game_mode/exports/steam_api_unversioned.cpp @@ -1,4 +1,3 @@ -#include #include #include #include diff --git a/src/steam_api_virtuals/isteamapps.cpp b/src/game_mode/virtuals/isteamapps.cpp similarity index 96% rename from src/steam_api_virtuals/isteamapps.cpp rename to src/game_mode/virtuals/isteamapps.cpp index f0e5f9f..36a128d 100644 --- a/src/steam_api_virtuals/isteamapps.cpp +++ b/src/game_mode/virtuals/isteamapps.cpp @@ -1,4 +1,4 @@ -#include +#include #include VIRTUAL(bool) ISteamApps_BIsSubscribedApp(PARAMS(AppId_t appID)) { diff --git a/src/steam_api_virtuals/isteamclient.cpp b/src/game_mode/virtuals/isteamclient.cpp similarity index 97% rename from src/steam_api_virtuals/isteamclient.cpp rename to src/game_mode/virtuals/isteamclient.cpp index 4844358..47906ba 100644 --- a/src/steam_api_virtuals/isteamclient.cpp +++ b/src/game_mode/virtuals/isteamclient.cpp @@ -1,4 +1,4 @@ -#include +#include #include VIRTUAL(void*) ISteamClient_GetISteamApps( diff --git a/src/steam_api_virtuals/isteaminventory.cpp b/src/game_mode/virtuals/isteaminventory.cpp similarity index 98% rename from src/steam_api_virtuals/isteaminventory.cpp rename to src/game_mode/virtuals/isteaminventory.cpp index be80b60..fba78fa 100644 --- a/src/steam_api_virtuals/isteaminventory.cpp +++ b/src/game_mode/virtuals/isteaminventory.cpp @@ -1,4 +1,4 @@ -#include +#include #include VIRTUAL(EResult) ISteamInventory_GetResultStatus(PARAMS(SteamInventoryResult_t resultHandle)) { diff --git a/src/steam_api_virtuals/isteamuser.cpp b/src/game_mode/virtuals/isteamuser.cpp similarity index 55% rename from src/steam_api_virtuals/isteamuser.cpp rename to src/game_mode/virtuals/isteamuser.cpp index c317452..a23e1e3 100644 --- a/src/steam_api_virtuals/isteamuser.cpp +++ b/src/game_mode/virtuals/isteamuser.cpp @@ -1,10 +1,18 @@ -#include +#include #include #include +#include VIRTUAL(EUserHasLicenseForAppResult) ISteamUser_UserHasLicenseForApp(PARAMS(CSteamID steamID, AppId_t dlcID)) { + AppId_t app_id = 0; + try { + app_id = steam_impl::get_app_id_or_throw(); + } catch (const Exception& e) { + LOG_ERROR("{} -> Error getting app id: {}", __func__, e.what()) + } + return steam_user::UserHasLicenseForApp( - __func__, steam_impl::get_app_id_or_throw(), dlcID, [&]() { + __func__, app_id, dlcID, [&]() { GET_ORIGINAL_HOOKED_FUNCTION(ISteamUser_UserHasLicenseForApp) return ISteamUser_UserHasLicenseForApp_o(ARGS(steamID, dlcID)); diff --git a/src/steam_api_virtuals/steam_api_virtuals.hpp b/src/game_mode/virtuals/steam_api_virtuals.hpp similarity index 100% rename from src/steam_api_virtuals/steam_api_virtuals.hpp rename to src/game_mode/virtuals/steam_api_virtuals.hpp diff --git a/src/koalageddon/kg_cache.cpp b/src/koalageddon/kg_cache.cpp deleted file mode 100644 index e5f0e70..0000000 --- a/src/koalageddon/kg_cache.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include -#include -#include - -constexpr auto KEY_KG_CONFIG = "koalageddon_config"; - -namespace koalageddon::kg_cache { - - std::optional get_koalageddon_config() { - try { - const auto config_json = koalabox::cache::read_from_cache(KEY_KG_CONFIG); - - return config_json.get(); - } catch (const Exception& e) { - LOG_ERROR("Failed to get cached koalageddon config: {}", e.what()) - - return std::nullopt; - } - } - - bool save_koalageddon_config(const KoalageddonConfig& config) { - try { - LOG_DEBUG("Caching koalageddon config") - - return koalabox::cache::save_to_cache(KEY_KG_CONFIG, Json(config)); - } catch (const Exception& e) { - LOG_ERROR("Failed to cache koalageddon config: {}", e.what()) - - return false; - } - } - -} diff --git a/src/koalageddon/kg_cache.hpp b/src/koalageddon/kg_cache.hpp deleted file mode 100644 index 4b6c8fe..0000000 --- a/src/koalageddon/kg_cache.hpp +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include - -namespace koalageddon::kg_cache { - - std::optional get_koalageddon_config(); - - bool save_koalageddon_config(const KoalageddonConfig& config); - -} diff --git a/src/koalageddon/vstdlib.hpp b/src/koalageddon/vstdlib.hpp deleted file mode 100644 index 3a74986..0000000 --- a/src/koalageddon/vstdlib.hpp +++ /dev/null @@ -1,27 +0,0 @@ -#include - -namespace koalageddon::vstdlib { - - struct CallbackData { - uintptr_t get_callback_intercept_address() { - return reinterpret_cast(this)[koalageddon::config.vstdlib_callback_interceptor_address_offset]; - } - - uintptr_t get_callback_address() { - return reinterpret_cast(this)[koalageddon::config.vstdlib_callback_address_offset]; - } - }; - - struct CoroutineData { - CallbackData* get_callback_data() { - return reinterpret_cast(this)[koalageddon::config.vstdlib_callback_data_offset]; - } - - const char* get_callback_name() { - return reinterpret_cast(this)[koalageddon::config.vstdlib_callback_name_offset]; - } - }; - - typedef uint32_t HCoroutine; - DLL_EXPORT(HCoroutine) Coroutine_Create(void* callback_address, CoroutineData* data); -} diff --git a/src/smoke_api/config.cpp b/src/smoke_api/config.cpp index 34a49ab..673e966 100644 --- a/src/smoke_api/config.cpp +++ b/src/smoke_api/config.cpp @@ -7,7 +7,7 @@ namespace smoke_api::config { Config instance; // NOLINT(cert-err58-cpp) - void init() { + void init_config() { const auto path = paths::get_config_path(); if (exists(path)) { @@ -66,6 +66,6 @@ namespace smoke_api::config { DLL_EXPORT(void) ReloadConfig() { LOG_INFO("Reloading config") - init(); + init_config(); } } diff --git a/src/smoke_api/config.hpp b/src/smoke_api/config.hpp index ac4c3e8..d22a8dd 100644 --- a/src/smoke_api/config.hpp +++ b/src/smoke_api/config.hpp @@ -1,7 +1,6 @@ #pragma once #include -#include namespace smoke_api::config { @@ -30,7 +29,7 @@ namespace smoke_api::config { bool auto_inject_inventory = true; Vector extra_inventory_items; // We have to use general json type here since the library doesn't support std::optional - Json koalageddon_config; + Json store_config; NLOHMANN_DEFINE_TYPE_INTRUSIVE( Config, // NOLINT(misc-const-correctness) @@ -43,13 +42,13 @@ namespace smoke_api::config { extra_dlcs, auto_inject_inventory, extra_inventory_items, - koalageddon_config + store_config ) }; extern Config instance; - void init(); + void init_config(); Vector get_extra_dlcs(AppId_t app_id); diff --git a/src/smoke_api/smoke_api.cpp b/src/smoke_api/smoke_api.cpp index 73ea5ab..43a42d3 100644 --- a/src/smoke_api/smoke_api.cpp +++ b/src/smoke_api/smoke_api.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -10,12 +11,30 @@ #include #include #include -#include +//#include -#if COMPILE_KOALAGEDDON -#include +#if COMPILE_STORE_MODE +#include #endif +// Hooking steam_api has shown 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_proxy_mode() { LOG_INFO("๐Ÿ”€ Detected proxy mode") @@ -34,24 +53,6 @@ void init_hook_mode() { koalabox::dll_monitor::shutdown_listener(); } ); - - // Hooking steam_api has shown 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. } bool is_valve_steam(const String& exe_name) noexcept { @@ -82,7 +83,7 @@ namespace smoke_api { globals::smokeapi_handle = module_handle; - config::init(); + config::init_config(); if (config::instance.logging) { koalabox::logger::init_file_logger(paths::get_log_path()); @@ -103,9 +104,9 @@ namespace smoke_api { koalabox::hook::init(true); if (is_valve_steam(exe_name)) { -#if COMPILE_KOALAGEDDON - LOG_INFO("๐Ÿจ๐Ÿ’ฅ Detected Koalageddon mode") - koalageddon::init(); +#if COMPILE_STORE_MODE + LOG_INFO("๐Ÿ›๏ธ Detected Store mode") + store::init_store_mode(); #endif } else { init_hook_mode(); diff --git a/src/smoke_api/smoke_api.hpp b/src/smoke_api/smoke_api.hpp index a86dda2..e322a5b 100644 --- a/src/smoke_api/smoke_api.hpp +++ b/src/smoke_api/smoke_api.hpp @@ -3,6 +3,7 @@ namespace smoke_api { void init(HMODULE module_handle); + void shutdown(); } diff --git a/src/steam_api_exports/steam_api_exports.hpp b/src/steam_api_exports/steam_api_exports.hpp deleted file mode 100644 index 60e1176..0000000 --- a/src/steam_api_exports/steam_api_exports.hpp +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -#include - -// Flat -DLL_EXPORT(bool) SteamAPI_ISteamApps_BIsSubscribedApp(void*, AppId_t); -DLL_EXPORT(bool) SteamAPI_ISteamApps_BIsDlcInstalled(void*, AppId_t); -DLL_EXPORT(int) SteamAPI_ISteamApps_GetDLCCount(void*); -DLL_EXPORT(bool) SteamAPI_ISteamApps_BGetDLCDataByIndex(void*, int, AppId_t*, bool*, char*, int); - -DLL_EXPORT(void*) SteamAPI_ISteamClient_GetISteamGenericInterface(void*, HSteamUser, HSteamPipe, const char*); - -DLL_EXPORT(EResult) SteamAPI_ISteamInventory_GetResultStatus(void*, SteamInventoryResult_t); -DLL_EXPORT(bool) SteamAPI_ISteamInventory_GetResultItems(void*, SteamInventoryResult_t, SteamItemDetails_t*, uint32_t*); -DLL_EXPORT(bool) SteamAPI_ISteamInventory_GetResultItemProperty( - void*, SteamInventoryResult_t, uint32_t, const char*, char*, uint32_t* -); -DLL_EXPORT(bool) SteamAPI_ISteamInventory_CheckResultSteamID(void*, SteamInventoryResult_t, CSteamID); -DLL_EXPORT(bool) SteamAPI_ISteamInventory_GetAllItems(void*, SteamInventoryResult_t*); -DLL_EXPORT(bool) SteamAPI_ISteamInventory_GetItemsByID( - void*, SteamInventoryResult_t*, const SteamItemInstanceID_t*, uint32_t -); -DLL_EXPORT(bool) SteamAPI_ISteamInventory_SerializeResult(void*, SteamInventoryResult_t, void*, uint32_t*); -DLL_EXPORT(bool) SteamAPI_ISteamInventory_GetItemDefinitionIDs(void*, SteamItemDef_t*, uint32_t*); - -DLL_EXPORT(EUserHasLicenseForAppResult) SteamAPI_ISteamUser_UserHasLicenseForApp(void*, CSteamID, AppId_t); - -// Internal -DLL_EXPORT(void*) SteamInternal_FindOrCreateUserInterface(HSteamUser, const char*); -DLL_EXPORT(void*) SteamInternal_CreateInterface(const char*); - -// Unversioned -DLL_EXPORT(void*) CreateInterface(const char*, int*); -DLL_EXPORT(void*) SteamApps(); -DLL_EXPORT(void*) SteamClient(); -DLL_EXPORT(void*) SteamUser(); diff --git a/src/steam_impl/steam_apps.cpp b/src/steam_impl/steam_apps.cpp index d7acbfb..2c021a4 100644 --- a/src/steam_impl/steam_apps.cpp +++ b/src/steam_impl/steam_apps.cpp @@ -1,6 +1,6 @@ #include #include -#include +#include #include #include #include diff --git a/src/steam_impl/steam_impl.cpp b/src/steam_impl/steam_impl.cpp index 7bfeac8..7af8164 100644 --- a/src/steam_impl/steam_impl.cpp +++ b/src/steam_impl/steam_impl.cpp @@ -1,16 +1,15 @@ #include -#include -#include +#include +#include #include #include -#include +#include #include #include -#include #include -#if COMPILE_KOALAGEDDON -#include +#if COMPILE_STORE_MODE +#include #endif namespace steam_impl { @@ -201,8 +200,8 @@ namespace steam_impl { HOOK_STEAM_INVENTORY(ISteamInventory_GetResultItemProperty) } } else if (version_string.starts_with(CLIENT_ENGINE)) { -#if COMPILE_KOALAGEDDON - koalageddon::steamclient::process_client_engine(reinterpret_cast(interface)); +#if COMPILE_STORE_MODE + store::steamclient::process_client_engine(reinterpret_cast(interface)); #endif } else { return; diff --git a/src/steam_impl/steam_inventory.hpp b/src/steam_impl/steam_inventory.hpp index ccde607..7258fe1 100644 --- a/src/steam_impl/steam_inventory.hpp +++ b/src/steam_impl/steam_inventory.hpp @@ -1,7 +1,6 @@ #pragma once #include -#include namespace steam_inventory { diff --git a/src/koalageddon/steamclient/client_app_manager.cpp b/src/store_mode/steamclient/client_app_manager.cpp similarity index 88% rename from src/koalageddon/steamclient/client_app_manager.cpp rename to src/store_mode/steamclient/client_app_manager.cpp index bb3020a..7d3fabc 100644 --- a/src/koalageddon/steamclient/client_app_manager.cpp +++ b/src/store_mode/steamclient/client_app_manager.cpp @@ -1,3 +1,4 @@ +#include #include VIRTUAL(bool) IClientAppManager_IsAppDlcInstalled(PARAMS(AppId_t app_id, AppId_t dlc_id)) { diff --git a/src/koalageddon/steamclient/client_apps.cpp b/src/store_mode/steamclient/client_apps.cpp similarity index 87% rename from src/koalageddon/steamclient/client_apps.cpp rename to src/store_mode/steamclient/client_apps.cpp index 2d1cbf8..8ab1def 100644 --- a/src/koalageddon/steamclient/client_apps.cpp +++ b/src/store_mode/steamclient/client_apps.cpp @@ -1,5 +1,5 @@ +#include #include -#include VIRTUAL(int) IClientApps_GetDLCCount(PARAMS(AppId_t appId)) { return steam_apps::GetDLCCount( @@ -31,7 +31,7 @@ VIRTUAL(bool) IClientApps_BGetDLCDataByIndex( ); }, [&](AppId_t dlc_id) { - const auto* app_manager_interface = koalageddon::steamclient::interface_name_to_address_map["IClientAppManager"]; + const auto* app_manager_interface = store::steamclient::interface_name_to_address_map["IClientAppManager"]; if (app_manager_interface) { IClientAppManager_IsAppDlcInstalled(app_manager_interface, EDX, appID, dlc_id); return true; diff --git a/src/koalageddon/steamclient/client_inventory.cpp b/src/store_mode/steamclient/client_inventory.cpp similarity index 98% rename from src/koalageddon/steamclient/client_inventory.cpp rename to src/store_mode/steamclient/client_inventory.cpp index 26d75f5..865bee3 100644 --- a/src/koalageddon/steamclient/client_inventory.cpp +++ b/src/store_mode/steamclient/client_inventory.cpp @@ -1,4 +1,4 @@ -#include +#include #include VIRTUAL(EResult) IClientInventory_GetResultStatus(PARAMS(SteamInventoryResult_t resultHandle)) { diff --git a/src/koalageddon/steamclient/client_user.cpp b/src/store_mode/steamclient/client_user.cpp similarity index 72% rename from src/koalageddon/steamclient/client_user.cpp rename to src/store_mode/steamclient/client_user.cpp index 93f37a7..0a48d1d 100644 --- a/src/koalageddon/steamclient/client_user.cpp +++ b/src/store_mode/steamclient/client_user.cpp @@ -1,8 +1,8 @@ -#include +#include #include VIRTUAL(bool) IClientUser_BIsSubscribedApp(PARAMS(AppId_t dlc_id)) { - const auto* utils_interface = koalageddon::steamclient::interface_name_to_address_map["IClientUtils"]; + const auto* utils_interface = store::steamclient::interface_name_to_address_map["IClientUtils"]; const auto app_id = utils_interface ? IClientUtils_GetAppID(utils_interface, EDX) : 0; diff --git a/src/koalageddon/steamclient/client_utils.cpp b/src/store_mode/steamclient/client_utils.cpp similarity index 75% rename from src/koalageddon/steamclient/client_utils.cpp rename to src/store_mode/steamclient/client_utils.cpp index 5a7839d..4978eec 100644 --- a/src/koalageddon/steamclient/client_utils.cpp +++ b/src/store_mode/steamclient/client_utils.cpp @@ -1,4 +1,4 @@ -#include +#include VIRTUAL(AppId_t) IClientUtils_GetAppID(PARAMS()) { GET_ORIGINAL_HOOKED_FUNCTION(IClientUtils_GetAppID) diff --git a/src/koalageddon/steamclient/steamclient.cpp b/src/store_mode/steamclient/steamclient.cpp similarity index 98% rename from src/koalageddon/steamclient/steamclient.cpp rename to src/store_mode/steamclient/steamclient.cpp index 3d5eb3b..6fb0b38 100644 --- a/src/koalageddon/steamclient/steamclient.cpp +++ b/src/store_mode/steamclient/steamclient.cpp @@ -1,5 +1,5 @@ -#include -#include +#include +#include #include #include #include @@ -7,7 +7,7 @@ #include #include -namespace koalageddon::steamclient { +namespace store::steamclient { using namespace koalabox; Map interface_name_to_address_map; // NOLINT(cert-err58-cpp) @@ -431,10 +431,10 @@ namespace koalageddon::steamclient { void process_client_engine(uintptr_t interface) { const auto* steam_client_internal = ((uintptr_t***) interface)[ - koalageddon::config.client_engine_steam_client_internal_ordinal + store::config.client_engine_steam_client_internal_ordinal ]; const auto interface_selector_address = (*steam_client_internal)[ - koalageddon::config.steam_client_internal_interface_selector_ordinal + store::config.steam_client_internal_interface_selector_ordinal ]; LOG_DEBUG("Found interface selector at: {}", (void*) interface_selector_address) diff --git a/src/koalageddon/steamclient/steamclient.hpp b/src/store_mode/steamclient/steamclient.hpp similarity index 97% rename from src/koalageddon/steamclient/steamclient.hpp rename to src/store_mode/steamclient/steamclient.hpp index bcf388f..56029f3 100644 --- a/src/koalageddon/steamclient/steamclient.hpp +++ b/src/store_mode/steamclient/steamclient.hpp @@ -29,7 +29,7 @@ VIRTUAL(bool) IClientUser_BIsSubscribedApp(PARAMS(AppId_t)); // IClientUtils VIRTUAL(AppId_t) IClientUtils_GetAppID(PARAMS()); -namespace koalageddon::steamclient { +namespace store::steamclient { /// We need this interface in other IClient* functions in order to call other functions extern Map interface_name_to_address_map; diff --git a/src/koalageddon/koalageddon.cpp b/src/store_mode/store.cpp similarity index 72% rename from src/koalageddon/koalageddon.cpp rename to src/store_mode/store.cpp index 6d03317..76b4384 100644 --- a/src/koalageddon/koalageddon.cpp +++ b/src/store_mode/store.cpp @@ -1,29 +1,28 @@ -#include -#include -#include -#include +#include +#include +#include +#include #include -#include -#include +#include #include #include -#include #include +#include -namespace koalageddon { +namespace store { - KoalageddonConfig config; // NOLINT(cert-err58-cpp) + StoreConfig config; // NOLINT(cert-err58-cpp) /** * @return A string representing the source of the config. */ - void init_koalageddon_config() { + void init_store_config() { const auto print_source = [](const String& source) { - LOG_INFO("Loaded Koalageddon config from the {}", source) + LOG_INFO("Loaded Store config from the {}", source) }; // First try to read a local config override - const auto& kg_config = smoke_api::config::instance.koalageddon_config; + const auto& kg_config = smoke_api::config::instance.store_config; if (!kg_config.is_null()) { try { config = kg_config.get(); @@ -31,17 +30,17 @@ namespace koalageddon { print_source("local config override"); return; } catch (const Exception& ex) { - LOG_ERROR("Failed to get local koalageddon config: {}", ex.what()) + LOG_ERROR("Failed to get local store_mode config: {}", ex.what()) } } // Then try to get a cached copy of a previously fetched config. try { - config = kg_cache::get_koalageddon_config().value(); + config = store_cache::get_store_config().value(); print_source("disk cache"); } catch (const Exception& ex) { - LOG_ERROR("Failed to get cached koalageddon config: {}", ex.what()) + LOG_ERROR("Failed to get cached store_mode config: {}", ex.what()) print_source("default config bundled in the binary"); @@ -53,40 +52,40 @@ namespace koalageddon { // if a new config has been fetched NEW_THREAD({ try { - const auto github_config_opt = api::fetch_koalageddon_config(); + const auto github_config_opt = api::fetch_store_config(); if (!github_config_opt) { return; } const auto github_config = *github_config_opt; - kg_cache::save_koalageddon_config(github_config); + store_cache::save_store_config(github_config); if (github_config == config) { - LOG_DEBUG("Fetched Koalageddon config is equal to existing config") + LOG_DEBUG("Fetched Store config is equal to existing config") return; } - LOG_DEBUG("Fetched a new Koalageddon config") + LOG_DEBUG("Fetched a new Store config") ::MessageBox( nullptr, TEXT( - "SmokeAPI has downloaded an updated config for Koalageddon mode. " - "Please restart Steam in order to apply the new Koalageddon config. " + "SmokeAPI has downloaded an updated config for Store mode. " + "Please restart Steam in order to apply the new Store config. " ), - TEXT("SmokeAPI - Koalageddon"), + TEXT("SmokeAPI - Store"), MB_SETFOREGROUND | MB_ICONINFORMATION | MB_OK ); } catch (const Exception& ex) { - LOG_ERROR("Failed to get remote koalageddon config: {}", ex.what()) + LOG_ERROR("Failed to get remote store_mode config: {}", ex.what()) } }) } - void init() { - init_koalageddon_config(); + void init_store_mode() { + init_store_config(); koalabox::dll_monitor::init_listener( {VSTDLIB_DLL, STEAMCLIENT_DLL}, [](const HMODULE& module_handle, const String& name) { @@ -120,7 +119,7 @@ namespace koalageddon { ); NEW_THREAD({ - koalabox::ipc::init_pipe_server("smoke_api.koalageddon", [](const koalabox::ipc::Request& request) { + koalabox::ipc::init_pipe_server("smoke_api.store_mode", [](const koalabox::ipc::Request& request) { koalabox::ipc::Response response; if (request.name < equals > "config::reload") { diff --git a/src/koalageddon/koalageddon.hpp b/src/store_mode/store.hpp similarity index 76% rename from src/koalageddon/koalageddon.hpp rename to src/store_mode/store.hpp index fa3aa61..229fbd2 100644 --- a/src/koalageddon/koalageddon.hpp +++ b/src/store_mode/store.hpp @@ -2,10 +2,10 @@ #include -namespace koalageddon { +namespace store { // Offset values are interpreted according to pointer arithmetic rules, i.e. // 1 unit offset represents 4 and 8 bytes in 32-bit and 64-bit architectures respectively. - class KoalageddonConfig { + class StoreConfig { public: uint32_t client_engine_steam_client_internal_ordinal = 12; uint32_t steam_client_internal_interface_selector_ordinal = 18; @@ -15,9 +15,9 @@ namespace koalageddon { uint32_t vstdlib_callback_name_offset = 4; // We do not use *_WITH_DEFAULT macro to ensure that overriding - // the koalageddon config requires definition of all keys + // the store_mode config requires definition of all keys NLOHMANN_DEFINE_TYPE_INTRUSIVE( - KoalageddonConfig, // NOLINT(misc-const-correctness) + StoreConfig, // NOLINT(misc-const-correctness) client_engine_steam_client_internal_ordinal, steam_client_internal_interface_selector_ordinal, vstdlib_callback_address_offset, @@ -26,11 +26,11 @@ namespace koalageddon { vstdlib_callback_name_offset ) - bool operator==(const KoalageddonConfig& other) const = default; + bool operator==(const StoreConfig& other) const = default; }; - extern KoalageddonConfig config; + extern StoreConfig config; - void init(); + void init_store_mode(); } diff --git a/src/store_mode/store_api.cpp b/src/store_mode/store_api.cpp new file mode 100644 index 0000000..1286bc3 --- /dev/null +++ b/src/store_mode/store_api.cpp @@ -0,0 +1,19 @@ +#include +#include + +namespace store::api { + + std::optional fetch_store_config() noexcept { + try { + const String url = + "https://raw.githubusercontent.com/acidicoala/public-entitlements/main/steam/v2/store_config.json"; + const auto kg_config_json = koalabox::http_client::fetch_json(url); + + return kg_config_json.get(); + } catch (const Exception& e) { + LOG_ERROR("Failed to fetch Store config from GitHub: {}", e.what()) + return std::nullopt; + } + } + +} \ No newline at end of file diff --git a/src/store_mode/store_api.hpp b/src/store_mode/store_api.hpp new file mode 100644 index 0000000..9fe8e4f --- /dev/null +++ b/src/store_mode/store_api.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include +#include + +namespace store::api { + + std::optional fetch_store_config() noexcept; + +} \ No newline at end of file diff --git a/src/store_mode/store_cache.cpp b/src/store_mode/store_cache.cpp new file mode 100644 index 0000000..e49a2fd --- /dev/null +++ b/src/store_mode/store_cache.cpp @@ -0,0 +1,32 @@ +#include +#include + +constexpr auto KEY_KG_CONFIG = "store_config"; + +namespace store::store_cache { + + std::optional get_store_config() { + try { + const auto config_json = koalabox::cache::read_from_cache(KEY_KG_CONFIG); + + return config_json.get(); + } catch (const Exception& e) { + LOG_ERROR("Failed to get cached store_mode config: {}", e.what()) + + return std::nullopt; + } + } + + bool save_store_config(const StoreConfig& config) { + try { + LOG_DEBUG("Caching store_mode config") + + return koalabox::cache::save_to_cache(KEY_KG_CONFIG, Json(config)); + } catch (const Exception& e) { + LOG_ERROR("Failed to cache store_mode config: {}", e.what()) + + return false; + } + } + +} diff --git a/src/store_mode/store_cache.hpp b/src/store_mode/store_cache.hpp new file mode 100644 index 0000000..cf33634 --- /dev/null +++ b/src/store_mode/store_cache.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include + +namespace store::store_cache { + + std::optional get_store_config(); + + bool save_store_config(const StoreConfig& config); + +} diff --git a/src/koalageddon/vstdlib.cpp b/src/store_mode/vstdlib/vstdlib.cpp similarity index 78% rename from src/koalageddon/vstdlib.cpp rename to src/store_mode/vstdlib/vstdlib.cpp index 03157a2..0c4d694 100644 --- a/src/koalageddon/vstdlib.cpp +++ b/src/store_mode/vstdlib/vstdlib.cpp @@ -1,47 +1,32 @@ -#include -#include -#include +#include -using namespace koalageddon; - -namespace koalageddon::vstdlib { - using namespace koalabox; - - VIRTUAL(bool) SharedLicensesLockStatus(PARAMS(void* arg) - ) { +namespace store::vstdlib { + VIRTUAL(bool) SharedLicensesLockStatus(PARAMS(void* arg)) { LOG_DEBUG("{}(this={}, arg={})", __func__, THIS, arg) ARGS(); return true; } - VIRTUAL(bool) SharedLibraryStopPlaying(PARAMS(void* arg) - ) { + VIRTUAL(bool) SharedLibraryStopPlaying(PARAMS(void* arg)) { LOG_DEBUG("{}(this={}, arg={})", __func__, THIS, arg) ARGS(); return true; } - VIRTUAL(void) VStdLib_Callback_Interceptor(PARAMS(const char** name_ptr) - ) { + VIRTUAL(void) VStdLib_Callback_Interceptor(PARAMS(const char** name_ptr)) { GET_ORIGINAL_HOOKED_FUNCTION(VStdLib_Callback_Interceptor) VStdLib_Callback_Interceptor_o(ARGS(name_ptr)); static auto lock_status_hooked = false; static auto stop_playing_hooked = false; - if ( - lock_status_hooked && stop_playing_hooked - ) { + if (lock_status_hooked && stop_playing_hooked) { return; } auto* const data = (CoroutineData*) THIS; - if ( - data && data - -> - get_callback_name() - ) { + if (data && data->get_callback_name()) { const auto name = String(data->get_callback_name()); LOG_TRACE("{}(ecx={}, edx={}, name='{}')", __func__, ARGS(), name) if (name == "SharedLicensesLockStatus" && !lock_status_hooked) { diff --git a/src/store_mode/vstdlib/vstdlib.hpp b/src/store_mode/vstdlib/vstdlib.hpp new file mode 100644 index 0000000..5f45d9a --- /dev/null +++ b/src/store_mode/vstdlib/vstdlib.hpp @@ -0,0 +1,27 @@ +#include + +namespace store::vstdlib { + + struct CallbackData { + uintptr_t get_callback_intercept_address() { + return reinterpret_cast(this)[store::config.vstdlib_callback_interceptor_address_offset]; + } + + uintptr_t get_callback_address() { + return reinterpret_cast(this)[store::config.vstdlib_callback_address_offset]; + } + }; + + struct CoroutineData { + CallbackData* get_callback_data() { + return reinterpret_cast(this)[store::config.vstdlib_callback_data_offset]; + } + + const char* get_callback_name() { + return reinterpret_cast(this)[store::config.vstdlib_callback_name_offset]; + } + }; + + typedef uint32_t HCoroutine; + DLL_EXPORT(HCoroutine) Coroutine_Create(void* callback_address, CoroutineData* data); +}